first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
@@ -0,0 +1,20 @@
name: PHP Composer
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
- name: Run test suite
run: composer run-script test
@@ -0,0 +1,22 @@
filter:
excluded_paths:
- 'tests/*'
build:
tests:
override:
- command: 'mkdir -p build/logs'
- command: 'php vendor/bin/phpunit --coverage-clover=build/logs/clover.xml'
coverage:
file: 'build/logs/clover.xml'
format: 'clover'
nodes:
tests: true
analysis:
tests:
override:
- command: phpcs-run ./
use_website_config: false
- php-scrutinizer-run
tools:
php_cs_fixer:
config: { level: psr2 } # or psr1 if you would just like to get fixes for PSR1
@@ -0,0 +1,117 @@
# CHANGELOG
## 2.2.5 2020-12-06
* Bugfix for [94](https://github.com/seboettg/citeproc-php/issues/95): Missing space between date parts, when date parts have no affixes
## 2.2.4 2020-11-22
* Bugfix for [95](https://github.com/seboettg/citeproc-php/issues/95): Missing space between authors and chapter
## 2.2.3 2020-10-18
* Bugfix for an error that occurred when accessing a missing plural form for a label.
## 2.2.2 2020-09-26
* Bugfix for [92](https://github.com/seboettg/citeproc-php/issues/92)
* Bugfix for [93](https://github.com/seboettg/citeproc-php/issues/93)
## 2.2.1 2020-09-12
* Bugfix for issue [82](https://github.com/seboettg/citeproc-php/issues/82)
* Bugfix for [84](https://github.com/seboettg/citeproc-php/issues/84)
* Merged PR [86](https://github.com/seboettg/citeproc-php/pull/86)
* Bugfix for [89](https://github.com/seboettg/citeproc-php/issues/89)
## 2.2.0 2020-04-04
* Compatibility for PHP 7.2, 7.3 and 7.4. This solves the issues [76](https://github.com/seboettg/citeproc-php/issues/76), [78](https://github.com/seboettg/citeproc-php/issues/75), [80](https://github.com/seboettg/citeproc-php/issues/80) and [81](https://github.com/seboettg/citeproc-php/issues/81).
* Merged Pull Requests [75](https://github.com/seboettg/citeproc-php/pull/75) and [79](https://github.com/seboettg/citeproc-php/pull/79)
Thanks to [@kchoong](https://github.com/kchoong) and [@westcomputerconsultancy](https://github.com/westcomputerconsultancy).
## 2.1.9 2019-11-04
* bugfix for [issue 68](https://github.com/seboettg/citeproc-php/issues/68)
* bugfix for [issue 69](https://github.com/seboettg/citeproc-php/issues/69)
* bugfix for [issue 70](https://github.com/seboettg/citeproc-php/issues/70)
* feature/enhancement for [issue 71](https://github.com/seboettg/citeproc-php/issues/71)
* refactoring of the code parts for rendering date ranges
* redesign/refactoring of constraints (condition handling for choose elements)
## 2.1.8 - 2019-09-13
* bugfix of [PR 66](https://github.com/seboettg/citeproc-php/pull/66)
* bugfix for displaced delimiters that appear when in the name list more than one empty entry exists.
## 2.1.7 - 2019-03-24
* bugfix of [PR 64](https://github.com/seboettg/citeproc-php/pull/64) Call to a member function getRangeDelimiter() on null
* bugfix of [PR 63](https://github.com/seboettg/citeproc-php/pull/63) Don't show "et-al" text for citation witch don't reach et-al number
## 2.1.6 - 2018-10-13
* bugfix of [issue 59](https://github.com/seboettg/citeproc-php/issues/59)
* bugfix of [issue 60](https://github.com/seboettg/citeproc-php/issues/60)
## 2.1.5 - 2018-09-23
* bugfix of [issue 57](https://github.com/seboettg/citeproc-php/issues/57)
* bugfix of [issue 58](https://github.com/seboettg/citeproc-php/issues/58)
## 2.1.4 - 2018-07-30
* bugfix of [PR 52](https://github.com/seboettg/citeproc-php/pull/52): Fix locale overrides using inactive language
* bugfix of [PR 53](https://github.com/seboettg/citeproc-php/pull/53): Guard against unset variable
* improvement of [PR 54](https://github.com/seboettg/citeproc-php/pull/54): Add loading of primary dialect
Thanks to [@jonathonwalz](https://github.com/jonathonwalz) for these Pull Requests.
## 2.1.3 - 2018-06-15
* bugfix for issue [50](https://github.com/seboettg/citeproc-php/issues/50): In some cases punctuation in quote did not work.
## 2.1.2 - 2018-04-18
* bugfix for issue [49](https://github.com/seboettg/citeproc-php/issues/49): Stylesheets that used the ``text-case="title"`` option, in combination with some in Slavic (or Serbo-Croatian) language, caused errors that destroyed the entire output. This was caused by the capitalization of non-letter characters.
## 2.1.1 - 2018-02-08
* Support for render variables that are using "-short" suffixes, if Text tags have a "form" attribute which is set to "short": ``<text form="short" .../>``. This is used e.g. for abbreviated journal title (container-title-short) and occurred a wrong output in different styles (for example AMA American Medical Association) in previous citeproc-php versions. Have a look at issue [47](https://github.com/seboettg/citeproc-php/issues/47).
## 2.1.0 - 2017-11-23
* possibility to filter specific citations independently from CSL input data (inspired by @CarlosCraviotto PR #39). Have a look [here](https://github.com/seboettg/citeproc-php/blob/master/README.md#filter-citations).
* possibility to use custom Lambda functions to enrich bibliographies and citations with additional HTML markup. Have a look [here](https://github.com/seboettg/citeproc-php/blob/master/README.md#advanced-usage-of-citeproc-php).
## 2.0.4 - 2017-11-15
* bugfix for issue [46](https://github.com/seboettg/citeproc-php/issues/46): initialize names didn't work for cyrillic characters
## 2.0.3 - 2017-11-11
* bugfix for issue [44](https://github.com/seboettg/citeproc-php/issues/44): Missing title with chicago-fullnote-bibliography. The problem occurred because of an incorrect implementation of the "none-condition" in ChooseIf.
* bugfix for an issue that appears sometimes in connection with date-parts.
## 2.0.2 - 2017-10-04
* bugfix for issue [41](https://github.com/seboettg/citeproc-php/issues/41): fixed missing suppression of substituted values
* bugfix for issue [42](https://github.com/seboettg/citeproc-php/issues/42): citeproc-php caused a fatal error if php 5.6 was used
* citeproc-php uses now version 1.2 of [seboettg/collection](https://packagist.org/packages/seboettg/collection)
## 2.0.1 - 2017-05-23
* bugfix for issue [36](https://github.com/seboettg/citeproc-php/issues/36).
* bugfix for issue [37](https://github.com/seboettg/citeproc-php/issues/37).
* solves a problem of exceeded script runtime which sometimes occurs while running `composer update`. Now,
the depended citation styles and locales are not longer composer dependencies but will be cloned by a shell script instead which simply will triggered from `composer update`.
## 2.0.0 - 2017-05-11
* 1st stable release
* fix issues that causing if styles using uncertain dates
* add info node parser and getInfo() method in Context class. Thus, it's now possible to get meta data of given stylesheet
* fix issues in et-al abbreviation which was appearing in citation
## 2.0.0-beta 2017-05-01
* beta release
* all features of milestone "Version 2.0" have been implemented
@@ -0,0 +1,59 @@
## Contribution ##
citeproc-php is an Open Source project. You can support it by reporting bugs, contributing code or contributing documentation.
### Star the Repo ###
Developing software is a hard job and one has to spend a lot of time. Every open-source developer is looking forward
about esteem for his work. If you use citeproc-php and if you like it, star it and talk about it in Blogs.
### Reporting a Bug ###
Use the [Issue Tracker](https://github.com/seboettg/citeproc-php/issues) in order to report a bug.
### Contribute Code ###
You are a developer and you like to help developing new features or bug fixes? Fork citeproc-php, setup a workspace and send
a pull request.
I would suggest the following way:
* Fork citeproc-php on Github
* Clone the forked repo
```bash
$ git clone https://github.com/<yourname>/citeproc-php
```
* Setup your preferred IDE
* create a new branch in your forked repo (do not commit your changes in the master branch)
* implement your fix or feature
* Run the UnitTests
* Write a test case for your issue. My tests are based on the original [test-suite](https://github.com/citation-style-language/test-suite). You can build custom (human-readable) test cases following the described [Fixture layout](https://github.com/citation-style-language/test-suite#fixture-layout).
* Additionally, you have to translate (human-readable) test-cases into json format (machine-readable)
```bash
$ cd <project-root>/tests/fixtures/basic-tests
$ ./processor.py -g
```
* create a test function within an already existing test class or create a new test class:
```php
<?php
namespace Seboettg\CiteProc;
use PHPUnit\Framework\TestCase;
class MyNewClassTest extends TestCase
{
use TestSuiteTestCaseTrait;
// ...
public function testMyBrandNewFunction()
{
//my brand new function is the file name (without file extension)
$this->_testRenderTestSuite("myBrandNewFunction");
}
// ...
}
```
* Make sure that your test case covers relevant code parts
* Implement the code until all tests finishing successfully
* Send a pull request as follows:
1. create a new pull request and select the forked repo and the new branch as head
2. select the "seboettg/citeproc-php" and the master branch as base
3. select "Allow edits by maintainers"
4. add title and a meaningful description for your PR
5. submit the pull request
@@ -0,0 +1,35 @@
# Please follow the general troubleshooting steps first:
- [ ] I read the README and followed the instructions.
- [ ] I am sure that the used CSL metadata follows the [CSL schema](https://github.com/citation-style-language/schema/blob/master/csl-data.json).
- [ ] I use a valid CSL stylesheet
### Bug reports:
Please replace this line with a brief summary of your issue
#### Used CSL stylesheet: ####
Please replace this line with the name of your used CSL stylesheet (i.e. chicago-author-date-16th-edition.csl)
#### Used CSL metadata ####
Please replace these lines with your used metadata, for instance:
[
{
"author": [
{
"family": "Anderson",
"given": "John"
},
{
"family": "Brown",
"given": "John"
}
],
"id": "ITEM-2",
"type": "book",
"title": "Two authors writing a book"
}
]
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Sebastian Böttger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,394 @@
# citeproc-php #
[![Latest Stable Version](https://poser.pugx.org/seboettg/citeproc-php/v/stable)](https://packagist.org/packages/seboettg/citeproc-php)
[![Total Downloads](https://poser.pugx.org/seboettg/citeproc-php/downloads)](https://packagist.org/packages/seboettg/citeproc-php/stats)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://opensource.org/licenses/MIT)
[![Build Status](https://scrutinizer-ci.com/g/seboettg/citeproc-php/badges/build.png?b=master)](https://scrutinizer-ci.com/g/seboettg/citeproc-php/build-status/master)
[![Code Coverage](https://scrutinizer-ci.com/g/seboettg/citeproc-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/seboettg/citeproc-php/code-structure/master/code-coverage/src/)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/seboettg/citeproc-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/seboettg/citeproc-php/?branch=master)
[![Code Intelligence Status](https://scrutinizer-ci.com/g/seboettg/citeproc-php/badges/code-intelligence.svg?b=master)](https://scrutinizer-ci.com/code-intelligence)
![PHP](https://img.shields.io/badge/PHP-7.3-green.svg?style=flat)
![PHP](https://img.shields.io/badge/PHP-7.4-green.svg?style=flat)
![PHP](https://img.shields.io/badge/PHP-8.0-green.svg?style=flat)
![PHP](https://img.shields.io/badge/PHP-8.1-green.svg?style=flat)
citeproc-php is a full-featured CSL 1.0.1 processor that renders bibliographic metadata into html formatted citations or bibliographies using CSL stylesheets. citeproc-php renders bibliographies as well as citations (except of [Citation-specific Options](http://docs.citationstyles.org/en/stable/specification.html#citation-specific-options)).
## Citation Style Language CSL ##
The Citation Style Language (CSL) is an XML-based format to describe the formatting of citations, notes and bibliographies, offering:
* An open format
* Compact and robust styles
* Extensive support for style requirements
* Automatic style localization
* Infrastructure for style distribution and updating
* Thousands of freely available styles (Creative Commons BY-SA licensed)
For additional documentation of CSL visit [http://citationstyles.org](http://citationstyles.org).
## Installing citeproc-php ##
The recommended way to install citeproc-php is through
[Composer](https://getcomposer.org).
```bash
$ curl -sS https://getcomposer.org/installer | php
```
Add the following lines to your `composer.json` file in order to add required program libraries as well as CSL styles and locales:
```json
{
"name": "vendor-name/program-name",
"repositories": [
{
"type": "package",
"package": {
"name": "citation-style-language/locales",
"version":"1.0.0",
"source": {
"type": "git",
"url": "https://github.com/citation-style-language/locales.git",
"reference": "master"
}
}
},
{
"type": "package",
"package": {
"name": "citation-style-language/styles",
"version":"1.0.0",
"source": {
"type": "git",
"url": "https://github.com/citation-style-language/styles.git",
"reference": "master"
}
}
}
],
"require": {
"citation-style-language/locales":"@dev",
"citation-style-language/styles":"@dev",
"seboettg/citeproc-php": "^2"
}
}
```
Next, run the Composer command to install the latest stable version of citeproc-php and its dependencies:
```bash
$ php composer.phar install --no-dev
```
After installing, you need to require Composer's autoloader:
```php
require 'vendor/autoload.php';
```
You can then later update citeproc-php using composer:
```bash
$ composer.phar update --no-dev
```
If you have trouble using composer you will find further information on [https://getcomposer.org/doc/](https://getcomposer.org/doc/).
## How to use citeproc-php ##
citeproc-php renders bibliographical metadata into html formatted citations or bibliographies using a stylesheet which defines the
citation rules.
### Get the metadata of your publications ###
Create a project folder:
```bash
$ mkdir mycslproject
$ cd mycslproject
```
First, you need json formatted metadata array of publication's metadata. There are a lot of services that supports CSL exports. For instance [BibSonomy](https://www.bibsonomy.org), [Zotero](https://www.zotero.org/), [Mendeley](https://www.mendeley.com/).
If you don't use any of these services, you can use the following test data for a first step.
```javascript
[
{
"author": [
{
"family": "Doe",
"given": "James",
"suffix": "III"
}
],
"id": "item-1",
"issued": {
"date-parts": [
[
"2001"
]
]
},
"title": "My Anonymous Heritage",
"type": "book"
},
{
"author": [
{
"family": "Anderson",
"given": "John"
},
{
"family": "Brown",
"given": "John"
}
],
"id": "ITEM-2",
"type": "book",
"title": "Two authors writing a book"
}
]
```
Copy this into a file in your project root and name that file `metadata.json`.
### Build a first simple script ###
```php
<?php
include "vendor/autoload.php";
use Seboettg\CiteProc\StyleSheet;
use Seboettg\CiteProc\CiteProc;
$data = file_get_contents("metadata.json");
$style = StyleSheet::loadStyleSheet("din-1505-2");
$citeProc = new CiteProc($style);
echo $citeProc->render(json_decode($data), "bibliography");
```
You can also render citations instead of bibliographies:
```php
echo $citeProc->render(json_decode($data), "citation");
```
### Filter Citations ###
Since version 2.1 you have also the possibility to apply a filter so that just specific citations appear.
```php
<p>This a wise sentence
<?php echo $citeProc->render($data, "citation", json_decode('[{"id":"item-1"}]')); ?>.</p>
<p>This is the most wise sentence
<?php echo $citeProc->render($data, "citation", json_decode('[{"id":"item-1"},{"id":"ITEM-2"}]')); ?>.</p>
```
### Bibliography-specific styles using CSS ###
Some CSL stylesheets use bibliography-specific style options like hanging indents or alignments. To get an effect of these options you can render separated Cascading Stylesheets using CiteProc.
You have to insert these styles within the `<head>` tag of your html output page.
```php
<?php
include "vendor/autoload.php";
use Seboettg\CiteProc\StyleSheet;
use Seboettg\CiteProc\CiteProc;
$data = file_get_contents("metadata.json");
$style = StyleSheet::loadStyleSheet("harvard-north-west-university");
$citeProc = new CiteProc($style);
$bibliography = $citeProc->render(json_decode($data), "bibliography");
$cssStyles = $citeProc->renderCssStyles();
?>
<html>
<head>
<title>CSL Test</title>
<style type="text/css" rel="stylesheet">
<?php echo $cssStyles; ?>
</style>
</head>
<body>
<h1>Bibliography</h1>
<?php echo $bibliography; ?>
</body>
</html>
```
Now, you can watch and test the output using PHP's internal web server:
```bash
$ php -S localhost:8080
```
Start your Browser and open the URL `http://localhost:8080`.
Under `examples` folder you will find another example script.
## Advanced usage of citeproc-php ##
Since version 2.1, citeproc-php comes with additional features that are not a part of the CSL specifications.
You can enrich bibliographies and citations with additional HTML tags to inject links (i.e. to set a link to an author's CV), or to add other html markup.
### Use Lambda Functions to setup citeproc-php in order to render advanced citations or bibliographies ###
```php
<?php
include "vendor/autoload.php";
use Seboettg\CiteProc\StyleSheet;
use Seboettg\CiteProc\CiteProc;
$data = file_get_contents("metadata.json");
$style = StyleSheet::loadStyleSheet("elsevier-vancouver");
// pimp the title
$titleFunction = function($cslItem, $renderedText) {
return '<a href="https://example.org/publication/' . $cslItem->id . '">' . $renderedText . '</a>';
};
//pimp author names
$authorFunction = function($authorItem, $renderedText) {
if (isset($authorItem->id)) {
return '<a href="https://example.org/author/' . $authorItem->id . '">' . $renderedText . '</a>';
}
return $renderedText;
};
?>
```
As you can see, `$titleFunction` wraps the title and `$authorFunction` wraps author's name in a link.
Assign these functions to its associated CSL variable (in this case title and author) as follows.
```php
<?php
$additionalMarkup = [
"title" => $titleFunction,
"author" => $authorFunction
];
$citeProc = new CiteProc($style, "en-US", $additionalMarkup);
?>
<html>
<head>
<title>CSL Test</title>
</head>
<body>
<h1>Bibliography</h1>
<?php echo $citeProc->render(json_decode($data), "bibliography"); ?>
</body>
</html>
```
You can also use custom Lambda Functions in order to enrich citations with additional HTML markup.
If you want to restrict citeproc-php to use a custom Lambda Function either for bibliographies or citations, or you want to apply different
functions for both, you can define the array as follows:
```php
<?php
$additionalMarkup = [
"bibliography" => [
"author" => $authorFunction,
"title" => $titleFunction,
"csl-entry" => function($cslItem, $renderedText) {
return '<a id="' . $cslItem->id .'" href="#' . $cslItem->id .'"></a>' . $renderedText;
}
],
"citation" => [
"citation-number" => function($cslItem, $renderedText) {
return '<a href="#' . $cslItem->id .'">'.$renderedText.'</a>';
}
]
];
$citeProc = new CiteProc($style, "en-US", $additionalMarkup);
?>
<p>This ia a wise sentence <?php echo $citeProc->render(json_decode($data), "citation", json_decode('[{"id":"item-1"}]')); ?>.</p>
<h3>Literature</h3>
<?php echo $citeProc->render(json_decode($data), "bibliography");
```
In this example each entry of the bibliography gets an anchor by its `id` and the citation (in Elsevier-Vancouver style [1]) gets an URL with a fragment by its `id`. Hence, every citation mark gets a link to its entry in the bibliography.
Further examples you will find in the example folder.
### Good to know ###
* A custom Lambda Function must have two parameters (`function ($item, $renderedValue) { ... }`) in their signature and must return a string.
* The 1st parameter of a custom Lambda Function is the item (either a citation item or a name item. Both of type `\stdClass`). The 2nd parameter is the rendered result of the associated item.
* Custom Lambda Functions may be applied on all Standard Variables (according to the [CSL specification](http://docs.citationstyles.org/en/1.0.1/specification.html#standard-variables)).
* Custom Lambda Functions may be applied on all Name Variables (according to the [CSL specification](http://docs.citationstyles.org/en/1.0.1/specification.html#name-variables)). Be aware, just one name item will passed as parameter instead of the full citation item.
* Custom Lambda Function for Number Variables or Date Variables will be ignored.
* ```csl-entry``` is not a valid variable according to the CSL specifications. citeproc-php use ```csl-entry``` to hook in and apply a custom Lambda Function after a whole citation item or bibliography entry is rendered.
## Contribution ##
citeproc-php is an Open Source project. You can support it by reporting bugs, contributing code or contributing documentation.
### Star the Repo ###
Developing software is a hard job and one has to spend a lot of time. Every open-source developer is looking forward
about esteem for his work. If you use citeproc-php and if you like it, star it and talk about it in Blogs.
### Reporting a Bug ###
Use the [Issue Tracker](https://github.com/seboettg/citeproc-php/issues) in order to report a bug.
### Contribute Code ###
You are a developer and you like to help developing new features or bug fixes? Fork citeproc-php, setup a workspace and send
a pull request.
I would suggest the following way:
* Fork citeproc-php on Github
* Clone the forked repo
```bash
$ git clone https://github.com/<yourname>/citeproc-php
```
* Setup your preferred IDE
* Run the UnitTests within your IDE
* Write a test case for your issue. My tests are based on the original [test-suite](https://github.com/citation-style-language/test-suite). You can build custom (human-readable) test cases following the described [Fixture layout](https://github.com/citation-style-language/test-suite#fixture-layout).
* Additionally, you have to translate (human-readable) test-cases into json format (machine-readable)
```bash
$ cd <project-root>/tests/fixtures/basic-tests
$ ./processor.py -g
```
* create a test function within an already existing test class or create a new test class:
```php
<?php
namespace Seboettg\CiteProc;
use PHPUnit\Framework\TestCase;
class MyNewClassTest extends TestCase
{
use TestSuiteTestCaseTrait;
// ...
public function testMyBrandNewFunction()
{
//my brand new function is the file name (without file extension)
$this->_testRenderTestSuite("myBrandNewFunction");
}
// ...
}
```
* Implement or adapt your code as long as all tests finishing successfully
* Make sure that your test case covers relevant code parts
* Send a pull request
## Testing ##
You can also run test cases without IDE:
```bash
$ composer test
```
## Known projects that use citeproc-php
* [Citation Style Language plugin for Open Journal Systems 3](https://github.com/pkp/citationStyleLanguage)
* [Islandora Scholar](https://github.com/Islandora/islandora_scholar)
* [Grav Bibliography Plugin](https://github.com/OleVik/grav-plugin-bibliography)
* [Bibcite Wordpress Plugin](https://github.com/ShadyChars/Bibcite)
* [DKAN Science Metadata](https://github.com/GetDKAN/dkan_sci_metadata)
* [Stud.IP](https://github.com/studip/studip)
* [bibliography Plugin for DokuWiki](https://github.com/gyger/dokuwiki-bibliography)
* [ldbase_citations (Plugin for LDbase)](https://github.com/ldbase/ldbase_citations)
* [Citations - (Drupal module)](https://github.com/fsulib/citations)
@@ -0,0 +1,69 @@
{
"name": "seboettg/citeproc-php",
"description": "Full-featured CSL processor (https://citationstyles.org)",
"license": "MIT",
"authors": [
{
"name": "Sebastian Böttger",
"email": "seboettg@gmail.com",
"homepage": "https://sebastianboettger.net",
"role": "Developer"
}
],
"support": {
"issues": "https://github.com/seboettg/citeproc-php/issues"
},
"autoload": {
"psr-4": {
"Seboettg\\CiteProc\\": "src/"
},
"files": [
"src/functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Seboettg\\CiteProc\\Test\\": "tests/src/"
}
},
"require": {
"seboettg/collection": ">=v3.1.0",
"myclabs/php-enum": "^1.8",
"ext-simplexml": "*",
"ext-json": "*",
"php": ">=7.3",
"ext-mbstring": "*",
"ext-intl": "*"
},
"require-dev": {
"php-coveralls/php-coveralls": "^1",
"phpunit/phpunit": "^8.5",
"squizlabs/php_codesniffer": "^3.5",
"phpmd/phpmd": "^2.8"
},
"suggest": {
"symfony/polyfill-mbstring": "^1.10"
},
"scripts": {
"post-install-cmd": [
"./install.sh styles",
"./install.sh locales",
"@compile-test-cases",
"chmod +x vendor/bin/phpunit"
],
"post-update-cmd": [
"./install.sh styles",
"./install.sh locales",
"@compile-test-cases",
"chmod +x vendor/bin/phpunit"
],
"test": "vendor/bin/phpunit -c phpunit.xml",
"check": [
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"compile-test-cases": "cd ./tests/fixtures/basic-tests/; ./processor.py -g"
}
}
@@ -0,0 +1,9 @@
#!/bin/bash
if [ -d ./vendor/citation-style-language/$1 ]
then
cd ./vendor/citation-style-language/$1
git pull origin master
else
git clone --branch=master https://github.com/citation-style-language/$1.git vendor/citation-style-language/$1
fi
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<ruleset name="citeproc-php coding standard">
<description>citeproc-php coding standard</description>
<!-- display progress -->
<arg value="p"/>
<arg name="colors"/>
<!-- inherit rules from: -->
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
<properties>
<property name="ignoreBlankLines" value="false"/>
</properties>
</rule>
<!-- Paths to check -->
<file>src</file>
</ruleset>
@@ -0,0 +1,20 @@
<?xml version="1.0"?>
<ruleset name="citeproc-php coding standard">
<description>citeproc-php coding standard</description>
<!-- display progress -->
<arg value="p"/>
<arg name="colors"/>
<!-- inherit rules from: -->
<rule ref="PSR2"/>
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
<properties>
<property name="ignoreBlankLines" value="false"/>
</properties>
</rule>
<!-- Paths to check -->
<file>src</file>
</ruleset>
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ citeproc-php
~
~ @link http://github.com/seboettg/citeproc-php for the source repository
~ @copyright Copyright (c) 2016 Sebastian Böttger.
~ @license https://opensource.org/licenses/MIT
-->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="tests/bootstrap.php">
<php>
<!-- -->
</php>
<testsuites>
<testsuite name="Project Test Suite">
<directory>tests/src</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
<exclude>
<directory>./vendor</directory>
<directory>./tests</directory>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-html" target="review/code-coverage"/>
</logging>
</phpunit>
@@ -0,0 +1,234 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc;
use InvalidArgumentException;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Root\Info;
use Seboettg\CiteProc\Style\Bibliography;
use Seboettg\CiteProc\Style\Citation;
use Seboettg\CiteProc\Style\Macro;
use Seboettg\CiteProc\Style\Options\GlobalOptions;
use Seboettg\CiteProc\Root\Root;
use Seboettg\CiteProc\Styles\Css\CssStyle;
use Seboettg\CiteProc\Util\CiteProcHelper;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class CiteProc
* @package Seboettg\CiteProc
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class CiteProc
{
/**
* @var Context
*/
private static $context;
/**
* @return Context
*/
public static function getContext()
{
return self::$context;
}
/**
* @param Context $context
*/
public static function setContext($context)
{
self::$context = $context;
}
private $lang;
/**
* @var string
*/
private $styleSheet;
/**
* @var SimpleXMLElement
*/
private $styleSheetXml;
/**
* @var array
*/
private $markupExtension;
/**
* CiteProc constructor.
* @param string $styleSheet xml formatted csl stylesheet
* @param string $lang
* @param array $markupExtension
*/
public function __construct($styleSheet, $lang = "en-US", $markupExtension = [])
{
$this->styleSheet = $styleSheet;
$this->lang = $lang;
$this->markupExtension = $markupExtension;
}
public function __destruct()
{
self::$context = null;
}
/**
* @param SimpleXMLElement $style
* @throws CiteProcException
*/
private function parse(SimpleXMLElement $style)
{
$root = new Root();
$root->initInheritableNameAttributes($style);
self::$context->setRoot($root);
$globalOptions = new GlobalOptions($style);
self::$context->setGlobalOptions($globalOptions);
/** @var SimpleXMLElement $node */
foreach ($style as $node) {
$name = $node->getName();
switch ($name) {
case 'info':
self::$context->setInfo(new Info($node));
break;
case 'locale':
self::$context->getLocale()->addXml($node);
break;
case 'macro':
$macro = new Macro($node, $root);
self::$context->addMacro($macro->getName(), $macro);
break;
case 'bibliography':
$bibliography = new Bibliography($node, $root);
self::$context->setBibliography($bibliography);
break;
case 'citation':
$citation = new Citation($node, $root);
self::$context->setCitation($citation);
break;
}
}
}
/**
* @param DataList $data
* @return string
*/
protected function bibliography($data)
{
return self::$context->getBibliography()->render($data);
}
/**
* @param DataList $data
* @param ArrayList $citationItems
* @return string
*/
protected function citation($data, $citationItems)
{
return self::$context->getCitation()->render($data, $citationItems);
}
/**
* @param array|DataList $data
* @param string $mode (citation|bibliography)
* @param array $citationItems
* @param bool $citationAsArray
* @return string
* @throws CiteProcException
*/
public function render($data, $mode = "bibliography", $citationItems = [], $citationAsArray = false)
{
if (is_array($data)) {
$data = CiteProcHelper::cloneArray($data);
}
if (!in_array($mode, ['citation', 'bibliography'])) {
throw new InvalidArgumentException("\"$mode\" is not a valid mode.");
}
$this->init($citationAsArray); //initialize
$res = "";
if (is_array($data)) {
$data = new DataList(...$data);
} elseif (!($data instanceof DataList)) {
throw new CiteProcException('No valid format for variable data. Either DataList or array expected');
}
switch ($mode) {
case 'bibliography':
self::$context->setMode($mode);
// set CitationItems to Context
self::getContext()->setCitationData($data);
$res = $this->bibliography($data);
break;
case 'citation':
if (is_array($citationItems)) {
$citationItems = new ArrayList(...$citationItems);
} elseif (!($citationItems instanceof ArrayList)) {
throw new CiteProcException('No valid format for variable `citationItems`, ArrayList expected.');
}
self::$context->setMode($mode);
// set CitationItems to Context
self::getContext()->setCitationItems($citationItems);
$res = $this->citation($data, $citationItems);
}
self::setContext(null);
return $res;
}
/**
* initializes CiteProc and start parsing XML stylesheet
* @param bool $citationAsArray
* @throws CiteProcException
*/
public function init($citationAsArray = false)
{
self::$context = new Context();
self::$context->setLocale(new Locale\Locale($this->lang)); //init locale
self::$context->setCitationsAsArray($citationAsArray);
// set markup extensions
self::$context->setMarkupExtension($this->markupExtension);
$this->styleSheetXml = new SimpleXMLElement($this->styleSheet);
$this->parse($this->styleSheetXml);
}
/**
* @return string
* @throws CiteProcException
*/
public function renderCssStyles()
{
if (self::getContext() === null) {
$this->init();
}
if (self::getContext()->getCssStyle() == null) {
$cssStyle = new CssStyle(self::getContext()->getBibliographySpecificOptions());
self::getContext()->setCssStyle($cssStyle);
}
return self::getContext()->getCssStyle()->render();
}
}
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
/*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2019 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use Seboettg\Collection\ArrayList;
use stdClass;
abstract class AbstractConstraint implements Constraint
{
/**
* @var string
*/
protected $match;
/**
* @var ArrayList\ArrayListInterface
*/
protected $conditionVariables;
/**
* @param string $variable
* @param stdClass $data;
* @return bool
*/
abstract protected function matchForVariable(string $variable, stdClass $data): bool;
/**
* Variable constructor.
* @param string $variableValues
* @param string $match
*/
public function __construct(string $variableValues, string $match = "any")
{
$this->conditionVariables = new ArrayList(...explode(" ", $variableValues));
$this->match = $match;
}
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return bool
*/
public function validate(stdClass $data, int $citationNumber = null): bool
{
switch ($this->match) {
case Constraint::MATCH_ALL:
return $this->matchAll($data);
case Constraint::MATCH_NONE:
return $this->matchNone($data); //no match for any value
case Constraint::MATCH_ANY:
default:
return $this->matchAny($data);
}
}
private function matchAny(stdClass $data): bool
{
return $this->conditionVariables
->map(function (string $conditionVariable) use ($data) {
return $this->matchForVariable($conditionVariable, $data);
})
->filter(function (bool $match) {
return $match === true;
})
->count() > 0;
}
private function matchAll(stdClass $data): bool
{
return $this->conditionVariables
->map(function (string $conditionVariable) use ($data) {
return $this->matchForVariable($conditionVariable, $data);
})
->filter(function (bool $match) {
return $match === true;
})
->count() === $this->conditionVariables->count();
}
private function matchNone(stdClass $data): bool
{
return $this->conditionVariables
->map(function (string $conditionVariable) use ($data) {
return $this->matchForVariable($conditionVariable, $data);
})
->filter(function (bool $match) {
return $match === false;
})
->count() === $this->conditionVariables->count();
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use stdClass;
/**
* Interface ConstraintInterface
* @package Seboettg\CiteProc\Constraint
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
/** @noinspection PhpUnused */
interface Constraint
{
const MATCH_NONE = "none";
const MATCH_ANY = "any";
const MATCH_ALL = "all";
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return bool
*/
public function validate(stdClass $data, int $citationNumber = null): bool;
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use stdClass;
/**
* Class Disambiguate
* When set to “true” (the only allowed value), the element content is only rendered if it disambiguates two otherwise
* identical citations. This attempt at disambiguation is only made when all other disambiguation methods have failed
* to uniquely identify the target source.
*/
class Disambiguate implements Constraint
{
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return bool
*/
public function validate(stdClass $data, $citationNumber = null): bool
{
return false;
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use Seboettg\CiteProc\Exception\ClassNotFoundException;
use function Seboettg\CiteProc\ucfirst;
class Factory extends \Seboettg\CiteProc\Util\Factory
{
const NAMESPACE_CONSTRAINTS = "Seboettg\\CiteProc\\Constraint\\";
/**
* @throws ClassNotFoundException
*/
public static function createConstraint(string $name, string $value, string $match): Constraint
{
$parts = explode("-", $name);
$className = implode("", array_map(function ($part) {
return ucfirst($part);//overridden function
}, $parts));
$className = self::NAMESPACE_CONSTRAINTS . $className;
if (!class_exists($className)) {
throw new ClassNotFoundException($className);
}
return new $className($value, $match);
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use NumberFormatter;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Util\NumberHelper;
use stdClass;
/**
* Class IsNumeric
* @package Seboettg\CiteProc\Choose\Constraint
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
/** @noinspection PhpUnused */
class IsNumeric extends AbstractConstraint
{
/**
* @param string $variable
* @param stdClass $data
* @return bool
*/
protected function matchForVariable(string $variable, stdClass $data): bool
{
if (isset($data->{$variable})) {
return $this->parseValue($data->{$variable});
}
return false;
}
/**
* Tests whether the given variables (Appendix IV - Variables) contain numeric content. Content is considered
* numeric if it solely consists of numbers. Numbers may have prefixes and suffixes (“D2”, “2b”, “L2d”), and may be
* separated by a comma, hyphen, or ampersand, with or without spaces (“2, 3”, “2-4”, “2 & 4”). For example, “2nd”
* tests “true” whereas “second” and “2nd edition” test “false”.
*
* @param $evalValue
* @return bool
*/
private function parseValue($evalValue): bool
{
if (is_numeric($evalValue)) {
return true;
} elseif (preg_match(NumberHelper::PATTERN_ORDINAL, $evalValue)) {
$numberFormatter = new NumberFormatter(
CiteProc::getContext()->getLocale()->getLanguage(),
NumberFormatter::ORDINAL
);
return $numberFormatter->parse($evalValue) !== false;
} elseif (preg_match(NumberHelper::PATTERN_ROMAN, $evalValue)) {
return NumberHelper::roman2Dec($evalValue) !== false;
} elseif (preg_match(NumberHelper::PATTERN_COMMA_AMPERSAND_RANGE, $evalValue)) {
return true;
}
return false;
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use stdClass;
/**
* Class isUncertainDate
* Tests whether the given date variables contain approximate dates.
*
* @package Seboettg\CiteProc\Constraint
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
/** @noinspection PhpUnused */
class IsUncertainDate extends AbstractConstraint
{
/**
* @param string $variable
* @param stdClass $data ;
* @return bool
*/
protected function matchForVariable(string $variable, stdClass $data): bool
{
if (!empty($data->{$variable})) {
if (isset($data->{$variable}->{'circa'})) {
return true;
}
}
return false;
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use stdClass;
/**
* Class Jurisdiction
* @package Seboettg\CiteProc\Constraint
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Jurisdiction implements Constraint
{
/**
* @codeCoverageIgnore
* @param stdClass $data
* @param int|null $citationNumber
* @return bool
*/
public function validate(stdClass $data, int $citationNumber = null): bool
{
return false;
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use Seboettg\CiteProc\CiteProc;
use stdClass;
/**
* Class Locator
*
* Tests whether the locator matches the given locator types (see Locators). Use “sub-verbo” to test for the
* “sub verbo” locator type.
*/
class Locator extends AbstractConstraint
{
/**
* @inheritDoc
*/
protected function matchForVariable(string $variable, stdClass $data): bool
{
if (!empty($data->id)) {
$citationItem = CiteProc::getContext()->getCitationItemById($data->id);
return !empty($citationItem) && !empty($citationItem->label) && $citationItem->label === $variable;
}
return false;
}
}
@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use Seboettg\CiteProc\CiteProc;
use Seboettg\Collection\ArrayList;
use stdClass;
/**
* Class Position
*
* Tests whether the cite position matches the given positions (terminology: citations consist of one or more cites to
* individual items). When called within the scope of cs:bibliography, position tests “false”. The positions that can
* be tested are:
* - “first”: position of cites that are the first to reference an item
* - “ibid”/”ibid-with-locator”/”subsequent”: cites referencing previously cited items have the “subsequent” position.
* Such cites may also have the “ibid” or “ibid-with-locator” position when:
* a) the current cite immediately follows on another cite, within the same citation, that references the
* same item, or
* b) the current cite is the first cite in the citation, and the previous citation consists of a single cite
* referencing the same item
* If either requirement is met, the presence of locators determines which position is assigned:
* - Preceding cite does not have a locator: if the current cite has a locator, the position of the current cite is
* “ibid-with-locator”. Otherwise the position is “ibid”.
* - Preceding cite does have a locator: if the current cite has the same locator, the position of the current cite
* is “ibid”. If the locator differs the position is “ibid-with-locator”. If the current cite lacks a locator
* its only position is “subsequent”.
* - “near-note”: position of a cite following another cite referencing the same item. Both cites have to be located
* in foot or endnotes, and the distance between both cites may not exceed the maximum distance (measured in number
* of foot or endnotes) set with the near-note-distance option.
*
* Whenever position=”ibid-with-locator” tests true, position=”ibid” also tests true. And whenever position=”ibid” or
* position=”near-note” test true, position=”subsequent” also tests true.
*
*/
class Position implements Constraint
{
const FIRST = "first";
const IBID = "ibid";
const IBID_WITH_LOCATOR = "ibid-with-locator";
const SUBSEQUENT = "subsequent";
const NEAR_NOTE = "near-note";
private $position;
private $match;
public function __construct(string $position, string $match = "all")
{
$this->position = $position;
$this->match = $match;
}
/**
* @codeCoverageIgnore
* @param stdClass $data
* @param int|null $citationNumber
* @return bool
*/
public function validate(stdClass $data, $citationNumber = null): bool
{
if (CiteProc::getContext()->isModeBibliography()) {
return false;
}
switch ($this->position) {
case self::FIRST:
return $this->getPosition($data) === null;
case self::IBID:
case self::IBID_WITH_LOCATOR:
case self::SUBSEQUENT:
return $this->isOnLastPosition($data);
}
return true;
}
private function getPosition(stdClass $data): ?string
{
foreach (CiteProc::getContext()->getCitedItems() as $key => $value) {
if (!empty($value->{'id'}) && $value->{'id'} === $data->{'id'}) {
return $key;
}
}
return null;
}
/**
* @param stdClass $data
* @return bool
*/
private function isOnLastPosition(stdClass $data): bool
{
$lastCitedItem = CiteProc::getContext()->getCitedItems()->last();
return !empty($lastCitedItem) && $lastCitedItem->{'id'} === $data->{'id'};
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use stdClass;
class Type extends AbstractConstraint
{
/**
* @param string $variable
* @param stdClass $data ;
* @return bool
*/
protected function matchForVariable(string $variable, stdClass $data): bool
{
return in_array($data->type, $this->conditionVariables->toArray());
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Constraint;
use Seboettg\CiteProc\CiteProc;
use stdClass;
class Variable extends AbstractConstraint
{
/**
* @param string $variable
* @param stdClass $data
* @return bool
*/
protected function matchForVariable(string $variable, stdClass $data): bool
{
$variableExistsInCitationItem = false;
if (CiteProc::getContext()->isModeCitation() && isset($data->id)) {
$citationItem = CiteProc::getContext()->getCitationItemById($data->id);
if (!empty($citationItem)) {
$variableExistsInCitationItem = !empty($citationItem->{$variable});
}
}
return !empty($data->{$variable}) || $variableExistsInCitationItem;
}
}
@@ -0,0 +1,465 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Locale\Locale;
use Seboettg\CiteProc\Root\Info;
use Seboettg\CiteProc\Style\Bibliography;
use Seboettg\CiteProc\Style\Citation;
use Seboettg\CiteProc\Style\Macro;
use Seboettg\CiteProc\Style\Options\BibliographyOptions;
use Seboettg\CiteProc\Style\Options\CitationOptions;
use Seboettg\CiteProc\Style\Options\GlobalOptions;
use Seboettg\CiteProc\Style\Sort\Sort;
use Seboettg\CiteProc\Root\Root;
use Seboettg\CiteProc\Styles\Css\CssStyle;
use Seboettg\Collection\ArrayList;
/**
* Class Context
* @package Seboettg\CiteProc
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Context
{
/**
* @var ArrayList
*/
private $macros;
/**
* @var Locale
*/
private $locale;
/**
* @var Bibliography
*/
private $bibliography;
/**
* @var Citation
*/
private $citation;
/**
* @var Sort
*/
private $sorting;
/**
* @var string
*/
private $mode;
/**
* @var DataList
*/
private $citationData;
/**
* @var ArrayList
*/
private $citationItems;
/**
* @var ArrayList
*/
private $results;
/**
* @var Root
*/
private $root;
/**
* @var GlobalOptions
*/
private $globalOptions;
/**
* @var BibliographyOptions
*/
private $bibliographySpecificOptions;
/**
* @var CitationOptions
*/
private $citationSpecificOptions;
/**
* @var RenderingState
*/
private $renderingState;
/**
* @var CssStyle
*/
private $cssStyle;
/**
* @var Info
*/
private $info;
/**
* @var array
*/
protected $markupExtension = [];
/**
* @var bool
*/
private $citationsAsArray = false;
/**
* @var ArrayList
*/
private $citedItems;
public function __construct($locale = null)
{
if (!empty($locale)) {
$this->locale = $locale;
}
$this->macros = new ArrayList();
$this->citationData = new DataList();
$this->results = new ArrayList();
$this->renderingState = RenderingState::RENDERING();
$this->citedItems = new ArrayList();
}
public function addMacro($key, $macro)
{
$this->macros->add($key, $macro);
}
/**
* @param $key
* @return Macro
*/
public function getMacro($key)
{
return $this->macros->get($key);
}
/**
* @param Locale $locale
*/
public function setLocale(Locale $locale)
{
$this->locale = $locale;
}
/**
* @return Locale
*/
public function getLocale()
{
return $this->locale;
}
/**
* @return Bibliography
*/
public function getBibliography()
{
return $this->bibliography;
}
/**
* @param Bibliography $bibliography
*/
public function setBibliography(Bibliography $bibliography)
{
$this->bibliography = $bibliography;
}
/**
* @return Citation
*/
public function getCitation()
{
return $this->citation;
}
/**
* @param Citation $citation
*/
public function setCitation($citation)
{
$this->citation = $citation;
}
/**
* @param $citationsAsArray
*/
public function setCitationsAsArray($citationsAsArray = true)
{
$this->citationsAsArray = $citationsAsArray;
}
public function isCitationsAsArray()
{
return $this->citationsAsArray;
}
public function setSorting($sorting)
{
$this->sorting = $sorting;
}
public function getSorting()
{
return $this->sorting;
}
/**
* return the render mode (citation|bibliography)
* @return string
*/
public function getMode()
{
return $this->mode;
}
/**
* @param string $mode
*/
public function setMode($mode)
{
$this->mode = $mode;
}
/**
* returns true if the render mode is set to citation
* @return bool
*/
public function isModeCitation()
{
return $this->mode === "citation";
}
/**
* returns true if the render mode is set to bibliography
* @return bool
*/
public function isModeBibliography()
{
return $this->mode === "bibliography";
}
/**
* @return DataList
*/
public function getCitationData()
{
return $this->citationData;
}
/**
* @param ArrayList|DataList $citationData
*/
public function setCitationData($citationData)
{
$this->citationData = $citationData;
}
/**
* @return ArrayList
*/
public function getCitationItems(): ArrayList
{
return $this->citationItems;
}
/**
* @param ArrayList $citationItems
*/
public function setCitationItems(ArrayList $citationItems): void
{
$this->citationItems = $citationItems;
}
public function hasCitationItems()
{
return ($this->citationData->count() > 0);
}
/**
* @return ArrayList
*/
public function getMacros()
{
return $this->macros;
}
/**
* @return ArrayList
*/
public function getResults()
{
return $this->results;
}
/**
* @return Root
*/
public function getRoot()
{
return $this->root;
}
/**
* @param Root $root
*/
public function setRoot(Root $root)
{
$this->root = $root;
}
/**
* @return GlobalOptions
*/
public function getGlobalOptions()
{
return $this->globalOptions;
}
/**
* @param GlobalOptions $globalOptions
*/
public function setGlobalOptions(GlobalOptions $globalOptions)
{
$this->globalOptions = $globalOptions;
}
/**
* @return RenderingState
*/
public function getRenderingState()
{
return $this->renderingState;
}
/**
* @param RenderingState|string $renderingState
*/
public function setRenderingState(RenderingState $renderingState)
{
$this->renderingState = $renderingState;
}
/**
* @return BibliographyOptions
*/
public function getBibliographySpecificOptions()
{
return $this->bibliographySpecificOptions;
}
/**
* @param BibliographyOptions $bibliographySpecificOptions
*/
public function setBibliographySpecificOptions(BibliographyOptions $bibliographySpecificOptions)
{
$this->bibliographySpecificOptions = $bibliographySpecificOptions;
}
/**
* @return CitationOptions
*/
public function getCitationSpecificOptions()
{
return $this->citationSpecificOptions;
}
/**
* @param CitationOptions $citationSpecificOptions
*/
public function setCitationSpecificOptions(CitationOptions $citationSpecificOptions)
{
$this->citationSpecificOptions = $citationSpecificOptions;
}
/**
* @param CssStyle $cssStyle
*/
public function setCssStyle(CssStyle $cssStyle)
{
$this->cssStyle = $cssStyle;
}
/**
* @return CssStyle
*/
public function getCssStyle()
{
return $this->cssStyle;
}
public function setInfo(Info $info)
{
$this->info = $info;
}
public function getInfo()
{
return $this->info;
}
/**
* @return array
*/
public function getMarkupExtension()
{
return $this->markupExtension;
}
/**
* @param array $markupExtension
*/
public function setMarkupExtension($markupExtension)
{
$this->markupExtension = $markupExtension;
}
public function getCitationItemById($id)
{
return $this->citationItems->filter(function ($item) use ($id) {
return $item->id === $id;
})->current();
}
/**
* @return ArrayList
*/
public function getCitedItems(): ArrayList
{
return $this->citedItems;
}
/**
* @param ArrayList $citedItems
*/
public function setCitedItems(ArrayList $citedItems): void
{
$this->citedItems = $citedItems;
}
public function appendCitedItem($citedItem)
{
$this->citedItems->append($citedItem);
return $this;
}
}
@@ -0,0 +1,65 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Data;
use Seboettg\CiteProc\Style\Citation;
use Seboettg\CiteProc\Style\Options\SubsequentAuthorSubstituteRule;
use Seboettg\Collection\ArrayList;
/**
* Class DataList
*
* @package Seboettg\CiteProc\Data
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class DataList extends ArrayList
{
/**
* @var string
*/
private $subsequentAuthorSubstitute;
/**
* @var SubsequentAuthorSubstituteRule
*/
private $subsequentAuthorSubstituteRule = "complete-all";
/**
* @return string
*/
public function getSubsequentAuthorSubstitute()
{
return $this->subsequentAuthorSubstitute;
}
/**
* @param string $subsequentAuthorSubstitute
*/
public function setSubsequentAuthorSubstitute($subsequentAuthorSubstitute)
{
$this->subsequentAuthorSubstitute = $subsequentAuthorSubstitute;
}
/**
* @return SubsequentAuthorSubstituteRule
*/
public function getSubsequentAuthorSubstituteRule()
{
return $this->subsequentAuthorSubstituteRule;
}
/**
* @param SubsequentAuthorSubstituteRule $subsequentAuthorSubstituteRule
*/
public function setSubsequentAuthorSubstituteRule(SubsequentAuthorSubstituteRule $subsequentAuthorSubstituteRule)
{
$this->subsequentAuthorSubstituteRule = $subsequentAuthorSubstituteRule;
}
}
@@ -0,0 +1,23 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Exception;
use Exception;
/**
* Class CiteProcException
* @package Seboettg\CiteProc\Exception
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class CiteProcException extends Exception
{
}
@@ -0,0 +1,29 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Exception;
use Exception;
/**
* Class ClassNotFoundException
* @package Seboettg\CiteProc\Exception
*
* @codeCoverageIgnore
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class ClassNotFoundException extends CiteProcException
{
public function __construct($class, $code = 0, Exception $previous = null)
{
parent::__construct("Class \"$class\" could not be found.", $code, $previous);
}
}
@@ -0,0 +1,13 @@
<?php
/*
* citeproc-php: InvalidDateException.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 2019-03-01, 16:42
*/
namespace Seboettg\CiteProc\Exception;
class InvalidDateTimeException extends CiteProcException
{
}
@@ -0,0 +1,13 @@
<?php
/*
* citeproc-php: InvalidStylesheetException.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 2019-03-01, 16:08
*/
namespace Seboettg\CiteProc\Exception;
class InvalidStylesheetException extends CiteProcException
{
}
@@ -0,0 +1,146 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Locale;
use InvalidArgumentException;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\StyleSheet;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
use stdClass;
/**
* Class Locale
*
* While localization data can be included in styles, locale files conveniently provide sets of default localization
* data, consisting of terms, date formats and grammar options. These default localizations are drawn from the
* “locales-xx-XX.xml” located in locales folder (which is included as git submodule). These default locales may be
* redefined or supplemented with cs:locale elements, which should be placed in the style sheet directly after the
* cs:info element.
*
* TODO: implement Locale Fallback (http://docs.citationstyles.org/en/stable/specification.html#locale-fallback)
*
* @package Seboettg\CiteProc\Locale
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Locale
{
use LocaleXmlParserTrait;
/**
* @var SimpleXMLElement
*/
private $localeXml;
/**
* @var string
*/
private $language;
/**
* Locale constructor.
* @param string $lang
* @param ?string $xmlString
* @throws CiteProcException
*/
public function __construct($lang = "en-US", $xmlString = null)
{
$this->language = $lang;
if (!empty($xmlString)) {
$this->localeXml = new SimpleXMLElement($xmlString);
} else {
$this->localeXml = new SimpleXMLElement(StyleSheet::loadLocales($lang));
}
$this->initLocaleXmlParser();
$this->parseXml($this->localeXml);
}
/**
* @param SimpleXMLElement $xml
* @return $this
*/
public function addXml(SimpleXMLElement $xml)
{
$lang = (string) $xml->attributes('http://www.w3.org/XML/1998/namespace')->{'lang'};
if (empty($lang) || $this->getLanguage() === $lang || explode('-', $this->getLanguage())[0] === $lang) {
$this->parseXml($xml);
}
return $this;
}
/**
* @return string
*/
public function getLanguage()
{
return $this->language;
}
/**
* @param string $type
* @param $name
* @param string $form
* @return stdClass
*/
public function filter($type, $name, $form = "long")
{
if ('options' === $type) {
return $this->option($name);
}
if (!isset($this->{$type})) {
throw new InvalidArgumentException("There is no locale of type \"$type\".");
}
/** @var ArrayList $localeList */
$localeList = $this->{$type};
if (is_null($name)) {
$name = "";
}
//filter by name
$array = $localeList->get($name);
if (empty($array)) {
$ret = new stdClass();
$ret->name = null;
$ret->single = null;
$ret->multiple = null;
return $ret;
}
//filter by form
if ($type !== "options") {
/** @var Term $value */
$array = array_filter($array, function ($term) use ($form) {
return $term->form === $form;
});
}
return array_pop($array);
}
private function option($name)
{
$result = null;
foreach ($this->options as $key => $value) {
if ($key === $name) {
if (is_array($value) && isset($value[1]) && is_array($value[1])) {
$result = reset($value[1]);
} else {
$result = reset($value);
}
}
}
return $result;
}
}
@@ -0,0 +1,177 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Locale;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
use stdClass;
/**
* Trait LocaleXmlParserTrait
* @package Seboettg\CiteProc\Locale
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait LocaleXmlParserTrait
{
/**
* @var ArrayList
*/
private $options;
/**
* @var ArrayList
*/
private $date;
/**
* @var ArrayList
*/
private $terms;
/**
* @var ArrayList
*/
private $optionsXml;
/**
* @var ArrayList
*/
private $dateXml;
/**
* @var ArrayList
*/
private $termsXml;
/**
* init parser
*/
protected function initLocaleXmlParser()
{
$this->options = new ArrayList();
$this->optionsXml = new ArrayList();
$this->date = new ArrayList();
$this->dateXml = new ArrayList();
$this->terms = new ArrayList();
$this->termsXml = new ArrayList();
}
/**
* @param SimpleXMLElement $locale
*/
private function parseXml(SimpleXMLElement $locale)
{
/** @var SimpleXMLElement $node */
foreach ($locale as $node) {
switch ($node->getName()) {
case 'style-options':
$this->optionsXml->add('options', $node);
foreach ($node->attributes() as $name => $value) {
if ((string) $value == 'true') {
$this->options->add($name, [true]);
} else {
$this->options->add($name, [false]);
}
}
break;
case 'terms':
$this->termsXml->add('terms', $node);
$plural = ['single', 'multiple'];
/** @var SimpleXMLElement $child */
foreach ($node->children() as $child) {
$term = new Term();
foreach ($child->attributes() as $key => $value) {
$term->{$key} = (string) $value;
}
$subChildren = $child->children();
$count = $subChildren->count();
if ($count > 0) {
/** @var SimpleXMLElement $subChild */
foreach ($subChildren as $subChild) {
$name = $subChild->getName();
$value = (string) $subChild;
if (in_array($subChild->getName(), $plural)) {
$term->{$name} = $value;
}
}
} else {
$value = (string) $child;
$term->{'single'} = $value;
$term->{'multiple'} = $value;
}
if (!$this->terms->hasKey($term->getName())) {
$this->terms->add($term->getName(), []);
}
$this->terms->add($term->getName(), $term);
}
break;
case 'date':
$form = (string) $node["form"];
$this->dateXml->add($form, $node);
foreach ($node->children() as $child) {
$date = new stdClass();
$name = "";
foreach ($child->attributes() as $key => $value) {
if ("name" === $key) {
$name = (string) $value;
}
$date->{$key} = (string) $value;
}
if ($child->getName() !== "name-part" && !$this->terms->hasKey($name)) {
$this->terms->add($name, []);
}
$this->date->add($form, $date);
}
break;
}
}
}
/**
* @return SimpleXMLElement
*/
public function getLatestOptionsXml()
{
$arr = $this->optionsXml->toArray();
return array_pop($arr);
}
/**
* @return array
*/
public function getDateXml()
{
return $this->dateXml->toArray();
}
/**
* @return SimpleXMLElement
*/
public function getLatestDateXml()
{
$arr = $this->dateXml->toArray();
return array_pop($arr['date']);
}
/**
* @return SimpleXMLElement
*/
public function getTermsXml()
{
$arr = $this->termsXml->toArray();
return array_pop($arr);
}
}
@@ -0,0 +1,68 @@
<?php /** @noinspection PhpUnusedPrivateFieldInspection */
/**
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Locale;
use InvalidArgumentException;
use function Seboettg\CiteProc\ucfirst;
/**
* Class Term
* @package Seboettg\CiteProc\Locale
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Term
{
private $name = "";
private $form = "long";
private $single = "";
private $multiple = "";
private $match = "";
private $genderForm = "";
private $gender = "";
public function __set($name, $value)
{
$nameParts = explode("-", $name);
$attr = "";
for ($i = count($nameParts) - 1; $i >= 0; --$i) {
if ($i > 0) {
$attr = ucfirst($nameParts[$i]) . $attr;
} else {
$attr = $nameParts[$i] . $attr;
}
}
if (!isset($this->{$attr})) {
throw new InvalidArgumentException("Property \"$attr\" ($name) does not exist in " . __CLASS__);
}
$this->{$attr} = $value;
}
public function __get($name)
{
return $this->{$name};
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
}
@@ -0,0 +1,117 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Choose;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\ClassNotFoundException;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\HasParent;
use Seboettg\CiteProc\Rendering\Rendering;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class Choose
*
* @package Seboettg\CiteProc\Node
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Choose implements Rendering, HasParent
{
/**
* @var ArrayList
*/
private $children;
private $parent;
/**
* Choose constructor.
*
* @param SimpleXMLElement $node
* @param $parent
* @throws ClassNotFoundException
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, $parent)
{
$this->parent = $parent;
$this->children = new ArrayList();
$elseIf = new ArrayList();
foreach ($node->children() as $child) {
switch ($child->getName()) {
case 'if':
$this->children->add("if", new ChooseIf($child, $this));
break;
case 'else-if':
$elseIf->append(new ChooseElseIf($child, $this));
break;
case 'else':
$this->children->add("else", new ChooseElse($child, $this));
break;
}
}
if ($elseIf->count() > 0) {
$this->children->add("elseif", $elseIf);
}
}
/**
* @param array|DataList $data
* @param null|int $citationNumber
* @return string
* @throws ArrayList\NotConvertibleToStringException
*/
public function render($data, $citationNumber = null)
{
$result = new ArrayList();
$matchedIfs = false;
$ifCondition = $this->children->get("if");
if ($ifCondition->match($data)) { //IF CONDITION
$matchedIfs = true;
$result->append($ifCondition->render($data));
} elseif ($this->children->hasKey("elseif")) { // ELSEIF
$elseIfs = $this->children->get("elseif")
->map(function (ChooseIf $elseIf) use ($data) {
return new Tuple($elseIf, $elseIf->match($data));
})
->filter(function (Tuple $elseIfToMatch) {
return $elseIfToMatch->second === true;
});
$matchedIfs = $elseIfs->count() > 0;
if ($matchedIfs) {
$result->append(
$elseIfs
->first() //returns a Tuple
->first
->render($data)
);
}
}
// !$matchedIfs ensures that each previous condition has not been met
if (!$matchedIfs && $this->children->hasKey("else")) { //ELSE
$result->append($this->children->get("else")->render($data));
}
return $result->collectToString("");
}
/**
* @return mixed
*/
public function getParent()
{
return $this->parent;
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Choose;
class ChooseElse extends ChooseIf
{
//render function is inherited from ChooseIf
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Choose;
class ChooseElseIf extends ChooseElse
{
}
@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Choose;
use Seboettg\CiteProc\Constraint\Constraint;
use Seboettg\CiteProc\Constraint\Factory;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\ClassNotFoundException;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\Group;
use Seboettg\CiteProc\Rendering\HasParent;
use Seboettg\CiteProc\Rendering\Rendering;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
class ChooseIf implements Rendering, HasParent
{
/**
* @var ArrayList<Constraint>|Constraint[]
*/
private $constraints;
/**
* @var ArrayList
*/
protected $children;
/**
* @var string
*/
private $match;
/**
* @var
*/
protected $parent;
/**
* @param SimpleXMLElement $node
* @param Choose $parent
* @throws InvalidStylesheetException
* @throws ClassNotFoundException
*/
public function __construct(SimpleXMLElement $node, Choose $parent)
{
$this->parent = $parent;
$this->constraints = new ArrayList();
$this->children = new ArrayList();
$this->match = (string) $node['match'];
if (empty($this->match)) {
$this->match = Constraint::MATCH_ALL;
}
foreach ($node->attributes() as $name => $value) {
if ('match' !== $name) {
$this->constraints->append(Factory::createConstraint((string) $name, (string) $value, $this->match));
}
}
foreach ($node->children() as $child) {
$this->children->append(Factory::create($child, $this));
}
}
/**
* @param array|DataList $data
* @param null|int $citationNumber
* @return string
*/
public function render($data, $citationNumber = null): string
{
$ret = [];
/** @var Rendering $child */
foreach ($this->children as $child) {
$ret[] = $child->render($data, $citationNumber);
}
$glue = "";
$parent = $this->parent->getParent();
if ($parent instanceof Group && $parent->hasDelimiter()) {
$glue = $parent->getDelimiter();
}
return implode($glue, array_filter($ret));
}
/**
* @param $data
* @param null|int $citationNumber
* @return bool
*/
public function match($data, int $citationNumber = null): bool
{
if ($this->constraints->count() === 1) {
return $this->constraints->current()->validate($data);
}
switch ($this->match) {
case Constraint::MATCH_ANY:
return $this->constraints
->map(function (Constraint $constraint) use ($data) {
return $constraint->validate($data);
})
->filter(function (bool $match) {
return $match === true;
})
->count() > 0;
case Constraint::MATCH_ALL:
return $this->constraints
->map(function (Constraint $constraint) use ($data) {
return $constraint->validate($data);
})
->filter(function (bool $match) {
return $match === true;
})
->count() === $this->constraints->count();
case Constraint::MATCH_NONE:
return !$this->constraints
->map(function (Constraint $constraint) use ($data) {
return $constraint->validate($data);
})
->filter(function (bool $match) {
return $match === false;
})
->count() === $this->constraints->count();
}
return false;
}
/**
* @noinspection PhpUnused
* @return Choose
*/
public function getParent(): Choose
{
return $this->parent;
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2022 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Choose;
class Tuple
{
public $first;
public $second;
public function __construct($first, $second)
{
$this->first = $first;
$this->second = $second;
}
}
@@ -0,0 +1,412 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Date;
use Exception;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\Date\DateRange\DateRangeRenderer;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\DisplayTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Styles\TextCaseTrait;
use Seboettg\CiteProc\Util;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class Date
* @package Seboettg\CiteProc\Rendering
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Date
{
use AffixesTrait,
DisplayTrait,
FormattingTrait,
TextCaseTrait;
// bitmask: ymd
const DATE_RANGE_STATE_NONE = 0; // 000
const DATE_RANGE_STATE_DAY = 1; // 001
const DATE_RANGE_STATE_MONTH = 2; // 010
const DATE_RANGE_STATE_MONTHDAY = 3; // 011
const DATE_RANGE_STATE_YEAR = 4; // 100
const DATE_RANGE_STATE_YEARDAY = 5; // 101
const DATE_RANGE_STATE_YEARMONTH = 6; // 110
const DATE_RANGE_STATE_YEARMONTHDAY = 7; // 111
private static $localizedDateFormats = [
'numeric',
'text'
];
/**
* @var ArrayList
*/
private $dateParts;
/**
* @var string
*/
private $form = "";
/**
* @var string
*/
private $variable = "";
/**
* @var string
*/
private $datePartsAttribute = "";
/**
* Date constructor.
* @param SimpleXMLElement $node
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node)
{
$this->dateParts = new ArrayList();
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'form':
$this->form = (string) $attribute;
break;
case 'variable':
$this->variable = (string) $attribute;
break;
case 'date-parts':
$this->datePartsAttribute = (string) $attribute;
}
}
/** @var SimpleXMLElement $child */
foreach ($node->children() as $child) {
if ($child->getName() === "date-part") {
$datePartName = (string) $child->attributes()["name"];
$this->dateParts->set($this->form . "-" . $datePartName, Util\Factory::create($child));
}
}
$this->initAffixesAttributes($node);
$this->initDisplayAttributes($node);
$this->initFormattingAttributes($node);
$this->initTextCaseAttributes($node);
}
/**
* @param $data
* @return string
* @throws InvalidStylesheetException
* @throws Exception
*/
public function render($data)
{
$ret = "";
$var = null;
if (isset($data->{$this->variable})) {
$var = $data->{$this->variable};
} else {
return "";
}
try {
$this->prepareDatePartsInVariable($data, $var);
} catch (CiteProcException $e) {
if (isset($data->{$this->variable}->{'raw'}) &&
!preg_match("/(\p{L}+)\s?([\-\&,])\s?(\p{L}+)/u", $data->{$this->variable}->{'raw'})) {
return $this->addAffixes($this->format($this->applyTextCase($data->{$this->variable}->{'raw'})));
} else {
if (isset($data->{$this->variable}->{'string-literal'})) {
return $this->addAffixes(
$this->format($this->applyTextCase($data->{$this->variable}->{'string-literal'}))
);
}
}
}
$form = $this->form;
$dateParts = !empty($this->datePartsAttribute) ? explode("-", $this->datePartsAttribute) : [];
$this->prepareDatePartsChildren($dateParts, $form);
// No date-parts in date-part attribute defined, take into account that the defined date-part children will
// be used.
if (empty($this->datePartsAttribute) && $this->dateParts->count() > 0) {
/** @var DatePart $part */
foreach ($this->dateParts as $part) {
$dateParts[] = $part->getName();
}
}
/* cs:date may have one or more cs:date-part child elements (see Date-part). The attributes set on
these elements override those specified for the localized date formats (e.g. to get abbreviated months for all
locales, the form attribute on the month-cs:date-part element can be set to “short”). These cs:date-part
elements do not affect which, or in what order, date parts are rendered. Affixes, which are very
locale-specific, are not allowed on these cs:date-part elements. */
if ($this->dateParts->count() > 0) {
if (!isset($var->{'date-parts'})) { // ignore empty date-parts
return "";
}
if (count($data->{$this->variable}->{'date-parts'}) === 1) {
$data_ = $this->createDateTime($data->{$this->variable}->{'date-parts'});
$ret .= $this->iterateAndRenderDateParts($dateParts, $data_);
} elseif (count($var->{'date-parts'}) === 2) { //date range
$data_ = $this->createDateTime($var->{'date-parts'});
$from = $data_[0];
$to = $data_[1];
$interval = $to->diff($from);
$delimiter = "";
$toRender = 0;
if ($interval->y > 0 && in_array('year', $dateParts)) {
$toRender |= self::DATE_RANGE_STATE_YEAR;
$delimiter = $this->dateParts->get($this->form."-year")->getRangeDelimiter();
}
if ($interval->m > 0 && $from->getMonth() - $to->getMonth() !== 0 && in_array('month', $dateParts)) {
$toRender |= self::DATE_RANGE_STATE_MONTH;
$delimiter = $this->dateParts->get($this->form."-month")->getRangeDelimiter();
}
if ($interval->d > 0 && $from->getDay() - $to->getDay() !== 0 && in_array('day', $dateParts)) {
$toRender |= self::DATE_RANGE_STATE_DAY;
$delimiter = $this->dateParts->get($this->form."-day")->getRangeDelimiter();
}
if ($toRender === self::DATE_RANGE_STATE_NONE) {
$ret .= $this->iterateAndRenderDateParts($dateParts, $data_);
} else {
$ret .= $this->renderDateRange($toRender, $from, $to, $delimiter);
}
}
if (isset($var->raw) && preg_match("/(\p{L}+)\s?([\-\&,])\s?(\p{L}+)/u", $var->raw, $matches)) {
return $matches[1].$matches[2].$matches[3];
}
} elseif (!empty($this->datePartsAttribute)) {
// fallback:
// When there are no dateParts children, but date-parts attribute in date
// render numeric
$data = $this->createDateTime($var->{'date-parts'});
$ret = $this->renderNumeric($data[0]);
}
return !empty($ret) ? $this->addAffixes($this->format($this->applyTextCase($ret))) : "";
}
/**
* @param array $dates
* @return array
* @throws Exception
*/
private function createDateTime($dates)
{
$data = [];
foreach ($dates as $date) {
$date = $this->cleanDate($date);
if ($date[0] < 1000) {
$dateTime = new DateTime(0, 0, 0);
$dateTime->setDay(0)->setMonth(0)->setYear(0);
$data[] = $dateTime;
}
$dateTime = new DateTime(
$date[0],
array_key_exists(1, $date) ? $date[1] : 1,
array_key_exists(2, $date) ? $date[2] : 1
);
if (!array_key_exists(1, $date)) {
$dateTime->setMonth(0);
}
if (!array_key_exists(2, $date)) {
$dateTime->setDay(0);
}
$data[] = $dateTime;
}
return $data;
}
/**
* @param int $toRender
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
private function renderDateRange($toRender, DateTime $from, DateTime $to, $delimiter)
{
$datePartRenderer = DateRangeRenderer::factory($this, $toRender);
return $datePartRenderer->parseDateRange($this->dateParts, $from, $to, $delimiter);
}
/**
* @param string $format
* @return bool
*/
private function hasDatePartsFromLocales($format)
{
$dateXml = CiteProc::getContext()->getLocale()->getDateXml();
return !empty($dateXml[$format]);
}
/**
* @param string $format
* @return array
*/
private function getDatePartsFromLocales($format)
{
$ret = [];
// date parts from locales
$dateFromLocale_ = CiteProc::getContext()->getLocale()->getDateXml();
$dateFromLocale = $dateFromLocale_[$format];
// no custom date parts within the date element (this)?
if (!empty($dateFromLocale)) {
$dateForm = array_filter(
is_array($dateFromLocale) ? $dateFromLocale : [$dateFromLocale],
function ($element) use ($format) {
/** @var SimpleXMLElement $element */
$dateForm = (string) $element->attributes()["form"];
return $dateForm === $format;
}
);
//has dateForm from locale children (date-part elements)?
$localeDate = array_pop($dateForm);
if ($localeDate instanceof SimpleXMLElement && $localeDate->count() > 0) {
foreach ($localeDate as $child) {
$ret[] = $child;
}
}
}
return $ret;
}
/**
* @return string
*/
public function getVariable()
{
return $this->variable;
}
/**
* @param $data
* @param $var
* @throws CiteProcException
*/
private function prepareDatePartsInVariable($data, $var)
{
if (!isset($data->{$this->variable}->{'date-parts'}) || empty($data->{$this->variable}->{'date-parts'})) {
if (isset($data->{$this->variable}->raw) && !empty($data->{$this->variable}->raw)) {
// try to parse date parts from "raw" attribute
$var->{'date-parts'} = Util\DateHelper::parseDateParts($data->{$this->variable});
} else {
throw new CiteProcException("No valid date format");
}
}
}
/**
* @param $dateParts
* @param string $form
* @throws InvalidStylesheetException
*/
private function prepareDatePartsChildren($dateParts, $form)
{
/* Localized date formats are selected with the optional form attribute, which must set to either “numeric”
(for fully numeric formats, e.g. “12-15-2005”), or “text” (for formats with a non-numeric month, e.g.
“December 15, 2005”). Localized date formats can be customized in two ways. First, the date-parts attribute may
be used to show fewer date parts. The possible values are:
- “year-month-day” - (default), renders the year, month and day
- “year-month” - renders the year and month
- “year” - renders the year */
if ($this->dateParts->count() < 1 && in_array($form, self::$localizedDateFormats)) {
if ($this->hasDatePartsFromLocales($form)) {
$datePartsFromLocales = $this->getDatePartsFromLocales($form);
array_filter($datePartsFromLocales, function (SimpleXMLElement $item) use ($dateParts) {
return in_array($item["name"], $dateParts);
});
foreach ($datePartsFromLocales as $datePartNode) {
$datePart = $datePartNode["name"];
$this->dateParts->set("$form-$datePart", Util\Factory::create($datePartNode));
}
} else { //otherwise create default date parts
foreach ($dateParts as $datePart) {
$this->dateParts->add(
"$form-$datePart",
new DatePart(
new SimpleXMLElement('<date-part name="'.$datePart.'" form="'.$form.'" />')
)
);
}
}
}
}
private function renderNumeric(DateTime $date)
{
return $date->renderNumeric();
}
public function getForm()
{
return $this->form;
}
private function cleanDate($date)
{
$ret = [];
foreach ($date as $key => $datePart) {
$ret[$key] = Util\NumberHelper::extractNumber(Util\StringHelper::removeBrackets($datePart));
}
return $ret;
}
/**
* @param array $dateParts
* @param array $data_
* @return string
*/
private function iterateAndRenderDateParts(array $dateParts, array $data_)
{
$result = [];
/** @var DatePart $datePart */
foreach ($this->dateParts as $key => $datePart) {
/** @noinspection PhpUnusedLocalVariableInspection */
list($f, $p) = explode("-", $key);
if (in_array($p, $dateParts)) {
$result[] = $datePart->render($data_[0], $this);
}
}
$result = array_filter($result);
$glue = $this->datePartsHaveAffixes() ? "" : " ";
$return = implode($glue, $result);
return trim($return);
}
/**
* @return bool
*/
private function datePartsHaveAffixes()
{
$result = $this->dateParts->filter(function (DatePart $datePart) {
return $datePart->renderSuffix() !== "" || $datePart->renderPrefix() !== "";
});
return $result->count() > 0;
}
}
@@ -0,0 +1,230 @@
<?php
/**
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Date;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Rendering\Layout;
use Seboettg\CiteProc\Rendering\Number;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Styles\RangeDelimiterTrait;
use Seboettg\CiteProc\Styles\TextCaseTrait;
use SimpleXMLElement;
/**
* Class DatePart
* @package Seboettg\CiteProc\Rendering\Date
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class DatePart
{
const DEFAULT_RANGE_DELIMITER = "";
use FormattingTrait,
AffixesTrait,
TextCaseTrait,
RangeDelimiterTrait;
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $form;
/**
* @var string
*/
private $rangeDelimiter;
/**
* @var Date
*/
private $parent;
public function __construct(SimpleXMLElement $node)
{
foreach ($node->attributes() as $attribute) {
if ("name" === $attribute->getName()) {
$this->name = (string) $attribute;
}
if ("form" === $attribute->getName()) {
$this->form = (string) $attribute;
}
if ("range-delimiter" === $attribute->getName()) {
$this->rangeDelimiter = (string) $attribute;
}
}
if (empty($this->rangeDelimiter)) {
$this->rangeDelimiter = self::DEFAULT_RANGE_DELIMITER;
}
$this->initFormattingAttributes($node);
$this->initAffixesAttributes($node);
$this->initTextCaseAttributes($node);
}
/**
* @param DateTime $date
* @param Date $parent
* @return string
*/
public function render(DateTime $date, Date $parent)
{
$this->parent = $parent; //set parent
$text = $this->renderWithoutAffixes($date);
return !empty($text) ? $this->addAffixes($text) : "";
}
/**
* @param DateTime $date
* @param Date|null $parent
* @return string
*/
public function renderWithoutAffixes(DateTime $date, Date $parent = null)
{
if (!is_null($parent)) {
$this->parent = $parent;
}
$text = "";
switch ($this->name) {
case 'year':
$text = $this->renderYear($date);
break;
case 'month':
$text = $this->renderMonth($date);
break;
case 'day':
$text = $this->renderDay($date);
}
return !empty($text) ? $this->format($this->applyTextCase($text)) : "";
}
/**
* @return string
*/
public function getForm()
{
return $this->form;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return string
*/
public function getRangeDelimiter()
{
return $this->rangeDelimiter;
}
/**
* @param DateTime $date
* @return string|int
*/
protected function renderYear(DateTime $date)
{
$text = $date->getYear();
if ($text > 0 && $text < 1000) {
$text = $text . CiteProc::getContext()->getLocale()->filter("terms", "ad")->single;
return $text;
} elseif ($text < 0) {
$text = $text * -1;
$text = $text . CiteProc::getContext()->getLocale()->filter("terms", "bc")->single;
return $text;
}
return $text;
}
/**
* @param DateTime $date
* @return string
*/
protected function renderMonth(DateTime $date)
{
if ($date->getMonth() < 1 || $date->getMonth() > 12) {
return "";
}
$text = $date->getMonth();
$form = !empty($this->form) ? $this->form : "long";
switch ($form) {
case 'numeric':
break;
case 'numeric-leading-zeros':
$text = sprintf("%02d", $text);
break;
case 'short':
case 'long':
default:
$text = $this->monthFromLocale($text, $form);
break;
}
return $text;
}
/**
* @param DateTime $date
* @return int|string
*/
protected function renderDay(DateTime $date)
{
if ($date->getDay() < 1 || $date->getDay() > 31) {
return "";
}
$text = $date->getDay();
$form = !empty($this->form) ? $this->form : $this->parent->getForm();
switch ($form) {
case 'numeric':
break;
case 'numeric-leading-zeros':
$text = sprintf("%02d", $text);
break;
case 'ordinal':
$limitDayOrdinals =
CiteProc::getContext()->getLocale()->filter("options", "limit-day-ordinals-to-day-1");
if (!$limitDayOrdinals || Layout::getNumberOfCitedItems() <= 1) {
$text = Number::ordinal($text);
}
}
return $text;
}
/**
* @param $text
* @param $form
* @return mixed
*/
protected function monthFromLocale($text, $form)
{
if (empty($form)) {
$form = "long";
}
$month = 'month-' . sprintf('%02d', $text);
$text = CiteProc::getContext()->getLocale()->filter('terms', $month, $form)->single;
return $text;
}
}
@@ -0,0 +1,121 @@
<?php
/*
* citeproc-php: DateRangeParser.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:00
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\CiteProc\Rendering\Date\Date;
use Seboettg\Collection\ArrayList;
/**
* Class DatePartRenderer
*
* @package Seboettg\CiteProc\Rendering\Date\DateRange
*/
abstract class DateRangeRenderer
{
/**
* @var Date
*/
protected $parentDateObject;
/**
* @param Date $dateObject
* @param int $toRender
* @return DateRangeRenderer
*/
public static function factory(Date $dateObject, $toRender)
{
$className = self::getRenderer($toRender);
return new $className($dateObject);
}
/**
* DatePartRenderer constructor.
*
* @param Date $parentDateObject
*/
public function __construct(Date $parentDateObject)
{
$this->parentDateObject = $parentDateObject;
}
private static function getRenderer($toRender)
{
$className = "";
switch ($toRender) {
case Date::DATE_RANGE_STATE_DAY:
$className = "DayRenderer";
break;
case Date::DATE_RANGE_STATE_MONTH:
$className = "MonthRenderer";
break;
case Date::DATE_RANGE_STATE_YEAR:
$className = "YearRenderer";
break;
case Date::DATE_RANGE_STATE_MONTHDAY:
$className = "MonthDayRenderer";
break;
case Date::DATE_RANGE_STATE_YEARDAY:
$className = "YearDayRenderer";
break;
case Date::DATE_RANGE_STATE_YEARMONTH:
$className = "YearMonthRenderer";
break;
case Date::DATE_RANGE_STATE_YEARMONTHDAY:
$className = "YearMonthDayRenderer";
break;
}
return __NAMESPACE__ . "\\" . $className;
}
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
abstract public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter);
/**
* @param DatePart $datePart
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
protected function renderOneRangePart(DatePart $datePart, DateTime $from, DateTime $to, $delimiter)
{
$prefix = $datePart->renderPrefix();
$from = $datePart->renderWithoutAffixes($from, $this->parentDateObject);
$to = $datePart->renderWithoutAffixes($to, $this->parentDateObject);
$suffix = !empty($to) ? $datePart->renderSuffix() : "";
return $prefix . $from . $delimiter . $to . $suffix;
}
protected function renderDateParts($dateParts, $from, $to, $delimiter)
{
$ret = "";
foreach ($dateParts as $datePart) {
if (is_array($datePart)) {
$renderedFrom = $datePart[0]->render($from, $this->parentDateObject);
$renderedFrom .= $datePart[1]->renderPrefix();
$renderedFrom .= $datePart[1]->renderWithoutAffixes($from, $this->parentDateObject);
$renderedTo = $datePart[0]->renderWithoutAffixes($to, $this->parentDateObject);
$renderedTo .= $datePart[0]->renderSuffix();
$renderedTo .= $datePart[1]->render($to, $this->parentDateObject);
$ret .= $renderedFrom . $delimiter . $renderedTo;
} else {
$ret .= $datePart->render($from, $this->parentDateObject);
}
}
return $ret;
}
}
@@ -0,0 +1,44 @@
<?php
/*
* citeproc-php: DateRangeDayRenderer.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:09
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class DayRenderer
* @package Seboettg\CiteProc\Rendering\Date\DateRange
*/
class DayRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$ret = "";
foreach ($dateParts as $key => $datePart) {
if (strpos($key, "year") !== false) {
$ret .= $datePart->render($from, $this->parentDateObject);
}
if (strpos($key, "month") !== false) {
$ret .= $datePart->render($from, $this->parentDateObject);
}
if (strpos($key, "day")) {
$ret .= $this->renderOneRangePart($datePart, $from, $to, $delimiter);
}
}
return $ret;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* citeproc-php: DateRangeMonthDayRenderer.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:51
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class MonthDayRenderer
* @package Seboettg\CiteProc\Rendering\Date\DateRange
*/
class MonthDayRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$dp = $dateParts->toArray();
$dateParts_ = [];
array_walk($dp, function ($datePart, $key) use (&$dateParts_) {
//$bit = sprintf("%03d", decbin($differentParts));
if (strpos($key, "month") !== false || strpos($key, "day") !== false) {
$dateParts_["monthday"][] = $datePart;
}
if (strpos($key, "year") !== false) {
$dateParts_["year"] = $datePart;
}
});
return $this->renderDateParts($dateParts_, $from, $to, $delimiter);
}
}
@@ -0,0 +1,46 @@
<?php
/*
* citeproc-php: DateRangeMonthRenderer.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:09
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class DateRangeMonthRenderer
* @package Seboettg\CiteProc\Rendering\Date
*/
class MonthRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$ret = "";
foreach ($dateParts as $key => $datePart) {
if (strpos($key, "year") !== false) {
$ret .= $datePart->render($from, $this->parentDateObject);
}
if (strpos($key, "month")) {
$ret .= $this->renderOneRangePart($datePart, $from, $to, $delimiter);
}
if (strpos($key, "day") !== false) {
$day = !empty($d = $from->getDay()) ? $datePart->render($from, $this->parentDateObject) : "";
$ret .= $day;
}
}
return $ret;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* citeproc-php: DateRangeYearDayRenderer.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:47
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class YearDayRenderer
* @package Seboettg\CiteProc\Rendering\Date\DateRange
*/
class YearDayRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$dp = $dateParts->toArray();
$dateParts_ = [];
array_walk($dp, function ($datePart, $key) use (&$dateParts_) {
if (strpos($key, "year") !== false || strpos($key, "day") !== false) {
$dateParts_["yearday"][] = $datePart;
}
if (strpos($key, "month") !== false) {
$dateParts_["month"] = $datePart;
}
});
return $this->renderDateParts($dateParts_, $from, $to, $delimiter);
}
}
@@ -0,0 +1,55 @@
<?php
/*
* citeproc-php: DateRangeYearMonthDayRenderer.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:24
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class YearMonthDayRenderer
* @package Seboettg\CiteProc\Rendering\Date\DateRange
*/
class YearMonthDayRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$ret = "";
$i = 0;
foreach ($dateParts as $datePart) {
if ($i === $dateParts->count() - 1) {
$ret .= $datePart->renderPrefix();
$ret .= $datePart->renderWithoutAffixes($from, $this->parentDateObject);
} else {
$ret .= $datePart->render($from, $this->parentDateObject);
}
++$i;
}
$ret .= $delimiter;
$i = 0;
/** @var DatePart $datePart */
foreach ($dateParts as $datePart) {
if ($i == 0) {
$ret .= $datePart->renderWithoutAffixes($to, $this->parentDateObject);
$ret .= $datePart->renderSuffix();
} else {
$ret .= $datePart->render($to, $this->parentDateObject);
}
++$i;
}
return $ret;
}
}
@@ -0,0 +1,42 @@
<?php
/*
* citeproc-php: DateRangeYearMonthRenderer.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:36
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class YearMonthRenderer
* @package Seboettg\CiteProc\Rendering\Date\DateRange
*/
class YearMonthRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$dp = $dateParts->toArray();
$dateParts_ = [];
array_walk($dp, function ($datePart, $key) use (&$dateParts_) {
if (strpos($key, "year") !== false || strpos($key, "month") !== false) {
$dateParts_["yearmonth"][] = $datePart;
}
if (strpos($key, "day") !== false) {
$dateParts_["day"] = $datePart;
}
});
return $this->renderDateParts($dateParts_, $from, $to, $delimiter);
}
}
@@ -0,0 +1,46 @@
<?php
/*
* citeproc-php: DateRangeYearParser.php
* User: Sebastian Böttger <sebastian.boettger@thomascook.de>
* created at 03.11.19, 20:01
*/
namespace Seboettg\CiteProc\Rendering\Date\DateRange;
use Seboettg\CiteProc\Rendering\Date\DatePart;
use Seboettg\CiteProc\Rendering\Date\DateTime;
use Seboettg\Collection\ArrayList;
/**
* Class DateRangeYearRenderer
* @package Seboettg\CiteProc\Rendering\Date
*/
class YearRenderer extends DateRangeRenderer
{
/**
* @param ArrayList<DatePart> $dateParts
* @param DateTime $from
* @param DateTime $to
* @param $delimiter
* @return string
*/
public function parseDateRange(ArrayList $dateParts, DateTime $from, DateTime $to, $delimiter)
{
$ret = "";
foreach ($dateParts as $key => $datePart) {
if (strpos($key, "year") !== false) {
$ret .= $this->renderOneRangePart($datePart, $from, $to, $delimiter);
}
if (strpos($key, "month") !== false) {
$day = !empty($d = $from->getMonth()) ? $d : "";
$ret .= $day;
}
if (strpos($key, "day") !== false) {
$day = !empty($d = $from->getDay()) ? $datePart->render($from, $this->parentDateObject) : "";
$ret .= $day;
}
}
return $ret;
}
}
@@ -0,0 +1,141 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Date;
use DateTimeZone;
use Exception;
use Seboettg\CiteProc\Exception\InvalidDateTimeException;
class DateTime extends \DateTime
{
/**
* @var int
*/
private $year = 0;
/**
* @var int
*/
private $month = 0;
/**
* @var int
*/
private $day = 0;
/**
* DateTime constructor.
* @param string $year
* @param string $month
* @param string $day
* @throws Exception
*/
public function __construct($year, $month, $day)
{
try {
parent::__construct("$year-$month-$day", new DateTimeZone("Europe/Berlin"));
} catch (Exception $e) {
throw new InvalidDateTimeException("Could not create valid date with year=$year, month=$month, day=$day.");
}
$this->year = intval(self::format("Y"));
$this->month = intval(self::format("n"));
$this->day = intval(self::format("j"));
}
/**
* @param int $year
* @return $this
*/
public function setYear($year)
{
$this->year = $year;
return $this;
}
/**
* @param int $month
* @return $this
*/
public function setMonth($month)
{
$this->month = $month;
return $this;
}
/**
* @param int $day
* @return $this
*/
public function setDay($day)
{
$this->day = $day;
return $this;
}
/**
* @param int $year
* @param int $month
* @param int $day
* @return $this
*/
#[\ReturnTypeWillChange]
public function setDate($year, $month, $day)
{
$this->year = $year;
$this->month = $month;
$this->day = $day;
parent::setDate($year, $month, $day);
return $this;
}
/**
* @return array
*/
public function getArray()
{
return [$this->year, $this->month, $this->day];
}
/**
* @return int
*/
public function getYear()
{
return $this->year;
}
/**
* @return int
*/
public function getMonth()
{
return $this->month;
}
/**
* @return int
*/
public function getDay()
{
return $this->day;
}
/**
* @return string
*/
public function renderNumeric()
{
$ret = $this->year;
$ret .= $this->month > 0 && $this->month < 13 ? "-".sprintf("%02s", $this->month) : "";
$ret .= $this->day > 0 && $this->day < 32 ? "-".sprintf("%02s", $this->day) : "";
return $ret;
}
}
@@ -0,0 +1,198 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
use Seboettg\CiteProc\Styles\DelimiterTrait;
use Seboettg\CiteProc\Styles\DisplayTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Util\Factory;
use Seboettg\CiteProc\Util\StringHelper;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class Group
* The cs:group rendering element must contain one or more rendering elements (with the exception of cs:layout).
* cs:group may carry the delimiter attribute to separate its child elements, as well as affixes and display attributes
* (applied to the output of the group as a whole) and formatting attributes (transmitted to the enclosed elements).
* cs:group implicitly acts as a conditional: cs:group and its child elements are suppressed if a) at least one
* rendering element in cs:group calls a variable (either directly or via a macro), and b) all variables that are
* called are empty. This accommodates descriptive cs:text elements.
*
* @package Seboettg\CiteProc\Rendering
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Group implements Rendering, HasParent
{
use DelimiterTrait,
AffixesTrait,
DisplayTrait,
FormattingTrait,
ConsecutivePunctuationCharacterTrait;
/**
* @var ArrayList
*/
private $children;
/**
* cs:group may carry the delimiter attribute to separate its child elements
*
* @var
*/
private $delimiter = "";
private $parent;
/**
* @var array
*/
private $renderedChildsWithVariable = [];
/**
* Group constructor.
*
* @param SimpleXMLElement $node
* @param $parent
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, $parent)
{
$this->parent = $parent;
$this->children = new ArrayList();
foreach ($node->children() as $child) {
$this->children->append(Factory::create($child, $this));
}
$this->initDisplayAttributes($node);
$this->initAffixesAttributes($node);
$this->initDelimiterAttributes($node);
$this->initFormattingAttributes($node);
}
/**
* @param $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
$textParts = [];
$terms = $variables = $haveVariables = $elementCount = 0;
foreach ($this->children as $child) {
$elementCount++;
if (($child instanceof Text)
&& ($child->getSource() == 'term'
|| $child->getSource() == 'value')
) {
++$terms;
}
if (($child instanceof Label)) {
++$terms;
}
if (method_exists($child, "getSource") && $child->getSource() == 'variable'
&& !empty($child->getVariable()) && $child->getVariable() != "date"
&& !empty($data->{$child->getVariable()})
) {
++$variables;
}
$text = $child->render($data, $citationNumber);
$delimiter = $this->delimiter;
if (!empty($text)) {
if ($delimiter && ($elementCount < count($this->children))) {
//check to see if the delimiter is already the last character of the text string
//if so, remove it so we don't have two of them when the group will be merged
$stext = strip_tags(trim($text));
if ((strrpos($stext, $delimiter[0]) + 1) == strlen($stext) && strlen($stext) > 1) {
$text = str_replace($stext, '----REPLACE----', $text);
$stext = substr($stext, 0, -1);
$text = str_replace('----REPLACE----', $stext, $text);
}
}
$textParts[] = $text;
if (method_exists($child, "getSource") && $child->getSource() == 'variable'
|| (method_exists(
$child,
"getVariable"
) && $child->getVariable() != "date" && !empty($child->getVariable()))
) {
$haveVariables++;
}
if (method_exists($child, "getSource") && $child->getSource() == 'macro') {
$haveVariables++;
}
}
}
return $this->formatting($textParts, $variables, $haveVariables, $terms);
}
/**
* @return mixed
*/
public function getParent()
{
return $this->parent;
}
/**
* @param $textParts
* @param $variables
* @param $haveVariables
* @param $terms
* @return string
*/
protected function formatting($textParts, $variables, $haveVariables, $terms)
{
if (empty($textParts)) {
return "";
}
if ($variables && !$haveVariables) {
return ""; // there has to be at least one other none empty value before the term is output
}
if (count($textParts) == $terms) {
return ""; // there has to be at least one other none empty value before the term is output
}
$text = StringHelper::implodeAndPreventConsecutiveChars($this->delimiter, $textParts);
if (!empty($text)) {
return $this->wrapDisplayBlock($this->addAffixes($this->format(($text))));
}
return "";
}
/**
* @return bool
*/
public function hasDelimiter()
{
return !empty($this->delimiter);
}
/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
}
@@ -0,0 +1,20 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
/**
* Interface HasParent
* @package Seboettg\CiteProc\Rendering
* @author Sebastian Böttger <seboettg@gmail.com>
*/
interface HasParent
{
public function getParent();
}
@@ -0,0 +1,262 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Styles\TextCaseTrait;
use SimpleXMLElement;
use stdClass;
/**
* Class Label
* @package Seboettg\CiteProc\Rendering
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Label implements Rendering
{
use AffixesTrait,
FormattingTrait,
TextCaseTrait;
private $variable;
/**
* Selects the form of the term, with allowed values:
*
* - “long” - (default), e.g. “page”/”pages” for the “page” term
* - “short” - e.g. “p.”/”pp.” for the “page” term
* - “symbol” - e.g. “§”/”§§” for the “section” term
*
* @var string
*/
private $form = "";
/**
* Sets pluralization of the term, with allowed values:
*
* - “contextual” - (default), the term plurality matches that of the variable content. Content is considered
* plural when it contains multiple numbers (e.g. “page 1”, “pages 1-3”, “volume 2”, “volumes 2 & 4”), or, in
* the case of the “number-of-pages” and “number-of-volumes” variables, when the number is higher than 1
* (“1 volume” and “3 volumes”).
* - “always” - always use the plural form, e.g. “pages 1” and “pages 1-3”
* - “never” - always use the singular form, e.g. “page 1” and “page 1-3”
*
* @var string
*/
private $plural = "contextual";
/**
* Label constructor.
* @param SimpleXMLElement $node
*/
public function __construct(SimpleXMLElement $node)
{
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case "variable":
$this->variable = (string) $attribute;
break;
case "form":
$this->form = (string) $attribute;
break;
case "plural":
$this->plural = (string) $attribute;
break;
}
}
$this->initFormattingAttributes($node);
$this->initAffixesAttributes($node);
$this->initTextCaseAttributes($node);
}
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
$lang = (isset($data->language) && $data->language != 'en') ? $data->language : 'en';
$text = '';
$variables = explode(' ', $this->variable);
$form = !empty($this->form) ? $this->form : 'long';
$plural = $this->defaultPlural();
if ($this->variable === "editortranslator") {
if (isset($data->editor) && isset($data->translator)) {
$plural = $this->getPlural($data, $plural, "editortranslator");
$term = CiteProc::getContext()->getLocale()->filter('terms', "editortranslator", $form);
$pluralForm = $term->{$plural};
if (!empty($pluralForm)) {
$text = $pluralForm;
}
}
} elseif ($this->variable === "locator") {
$citationItem = CiteProc::getContext()->getCitationItemById($data->id);
if (!empty($citationItem->label)) {
$plural = $this->evaluateStringPluralism($citationItem->locator, $citationItem->label);
$term = CiteProc::getContext()->getLocale()->filter('terms', $citationItem->label, $form);
$pluralForm = $term->{$plural} ?? "";
if (!empty($citationItem->locator) && !empty($pluralForm)) {
$text = $pluralForm;
}
}
} else {
foreach ($variables as $variable) {
if (isset($data->{$variable})) {
$plural = $this->getPlural($data, $plural, $variable);
$term = CiteProc::getContext()->getLocale()->filter('terms', $variable, $form);
$pluralForm = $term->{$plural} ?? "";
if (!empty($data->{$variable}) && !empty($pluralForm)) {
$text = $pluralForm;
break;
}
}
}
}
return $this->formatting($text, $lang);
}
/**
* @param string $str
* @param string $variable
* @return string
*/
private function evaluateStringPluralism($str, $variable)
{
$plural = 'single';
if (!empty($str)) {
switch ($variable) {
case 'page':
case 'chapter':
case 'folio':
$pageRegex = "/([a-zA-Z]*)([0-9]+)\s*(?:|-)\s*([a-zA-Z]*)([0-9]+)/";
$err = preg_match($pageRegex, $str, $m);
if ($err !== false && count($m) == 0) {
$plural = 'single';
} elseif ($err !== false && count($m)) {
$plural = 'multiple';
}
break;
default:
if (is_numeric($str)) {
return $str > 1 ? 'multiple' : 'single';
}
}
}
return $plural;
}
/**
* @param string $variable
*/
public function setVariable($variable)
{
$this->variable = $variable;
}
/**
* @param $data
* @param $plural
* @param $variable
* @return string
*/
protected function getPlural($data, $plural, $variable)
{
if ($variable === "editortranslator" && isset($data->editor)) {
$var = $data->editor;
} else {
$var = $data->{$variable};
}
if (((!isset($this->plural) || empty($plural))) && !empty($var)) {
if (is_array($var)) {
$count = count($var);
if ($count == 1) {
$plural = 'single';
return $plural;
} elseif ($count > 1) {
$plural = 'multiple';
return $plural;
}
return $plural;
} else {
return $this->evaluateStringPluralism($data->{$variable}, $variable);
}
} else {
if ($this->plural != "always") {
$plural = $this->evaluateStringPluralism($data->{$variable}, $variable);
return $plural;
}
return $plural;
}
}
/**
* @return string
*/
public function getForm()
{
return $this->form;
}
/**
* @param string $form
*/
public function setForm($form)
{
$this->form = $form;
}
/**
* @param $text
* @param $lang
* @return string
*/
protected function formatting($text, $lang)
{
if (empty($text)) {
return "";
}
if ($this->stripPeriods) {
$text = str_replace('.', '', $text);
}
$text = preg_replace("/\s&\s/", " &#38; ", $text); //replace ampersands by html entity
$text = $this->format($this->applyTextCase($text, $lang));
return $this->addAffixes($text);
}
/**
* @return string
*/
protected function defaultPlural()
{
$plural = "";
switch ($this->plural) {
case 'never':
$plural = 'single';
break;
case 'always':
$plural = 'multiple';
break;
case 'contextual':
default:
}
return $plural;
}
}
@@ -0,0 +1,264 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\RenderingState;
use Seboettg\CiteProc\Style\StyleElement;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
use Seboettg\CiteProc\Styles\DelimiterTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Util\CiteProcHelper;
use Seboettg\CiteProc\Util\Factory;
use Seboettg\CiteProc\Util\StringHelper;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
use stdClass;
/**
* Class Layout
*
* @package Seboettg\CiteProc\Rendering
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Layout implements Rendering
{
private static $numberOfCitedItems = 0;
use AffixesTrait,
FormattingTrait,
DelimiterTrait,
ConsecutivePunctuationCharacterTrait;
/**
* @var ArrayList
*/
private $children;
/**
* When used within cs:citation, the delimiter attribute may be used to specify a delimiter for cites within a
* citation.
*
* @var string
*/
private $delimiter = "";
private $parent;
/**
* @param SimpleXMLElement $node
* @param StyleElement $parent
* @throws InvalidStylesheetException
*/
public function __construct($node, $parent)
{
$this->parent = $parent;
self::$numberOfCitedItems = 0;
$this->children = new ArrayList();
foreach ($node->children() as $child) {
$this->children->append(Factory::create($child, $this));
}
$this->initDelimiterAttributes($node);
$this->initAffixesAttributes($node);
$this->initFormattingAttributes($node);
}
/**
* @param array|DataList $data
* @param array|ArrayList $citationItems
* @return string|array
*/
public function render($data, $citationItems = [])
{
$ret = "";
$sorting = CiteProc::getContext()->getSorting();
if (!empty($sorting)) {
CiteProc::getContext()->setRenderingState(new RenderingState("sorting"));
$sorting->sort($data);
CiteProc::getContext()->setRenderingState(new RenderingState("rendering"));
}
if (CiteProc::getContext()->isModeBibliography()) {
foreach ($data as $citationNumber => $item) {
++self::$numberOfCitedItems;
CiteProc::getContext()->getResults()->append(
$this->wrapBibEntry($item, $this->renderSingle($item, $citationNumber))
);
}
$ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
$ret = StringHelper::clearApostrophes($ret);
return "<div class=\"csl-bib-body\">".$ret."\n</div>";
} elseif (CiteProc::getContext()->isModeCitation()) {
if ($citationItems->count() > 0) { //is there a filter for specific citations?
if ($this->isGroupedCitations($citationItems)) { //if citation items grouped?
return $this->renderGroupedCitations($data, $citationItems);
} else {
$data = $this->filterCitationItems($data, $citationItems);
$ret = $this->renderCitations($data, $ret);
}
} else {
$ret = $this->renderCitations($data, $ret);
}
}
$ret = StringHelper::clearApostrophes($ret);
return $this->addAffixes($ret);
}
/**
* @param $data
* @param int|null $citationNumber
* @return string
*/
private function renderSingle($data, $citationNumber = null)
{
$bibliographyOptions = CiteProc::getContext()->getBibliographySpecificOptions();
$inMargin = [];
$margin = [];
foreach ($this->children as $key => $child) {
$rendered = $child->render($data, $citationNumber);
$this->getChildrenAffixesAndDelimiter($child);
if (CiteProc::getContext()->isModeBibliography()
&& $bibliographyOptions->getSecondFieldAlign() === "flush"
) {
if ($key === 0 && !empty($rendered)) {
$inMargin[] = $rendered;
} else {
$margin[] = $rendered;
}
} else {
$inMargin[] = $rendered;
}
}
$inMargin = array_filter($inMargin);
$margin = array_filter($margin);
if (!empty($inMargin) && !empty($margin) && CiteProc::getContext()->isModeBibliography()) {
$leftMargin = $this->removeConsecutiveChars($this->htmlentities($this->format(implode("", $inMargin))));
$rightInline = $this->removeConsecutiveChars(
$this->htmlentities($this->format(implode("", $margin))).
$this->suffix
);
$res = '<div class="csl-left-margin">' . trim($leftMargin) . '</div>';
$res .= '<div class="csl-right-inline">' . trim($rightInline) . '</div>';
return $res;
} elseif (!empty($inMargin)) {
$res = $this->format(implode("", $inMargin));
return $this->htmlentities($this->removeConsecutiveChars($res));
}
return "";
}
/**
* @return int
*/
public static function getNumberOfCitedItems()
{
return self::$numberOfCitedItems;
}
/**
* @param stdClass $dataItem
* @param string $value
* @return string
*/
private function wrapBibEntry($dataItem, $value)
{
$value = $this->addAffixes($value);
return "\n ".
"<div class=\"csl-entry\">" .
$renderedItem = CiteProcHelper::applyAdditionMarkupFunction($dataItem, "csl-entry", $value) .
"</div>";
}
/**
* @param string $text
* @return string
*/
private function htmlentities($text)
{
$text = preg_replace("/(.*)&([^#38|amp];.*)/u", "$1&#38;$2", $text);
return $text;
}
/**
* @param $data
* @param $ret
* @return string
*/
private function renderCitations($data, $ret)
{
CiteProc::getContext()->getResults()->replace([]);
foreach ($data as $citationNumber => $item) {
$renderedItem = $this->renderSingle($item, $citationNumber);
$renderedItem = CiteProcHelper::applyAdditionMarkupFunction($item, "csl-entry", $renderedItem);
CiteProc::getContext()->getResults()->append($renderedItem);
CiteProc::getContext()->appendCitedItem($item);
}
$ret .= implode($this->delimiter, CiteProc::getContext()->getResults()->toArray());
return $ret;
}
/**
* @param DataList $data
* @param ArrayList $citationItems
* @return mixed
*/
private function filterCitationItems($data, $citationItems)
{
$arr = $data->toArray();
$arr_ = array_filter($arr, function ($dataItem) use ($citationItems) {
foreach ($citationItems as $citationItem) {
if ($dataItem->id === $citationItem->id) {
return true;
}
}
return false;
});
return $data->replace($arr_);
}
/**
* @param ArrayList $citationItems
* @return bool
*/
private function isGroupedCitations(ArrayList $citationItems)
{
$firstItem = array_values($citationItems->toArray())[0];
if (is_array($firstItem)) {
return true;
}
return false;
}
/**
* @param DataList $data
* @param ArrayList $citationItems
* @return array|string
*/
private function renderGroupedCitations($data, $citationItems)
{
$group = [];
foreach ($citationItems as $citationItemGroup) {
$data_ = $this->filterCitationItems(clone $data, $citationItemGroup);
CiteProc::getContext()->setCitationData($data_);
$group[] = $this->addAffixes(StringHelper::clearApostrophes($this->renderCitations($data_, "")));
}
if (CiteProc::getContext()->isCitationsAsArray()) {
return $group;
}
return implode("\n", $group);
}
}
@@ -0,0 +1,60 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Name;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Rendering\Rendering;
use Seboettg\CiteProc\Styles\FormattingTrait;
use SimpleXMLElement;
use stdClass;
/**
* Class EtAl
* Et-al abbreviation, controlled via the et-al-... attributes (see Name), can be further customized with the optional
* cs:et-al element, which must follow the cs:name element (if present). The term attribute may be set to either “et-al”
* (the default) or to “and others” to use either term. The formatting attributes may also be used, for example to
* italicize the “et-al” term.
*
* @package Seboettg\CiteProc\Rendering\Name
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class EtAl implements Rendering
{
use FormattingTrait;
private $term;
public function __construct(SimpleXMLElement $node)
{
/**
* @var SimpleXMLElement $attribute
*/
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'term':
$this->term = (string) $attribute;
break;
}
}
$this->initFormattingAttributes($node);
}
/**
* @param array|DataList|stdClass $data
* @param null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
return $this->format(CiteProc::getContext()->getLocale()->filter('terms', $this->term)->single);
}
}
@@ -0,0 +1,655 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Name;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\HasParent;
use Seboettg\CiteProc\Style\InheritableNameAttributesTrait;
use Seboettg\CiteProc\Style\Options\DemoteNonDroppingParticle;
use Seboettg\CiteProc\Style\Options\SubsequentAuthorSubstituteRule;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\DelimiterTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Util\CiteProcHelper;
use Seboettg\CiteProc\Util\Factory;
use Seboettg\CiteProc\Util\NameHelper;
use Seboettg\CiteProc\Util\StringHelper;
use SimpleXMLElement;
use stdClass;
/**
* Class Name
*
* The cs:name element, an optional child element of cs:names, can be used to describe the formatting of individual
* names, and the separation of names within a name variable.
*
* @package Seboettg\CiteProc\Rendering\Name
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Name implements HasParent
{
use InheritableNameAttributesTrait,
FormattingTrait,
AffixesTrait,
DelimiterTrait;
/**
* @var array
*/
protected $nameParts;
/**
* Specifies the text string used to separate names in a name variable. Default is ”, ” (e.g. “Doe, Smith”).
*
* @var
*/
private $delimiter = ", ";
/**
* @var Names
*/
private $parent;
/**
* @var SimpleXMLElement
*/
private $node;
/**
* @var string
*/
private $etAl;
/**
* @var string
*/
private $variable;
/**
* Name constructor.
*
* @param SimpleXMLElement $node
* @param Names $parent
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, Names $parent)
{
$this->node = $node;
$this->parent = $parent;
$this->nameParts = [];
/**
* @var SimpleXMLElement $child
*/
foreach ($node->children() as $child) {
switch ($child->getName()) {
case "name-part":
/** @var NamePart $namePart */
$namePart = Factory::create($child, $this);
$this->nameParts[$namePart->getName()] = $namePart;
}
}
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'form':
$this->form = (string) $attribute;
break;
}
}
$this->initFormattingAttributes($node);
$this->initAffixesAttributes($node);
$this->initDelimiterAttributes($node);
}
/**
* @param stdClass $data
* @param string $var
* @param integer|null $citationNumber
* @return string
* @throws CiteProcException
*/
public function render($data, $var, $citationNumber = null)
{
$this->variable = $var;
$name = $data->{$var};
if (!$this->attributesInitialized) {
$this->initInheritableNameAttributes($this->node);
}
if ("text" === $this->and) {
$this->and = CiteProc::getContext()->getLocale()->filter('terms', 'and')->single;
} elseif ('symbol' === $this->and) {
$this->and = '&#38;';
}
$resultNames = $this->handleSubsequentAuthorSubstitution($name, $citationNumber);
if (empty($resultNames)) {
return CiteProc::getContext()->getCitationData()->getSubsequentAuthorSubstitute();
}
$resultNames = $this->prepareAbbreviation($resultNames);
/* When set to “true” (the default is “false”), name lists truncated by et-al abbreviation are followed by
the name delimiter, the ellipsis character, and the last name of the original name list. This is only
possible when the original name list has at least two more names than the truncated name list (for this
the value of et-al-use-first/et-al-subsequent-min must be at least 2 less than the value of
et-al-min/et-al-subsequent-use-first). */
if ($this->etAlUseLast && $this->isEtAl($name, $resultNames)) {
$this->and = ""; // set "and"
$this->etAl = null; //reset $etAl;
}
/* add "and" */
$this->addAnd($resultNames);
$text = $this->renderDelimiterPrecedesLast($resultNames);
if (empty($text)) {
$text = implode($this->delimiter, $resultNames);
}
$text = $this->appendEtAl($name, $text, $resultNames);
/* A third value, “count”, returns the total number of names that would otherwise be rendered by the use of the
cs:names element (taking into account the effects of et-al abbreviation and editor/translator collapsing),
which allows for advanced sorting. */
if ($this->form == 'count') {
return (int) count($resultNames);
}
return $text;
}
/**
* @param stdClass $nameItem
* @param int $rank
* @return string
* @throws CiteProcException
*/
private function formatName($nameItem, $rank)
{
$nameObj = $this->cloneNamePOSC($nameItem);
$useInitials = $this->initialize && !is_null($this->initializeWith) && $this->initializeWith !== false;
if ($useInitials && isset($nameItem->given)) {
$nameObj->given = StringHelper::initializeBySpaceOrHyphen($nameItem->given, $this->initializeWith);
}
$renderedResult = $this->getNamesString($nameObj, $rank);
CiteProcHelper::applyAdditionMarkupFunction($nameItem, $this->parent->getVariables()[0], $renderedResult);
return trim($renderedResult);
}
/**
* @param stdClass $name
* @param int $rank
* @return string
* @throws CiteProcException
*/
private function getNamesString($name, $rank)
{
$text = "";
if (!isset($name->family)) {
return $text;
}
$text = $this->nameOrder($name, $rank);
//contains nbsp prefixed by normal space or followed by normal space?
$text = htmlentities($text);
if (strpos($text, " &nbsp;") !== false || strpos($text, "&nbsp; ") !== false) {
$text = preg_replace("/[\s]+/", "", $text); //remove normal spaces
return preg_replace("/&nbsp;+/", " ", $text);
}
$text = html_entity_decode(preg_replace("/[\s]+/", " ", $text));
return $this->format(trim($text));
}
/**
* @param stdClass $name
* @return stdClass
*/
private function cloneNamePOSC($name)
{
$nameObj = new stdClass();
if (isset($name->family)) {
$nameObj->family = $name->family;
}
if (isset($name->given)) {
$nameObj->given = $name->given;
}
if (isset($name->{'non-dropping-particle'})) {
$nameObj->{'non-dropping-particle'} = $name->{'non-dropping-particle'};
}
if (isset($name->{'dropping-particle'})) {
$nameObj->{'dropping-particle'} = $name->{'dropping-particle'};
}
if (isset($name->{'suffix'})) {
$nameObj->{'suffix'} = $name->{'suffix'};
}
return $nameObj;
}
/**
* @param $data
* @param $resultNames
* @return bool
*/
protected function isEtAl($data, $resultNames): bool
{
return count($data) > 1
&& !empty($resultNames)
&& !empty($this->etAl)
&& !empty($this->etAlMin)
&& !empty($this->etAlUseFirst)
&& count($data) != count($resultNames);
}
/**
* @param $data
* @param $text
* @param $resultNames
* @return string
*/
protected function appendEtAl($data, $text, $resultNames)
{
//append et al abbreviation
if ($this->isEtAl($data, $resultNames)) {
/* By default, when a name list is truncated to a single name, the name and the “et-al” (or “and others”)
term are separated by a space (e.g. “Doe et al.”). When a name list is truncated to two or more names, the
name delimiter is used (e.g. “Doe, Smith, et al.”). This behavior can be changed with the
delimiter-precedes-et-al attribute. */
switch ($this->delimiterPrecedesEtAl) {
case 'never':
$text = $text . " " . $this->etAl;
break;
case 'always':
$text = $text . $this->delimiter . $this->etAl;
break;
case 'contextual':
default:
if (count($resultNames) === 1) {
$text .= " " . $this->etAl;
} else {
$text .= $this->delimiter . $this->etAl;
}
}
}
return $text;
}
/**
* @param $resultNames
* @return array
*/
protected function prepareAbbreviation($resultNames)
{
$cnt = count($resultNames);
/* Use of et-al-min and et-al-user-first enables et-al abbreviation. If the number of names in a name variable
matches or exceeds the number set on et-al-min, the rendered name list is truncated after reaching the number of
names set on et-al-use-first. */
if (isset($this->etAlMin) && isset($this->etAlUseFirst)) {
if ($this->etAlMin <= $cnt) {
if ($this->etAlUseLast && $this->etAlMin - $this->etAlUseFirst >= 2) {
/* et-al-use-last: When set to “true” (the default is “false”), name lists truncated by et-al
abbreviation are followed by the name delimiter, the ellipsis character, and the last name of the
original name list. This is only possible when the original name list has at least two more names
than the truncated name list (for this the value of et-al-use-first/et-al-subsequent-min must be at
least 2 less than the value of et-al-min/et-al-subsequent-use-first).*/
$lastName = array_pop($resultNames); //remove last Element and remember in $lastName
}
for ($i = $this->etAlUseFirst; $i < $cnt; ++$i) {
unset($resultNames[$i]);
}
$resultNames = array_values($resultNames);
if (!empty($lastName)) { // append $lastName if exist
$resultNames[] = $lastName;
}
if ($this->parent->hasEtAl()) {
$this->etAl = $this->parent->getEtAl()->render(null);
return $resultNames;
} else {
$this->etAl = CiteProc::getContext()->getLocale()->filter('terms', 'et-al')->single;
return $resultNames;
}
}
return $resultNames;
}
return $resultNames;
}
/**
* @param $data
* @param stdClass $preceding
* @return array
* @throws CiteProcException
*/
protected function renderSubsequentSubstitution($data, $preceding)
{
$resultNames = [];
$subsequentSubstitution = CiteProc::getContext()->getCitationData()->getSubsequentAuthorSubstitute();
$subsequentSubstitutionRule = CiteProc::getContext()->getCitationData()->getSubsequentAuthorSubstituteRule();
/**
* @var string $type
* @var stdClass $name
*/
foreach ($data as $rank => $name) {
switch ($subsequentSubstitutionRule) {
/* “partial-each” - when one or more rendered names in the name variable match those in the preceding
bibliographic entry, the value of subsequent-author-substitute substitutes for each matching name.
Matching starts with the first name, and continues up to the first mismatch. */
case SubsequentAuthorSubstituteRule::PARTIAL_EACH:
if (NameHelper::precedingHasAuthor($preceding, $name)) {
$resultNames[] = $subsequentSubstitution;
} else {
$resultNames[] = $this->formatName($name, $rank);
}
break;
/* “partial-first” - as “partial-each”, but substitution is limited to the first name of the name
variable. */
case SubsequentAuthorSubstituteRule::PARTIAL_FIRST:
if ($rank === 0) {
if ($preceding->author[0]->family === $name->family) {
$resultNames[] = $subsequentSubstitution;
} else {
$resultNames[] = $this->formatName($name, $rank);
}
} else {
$resultNames[] = $this->formatName($name, $rank);
}
break;
/* “complete-each” - requires a complete match like “complete-all”, but now the value of
subsequent-author-substitute substitutes for each rendered name. */
case SubsequentAuthorSubstituteRule::COMPLETE_EACH:
try {
if (NameHelper::identicalAuthors($preceding, $data)) {
$resultNames[] = $subsequentSubstitution;
} else {
$resultNames[] = $this->formatName($name, $rank);
}
} catch (CiteProcException $e) {
$resultNames[] = $this->formatName($name, $rank);
}
break;
}
}
return $resultNames;
}
/**
* @param array $data
* @param int $citationNumber
* @return array
* @throws CiteProcException
*/
private function handleSubsequentAuthorSubstitution($data, $citationNumber)
{
$hasPreceding = CiteProc::getContext()->getCitationData()->hasKey($citationNumber - 1);
$subsequentSubstitution = CiteProc::getContext()->getCitationData()->getSubsequentAuthorSubstitute();
$subsequentSubstitutionRule = CiteProc::getContext()->getCitationData()->getSubsequentAuthorSubstituteRule();
$preceding = CiteProc::getContext()->getCitationData()->get($citationNumber - 1);
if ($hasPreceding && !is_null($subsequentSubstitution) && !empty($subsequentSubstitutionRule)) {
/**
* @var stdClass $preceding
*/
if ($subsequentSubstitutionRule == SubsequentAuthorSubstituteRule::COMPLETE_ALL) {
try {
if (NameHelper::identicalAuthors($preceding, $data)) {
return [];
} else {
$resultNames = $this->getFormattedNames($data);
}
} catch (CiteProcException $e) {
$resultNames = $this->getFormattedNames($data);
}
} else {
$resultNames = $this->renderSubsequentSubstitution($data, $preceding);
}
} else {
$resultNames = $this->getFormattedNames($data);
}
return $resultNames;
}
/**
* @param array $data
* @return array
* @throws CiteProcException
*/
protected function getFormattedNames($data)
{
$resultNames = [];
foreach ($data as $rank => $name) {
$formatted = $this->formatName($name, $rank);
$resultNames[] = NameHelper::addExtendedMarkup($this->variable, $name, $formatted);
}
return $resultNames;
}
/**
* @param $resultNames
* @return string
*/
protected function renderDelimiterPrecedesLastNever($resultNames)
{
$text = "";
if (!$this->etAlUseLast) {
if (count($resultNames) === 1) {
$text = $resultNames[0];
} elseif (count($resultNames) === 2) {
$text = implode(" ", $resultNames);
} else { // >2
$lastName = array_pop($resultNames);
$text = implode($this->delimiter, $resultNames)." ".$lastName;
}
}
return $text;
}
/**
* @param $resultNames
* @return string
*/
protected function renderDelimiterPrecedesLastContextual($resultNames)
{
if (count($resultNames) === 1) {
$text = $resultNames[0];
} elseif (count($resultNames) === 2) {
$text = implode(" ", $resultNames);
} else {
$text = implode($this->delimiter, $resultNames);
}
return $text;
}
/**
* @param $resultNames
*/
protected function addAnd(&$resultNames)
{
$count = count($resultNames);
if (!empty($this->and) && $count > 1 && empty($this->etAl)) {
$new = $this->and.' '.end($resultNames); // add and-prefix of the last name if "and" is defined
// set prefixed last name at the last position of $resultNames array
$resultNames[count($resultNames) - 1] = $new;
}
}
/**
* @param $resultNames
* @return array|string
*/
protected function renderDelimiterPrecedesLast($resultNames)
{
$text = "";
if (!empty($this->and) && empty($this->etAl)) {
switch ($this->delimiterPrecedesLast) {
case 'after-inverted-name':
//TODO: implement
break;
case 'always':
$text = implode($this->delimiter, $resultNames);
break;
case 'never':
$text = $this->renderDelimiterPrecedesLastNever($resultNames);
break;
case 'contextual':
default:
$text = $this->renderDelimiterPrecedesLastContextual($resultNames);
}
}
return $text;
}
/**
* @param stdClass $data
* @param integer $rank
*
* @return string
* @throws CiteProcException
*/
private function nameOrder($data, $rank)
{
$nameAsSortOrder = (($this->nameAsSortOrder === "first" && $rank === 0) || $this->nameAsSortOrder === "all");
$demoteNonDroppingParticle = CiteProc::getContext()->getGlobalOptions()->getDemoteNonDroppingParticles();
$normalizedName = NameHelper::normalizeName($data);
if (StringHelper::isLatinString($normalizedName) || StringHelper::isCyrillicString($normalizedName)) {
if ($this->form === "long"
&& $nameAsSortOrder
&& ((string) $demoteNonDroppingParticle === DemoteNonDroppingParticle::NEVER
|| (string) $demoteNonDroppingParticle === DemoteNonDroppingParticle::SORT_ONLY)
) {
// [La] [Fontaine], [Jean] [de], [III]
NameHelper::prependParticleTo($data, "family", "non-dropping-particle");
NameHelper::appendParticleTo($data, "given", "dropping-particle");
list($family, $given) = $this->renderNameParts($data);
$text = $family.(!empty($given) ? $this->sortSeparator.$given : "");
$text .= !empty($data->suffix) ? $this->sortSeparator.$data->suffix : "";
} elseif ($this->form === "long"
&& $nameAsSortOrder
&& (is_null($demoteNonDroppingParticle)
|| (string) $demoteNonDroppingParticle === DemoteNonDroppingParticle::DISPLAY_AND_SORT)
) {
// [Fontaine], [Jean] [de] [La], [III]
NameHelper::appendParticleTo($data, "given", "dropping-particle");
NameHelper::appendParticleTo($data, "given", "non-dropping-particle");
list($family, $given) = $this->renderNameParts($data);
$text = $family;
$text .= !empty($given) ? $this->sortSeparator.$given : "";
$text .= !empty($data->suffix) ? $this->sortSeparator.$data->suffix : "";
} elseif ($this->form === "long" && $nameAsSortOrder && empty($demoteNonDroppingParticle)) {
list($family, $given) = $this->renderNameParts($data);
$text = $family;
$text .= !empty($given) ? $this->delimiter.$given : "";
$text .= !empty($data->suffix) ? $this->sortSeparator.$data->suffix : "";
} elseif ($this->form === "short") {
// [La] [Fontaine]
NameHelper::prependParticleTo($data, "family", "non-dropping-particle");
$text = $data->family;
} else {// form "long" (default)
// [Jean] [de] [La] [Fontaine] [III]
NameHelper::prependParticleTo($data, "family", "non-dropping-particle");
NameHelper::prependParticleTo($data, "family", "dropping-particle");
NameHelper::appendParticleTo($data, "family", "suffix");
list($family, $given) = $this->renderNameParts($data);
$text = !empty($given) ? $given." ".$family : $family;
}
} elseif (StringHelper::isAsianString(NameHelper::normalizeName($data))) {
$text = $this->form === "long" ? $data->family . $data->given : $data->family;
} else {
$text = $this->form === "long" ? $data->family . " " . $data->given : $data->family;
}
return $text;
}
/**
* @param $data
* @return array
*/
private function renderNameParts($data)
{
$given = "";
if (array_key_exists("family", $this->nameParts)) {
$family = $this->nameParts["family"]->render($data);
} else {
$family = $data->family;
}
if (isset($data->given)) {
if (array_key_exists("given", $this->nameParts)) {
$given = $this->nameParts["given"]->render($data);
} else {
$given = $data->given;
}
}
return [$family, $given];
}
/**
* @return string
*/
public function getForm()
{
return $this->form;
}
/**
* @return string
*/
public function isNameAsSortOrder()
{
return $this->nameAsSortOrder;
}
/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* @param mixed $delimiter
*/
public function setDelimiter($delimiter)
{
$this->delimiter = $delimiter;
}
/**
* @return Names
*/
public function getParent()
{
return $this->parent;
}
}
@@ -0,0 +1,108 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Name;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Styles\TextCaseTrait;
use SimpleXMLElement;
/**
* Class NamePart
*
* The cs:name element may contain one or two cs:name-part child elements for name-part-specific formatting.
* cs:name-part must carry the name attribute, set to either “given” or “family”.
*
* If set to “given”, formatting and text-case attributes on cs:name-part affect the “given” and “dropping-particle”
* name-parts. affixes surround the “given” name-part, enclosing any demoted name particles for inverted names.
*
* If set to “family”, formatting and text-case attributes affect the “family” and “non-dropping-particle” name-parts.
* affixes surround the “family” name-part, enclosing any preceding name particles, as well as the “suffix” name-part
* for non-inverted names.
*
* The “suffix” name-part is not subject to name-part formatting. The use of cs:name-part elements does not influence
* which, or in what order, name-parts are rendered.
*
*
* @package Seboettg\CiteProc\Rendering\Name
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class NamePart
{
use FormattingTrait,
TextCaseTrait,
AffixesTrait;
private $name;
/**
* @var Name
*/
private $parent;
/**
* NamePart constructor.
* @param SimpleXMLElement $node
* @param Name $parent
*/
public function __construct(SimpleXMLElement $node, $parent)
{
$this->parent = $parent;
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
if ($attribute->getName() === 'name') {
$this->name = (string) $attribute;
}
}
$this->initFormattingAttributes($node);
$this->initTextCaseAttributes($node);
$this->initAffixesAttributes($node);
}
/**
* @param $data
* @return mixed
* @throws CiteProcException
*/
public function render($data)
{
if (!isset($data->{$this->name})) {
return "";
}
switch ($this->name) {
/* If set to “given”, formatting and text-case attributes on cs:name-part affect the “given” and
“dropping-particle” name-parts. affixes surround the “given” name-part, enclosing any demoted name particles
for inverted names.*/
case 'given':
return $this->addAffixes($this->format($this->applyTextCase($data->given)));
/* if name set to “family”, formatting and text-case attributes affect the “family” and
“non-dropping-particle” name-parts. affixes surround the “family” name-part, enclosing any preceding name
particles, as well as the “suffix” name-part for non-inverted names.*/
case 'family':
return $this->addAffixes($this->format($this->applyTextCase($data->family)));
}
throw new CiteProcException("This shouldn't happen.");
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
}
@@ -0,0 +1,422 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Name;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\HasParent;
use Seboettg\CiteProc\Rendering\Label;
use Seboettg\CiteProc\Rendering\Rendering;
use Seboettg\CiteProc\Rendering\Term\Punctuation;
use Seboettg\CiteProc\RenderingState;
use Seboettg\CiteProc\Style\InheritableNameAttributesTrait;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\DelimiterTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Util\Factory;
use Seboettg\CiteProc\Util\NameHelper;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
use stdClass;
/**
* Class Names
*
* @package Seboettg\CiteProc\Rendering\Name
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Names implements Rendering, HasParent
{
use DelimiterTrait,
AffixesTrait,
FormattingTrait,
InheritableNameAttributesTrait;
/**
* Variables (selected with the required variable attribute), each of which can contain multiple names (e.g. the
* “author” variable contains all the author names of the cited item). If multiple variables are selected
* (separated by single spaces, see example below), each variable is independently rendered in the order specified.
*
* @var ArrayList
*/
private $variables;
/**
* The Name element, an optional child element of Names, can be used to describe the formatting of individual
* names, and the separation of names within a name variable.
*
* @var Name
*/
private $name;
/**
* The optional Label element must be included after the Name and EtAl elements, but before
* the Substitute element. When used as a child element of Names, Label does not carry the variable
* attribute; it uses the variable(s) set on the parent Names element instead.
*
* @var Label
*/
private $label;
/**
* The optional Substitute element, which must be included as the last child element of Names, adds
* substitution in case the name variables specified in the parent cs:names element are empty. The substitutions
* are specified as child elements of Substitute, and must consist of one or more rendering elements (with the
* exception of Layout). A shorthand version of Names without child elements, which inherits the attributes
* values set on the cs:name and EtAl child elements of the original Names element, may also be used. If
* Substitute contains multiple child elements, the first element to return a non-empty result is used for
* substitution. Substituted variables are suppressed in the rest of the output to prevent duplication. An example,
* where an empty “author” name variable is substituted by the “editor” name variable, or, when no editors exist,
* by the “title” macro:
*
* <macro name="author">
* <names variable="author">
* <substitute>
* <names variable="editor"/>
* <text macro="title"/>
* </substitute>
* </names>
* </macro>
*
* @var Substitute
*/
private $substitute;
/**
* Et-al abbreviation, controlled via the et-al-... attributes (see Name), can be further customized with the
* optional cs:et-al element, which must follow the cs:name element (if present). The term attribute may be set to
* either “et-al” (the default) or to “and others” to use either term. The formatting attributes may also be used,
* for example to italicize the “et-al” term:
*
* @var EtAl
*/
private $etAl;
/**
* The delimiter attribute may be set on cs:names to separate the names of the different name variables (e.g. the
* semicolon in “Doe, Smith (editors); Johnson (translator)”).
*
* @var string
*/
private $delimiter = ", ";
private $parent;
/**
* @var bool
*/
private $renderLabelBeforeName = false;
/**
* Names constructor.
*
* @param SimpleXMLElement $node
* @param $parent
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, $parent)
{
$this->initInheritableNameAttributes($node);
$this->parent = $parent;
/**
* @var SimpleXMLElement $child
*/
foreach ($node->children() as $child) {
switch ($child->getName()) {
case "name":
$this->name = Factory::create($child, $this);
if ($this->label !== null) {
$this->renderLabelBeforeName = true;
}
break;
case "label":
$this->label = Factory::create($child);
break;
case "substitute":
$this->substitute = new Substitute($child, $this);
break;
case "et-al":
$this->etAl = Factory::create($child);
}
}
/**
* @var SimpleXMLElement $attribute
*/
foreach ($node->attributes() as $attribute) {
if ("variable" === $attribute->getName()) {
$this->variables = new ArrayList(...explode(" ", (string) $attribute));
break;
}
}
$this->initDelimiterAttributes($node);
$this->initAffixesAttributes($node);
$this->initFormattingAttributes($node);
}
/**
* This outputs the contents of one or more name variables (selected with the required variable attribute), each
* of which can contain multiple names (e.g. the “author” variable contains all the author names of the cited item).
* If multiple variables are selected (separated by single spaces), each variable is independently rendered in the
* order specified, with one exception: when the selection consists of “editor” and “translator”, and when the
* contents of these two name variables is identical, then the contents of only one name variable is rendered. In
* addition, the “editortranslator” term is used if the Names element contains a Label element, replacing the
* default “editor” and “translator” terms (e.g. resulting in “Doe (editor & translator)”).
*
* @param stdClass $data
* @param int|null $citationNumber
* @return string
* @throws CiteProcException
*/
public function render($data, $citationNumber = null)
{
$str = "";
/* when the selection consists of “editor” and “translator”, and when the contents of these two name variables
is identical, then the contents of only one name variable is rendered. In addition, the “editortranslator”
term is used if the cs:names element contains a cs:label element, replacing the default “editor” and
“translator” terms (e.g. resulting in “Doe (editor & translator)”) */
if ($this->variables->hasElement("editor") && $this->variables->hasElement("translator")) {
if (isset($data->editor)
&& isset($data->translator) && NameHelper::sameNames($data->editor, $data->translator)
) {
if (isset($this->name)) {
$str .= $this->name->render($data, 'editor');
} else {
$arr = [];
foreach ($data->editor as $editor) {
$edt = $this->format($editor->family.", ".$editor->given);
$results[] = NameHelper::addExtendedMarkup('editor', $editor, $edt);
}
$str .= implode($this->delimiter, $arr);
}
if (isset($this->label)) {
$this->label->setVariable("editortranslator");
$str .= $this->label->render($data);
}
$vars = $this->variables->toArray();
$vars = array_filter($vars, function ($value) {
return !($value === "editor" || $value === "translator");
});
$this->variables->setArray($vars);
}
}
$results = [];
foreach ($this->variables as $var) {
if (!empty($data->{$var})) {
if (!empty($this->name)) {
$res = $this->name->render($data, $var, $citationNumber);
$name = $res;
if (!empty($this->label)) {
$name = $this->appendLabel($data, $var, $name);
}
//add multiple counting values
if (is_numeric($name) && $this->name->getForm() === "count") {
$results = $this->addCountValues($res, $results);
} else {
$results[] = $this->format($name);
}
} else {
foreach ($data->{$var} as $name) {
$formatted = $this->format($name->given." ".$name->family);
$results[] = NameHelper::addExtendedMarkup($var, $name, $formatted);
}
}
// suppress substituted variables
if (CiteProc::getContext()->getRenderingState()->getValue() === RenderingState::SUBSTITUTION) {
unset($data->{$var});
}
} else {
if (!empty($this->substitute)) {
$results[] = $this->substitute->render($data);
}
}
}
$results = $this->filterEmpty($results);
$str .= implode($this->delimiter, $results);
return !empty($str) ? $this->addAffixes($str) : "";
}
/**
* @param $data
* @param $var
* @param $name
* @return string
*/
private function appendLabel($data, $var, $name): string
{
$this->label->setVariable($var);
$renderedLabel = trim($this->label->render($data));
if (empty($renderedLabel)) {
return $name;
}
if ($this->renderLabelBeforeName) {
$delimiter = !in_array(
trim($this->label->renderSuffix()),
Punctuation::getAllPunctuations()
) ? " " : "";
$result = $renderedLabel . $delimiter . trim($name);
} else {
$delimiter = !in_array(
trim($this->label->renderPrefix()),
Punctuation::getAllPunctuations()
) ? " " : "";
$result = trim($name) . $delimiter . $renderedLabel;
}
return $result;
}
/**
* @param $res
* @param $results
* @return array
*/
private function addCountValues($res, $results)
{
$lastElement = current($results);
$key = key($results);
if (!empty($lastElement)) {
$lastElement += $res;
$results[$key] = $lastElement;
} else {
$results[] = $res;
}
return $results;
}
/**
* @return bool
*/
public function hasEtAl()
{
return !empty($this->etAl);
}
/**
* @return EtAl
*/
public function getEtAl()
{
return $this->etAl;
}
/**
* @param EtAl $etAl
* @return $this
*/
public function setEtAl(EtAl $etAl)
{
$this->etAl = $etAl;
return $this;
}
/**
* @return bool
*/
public function hasName()
{
return !empty($this->name);
}
/**
* @return Name
*/
public function getName()
{
return $this->name;
}
/**
* @param Name $name
* @return $this
*/
public function setName(Name $name)
{
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getDelimiter()
{
return $this->delimiter;
}
/**
* @return ArrayList
*/
public function getVariables()
{
return $this->variables;
}
/**
* @return bool
*/
public function hasLabel()
{
return !empty($this->label);
}
/**
* @return Label
*/
public function getLabel()
{
return $this->label;
}
/**
* @param Label $label
*/
public function setLabel($label)
{
$this->label = $label;
}
/**
* @return mixed
*/
public function getParent()
{
return $this->parent;
}
private function filterEmpty(array $results)
{
return array_filter($results, function ($item) {
return !empty($item);
});
}
/**
* @return bool
*/
public function isRenderLabelBeforeName(): bool
{
return $this->renderLabelBeforeName;
}
/**
* @param bool $renderLabelBeforeName
*/
public function setRenderLabelBeforeName(bool $renderLabelBeforeName): void
{
$this->renderLabelBeforeName = $renderLabelBeforeName;
}
}
@@ -0,0 +1,133 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering\Name;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\Rendering;
use Seboettg\CiteProc\RenderingState;
use Seboettg\CiteProc\Util\Factory;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
use stdClass;
/**
* Class Substitute
* The optional cs:substitute element, which must be included as the last child element of cs:names, adds substitution
* in case the name variables specified in the parent cs:names element are empty. The substitutions are specified as
* child elements of cs:substitute, and must consist of one or more rendering elements (with the exception of
* cs:layout).
*
* A shorthand version of cs:names without child elements, which inherits the attributes values set on the cs:name and
* cs:et-al child elements of the original cs:names element, may also be used.
*
* If cs:substitute contains multiple child elements, the first element to return a non-empty result is used for
* substitution. Substituted variables are suppressed in the rest of the output to prevent duplication. An example,
* where an empty “author” name variable is substituted by the “editor” name variable, or, when no editors exist, by
* the “title” macro:
* <macro name="author">
* <names variable="author">
* <substitute>
* <names variable="editor"/>
* <text macro="title"/>
* </substitute>
* </names>
* </macro>
* @package Seboettg\CiteProc\Rendering\Name
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Substitute implements Rendering
{
/**
* @var ArrayList
*/
private $children;
/**
* @var Names
*/
private $parent;
/**
* Substitute constructor.
* @param SimpleXMLElement $node
* @param Names $parent
* @throws InvalidStylesheetException
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, Names $parent)
{
$this->parent = $parent;
$this->children = new ArrayList();
foreach ($node->children() as $child) {
/** @var SimpleXMLElement $child */
if ($child->getName() === "names") {
/** @var Names $names */
$names = Factory::create($child, $this);
/* A shorthand version of cs:names without child elements, which inherits the attributes values set on
the cs:name and cs:et-al child elements of the original cs:names element, may also be used. */
if (!$names->hasEtAl()) {
// inherit et-al
if ($this->parent->hasEtAl()) {
$names->setEtAl($this->parent->getEtAl());
}
}
if (!$names->hasName()) {
// inherit name
if ($this->parent->hasName()) {
$names->setName($this->parent->getName());
}
}
// inherit label
if (!$names->hasLabel() && $this->parent->hasLabel()) {
$names->setLabel($this->parent->getLabel());
}
$this->children->append($names);
} else {
$object = Factory::create($child, $this);
$this->children->append($object);
}
}
}
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
$ret = [];
if (CiteProc::getContext()->getRenderingState()->getValue() !== RenderingState::SORTING) {
CiteProc::getContext()->setRenderingState(new RenderingState(RenderingState::SUBSTITUTION));
}
/** @var Rendering $child */
foreach ($this->children as $child) {
/* If cs:substitute contains multiple child elements, the first element to return a
non-empty result is used for substitution. */
$res = $child->render($data, $citationNumber);
if (!empty($res)) {
$ret[] = $res;
break;
}
}
if (CiteProc::getContext()->getRenderingState()->getValue() === RenderingState::SUBSTITUTION) {
CiteProc::getContext()->setRenderingState(new RenderingState(RenderingState::RENDERING));
}
return implode("", $ret);
}
}
@@ -0,0 +1,225 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\DisplayTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Styles\TextCaseTrait;
use Seboettg\CiteProc\Util;
use SimpleXMLElement;
use stdClass;
/**
* Class Number
* @package Seboettg\CiteProc\Rendering
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Number implements Rendering
{
private const RANGE_DELIMITER_HYPHEN = "-";
private const RANGE_DELIMITER_AMPERSAND = "&";
private const RANGE_DELIMITER_COMMA = ",";
private const PATTERN_ORDINAL = "/\s*(\d+)\s*([\-\&,])\s*(\d+)\s*/";
private const PATTERN_LONG_ORDINAL = "/\s*(\d+)\s*([\-\&,])\s*(\d+)\s*/";
private const PATTERN_ROMAN = "/\s*(\d+)\s*([\-\&,])\s*(\d+)\s*/";
private const PATTERN_NUMERIC_DEFAULT = "/\s*(\d+)\s*([\-\&,])\s*(\d+)\s*/";
use FormattingTrait,
AffixesTrait,
TextCaseTrait,
DisplayTrait;
/**
* @var string
*/
private $variable;
/**
* @var string
*/
private $form;
/**
* Number constructor.
* @param SimpleXMLElement $node
*/
public function __construct(SimpleXMLElement $node)
{
//<number variable="edition" form="ordinal"/>
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'variable':
$this->variable = (string) $attribute;
break;
case 'form':
$this->form = (string) $attribute;
}
}
$this->initFormattingAttributes($node);
$this->initAffixesAttributes($node);
$this->initTextCaseAttributes($node);
}
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null): string
{
$lang = (isset($data->language) && $data->language != 'en') ? $data->language : 'en';
if (empty($this->variable) || empty($data->{$this->variable})) {
return "";
}
$number = $data->{$this->variable};
$decimalNumber = $this->toDecimalNumber($number);
switch ($this->form) {
case 'ordinal':
if (preg_match(self::PATTERN_ORDINAL, $decimalNumber, $matches)) {
$num1 = self::ordinal($matches[1]);
$num2 = self::ordinal($matches[3]);
$text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
} else {
$text = self::ordinal($decimalNumber);
}
break;
case 'long-ordinal':
if (preg_match(self::PATTERN_LONG_ORDINAL, $decimalNumber, $matches)) {
if ($this->textCase === "capitalize-first" || $this->textCase === "sentence") {
$num1 = self::longOrdinal($matches[1]);
$num2 = self::longOrdinal($matches[3]);
} else {
$num1 = $this->applyTextCase(self::longOrdinal($matches[1]));
$num2 = $this->applyTextCase(self::longOrdinal($matches[3]));
}
$text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
} else {
$text = self::longOrdinal($decimalNumber);
}
break;
case 'roman':
if (preg_match(self::PATTERN_ROMAN, $decimalNumber, $matches)) {
$num1 = Util\NumberHelper::dec2roman($matches[1]);
$num2 = Util\NumberHelper::dec2roman($matches[3]);
$text = $this->buildNumberRangeString($num1, $num2, $matches[2]);
} else {
$text = Util\NumberHelper::dec2roman($decimalNumber);
}
break;
case 'numeric':
default:
/*
During the extraction, numbers separated by a hyphen are stripped of intervening spaces (“2 - 4”
becomes “2-4”). Numbers separated by a comma receive one space after the comma (“2,3” and “2 , 3”
become “2, 3”), while numbers separated by an ampersand receive one space before and one after the
ampersand (“2&3” becomes “2 & 3”).
*/
$decimalNumber = $data->{$this->variable};
if (preg_match(self::PATTERN_NUMERIC_DEFAULT, $decimalNumber, $matches)) {
$text = $this->buildNumberRangeString($matches[1], $matches[3], $matches[2]);
} else {
$text = $decimalNumber;
}
break;
}
return $this->wrapDisplayBlock($this->addAffixes($this->format($this->applyTextCase($text, $lang))));
}
/**
* @param $num
* @return string
*/
public static function ordinal($num): string
{
if ((int) ($num / 10) % 10 == 1) {
$ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal')->single;
} elseif ($num % 10 == 1) {
$ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-01')->single;
} elseif ($num % 10 == 2) {
$ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-02')->single;
} elseif ($num % 10 == 3) {
$ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-03')->single;
} else {
$ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal-04')->single;
}
if (empty($ordinalSuffix)) {
$ordinalSuffix = CiteProc::getContext()->getLocale()->filter('terms', 'ordinal')->single;
}
return $num . $ordinalSuffix;
}
/**
* @param $num
* @return string
*/
public static function longOrdinal($num)
{
$num = sprintf("%02d", $num);
$ret = CiteProc::getContext()->getLocale()->filter('terms', 'long-ordinal-'.$num)->single;
if (!$ret) {
return self::ordinal($num);
}
return $ret;
}
/**
* @param string|int $num1
* @param string|int $num2
* @param string $delim
* @return string
*/
public function buildNumberRangeString($num1, $num2, $delim)
{
if (self::RANGE_DELIMITER_AMPERSAND === $delim) {
$numRange = "$num1 ".htmlentities(self::RANGE_DELIMITER_AMPERSAND)." $num2";
} else {
if (self::RANGE_DELIMITER_COMMA === $delim) {
$numRange = $num1.htmlentities(self::RANGE_DELIMITER_COMMA)." $num2";
} else {
$numRange = $num1.self::RANGE_DELIMITER_HYPHEN.$num2;
}
}
return $numRange;
}
/**
* @param string $number
* @return string
*/
private function toDecimalNumber($number)
{
$decimalNumber = $number;
if (Util\NumberHelper::isRomanNumber($number)) {
$decimalNumber = Util\NumberHelper::roman2Dec($number);
} else {
$number = mb_strtolower($number);
if (preg_match(Util\NumberHelper::PATTERN_ROMAN_RANGE, $number, $matches)) {
$num1 = Util\NumberHelper::roman2Dec(mb_strtoupper($matches[1]));
$num2 = Util\NumberHelper::roman2Dec(mb_strtoupper($matches[3]));
$decimalNumber = sprintf('%d%s%d', $num1, $matches[2], $num2);
}
}
return $decimalNumber;
}
}
@@ -0,0 +1,31 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
use Seboettg\CiteProc\Data\DataList;
use stdClass;
/**
* Interface RenderingInterface
*
* Defines "render" function.
*
* @package Seboettg\CiteProc\Rendering
*/
interface Rendering
{
/**
* @param array|DataList|stdClass $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = []);
}
@@ -0,0 +1,32 @@
<?php
namespace Seboettg\CiteProc\Rendering\Term;
use MyCLabs\Enum\Enum;
use Seboettg\CiteProc\CiteProc;
use Seboettg\Collection\ArrayList;
class Punctuation extends Enum
{
public const OPEN_QUOTE = "open-quote";
public const CLOSE_QUOTE = "close-quote";
public const OPEN_INNER_QUOTE = "open-inner-quote";
public const CLOSE_INNER_QUOTE = "close-inner-quote";
public const PAGE_RANGE_DELIMITER = "page-range-delimiter";
public const COLON = "colon";
public const COMMA = "comma";
public const SEMICOLON = "semicolon";
public static function getAllPunctuations(): array
{
$values = new ArrayList();
return $values
->setArray(Punctuation::toArray())
->map(function (string $punctuation) {
return CiteProc::getContext()->getLocale()->filter("terms", $punctuation)->single;
})
->collect(function ($items) {
return array_values($items);
});
}
}
@@ -0,0 +1,294 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Rendering;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\RenderingState;
use Seboettg\CiteProc\Styles\AffixesTrait;
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
use Seboettg\CiteProc\Styles\DisplayTrait;
use Seboettg\CiteProc\Styles\FormattingTrait;
use Seboettg\CiteProc\Styles\QuotesTrait;
use Seboettg\CiteProc\Styles\TextCaseTrait;
use Seboettg\CiteProc\Terms\Locator;
use Seboettg\CiteProc\Util\CiteProcHelper;
use Seboettg\CiteProc\Util\NumberHelper;
use Seboettg\CiteProc\Util\PageHelper;
use Seboettg\CiteProc\Util\StringHelper;
use SimpleXMLElement;
use stdClass;
use function Seboettg\CiteProc\ucfirst;
/**
* Class Term
*
* @package Seboettg\CiteProc\Node\Style
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Text implements Rendering
{
use FormattingTrait,
AffixesTrait,
TextCaseTrait,
DisplayTrait,
ConsecutivePunctuationCharacterTrait,
QuotesTrait;
/**
* @var string
*/
private $toRenderType;
/**
* @var string
*/
private $toRenderTypeValue;
/**
* @var string
*/
private $form = "long";
/**
* Text constructor.
*
* @param SimpleXMLElement $node
*/
public function __construct(SimpleXMLElement $node)
{
foreach ($node->attributes() as $attribute) {
$name = $attribute->getName();
if (in_array($name, ['value', 'variable', 'macro', 'term'])) {
$this->toRenderType = $name;
$this->toRenderTypeValue = (string) $attribute;
}
if ($name === "form") {
$this->form = (string) $attribute;
}
}
$this->initFormattingAttributes($node);
$this->initDisplayAttributes($node);
$this->initTextCaseAttributes($node);
$this->initAffixesAttributes($node);
$this->initQuotesAttributes($node);
}
/**
* @param stdClass $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
$lang = (isset($data->language) && $data->language != 'en') ? $data->language : 'en';
$renderedText = "";
switch ($this->toRenderType) {
case 'value':
$renderedText = $this->applyTextCase($this->toRenderTypeValue, $lang);
break;
case 'variable':
if ($this->toRenderTypeValue === "locator" && CiteProc::getContext()->isModeCitation()) {
$renderedText = $this->renderLocator($data, $citationNumber);
// for test sort_BibliographyCitationNumberDescending.json
} elseif ($this->toRenderTypeValue === "citation-number") {
$renderedText = $this->renderCitationNumber($data, $citationNumber);
break;
} elseif (in_array($this->toRenderTypeValue, ["page", "chapter-number", "folio"])) {
$renderedText = !empty($data->{$this->toRenderTypeValue}) ?
$this->renderPage($data->{$this->toRenderTypeValue}) : '';
} else {
$renderedText = $this->renderVariable($data, $lang);
}
if (CiteProc::getContext()->getRenderingState()->getValue() === RenderingState::SUBSTITUTION) {
unset($data->{$this->toRenderTypeValue});
}
if (!CiteProcHelper::isUsingAffixesByMarkupExtentsion($data, $this->toRenderTypeValue)) {
$renderedText = $this->applyAdditionalMarkupFunction($data, $renderedText);
}
break;
case 'macro':
$renderedText = $this->renderMacro($data);
break;
case 'term':
$term = CiteProc::getContext()
->getLocale()
->filter("terms", $this->toRenderTypeValue, $this->form)
->single;
$renderedText = !empty($term) ? $this->applyTextCase($term, $lang) : "";
}
if (!empty($renderedText)) {
$renderedText = $this->formatRenderedText($data, $renderedText);
}
return $renderedText;
}
/**
* @return string
*/
public function getSource()
{
return $this->toRenderType;
}
/**
* @return string
*/
public function getVariable()
{
return $this->toRenderTypeValue;
}
private function renderPage($page)
{
if (preg_match(NumberHelper::PATTERN_COMMA_AMPERSAND_RANGE, $page)) {
$page = $this->normalizeDateRange($page);
$ranges = preg_split("/[-]/", trim($page));
if (count($ranges) > 1) {
if (!empty(CiteProc::getContext()->getGlobalOptions())
&& !empty(CiteProc::getContext()->getGlobalOptions()->getPageRangeFormat())
) {
return PageHelper::processPageRangeFormats(
$ranges,
CiteProc::getContext()->getGlobalOptions()->getPageRangeFormat()
);
}
list($from, $to) = $ranges;
return $from . "" . $to;
}
}
return $page;
}
private function renderLocator($data, $citationNumber)
{
$citationItem = CiteProc::getContext()->getCitationItemById($data->id);
if (!empty($citationItem->label)) {
$locatorData = new stdClass();
$propertyName = Locator::mapLocatorLabelToRenderVariable($citationItem->label);
$locatorData->{$propertyName} = trim($citationItem->locator);
$renderTypeValueTemp = $this->toRenderTypeValue;
$this->toRenderTypeValue = $propertyName;
$result = $this->render($locatorData, $citationNumber);
$this->toRenderTypeValue = $renderTypeValueTemp;
return $result;
}
return isset($citationItem->locator) ? trim($citationItem->locator) : '';
}
private function normalizeDateRange($page)
{
if (preg_match("/^(\d+)\s?--?\s?(\d+)$/", trim($page), $matches)) {
return $matches[1]."-".$matches[2];
}
return $page;
}
/**
* @param $data
* @param $renderedText
* @return mixed
*/
private function applyAdditionalMarkupFunction($data, $renderedText)
{
return CiteProcHelper::applyAdditionMarkupFunction($data, $this->toRenderTypeValue, $renderedText);
}
/**
* @param $data
* @param $lang
* @return string
*/
private function renderVariable($data, $lang)
{
// check if there is an attribute with prefix short or long e.g. shortTitle or longAbstract
// test case group_ShortOutputOnly.json
$value = "";
if (in_array($this->form, ["short", "long"])) {
$attrWithPrefix = $this->form . ucfirst($this->toRenderTypeValue);
$attrWithSuffix = $this->toRenderTypeValue . "-" . $this->form;
if (isset($data->{$attrWithPrefix}) && !empty($data->{$attrWithPrefix})) {
$value = $data->{$attrWithPrefix};
} else {
if (isset($data->{$attrWithSuffix}) && !empty($data->{$attrWithSuffix})) {
$value = $data->{$attrWithSuffix};
} else {
if (isset($data->{$this->toRenderTypeValue})) {
$value = $data->{$this->toRenderTypeValue};
}
}
}
} else {
if (!empty($data->{$this->toRenderTypeValue})) {
$value = $data->{$this->toRenderTypeValue};
}
}
return $this->applyTextCase(
StringHelper::clearApostrophes(
htmlspecialchars($value, ENT_HTML5)
),
$lang
);
}
/**
* @param $data
* @param $renderedText
* @return string
*/
private function formatRenderedText($data, $renderedText)
{
$text = $this->format($renderedText);
$res = $this->addAffixes($text);
if (CiteProcHelper::isUsingAffixesByMarkupExtentsion($data, $this->toRenderTypeValue)) {
$res = $this->applyAdditionalMarkupFunction($data, $res);
}
if (!empty($res)) {
$res = $this->removeConsecutiveChars($res);
}
$res = $this->addSurroundingQuotes($res);
return $this->wrapDisplayBlock($res);
}
/**
* @param $data
* @param $citationNumber
* @return int|mixed
*/
private function renderCitationNumber($data, $citationNumber)
{
$renderedText = $citationNumber + 1;
if (!CiteProcHelper::isUsingAffixesByMarkupExtentsion($data, $this->toRenderTypeValue)) {
$renderedText = $this->applyAdditionalMarkupFunction($data, $renderedText);
}
return $renderedText;
}
/**
* @param $data
* @return string
*/
private function renderMacro($data)
{
$macro = CiteProc::getContext()->getMacro($this->toRenderTypeValue);
if (is_null($macro)) {
try {
throw new CiteProcException("Macro \"".$this->toRenderTypeValue."\" does not exist.");
} catch (CiteProcException $e) {
$renderedText = "";
}
} else {
$renderedText = $macro->render($data);
}
return $renderedText;
}
}
@@ -0,0 +1,34 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc;
use MyCLabs\Enum\Enum;
/**
* RenderingState defines the mode in which mode the processor currently works.
* There are three modes:
* - Rendering
* - Sorting
* - Substitution
*
* @package Seboettg\CiteProc
* @author Sebastian Böttger <seboettg@gmail.com>
* @method static RENDERING()
* @method static SORTING()
* @method static SUBSTITUTION()
*/
class RenderingState extends Enum
{
const RENDERING = "rendering";
const SORTING = "sorting";
const SUBSTITUTION = "substitution";
}
@@ -0,0 +1,98 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Root;
use SimpleXMLElement;
use stdClass;
class Info
{
/**
* @var string
*/
private $title;
/**
* @var string
*/
private $id;
/**
* @var array
*/
private $authors;
/**
* @var array
*/
private $links;
public function __construct(SimpleXMLElement $node)
{
$this->authors = [];
$this->links = [];
/** @var SimpleXMLElement $child */
foreach ($node->children() as $child) {
switch ($child->getName()) {
case 'author':
case 'contributor':
$author = new stdClass();
/** @var SimpleXMLElement $authorNode */
foreach ($child->children() as $authorNode) {
$author->{$authorNode->getName()} = (string) $authorNode;
}
$this->authors[] = $author;
break;
case 'link':
foreach ($child->attributes() as $attribute) {
if ($attribute->getName() === "value") {
$this->links[] = (string) $attribute;
}
}
break;
default:
$this->{$child->getName()} = (string) $child;
}
}
}
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* @return array
*/
public function getAuthors()
{
return $this->authors;
}
/**
* @return array
*/
public function getLinks()
{
return $this->links;
}
}
@@ -0,0 +1,22 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Root;
use Seboettg\CiteProc\Style\InheritableNameAttributesTrait;
/**
* Class Root
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Root
{
use InheritableNameAttributesTrait;
}
@@ -0,0 +1,78 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Root\Root;
use Seboettg\CiteProc\Style\Options\BibliographyOptions;
use SimpleXMLElement;
/**
* Class Bibliography
*
* The cs:bibliography element describes the formatting of bibliographies, which list one or more bibliographic sources.
* The required cs:layout child element describes how each bibliographic entry should be formatted. cs:layout may be
* preceded by a cs:sort element, which can be used to specify how references within the bibliography should be sorted
* (see Sorting).
*
* @package Seboettg\CiteProc
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Bibliography extends StyleElement
{
private $node;
/**
* Bibliography constructor.
* @param SimpleXMLElement $node
* @param Root $parent
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, Root $parent)
{
parent::__construct($node, $parent);
$this->node = $node;
$bibliographyOptions = new BibliographyOptions($node);
CiteProc::getContext()->setBibliographySpecificOptions($bibliographyOptions);
$this->initInheritableNameAttributes($node);
}
/**
* @param array|DataList $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
if (!$this->attributesInitialized) {
$this->initInheritableNameAttributes($this->node);
}
$subsequentAuthorSubstitute = CiteProc::getContext()
->getBibliographySpecificOptions()
->getSubsequentAuthorSubstitute();
$subsequentAuthorSubstituteRule = CiteProc::getContext()
->getBibliographySpecificOptions()
->getSubsequentAuthorSubstituteRule();
if ($subsequentAuthorSubstitute !== null && !empty($subsequentAuthorSubstituteRule)) {
CiteProc::getContext()
->getCitationData()
->setSubsequentAuthorSubstitute($subsequentAuthorSubstitute);
CiteProc::getContext()
->getCitationData()
->setSubsequentAuthorSubstituteRule($subsequentAuthorSubstituteRule);
}
return $this->layout->render($data, $citationNumber);
}
}
@@ -0,0 +1,62 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Style\Options\CitationOptions;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class Citation
*
* The cs:citation element describes the formatting of citations, which consist of one or more references (“cites”) to
* bibliographic sources. Citations appear in the form of either in-text citations (in the author (e.g. “[Doe]”),
* author-date (“[Doe 1999]”), label (“[doe99]”) or number (“[1]”) format) or notes. The required cs:layout child
* element describes what, and how, bibliographic data should be included in the citations (see Layout).
*
* @package Seboettg\CiteProc\Node\Style
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Citation extends StyleElement
{
private $node;
/**
* Citation constructor.
* @param SimpleXMLElement $node
* @param $parent
* @throws InvalidStylesheetException
*/
public function __construct(SimpleXMLElement $node, $parent)
{
parent::__construct($node, $parent);
$citationOptions = new CitationOptions($node);
CiteProc::getContext()->setCitationSpecificOptions($citationOptions);
$this->node = $node;
}
/**
* @param array|DataList $data
* @param ArrayList $citationItems
* @return string
*/
public function render($data, $citationItems)
{
if (!$this->attributesInitialized) {
$this->initInheritableNameAttributes($this->node);
}
return $this->layout->render($data, $citationItems);
}
}
@@ -0,0 +1,651 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Rendering\HasParent;
use Seboettg\CiteProc\Rendering\Name\Name;
use Seboettg\CiteProc\Rendering\Name\Names;
use Seboettg\CiteProc\Root\Root;
use SimpleXMLElement;
/**
* Class InheritableNameAttributesTrait
*
* Attributes for the cs:names and cs:name elements may also be set on cs:style, cs:citation and cs:bibliography. This
* eliminates the need to repeat the same attributes and attribute values for every occurrence of the cs:names and
* cs:name elements.
*
* The available inheritable attributes for cs:name are and, delimiter-precedes-et-al, delimiter-precedes-last,
* et-al-min, et-al-use-first, et-al-use-last, et-al-subsequent-min, et-al-subsequent-use-first, initialize,
* initialize-with, name-as-sort-order and sort-separator. The attributes name-form and name-delimiter correspond to the
* form and delimiter attributes on cs:name. Similarly, names-delimiter corresponds to the delimiter attribute on
* cs:names.
*
*
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait InheritableNameAttributesTrait
{
public static $attributes = [
'and',
'delimiter-precedes-et-al',
'delimiter-precedes-last',
'et-al-min',
'et-al-use-first',
'et-al-use-last',
'et-al-subsequent-min',
'et-al-subsequent-use-first',
'initialize',
'initialize-with',
'name-as-sort-order',
'sort-separator',
'name-form',
'form',
'name-delimiter',
'delimiter'
];
/**
* @var bool
*/
protected $attributesInitialized = false;
/**
* Specifies the delimiter between the second to last and last name of the names in a name variable. Allowed values
* are “text” (selects the “and” term, e.g. “Doe, Johnson and Smith”) and “symbol” (selects the ampersand,
* e.g. “Doe, Johnson & Smith”).
*
* @var string
*/
private $and;
/**
* Determines when the name delimiter or a space is used between a truncated name list and the “et-al”
* (or “and others”) term in case of et-al abbreviation. Allowed values:
* - “contextual” - (default), name delimiter is only used for name lists truncated to two or more names
* - 1 name: “J. Doe et al.”
* - 2 names: “J. Doe, S. Smith, et al.”
* - “after-inverted-name” - name delimiter is only used if the preceding name is inverted as a result of the
* - name-as-sort-order attribute. E.g. with name-as-sort-order set to “first”:
* - “Doe, J., et al.”
* - “Doe, J., S. Smith et al.”
* - “always” - name delimiter is always used
* - 1 name: “J. Doe, et al.”
* - 2 names: “J. Doe, S. Smith, et al.”
* - “never” - name delimiter is never used
* - 1 name: “J. Doe et al.”
* - 2 names: “J. Doe, S. Smith et al.”
*
* @var string
*/
private $delimiterPrecedesEtAl;
/**
* Determines when the name delimiter is used to separate the second to last and the last name in name lists (if
* and is not set, the name delimiter is always used, regardless of the value of delimiter-precedes-last). Allowed
* values:
*
* - “contextual” - (default), name delimiter is only used for name lists with three or more names
* - 2 names: “J. Doe and T. Williams”
* - 3 names: “J. Doe, S. Smith, and T. Williams”
* - “after-inverted-name” - name delimiter is only used if the preceding name is inverted as a result of the
* name-as-sort-order attribute. E.g. with name-as-sort-order set to “first”:
* - “Doe, J., and T. Williams”
* - “Doe, J., S. Smith and T. Williams”
* - “always” - name delimiter is always used
* - 2 names: “J. Doe, and T. Williams”
* - 3 names: “J. Doe, S. Smith, and T. Williams”
* - “never” - name delimiter is never used
* - 2 names: “J. Doe and T. Williams”
* - 3 names: “J. Doe, S. Smith and T. Williams”
*
* @var string
*/
private $delimiterPrecedesLast;
/**
* Use of etAlMin (et-al-min attribute) and etAlUseFirst (et-al-use-first attribute) enables et-al abbreviation. If
* the number of names in a name variable matches or exceeds the number set on etAlMin, the rendered name list is
* truncated after reaching the number of names set on etAlUseFirst.
*
* @var int
*/
private $etAlMin;
/**
* Use of etAlMin (et-al-min attribute) and etAlUseFirst (et-al-use-first attribute) enables et-al abbreviation. If
* the number of names in a name variable matches or exceeds the number set on etAlMin, the rendered name list is
* truncated after reaching the number of names set on etAlUseFirst.
*
* @var int
*/
private $etAlUseFirst;
/**
* When set to “true” (the default is “false”), name lists truncated by et-al abbreviation are followed by the name
* delimiter, the ellipsis character, and the last name of the original name list. This is only possible when the
* original name list has at least two more names than the truncated name list (for this the value of
* et-al-use-first/et-al-subsequent-min must be at least 2 less than the value of
* et-al-min/et-al-subsequent-use-first).
*
* @var bool
*/
private $etAlUseLast = false;
/**
* If used, the values of these attributes (et-al-subsequent-min and et-al-subsequent-use-first) replace those of
* respectively et-al-min and et-al-use-first for subsequent cites (cites referencing earlier cited items).
*
* @var int
*/
private $etAlSubsequentMin;
/**
* If used, the values of these attributes (et-al-subsequent-min and et-al-subsequent-use-first) replace those of
* respectively et-al-min and et-al-use-first for subsequent cites (cites referencing earlier cited items).
*
* @var int
*/
private $etAlSubsequentUseFirst;
/**
* When set to “false” (the default is “true”), given names are no longer initialized when “initialize-with” is set.
* However, the value of “initialize-with” is still added after initials present in the full name (e.g. with
* initialize set to “false”, and initialize-with set to ”.”, “James T Kirk” becomes “James T. Kirk”).
*
* @var bool
*/
private $initialize = true;
/**
* When set, given names are converted to initials. The attribute value is added after each initial (”.” results
* in “J.J. Doe”). For compound given names (e.g. “Jean-Luc”), hyphenation of the initials can be controlled with
* the global initialize-with-hyphen option
*
* @var string
*/
private $initializeWith = false;
/**
* Specifies that names should be displayed with the given name following the family name (e.g. “John Doe” becomes
* “Doe, John”). The attribute has two possible values:
* - “first” - attribute only has an effect on the first name of each name variable
* - “all” - attribute has an effect on all names
* Note that even when name-as-sort-order changes the name-part order, the display order is not necessarily the same
* as the sorting order for names containing particles and suffixes (see Name-part order). Also, name-as-sort-order
* only affects names written in the latin or Cyrillic alphabets. Names written in other alphabets (e.g. Asian
* scripts) are always displayed with the family name preceding the given name.
*
* @var string
*/
private $nameAsSortOrder = "";
/**
* Sets the delimiter for name-parts that have switched positions as a result of name-as-sort-order. The default
* value is ”, ” (“Doe, John”). As is the case for name-as-sort-order, this attribute only affects names written in
* the latin or Cyrillic alphabets.
*
* @var string
*/
private $sortSeparator = ", ";
/**
* Specifies whether all the name-parts of personal names should be displayed (value “long”, the default), or only
* the family name and the non-dropping-particle (value “short”). A third value, “count”, returns the total number
* of names that would otherwise be rendered by the use of the cs:names element (taking into account the effects of
* et-al abbreviation and editor/translator collapsing), which allows for advanced sorting.
*
* @var string
*/
private $form;
private $nameForm = "long";
private $nameDelimiter = ", ";
public function isDescendantOfMacro()
{
$parent = $this->parent;
while ($parent != null && $parent instanceof HasParent) {
if ($parent instanceof Macro) {
return true;
}
$parent = $parent->getParent();
}
return false;
}
/**
* @param SimpleXMLElement $node
*/
public function initInheritableNameAttributes(SimpleXMLElement $node)
{
$context = CiteProc::getContext();
$parentStyleElement = null;
$root = $context->getRoot();
if ($this instanceof Name || $this instanceof Names) {
if ($context->getMode() === "bibliography") {
$parentStyleElement = $context->getBibliography();
} else {
$parentStyleElement = $context->getCitation();
}
} elseif ($this instanceof StyleElement) {
$parentStyleElement = $root;
}
foreach (self::$attributes as $nameAttribute) {
$attribute = $node[$nameAttribute];
switch ($nameAttribute) {
case 'and':
if (!empty($attribute)) {
$this->setAnd((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getAnd())) {
$this->setAnd($parentStyleElement->getAnd());
} elseif (!empty($root) && !empty($root->getAnd())) {
$this->setAnd($root->getAnd());
}
break;
case 'delimiter-precedes-et-al':
if (!empty($attribute)) {
$this->setDelimiterPrecedesEtAl((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getDelimiterPrecedesEtAl())) {
$this->setDelimiterPrecedesEtAl($parentStyleElement->getDelimiterPrecedesEtAl());
} elseif (!empty($root)) {
$this->setDelimiterPrecedesEtAl($root->getDelimiterPrecedesEtAl());
}
break;
case 'delimiter-precedes-last':
if (!empty($attribute)) {
$this->setDelimiterPrecedesLast((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getDelimiterPrecedesLast())) {
$this->setDelimiterPrecedesLast($parentStyleElement->getDelimiterPrecedesLast());
} elseif (!empty($root)) {
$this->setDelimiterPrecedesLast($root->getDelimiterPrecedesLast());
}
break;
case 'et-al-min':
if (!empty($attribute)) {
$this->setEtAlMin(intval((string) $attribute));
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getEtAlMin())) {
$this->setEtAlMin($parentStyleElement->getEtAlMin());
} elseif (!empty($root)) {
$this->setEtAlMin($root->getEtAlMin());
}
break;
case 'et-al-use-first':
if (!empty($attribute)) {
$this->setEtAlUseFirst(intval((string) $attribute));
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getEtAlUseFirst())) {
$this->setEtAlUseFirst($parentStyleElement->getEtAlUseFirst());
} elseif (!empty($root)) {
$this->setEtAlUseFirst($root->getEtAlUseFirst());
}
break;
case 'et-al-subsequent-min':
if (!empty($attribute)) {
$this->setEtAlSubsequentMin(intval((string) $attribute));
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getEtAlSubsequentMin())) {
$this->setEtAlSubsequentMin($parentStyleElement->getEtAlSubsequentMin());
} elseif (!empty($root)) {
$this->setEtAlSubsequentMin($root->getEtAlSubsequentMin());
}
break;
case 'et-al-subsequent-use-first':
if (!empty($attribute)) {
$this->etAlSubsequentUseFirst = intval((string) $attribute);
$this->setEtAlSubsequentUseFirst(intval((string) $attribute));
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getEtAlSubsequentUseFirst())) {
$this->setEtAlSubsequentUseFirst($parentStyleElement->getEtAlSubsequentUseFirst());
} elseif (!empty($root)) {
$this->setEtAlSubsequentUseFirst($root->getEtAlSubsequentUseFirst());
}
break;
case 'et-al-use-last':
if (!empty($attribute)) {
$this->setEtAlUseLast(((string) $attribute) === "true");
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getEtAlUseLast())) {
$this->setEtAlUseLast($parentStyleElement->getEtAlUseLast());
} elseif (!empty($root)) {
$this->setEtAlUseLast($root->getEtAlUseLast());
}
break;
case 'initialize':
if (!empty($attribute)) {
$this->setInitialize(((string) $attribute) === "true");
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getInitialize())) {
$this->setInitialize($parentStyleElement->getInitialize());
} elseif (!empty($root)) {
$this->setInitialize($root->getInitialize());
}
break;
case 'initialize-with':
if (!empty($attribute)) {
$this->setInitializeWith((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getInitializeWith())) {
$this->setInitializeWith($parentStyleElement->getInitializeWith());
} elseif (!empty($root)) {
$this->setInitializeWith($root->getInitializeWith());
}
break;
case 'name-as-sort-order':
if (!empty($attribute)) {
$this->setNameAsSortOrder((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getNameAsSortOrder())) {
$this->setNameAsSortOrder($parentStyleElement->getNameAsSortOrder());
} elseif (!empty($root)) {
$this->setNameAsSortOrder($root->getNameAsSortOrder());
}
break;
case 'sort-separator':
if (!empty($attribute)) {
$this->setSortSeparator((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getSortSeparator())) {
$this->setSortSeparator($parentStyleElement->getSortSeparator());
} elseif (!empty($root)) {
$this->setSortSeparator($root->getSortSeparator());
}
break;
case 'name-form':
if ($this instanceof Root || $this instanceof StyleElement) {
if (!empty($attribute)) {
$this->setNameForm((string) $attribute);
} elseif (!empty($parentStyleElement)) {
$this->setNameForm($parentStyleElement->getNameForm());
}
}
break;
case 'form':
if ($this instanceof Name) {
if (!empty($attribute)) {
$this->setForm((string) $attribute);
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getNameForm())) {
$this->setForm($parentStyleElement->getNameForm());
} elseif (!empty($root)) {
$this->setForm($root->getNameForm());
}
}
break;
case 'name-delimiter':
if ($this instanceof Root || $this instanceof StyleElement) {
if (!empty($attribute)) {
$this->setNameDelimiter((string) $attribute);
} elseif (!empty($parentStyleElement)) {
$this->setNameDelimiter($parentStyleElement->getNameDelimiter());
}
} else {
/* The attributes name-form and name-delimiter correspond to the form and delimiter attributes
on cs:name. Similarly, names-delimiter corresponds to the delimiter attribute on cs:names. */
if (!empty($attribute)) {
$this->nameDelimiter = $this->delimiter = (string) $attribute;
} elseif (!empty($parentStyleElement) && !empty($parentStyleElement->getNameDelimiter())) {
$this->nameDelimiter = $this->delimiter = $parentStyleElement->getNameDelimiter();
} elseif (!empty($root)) {
$this->nameDelimiter = $this->delimiter = $root->getNameDelimiter();
}
}
break;
case 'delimiter':
if ($this instanceof Name) {
if (!empty($attribute)) {
$this->setDelimiter((string) $attribute);
} elseif (!empty($parentStyleElement)) {
$this->setDelimiter($parentStyleElement->getNameDelimiter());
} elseif (!empty($root)) {
$this->setDelimiter($root->getNameDelimiter());
}
}
}
}
$this->attributesInitialized = true;
}
/**
* @return string
*/
public function getAnd()
{
return $this->and;
}
/**
* @param string $and
*/
public function setAnd($and)
{
$this->and = $and;
}
/**
* @return string
*/
public function getDelimiterPrecedesEtAl()
{
return $this->delimiterPrecedesEtAl;
}
/**
* @param string $delimiterPrecedesEtAl
*/
public function setDelimiterPrecedesEtAl($delimiterPrecedesEtAl)
{
$this->delimiterPrecedesEtAl = $delimiterPrecedesEtAl;
}
/**
* @return string
*/
public function getDelimiterPrecedesLast()
{
return $this->delimiterPrecedesLast;
}
/**
* @param string $delimiterPrecedesLast
*/
public function setDelimiterPrecedesLast($delimiterPrecedesLast)
{
$this->delimiterPrecedesLast = $delimiterPrecedesLast;
}
/**
* @return int
*/
public function getEtAlMin()
{
return $this->etAlMin;
}
/**
* @param int $etAlMin
*/
public function setEtAlMin($etAlMin)
{
$this->etAlMin = $etAlMin;
}
/**
* @return int
*/
public function getEtAlUseFirst()
{
return $this->etAlUseFirst;
}
/**
* @param int $etAlUseFirst
*/
public function setEtAlUseFirst($etAlUseFirst)
{
$this->etAlUseFirst = $etAlUseFirst;
}
/**
* @return bool
*/
public function getEtAlUseLast()
{
return $this->etAlUseLast;
}
/**
* @param bool $etAlUseLast
*/
public function setEtAlUseLast($etAlUseLast)
{
$this->etAlUseLast = $etAlUseLast;
}
/**
* @return int
*/
public function getEtAlSubsequentMin()
{
return $this->etAlSubsequentMin;
}
/**
* @param int $etAlSubsequentMin
*/
public function setEtAlSubsequentMin($etAlSubsequentMin)
{
$this->etAlSubsequentMin = $etAlSubsequentMin;
}
/**
* @return int
*/
public function getEtAlSubsequentUseFirst()
{
return $this->etAlSubsequentUseFirst;
}
/**
* @param int $etAlSubsequentUseFirst
*/
public function setEtAlSubsequentUseFirst($etAlSubsequentUseFirst)
{
$this->etAlSubsequentUseFirst = $etAlSubsequentUseFirst;
}
/**
* @return bool
*/
public function getInitialize()
{
return $this->initialize;
}
/**
* @param bool $initialize
*/
public function setInitialize($initialize)
{
$this->initialize = $initialize;
}
/**
* @return string
*/
public function getInitializeWith()
{
return $this->initializeWith;
}
/**
* @param string $initializeWith
*/
public function setInitializeWith($initializeWith)
{
$this->initializeWith = $initializeWith;
}
/**
* @return string
*/
public function getNameAsSortOrder()
{
return $this->nameAsSortOrder;
}
/**
* @param string $nameAsSortOrder
*/
public function setNameAsSortOrder($nameAsSortOrder)
{
$this->nameAsSortOrder = $nameAsSortOrder;
}
/**
* @return string
*/
public function getSortSeparator()
{
return $this->sortSeparator;
}
/**
* @param string $sortSeparator
*/
public function setSortSeparator($sortSeparator)
{
$this->sortSeparator = $sortSeparator;
}
/**
* @return mixed
*/
public function getForm()
{
return $this->form;
}
/**
* @param mixed $form
*/
public function setForm($form)
{
$this->form = $form;
}
/**
* @return string
*/
public function getNameForm()
{
return $this->nameForm;
}
/**
* @param string $nameForm
*/
public function setNameForm($nameForm)
{
$this->nameForm = $nameForm;
}
/**
* @return string
*/
public function getNameDelimiter()
{
return $this->nameDelimiter;
}
/**
* @param string $nameDelimiter
*/
public function setNameDelimiter($nameDelimiter)
{
$this->nameDelimiter = $nameDelimiter;
}
}
@@ -0,0 +1,114 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Rendering\HasParent;
use Seboettg\CiteProc\Rendering\Rendering;
use Seboettg\CiteProc\Root\Root;
use Seboettg\CiteProc\Styles\ConsecutivePunctuationCharacterTrait;
use Seboettg\CiteProc\Util\Factory;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class Macro
*
* Macros, defined with cs:macro elements, contain formatting instructions. Macros can be called with cs:text from
* within other macros and the cs:layout element of cs:citation and cs:bibliography, and with cs:key from within cs:sort
* of cs:citation and cs:bibliography. It is recommended to place macros after any cs:locale elements and before the
* cs:citation element.
*
* Macros are referenced by the value of the required name attribute on cs:macro. The cs:macro element must contain one
* or more rendering elements.
*
* @package Seboettg\CiteProc\Rendering
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Macro implements Rendering, HasParent
{
use ConsecutivePunctuationCharacterTrait;
/**
* @var ArrayList
*/
private $children;
/**
* @var string
*/
private $name;
/**
* @var Root
*/
private $parent;
/**
* Macro constructor.
* @param SimpleXMLElement $node
* @param Root $parent
* @throws CiteProcException
*/
public function __construct(SimpleXMLElement $node, $parent)
{
$this->parent = $parent;
$attr = $node->attributes();
if (!isset($attr['name'])) {
throw new CiteProcException("Attribute \"name\" needed.");
}
$this->name = (string) $attr['name'];
$this->children = new ArrayList();
foreach ($node->children() as $child) {
$this->children->append(Factory::create($child, $this));
}
}
/**
* @param array|DataList $data
* @param int|null $citationNumber
* @return string
*/
public function render($data, $citationNumber = null)
{
$ret = [];
/** @var Rendering $child */
foreach ($this->children as $child) {
$res = $child->render($data, $citationNumber);
$this->getChildrenAffixesAndDelimiter($child);
if (!empty($res)) {
$ret[] = $res;
}
}
$res = implode("", $ret);
if (!empty($res)) {
$res = $this->removeConsecutiveChars($res);
}
return $res;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return Root
*/
public function getParent()
{
return $this->parent;
}
}
@@ -0,0 +1,193 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Options;
use SimpleXMLElement;
/**
* Class GlobalOptionsTrait
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class BibliographyOptions
{
/**
* If set, the value of this attribute replaces names in a bibliographic entry that also occur in the preceding
* entry. The exact method of substitution depends on the value of the subsequent-author-substitute-rule attribute.
* Substitution is limited to the names of the first cs:names element rendered. (Bibliography-specific option)
*
* @var string
*/
private $subsequentAuthorSubstitute;
/**
* Specifies when and how names are substituted as a result of subsequent-author-substitute.
* (Bibliography-specific option)
*
* @var SubsequentAuthorSubstituteRule
*/
private $subsequentAuthorSubstituteRule;
/**
* If set to “true” (“false” is the default), bibliographic entries are rendered with hanging-indents.
* @var string
*/
private $hangingIndent = false;
/**
* If set, subsequent lines of bibliographic entries are aligned along the second field. With “flush”, the first
* field is flush with the margin. With “margin”, the first field is put in the margin, and subsequent lines are
* aligned with the margin.
* @var string
*/
private $secondFieldAlign;
/**
* Specifies vertical line distance. Defaults to “1” (single-spacing), and can be set to any positive integer to
* specify a multiple of the standard unit of line height (e.g. “2” for double-spacing).
* @var string
*/
private $lineSpacing;
/**
* Specifies vertical distance between bibliographic entries. By default (with a value of “1”), entries are
* separated by a single additional line-height (as set by the line-spacing attribute). Can be set to any
* non-negative integer to specify a multiple of this amount.
* @var string
*/
private $entrySpacing;
public function __construct(SimpleXMLElement $node)
{
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'subsequent-author-substitute':
$this->subsequentAuthorSubstitute = (string) $attribute;
break;
case 'subsequent-author-substitute-rule':
$this->subsequentAuthorSubstituteRule = new SubsequentAuthorSubstituteRule((string) $attribute);
break;
case 'hanging-indent':
$this->hangingIndent = "true" === (string) $attribute ? true : false;
break;
case 'second-field-align':
$this->secondFieldAlign = (string) $attribute;
break;
case 'line-spacing':
$this->lineSpacing = (string) $attribute;
break;
case 'entry-spacing':
$this->entrySpacing = (string) $attribute;
}
}
if (empty($this->subsequentAuthorSubstituteRule)) {
$this->subsequentAuthorSubstituteRule = new SubsequentAuthorSubstituteRule("complete-all");
}
}
/**
* @return string
*/
public function getSubsequentAuthorSubstitute()
{
return $this->subsequentAuthorSubstitute;
}
/**
* @param string $subsequentAuthorSubstitute
*/
public function setSubsequentAuthorSubstitute($subsequentAuthorSubstitute)
{
$this->subsequentAuthorSubstitute = $subsequentAuthorSubstitute;
}
/**
* @return SubsequentAuthorSubstituteRule
*/
public function getSubsequentAuthorSubstituteRule()
{
return $this->subsequentAuthorSubstituteRule;
}
/**
* @param SubsequentAuthorSubstituteRule $subsequentAuthorSubstituteRule
*/
public function setSubsequentAuthorSubstituteRule($subsequentAuthorSubstituteRule)
{
$this->subsequentAuthorSubstituteRule = $subsequentAuthorSubstituteRule;
}
/**
* @return string
*/
public function getHangingIndent()
{
return $this->hangingIndent;
}
/**
* @param string $hangingIndent
*/
public function setHangingIndent($hangingIndent)
{
$this->hangingIndent = $hangingIndent;
}
/**
* @return string
*/
public function getSecondFieldAlign()
{
return $this->secondFieldAlign;
}
/**
* @param string $secondFieldAlign
*/
public function setSecondFieldAlign($secondFieldAlign)
{
$this->secondFieldAlign = $secondFieldAlign;
}
/**
* @return string
*/
public function getLineSpacing()
{
return $this->lineSpacing;
}
/**
* @param string $lineSpacing
*/
public function setLineSpacing($lineSpacing)
{
$this->lineSpacing = $lineSpacing;
}
/**
* @return string
*/
public function getEntrySpacing()
{
return $this->entrySpacing;
}
/**
* @param string $entrySpacing
*/
public function setEntrySpacing($entrySpacing)
{
$this->entrySpacing = $entrySpacing;
}
}
@@ -0,0 +1,25 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Options;
use SimpleXMLElement;
/**
* Class GlobalOptionsTrait
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class CitationOptions
{
public function __construct(SimpleXMLElement $node)
{
//TODO: implement
}
}
@@ -0,0 +1,45 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Options;
use MyCLabs\Enum\Enum;
/**
* Class DemoteNonDroppingParticle
*
* Sets the display and sorting behavior of the non-dropping-particle in inverted names (e.g. “Koning, W. de”).
* Some names include a particle that should never be demoted. For these cases the particle should just be included in
* the family name field, for example for the French general Charles de Gaulle:
*
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class DemoteNonDroppingParticle extends Enum
{
/**
* “never”: the non-dropping-particle is treated as part of the family name, whereas the dropping-particle is
* appended (e.g. “de Koning, W.”, “La Fontaine, Jean de”). The non-dropping-particle is part of the primary sort
* key (sort order A, e.g. “de Koning, W.” appears under “D”).
*/
const NEVER = "never";
/**
* “sort-only”: same display behavior as “never”, but the non-dropping-particle is demoted to a secondary sort key
* (sort order B, e.g. “de Koning, W.” appears under “K”).
*/
const SORT_ONLY = "sort-only";
/**
* “display-and-sort” (default): the dropping and non-dropping-particle are appended (e.g. “Koning, W. de” and
* “Fontaine, Jean de La”). For name sorting, all particles are part of the secondary sort key (sort order B, e.g.
* “Koning, W. de” appears under “K”).
*/
const DISPLAY_AND_SORT = "display-and-sort";
}
@@ -0,0 +1,87 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Options;
use SimpleXMLElement;
/**
* Class GlobalOptionsTrait
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class GlobalOptions
{
/**
* @var bool
*/
private $initializeWithHyphen = true;
/**
* @var PageRangeFormats
*/
private $pageRangeFormat;
/**
* @var DemoteNonDroppingParticle
*/
private $demoteNonDroppingParticles;
/**
* GlobalOptions constructor.
* @param SimpleXMLElement $node
*/
public function __construct(SimpleXMLElement $node)
{
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'initialize-with-hyphen':
$this->initializeWithHyphen = !("false" === (string) $attribute);
break;
case 'page-range-format':
$this->pageRangeFormat = new PageRangeFormats((string) $attribute);
break;
case 'demote-non-dropping-particle':
$this->demoteNonDroppingParticles = new DemoteNonDroppingParticle((string) $attribute);
}
}
}
/**
* Specifies whether compound given names (e.g. “Jean-Luc”) should be initialized with a hyphen (“J.-L.”, value
* “true”, default) or without (“J.L.”, value “false”).
* @return bool
*/
public function isInitializeWithHyphen(): bool
{
return $this->initializeWithHyphen;
}
/**
* Activates expansion or collapsing of page ranges: “chicago” (“32128”), “expanded” (e.g. “321328”),
* “minimal” (“3218”), or “minimal-two” (“32128”). Delimits page ranges
* with the “page-range-delimiter” term (introduced with CSL 1.0.1, and defaults to an en-dash). If the attribute is
* not set, page ranges are rendered without reformatting.
* @return PageRangeFormats
*/
public function getPageRangeFormat(): ?PageRangeFormats
{
return $this->pageRangeFormat;
}
/**
* Sets the display and sorting behavior of the non-dropping-particle in inverted names (e.g. “Koning, W. de”).
* @return DemoteNonDroppingParticle
*/
public function getDemoteNonDroppingParticles(): ?DemoteNonDroppingParticle
{
return $this->demoteNonDroppingParticles;
}
}
@@ -0,0 +1,61 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Options;
use MyCLabs\Enum\Enum;
/**
* Class PageRangeFormats
*
* Activates expansion or collapsing of page ranges: “chicago” (“32128”), “expanded” (e.g. “321328”),
* “minimal” (“3218”), or “minimal-two” (“32128”) (see also Appendix V - Page Range Formats). Delimits page ranges
* with the “page-range-delimiter” term (introduced with CSL 1.0.1, and defaults to an en-dash). If the attribute is
* not set, page ranges are rendered without reformatting.
*
* @package Seboettg\CiteProc\Style
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class PageRangeFormats extends Enum
{
/**
* Page ranges are abbreviated according to the Chicago Manual of Style-rules:
*
* First number Second number Examples
* =================================================================================================================
* Less than 100 Use all digits 310; 7172
* -----------------------------------------------------------------------------------------------------------------
* 100 or multiple of 100 Use all digits 100104; 600613;
* 11001123
* -----------------------------------------------------------------------------------------------------------------
* 101 through 109 (in multiples of 100) Use changed part only, omitting unneeded zeros 1078; 50517; 10026
* -----------------------------------------------------------------------------------------------------------------
* 110 through 199 (in multiples of 100) Use two digits, or more as needed 32125; 415532;
* 1156468; 13792803
* -----------------------------------------------------------------------------------------------------------------
* 4 digits If numbers are four digits long and three
* digits change, use all digits
*/
const CHICAGO = "chicago";
/**
* Abbreviated page ranges are expanded to their non-abbreviated form: 4245, 321328, 27872816
*/
const EXPANDED = "expanded";
/**
* All digits repeated in the second number are left out: 425, 3218, 2787816
*/
const MINIMAL = "minimal";
/**
* As “minimal”, but at least two digits are kept in the second number when it has two or more digits long.
*/
const MINIMAL_TWO = "minimal-two";
}
@@ -0,0 +1,50 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Options;
use MyCLabs\Enum\Enum;
/**
* Class SubsequentAuthorSubstituteRule
*
* Specifies when and how names are substituted as a result of subsequent-author-substitute.
*
* @package Seboettg\CiteProc\Style
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class SubsequentAuthorSubstituteRule extends Enum
{
/**
* “complete-all” - (default), when all rendered names of the name variable match those in the preceding
* bibliographic entry, the value of subsequent-author-substitute replaces the entire name list (including
* punctuation and terms like “et al” and “and”), except for the affixes set on the cs:names element.
*/
const COMPLETE_ALL = "complete-all";
/**
* “complete-each” - requires a complete match like “complete-all”, but now the value of
* subsequent-author-substitute substitutes for each rendered name.
*/
const COMPLETE_EACH = "complete-each";
/**
* “partial-each” - when one or more rendered names in the name variable match those in the preceding bibliographic
* entry, the value of subsequent-author-substitute substitutes for each matching name. Matching starts with the
* first name, and continues up to the first mismatch.
*/
const PARTIAL_EACH = "partial-each";
/**
* “partial-first” - as “partial-each”, but substitution is limited to the first name of the name variable.
*/
const PARTIAL_FIRST = "partial-first";
}
@@ -0,0 +1,157 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Sort;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Util\Variables;
use SimpleXMLElement;
/**
* Class Key
*
* The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key, must
* be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can be set to
* either “ascending” (default) or “descending” with the sort attribute. The attributes names-min, names-use-first, and
* names-use-last may be used to override the values of the corresponding et-al-min/et-al-subsequent-min,
* et-al-use-first/et-al-subsequent-use-first and et-al-use-last attributes, and affect all names generated via macros
* called by cs:key.
*
* @package Seboettg\CiteProc\Style\Sort
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Key implements SortKey
{
/**
* variable name or macro
* @var string
*/
private $variable;
/**
* the sort direction can be set to either “ascending” (default) or “descending” with the sort attribute
* @var string
*/
private $sort = "ascending";
/**
* macro name
* @var string
*/
private $macro;
/**
* only relevant for date ranges
* @var int
*/
private $rangePart = 1;
/**
* Key constructor.
* The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key,
* must be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can
* be set to either “ascending” (default) or “descending” with the sort attribute.
*
* TODO: The attributes names-min, names-use-first, and names-use-last may be used to override the values of the
* corresponding et-al-min/et-al-subsequent-min, et-al-use-first/et-al-subsequent-use-first and et-al-use-last
* attributes, and affect all names generated via macros called by cs:key.
*
* @param SimpleXMLElement $node
*/
public function __construct(SimpleXMLElement $node)
{
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
$name = $attribute->getName();
if ($name === "variable") {
$this->variable = (string) $attribute;
}
if ($name === "sort") {
$this->sort = (string) $attribute;
}
if ($name === "macro") {
$this->variable = "macro";
$this->macro = (string) $attribute;
}
}
}
/**
* @return string
*/
public function getVariable()
{
return $this->variable;
}
/**
* @return string (ascending|descending)
*/
public function getSort()
{
return $this->sort;
}
/**
* @return string
*/
public function getMacro()
{
return $this->macro;
}
/**
* @return bool
*/
public function isNameVariable()
{
return Variables::isNameVariable($this->variable);
}
/**
* @return bool
*/
public function isNumberVariable()
{
return Variables::isNumberVariable($this->variable);
}
/**
* @return bool
*/
public function isDateVariable()
{
return Variables::isDateVariable($this->variable);
}
/**
* @return bool
*/
public function isMacro()
{
return $this->variable === "macro" && !empty(CiteProc::getContext()->getMacro($this->macro));
}
/**
* @param $rangePart
*/
public function setRangePart($rangePart)
{
$this->rangePart = $rangePart;
}
/**
* @return int
*/
public function getRangePart()
{
return $this->rangePart;
}
}
@@ -0,0 +1,167 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Sort;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Data\DataList;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Util\DateHelper;
use Seboettg\CiteProc\Util\Variables;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Class Sort
*
* cs:citation and cs:bibliography may include a cs:sort child element before the cs:layout element to specify the
* sorting order of respectively cites within citations, and bibliographic entries within the bibliography.
*
* The cs:sort element must contain one or more cs:key child elements. The sort key, set as an attribute on cs:key, must
* be a variable (see Appendix IV - Variables) or macro name. For each cs:key element, the sort direction can be set to
* either “ascending” (default) or “descending” with the sort attribute.
*
* @package Seboettg\CiteProc\Style
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Sort
{
/**
* ordered list contains sorting keys
*
* @var ArrayList
*/
private $sortingKeys;
/**
* @var SimpleXMLElement $node
*/
public function __construct(SimpleXMLElement $node)
{
$this->sortingKeys = new ArrayList();
/** @var SimpleXMLElement $child */
foreach ($node->children() as $child) {
if ("key" === $child->getName()) {
$this->sortingKeys->append(new Key($child));
}
}
}
/**
* Function in order to sort a set of csl items by one or multiple sort keys.
* Sort keys are evaluated in sequence. A primary sort is performed on all items using the first sort key.
* A secondary sort, using the second sort key, is applied to items sharing the first sort key value. A tertiary
* sort, using the third sort key, is applied to items sharing the first and second sort key values. Sorting
* continues until either the order of all items is fixed, or until the sort keys are exhausted. Items with an
* empty sort key value are placed at the end of the sort, both for ascending and descending sorts.
*
* @param DataList|array $data reference
*/
public function sort(&$data)
{
if (is_array($data)) {
$data = new DataList(...$data);
}
$dataToSort = $data->toArray();
try {
$data->replace($this->performSort(0, $dataToSort));
} catch (CiteProcException $e) {
//nothing to do, because $data is passed by referenced
}
}
/**
* Recursive function in order to sort a set of csl items by one or multiple sort keys.
* All items will be distributed by the value (defined in respective sort key) in an associative array (grouped).
* Afterwards the array will be sorted by the array key. If a further sort key exist, each of these groups will be
* sorted by a recursive function call. Finally the array will be flatted.
*
* @param $keyNumber
* @param array $dataToSort
* @return array
* @throws CiteProcException
*/
private function performSort($keyNumber, $dataToSort)
{
if (count($dataToSort) < 2) {
return $dataToSort;
}
/** @var Key $key */
$key = $this->sortingKeys->get($keyNumber);
$variable = $key->getVariable();
$groupedItems = [];
if ($key->isDateVariable()) {
if (DateHelper::hasDateRanges($dataToSort, $variable, "all")) {
$newKey = clone $key;
$newKey->setRangePart(2);
$key->setRangePart(1);
$this->sortingKeys->append($newKey);
}
}
//grouping by value
foreach ($dataToSort as $citationNumber => $dataItem) {
if ($key->isNameVariable()) {
$sortKey = Variables::nameHash($dataItem, $variable);
} elseif ($key->isNumberVariable()) {
$sortKey = $dataItem->{$variable};
} elseif ($key->isDateVariable()) {
$sortKey = DateHelper::getSortKeyDate($dataItem, $key);
} elseif ($key->isMacro()) {
$sortKey = mb_strtolower(strip_tags(CiteProc::getContext()->getMacro(
$key->getMacro()
)->render($dataItem, $citationNumber)));
} elseif ($variable === "citation-number") {
$sortKey = $citationNumber + 1;
} else {
$sortKey = mb_strtolower(strip_tags($dataItem->{$variable}));
}
$groupedItems[$sortKey][] = $dataItem;
}
if ($this->sortingKeys->count() > ++$keyNumber) {
foreach ($groupedItems as $group => &$array) {
if (count($array) > 1) {
$array = $this->performSort($keyNumber, $array);
}
}
}
//sorting by array keys
if ($key->getSort() === "ascending") {
ksort($groupedItems); //ascending
} else {
krsort($groupedItems); //reverse
}
//the flattened array is the result
$sortedDataGroups = array_values($groupedItems);
return $this->flatten($sortedDataGroups);
}
public function flatten($array)
{
$returnArray = [];
array_walk_recursive($array, function ($a) use (&$returnArray) {
$returnArray[] = $a;
});
return $returnArray;
}
/**
* @return ArrayList
*/
public function getSortingKeys()
{
return $this->sortingKeys;
}
}
@@ -0,0 +1,25 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style\Sort;
interface SortKey
{
public function getVariable();
public function getSort();
public function isNameVariable();
public function isNumberVariable();
public function isDateVariable();
public function isMacro();
}
@@ -0,0 +1,87 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Style;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\Layout;
use Seboettg\CiteProc\Root\Root;
use Seboettg\CiteProc\Style\Sort\Sort;
use SimpleXMLElement;
/**
* Class StyleElement
*
* StyleElement is an abstract class which must be extended by Citation and Bibliography class. The constructor
* of StyleElement class parses the cs:layout element (necessary for cs:citation and cs:bibliography) and the optional
* cs:sort element.
*
* @package Seboettg\CiteProc\Style
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
abstract class StyleElement
{
use InheritableNameAttributesTrait;
/**
* @var Layout
*/
protected $layout;
/**
* @var bool
*/
protected $doNotSort;
protected $parent;
/**
* Parses the configuration.
*
* @param SimpleXMLElement $node
* @param Root $parent
* @throws InvalidStylesheetException
*/
protected function __construct(SimpleXMLElement $node, $parent)
{
$this->parent = $parent;
// init child elements
/** @var SimpleXMLElement $child */
foreach ($node->children() as $child) {
switch ($child->getName()) {
/* The cs:layout rendering element is a required child element of cs:citation and cs:bibliography. It
* must contain one or more of the other rendering elements described below, and may carry affixes and
* formatting attributes.
*/
case 'layout':
$this->layout = new Layout($child, $this);
break;
/* cs:citation and cs:bibliography may include a cs:sort child element before the cs:layout element to
* specify the sorting order of respectively cites within citations, and bibliographic entries within
* the bibliography. In the absence of cs:sort, cites and bibliographic entries appear in the order in
* which they are cited.
*/
case 'sort':
$sorting = new Sort($child);
CiteProc::getContext()->setSorting($sorting);
}
}
}
/**
* @return Root
*/
public function getParent()
{
return $this->parent;
}
}
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc;
use Seboettg\CiteProc\Exception\CiteProcException;
/**
* Class StyleSheet
*
* Helper class for loading CSL styles and CSL locales
*
* @package Seboettg\CiteProc
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class StyleSheet
{
/**
* Loads xml formatted CSL stylesheet of a given stylesheet name, e.g. "american-physiological-society" for
* apa style.
*
* See in styles folder (which is included as git submodule) for all available style sheets
*
* @param string $styleName e.g. "american-physiological-society" for apa
* @return string
* @throws CiteProcException
*/
public static function loadStyleSheet(string $styleName): string
{
$stylesPath = self::vendorPath() . "/citation-style-language/styles";
return self::readFileContentsOrThrowException("$stylesPath/$styleName.csl");
}
/**
* Loads xml formatted locales of given language key
*
* @param string $langKey e.g. "en-US", or "de-CH"
* @return string
* @throws CiteProcException
*/
public static function loadLocales(string $langKey): string
{
$localesPath = self::vendorPath()."/citation-style-language/locales";
$localeFile = "$localesPath/locales-${langKey}.xml";
if (file_exists($localeFile)) {
return self::readFileContentsOrThrowException($localeFile);
} else {
$metadata = self::loadLocalesMetadata();
if (!empty($metadata->{'primary-dialects'}->{$langKey})) {
return self::readFileContentsOrThrowException(
sprintf("%s/locales-%s.xml", $localesPath, $metadata->{'primary-dialects'}->{$langKey})
);
}
}
throw new CiteProcException("No Locale file found for $langKey");
}
/**
* @throws CiteProcException
*/
private static function readFileContentsOrThrowException($path): string
{
$fileContent = file_get_contents($path);
if (false === $fileContent) {
throw new CiteProcException("Couldn't read $path");
}
return $fileContent;
}
/**
* @return mixed
* @throws CiteProcException
*/
public static function loadLocalesMetadata()
{
$localesMetadataPath = self::vendorPath() . "/citation-style-language/locales/locales.json";
return json_decode(self::readFileContentsOrThrowException($localesMetadataPath));
}
/**
* @return bool|string
* @throws CiteProcException
*/
private static function vendorPath()
{
include_once realpath(__DIR__ . '/../') . '/vendorPath.php';
if (!($vendorPath = vendorPath())) {
throw new CiteProcException('vendor path not found. Use composer to initialize your project');
}
return $vendorPath;
}
}
@@ -0,0 +1,110 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
use Seboettg\CiteProc\CiteProc;
use SimpleXMLElement;
/**
* Trait AffixesTrait
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait AffixesTrait
{
/**
* @var string
*/
private $prefix = "";
/**
* @var string
*/
private $suffix = "";
/**
* @var bool
*/
private $quote = false;
/**
* @param SimpleXMLElement $node
*/
protected function initAffixesAttributes(SimpleXMLElement $node)
{
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
$name = (string) $attribute->getName();
$value = (string) $attribute;
switch ($name) {
case 'prefix':
$this->prefix = $value;
break;
case 'suffix':
$this->suffix = $value;
break;
case 'quote':
$this->quote = (bool) $attribute;
}
}
}
/**
* @param $text
* @return string
*/
protected function addAffixes($text)
{
$prefix = $this->prefix;
$suffix = $this->suffix;
if (!empty($suffix)) { // guard against repeated suffixes...
$no_tags = strip_tags($text);
if (strlen($no_tags) && ($no_tags[(strlen($no_tags) - 1)] == $suffix[0])) {
$suffix = substr($suffix, 1);
}
// punctuation in quote?
$piq = CiteProc::getContext()
->getLocale()
->filter('options', 'punctuation-in-quote');
$punctuationInQuote = is_array($piq) ? current($piq) : $piq;
if ($punctuationInQuote && in_array($suffix, [',', ';', '.'])) {
$closeQuote = CiteProc::getContext()->getLocale()->filter("terms", "close-quote")->single;
$lastChar = mb_substr($text, -1, 1);
if ($closeQuote === $lastChar) { // last char is closing quote?
$text = mb_substr($text, 0, mb_strlen($text) - 1); //set suffix before
return $prefix . $text . $suffix . $lastChar;
}
}
}
return $prefix . $text . $suffix;
}
/**
* @return string
*/
public function renderPrefix()
{
return $this->prefix;
}
/**
* @return string
*/
public function renderSuffix()
{
return $this->suffix;
}
}
@@ -0,0 +1,94 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
/**
* Trait ConsecutivePunctuationCharacterTrait
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait ConsecutivePunctuationCharacterTrait
{
/**
* @var array
*/
private $childrenPrefixes = [];
/**
* @var array
*/
private $childrenSuffixes = [];
/**
* @var array
*/
private $childrenDelimiter = [];
/**
* @param $punctuationSign
* @param $subject
* @return string
*/
public function removeConsecutivePunctuation($punctuationSign, $subject): string
{
if (empty($punctuationSign) || preg_match("/^\s+$/", $punctuationSign)) {
return $subject;
}
$pattern = '/'.preg_quote(trim($punctuationSign), '/').'{2,}/';
if (preg_match($pattern, $subject)) {
return preg_replace($pattern, $punctuationSign, $subject);
}
return $subject;
}
/**
* @param $child
*/
protected function getChildrenAffixesAndDelimiter($child)
{
if (method_exists($child, "renderPrefix")) {
if (!empty($child->renderPrefix()) && !in_array($child->renderPrefix(), $this->childrenPrefixes)) {
$this->childrenPrefixes[] = $child->renderPrefix();
}
}
if (method_exists($child, "renderSuffix")) {
if (!empty($child->renderSuffix()) && !in_array($child->renderSuffix(), $this->childrenSuffixes)) {
$this->childrenSuffixes[] = $child->renderSuffix();
}
}
if (method_exists($child, "getDelimiter")) {
if (!empty($child->getDelimiter()) && !in_array($child->getDelimiter(), $this->childrenDelimiter)) {
$this->childrenDelimiter[] = $child->getDelimiter();
}
}
}
/**
* @param string $string
* @return string
*/
protected function removeConsecutiveChars($string)
{
foreach ($this->childrenPrefixes as $prefix) {
$string = $this->removeConsecutivePunctuation($prefix, $string);
}
foreach ($this->childrenSuffixes as $suffix) {
$string = $this->removeConsecutivePunctuation($suffix, $string);
}
foreach ($this->childrenDelimiter as $delimiter) {
$string = $this->removeConsecutivePunctuation($delimiter, $string);
}
$string = preg_replace("/\s{2,}/", " ", $string);
return $string;
}
}
@@ -0,0 +1,70 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles\Css;
use Seboettg\Collection\ArrayList;
/**
* Class CssRule
* @package Seboettg\CiteProc\Styles\Css
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class CssRule
{
const SELECTOR_TYPE_ID = "#";
const SELECTOR_TYPE_CLASS = ".";
/**
* @var string
*/
private $selectorType;
/**
* @var string
*/
private $selector;
/**
* @var ArrayList
*/
private $directives;
/**
* CssRule constructor.
* @param string $selector
* @param string $selectorType
*/
public function __construct($selector, $selectorType = self::SELECTOR_TYPE_CLASS)
{
$this->selector = $selector;
$this->selectorType = $selectorType;
$this->directives = new ArrayList();
}
/**
*
* @param string $property
* @param string $value
*/
public function addDirective($property, $value)
{
$this->directives->append("$property: $value;");
}
/**
* @return string
*/
public function __toString()
{
$directives = "\t".implode("\n\t", $this->directives->toArray());
return $this->selectorType.$this->selector." {\n".$directives."\n}\n";
}
}
@@ -0,0 +1,32 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles\Css;
use Seboettg\Collection\ArrayList;
/**
* Class CssRules
* @package Seboettg\CiteProc\Styles\Css
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class CssRules extends ArrayList
{
/**
* @param $rule
* @return CssRule
*/
public function getRule($rule)
{
if (!$this->hasKey($rule)) {
$this->set($rule, new CssRule(substr($rule, 1), substr($rule, 0, 1)));
}
return $this->get($rule);
}
}
@@ -0,0 +1,85 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles\Css;
use Seboettg\CiteProc\Style\Options\BibliographyOptions;
/**
* Class CssStyle
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class CssStyle
{
/**
* @var BibliographyOptions
*/
private $bibliographyOptions;
/**
* @var CssRules
*/
private $cssRules = null;
/**
* CssStyle constructor.
* @param BibliographyOptions $bibliographyOptions
*/
public function __construct(BibliographyOptions $bibliographyOptions)
{
$this->bibliographyOptions = $bibliographyOptions;
$this->cssRules = new CssRules();
$this->init();
}
/**
* renders CSS output
* @return string
*/
public function render()
{
return implode("\n", array_filter($this->cssRules->toArray()));
}
/**
* initialize CSS rules
*/
private function init()
{
$lineSpacing = $this->bibliographyOptions->getLineSpacing();
$entrySpacing = $this->bibliographyOptions->getEntrySpacing();
$hangingIndent = $this->bibliographyOptions->getHangingIndent();
if ($lineSpacing || $entrySpacing || $hangingIndent) {
$rule = $this->cssRules->getRule(".csl-entry");
if (!empty($lineSpacing)) {
$rule->addDirective("line-height", intval($lineSpacing) . "em");
}
if (!empty($entrySpacing)) {
$rule->addDirective("margin-bottom", intval($entrySpacing) . "em");
}
if (!empty($hangingIndent)) {
$rule->addDirective("padding-left", "2em");
$rule->addDirective("text-indent", "-2em");
}
}
if ("flush" === $this->bibliographyOptions->getSecondFieldAlign()) {
$rule = $this->cssRules->getRule(".csl-left-margin");
$rule->addDirective("display", "block");
$rule->addDirective("float", "left");
$rule = $this->cssRules->getRule(".csl-right-inline");
$rule->addDirective("margin-left", "35px");
}
}
}
@@ -0,0 +1,39 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
use SimpleXMLElement;
/**
* Trait DelimiterTrait
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait DelimiterTrait
{
/**
* @param SimpleXMLElement $node
*/
protected function initDelimiterAttributes(SimpleXMLElement $node)
{
foreach ($node->attributes() as $attribute) {
/** @var string $name */
$name = (string) $attribute->getName();
$value = (string) $attribute;
switch ($name) {
case 'delimiter':
$this->delimiter = $value;
return;
}
}
}
}
@@ -0,0 +1,63 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
use SimpleXMLElement;
/**
* Trait DisplayTrait
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait DisplayTrait
{
/**
* @var array
*/
private static $allowedValues = [
"block",
"left-margin",
"right-inline",
"indent"
];
/**
* @var string
*/
private $display;
/**
* @param $node
*/
public function initDisplayAttributes(SimpleXMLElement $node)
{
foreach ($node->attributes() as $attribute) {
switch ($attribute->getName()) {
case 'display':
$this->display = (string) $attribute;
return;
}
}
}
/**
* @param $text
* @return string
*/
public function wrapDisplayBlock($text)
{
if (!in_array($this->display, self::$allowedValues)) {
return $text;
}
$divStyle = "class=\"csl-".$this->display."\"";
return "<div $divStyle>$text</div>";
}
}
@@ -0,0 +1,105 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
use Seboettg\Collection\ArrayList;
use SimpleXMLElement;
/**
* Trait FormattingTrait
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait FormattingTrait
{
/**
* @var array
*/
private static $formattingAttributes = [
'font-style',
'font-family',
'font-weight',
'font-variant',
'text-decoration',
'vertical-align'
];
/**
* @var ArrayList
*/
private $formattingOptions;
/**
* @var bool
*/
private $stripPeriods = false;
/**
* @var string
*/
private $format;
/**
* @param SimpleXMLElement $node
*/
protected function initFormattingAttributes(SimpleXMLElement $node)
{
$this->formattingOptions = new ArrayList();
/** @var SimpleXMLElement $attribute */
foreach ($node->attributes() as $attribute) {
/** @var string $name */
$name = (string) $attribute->getName();
$value = (string) $attribute;
if (in_array($name, self::$formattingAttributes)) {
$this->formattingOptions->add($name, $value);
continue;
}
}
}
protected function format($text)
{
if (empty($text)) {
return $text;
}
if ($this->formattingOptions->count() > 0) {
$format = "";
foreach ($this->formattingOptions as $option => $optionValue) {
if ($optionValue === "italic") {
$text = "<i>$text</i>";
} elseif ($optionValue === "bold") {
$text = "<b>$text</b>";
} elseif ($optionValue === "normal") {
$text = "$text";
} elseif ($option === "vertical-align") {
if ($optionValue === "sub") {
$text = "<sub>$text</sub>";
} elseif ($optionValue === "sup") {
$text = "<sup>$text</sup>";
}
} elseif ($option === "text-decoration" && $optionValue === "none") {
$format .= "";
} else {
$format .= "$option: $optionValue;";
}
}
if (!empty($format)) {
$text = '<span style="' . $format . '">' . $text . '</span>';
}
}
return $text;
}
}
@@ -0,0 +1,98 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Util\StringHelper;
use SimpleXMLElement;
/**
* Trait QuotesTrait
*
* The quotes attribute may set on cs:text. When set to “true” (“false” is default), the rendered text is wrapped in
* quotes (the quotation marks used are terms). The localized punctuation-in-quote option controls whether an adjoining
* comma or period appears outside (default) or inside the closing quotation mark.
*
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait QuotesTrait
{
/**
* @var bool
*/
private $quotes = false;
public function initQuotesAttributes(SimpleXMLElement $node)
{
if (isset($node['quotes']) && "true" === (string) $node['quotes']) {
$this->quotes = true;
}
}
/**
* @param string $text
* @return string
*/
public function addSurroundingQuotes($text)
{
if ($this->quotes) {
$openQuote = CiteProc::getContext()->getLocale()->filter("terms", "open-quote")->single;
$closeQuote = CiteProc::getContext()->getLocale()->filter("terms", "close-quote")->single;
$punctuationInQuotes = CiteProc::getContext()->getLocale()->filter("options", "punctuation-in-quote");
$text = $this->replaceOuterQuotes($text, $openQuote, $closeQuote);
if (null !== $punctuationInQuotes || $punctuationInQuotes === false) {
if (preg_match("/^(.+)([\.,;]+)$/", $text, $match)) {
$punctuation = substr($match[2], -1);
if ($this->suffix !== $punctuation) {
$text = $match[1] . substr($match[2], 0, strlen($match[2]) - 1);
return $openQuote . $text . $closeQuote . $punctuation;
}
}
}
return $openQuote . $text . $closeQuote;
}
return $text;
}
/**
* @param $text
* @param $outerOpenQuote
* @param $outerCloseQuote
* @return string
*/
private function replaceOuterQuotes($text, $outerOpenQuote, $outerCloseQuote)
{
$innerOpenQuote = CiteProc::getContext()
->getLocale()
->filter("terms", "open-inner-quote")
->single;
$innerCloseQuote = CiteProc::getContext()
->getLocale()
->filter("terms", "close-inner-quote")
->single;
$text = StringHelper::replaceOuterQuotes(
$text,
"\"",
"\"",
$innerOpenQuote,
$innerCloseQuote
);
$text = StringHelper::replaceOuterQuotes(
$text,
$outerOpenQuote,
$outerCloseQuote,
$innerOpenQuote,
$innerCloseQuote
);
return $text;
}
}
@@ -0,0 +1,19 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
/**
* Trait RangeDelimiterTrait
* @package Seboettg\CiteProc\Styles
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait RangeDelimiterTrait
{
}
@@ -0,0 +1,95 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Styles;
use Seboettg\CiteProc\Util\StringHelper;
use SimpleXMLElement;
/**
* Trait TextCase
*
* @package Seboettg\CiteProc\Styles
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
trait TextCaseTrait
{
private $textCase;
protected function initTextCaseAttributes(SimpleXMLElement $node)
{
foreach ($node->attributes() as $attribute) {
$name = $attribute->getName();
$value = (string) $attribute;
switch ($name) {
case 'text-case':
$this->textCase = $value;
return;
}
}
}
/**
* @param string $text
* @param string $lang
* @return string
*/
public function applyTextCase($text, $lang = "en")
{
switch ($this->textCase) {
case 'uppercase':
$text = $this->keepNoCase(mb_strtoupper($text), $text);
break;
case 'lowercase':
$text = $this->keepNoCase(mb_strtolower($text), $text);
break;
case 'sentence':
if (StringHelper::checkUpperCaseString($text)) {
$text = mb_strtolower($text);
return StringHelper::mb_ucfirst($text);
} else {
return StringHelper::mb_ucfirst($text);
}
break;
case 'capitalize-all':
$text = $this->keepNoCase(StringHelper::capitalizeAll($text), $text);
break;
case 'title':
if ($lang === "en") {
$text = $this->keepNoCase(StringHelper::capitalizeForTitle($text), $text);
}
break;
case 'capitalize-first':
$text = $this->keepNoCase(StringHelper::mb_ucfirst($text), $text);
break;
default:
break;
}
return $text;
}
/**
* @param string $render
* @param string $original
* @return string|string[]|null
*/
private function keepNoCase($render, $original)
{
if (preg_match('/<span class=\"nocase\">(\p{L}+)<\/span>/i', $original, $match)) {
return preg_replace('/(<span class=\"nocase\">\p{L}+<\/span>)/i', $match[1], $render);
}
return $render;
}
}
@@ -0,0 +1,48 @@
<?php
/*
* citeproc-php: Locator.php
* User: Sebastian Böttger <seboettg@gmail.com>
* created at 08.04.20, 15:21
*/
namespace Seboettg\CiteProc\Terms;
use MyCLabs\Enum\Enum;
class Locator extends Enum
{
const BOOK = "book";
const CHAPTER = "chapter";
const COLUMN = "column";
const FIGURE = "figure";
const FOLIO = "folio";
const ISSUE = "issue";
const LINE = "line";
const NOTE = "note";
const OPUS = "opus";
const PAGE = "page";
const PARAGRAPH = "paragraph";
const PART = "part";
const SECTION = "section";
const SUB_VERBO = "sub-verbo";
const VERSE = "verse";
const VOLUME = "volume";
private const LABEL_TO_VARIABLE_MAP = [
"chapter" => "chapter-number",
];
/**
* @param string|Locator $locatorTerm
* @return string
*/
public static function mapLocatorLabelToRenderVariable($locatorTerm)
{
if ($locatorTerm instanceof Locator) {
$locatorTerm = (string)$locatorTerm;
}
return
array_key_exists($locatorTerm, self::LABEL_TO_VARIABLE_MAP) ?
self::LABEL_TO_VARIABLE_MAP[$locatorTerm] : $locatorTerm;
}
}
@@ -0,0 +1,88 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use Seboettg\CiteProc\CiteProc;
use stdClass;
class CiteProcHelper
{
/**
* Applies additional functions for markup extension
*
* @param stdClass $dataItem the actual item
* @param string $valueToRender value the has to apply on
* @param string $renderedText actual by citeproc rendered text
* @return string
*/
public static function applyAdditionMarkupFunction($dataItem, $valueToRender, $renderedText)
{
$markupExtension = CiteProc::getContext()->getMarkupExtension();
if (array_key_exists($valueToRender, $markupExtension)) {
if (is_array($markupExtension[$valueToRender]) && array_key_exists('function', $markupExtension[$valueToRender])) {
$function = $markupExtension[$valueToRender]['function'];
} else {
$function = $markupExtension[$valueToRender];
}
if (is_callable($function)) {
$renderedText = $function($dataItem, $renderedText);
}
} elseif (array_key_exists($mode = CiteProc::getContext()->getMode(), $markupExtension)) {
if (array_key_exists($valueToRender, $markupExtension[$mode])) {
if (is_array($markupExtension[$mode][$valueToRender]) && array_key_exists('function', $markupExtension[$mode][$valueToRender])) {
$function = CiteProc::getContext()->getMarkupExtension()[$mode][$valueToRender]['function'];
} else {
$function = CiteProc::getContext()->getMarkupExtension()[$mode][$valueToRender];
}
if (is_callable($function)) {
$renderedText = $function($dataItem, $renderedText);
}
}
}
return $renderedText;
}
/**
* @param array $array
* @return array
*/
public static function cloneArray(array $array)
{
$newArray = [];
foreach ($array as $key => $value) {
$newArray[$key] = clone $value;
}
return $newArray;
}
/**
* @param stdClass $dataItem the actual item
* @param string $valueToRender value the has to apply on
* @return bool
*/
public static function isUsingAffixesByMarkupExtentsion($dataItem, $valueToRender)
{
$markupExtension = CiteProc::getContext()->getMarkupExtension();
if (array_key_exists($valueToRender, $markupExtension)) {
if (is_array($markupExtension[$valueToRender]) && array_key_exists('affixes', $markupExtension[$valueToRender])) {
return $markupExtension[$valueToRender]['affixes'];
}
} elseif (array_key_exists($mode = CiteProc::getContext()->getMode(), $markupExtension)) {
if (array_key_exists($valueToRender, $markupExtension[$mode])) {
if (is_array($markupExtension[$mode][$valueToRender]) && array_key_exists('affixes', $markupExtension[$mode][$valueToRender])) {
return CiteProc::getContext()->getMarkupExtension()[$mode][$valueToRender]['affixes'];
}
}
}
return false;
}
}
@@ -0,0 +1,119 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use DateTime;
use Exception;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Style\Sort\Key;
/**
* Class Date
*
* Just a helper class for date issues
*
* @package Seboettg\CiteProc\Util
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class DateHelper
{
/**
* dates: Date variables called via the variable attribute are returned in the YYYYMMDD format, with zeros
* substituted for any missing date-parts (e.g. 20001200 for December 2000). As a result, less specific dates
* precede more specific dates in ascending sorts, e.g. “2000, May 2000, May 1st 2000”. Negative years are sorted
* inversely, e.g. “100BC, 50BC, 50AD, 100AD”. Seasons are ignored for sorting, as the chronological order of the
* seasons differs between the northern and southern hemispheres.
*
* @param array $dateParts
* @return string
*/
public static function serializeDate($dateParts)
{
$year = isset($dateParts[0]) ? $dateParts[0] : "0000";
$month = isset($dateParts[1]) ? $dateParts[1] : "00";
$day = isset($dateParts[2]) ? $dateParts[2] : "00";
return sprintf("%04d%02d%02d", $year, $month, $day);
}
/**
* @param $date
* @return array
* @throws CiteProcException
*/
public static function parseDateParts($date)
{
if (!isset($date->{'raw'})) {
return [];
}
try {
$dateTime = new DateTime($date->{'raw'});
$arr = [[$dateTime->format("Y"), $dateTime->format("m"), $dateTime->format("d")]];
} catch (Exception $e) {
throw new CiteProcException("Could not parse date \"".$date->{'raw'}."\".", 0, $e);
}
return $arr;
}
/**
* creates sort key for variables containing date and date ranges
* @param array $dataItem
* @param Key $key
* @return string
* @throws CiteProcException
*/
public static function getSortKeyDate($dataItem, $key)
{
$variable = $variable = $key->getVariable();
$part = $key->getRangePart();
if (count($dataItem->{$variable}->{'date-parts'}) > 1) {
//Date range
$datePart = $dataItem->{$variable}->{'date-parts'}[$part];
$sortKey = self::serializeDate($datePart);
if ($key->getSort() === "descending" && $part === 0 ||
$key->getSort() === "ascending" && $part === 1) {
$sortKey .= "-";
}
} else {
if (!isset($dataItem->{$variable}->{'date-parts'})) {
$dateParts = self::parseDateParts($dataItem->{$variable});
} else {
$dateParts = $dataItem->{$variable}->{'date-parts'}[0];
}
$sortKey = self::serializeDate($dateParts);
}
return $sortKey;
}
/**
* @param array $items
* @param string $variable name of the date variable
* @param string $match ("all"|"any") default "all"
* @return bool
*/
public static function hasDateRanges($items, $variable, $match = "all")
{
$ret = true;
foreach ($items as $item) {
$dateParts = $item->{$variable}->{"date-parts"};
if ($match === "all" && count($dateParts) !== 2) {
return false;
} elseif ($match === "any" && count($dateParts) === 2) {
return true;
} else {
$ret = ($match === "all") ? $ret&true : $ret|true;
}
}
return (bool) $ret;
}
}
@@ -0,0 +1,68 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use SimpleXMLElement;
/**
* Class Factory
* @package Seboettg\CiteProc\Util
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Factory
{
const CITE_PROC_NODE_NAMESPACE = "Seboettg\\CiteProc\\Rendering";
/**
* @var array
*/
private static $nodes = [
'layout' => "\\Layout",
'text' => "\\Text",
"macro" => "\\Macro",
"number" => "\\Number",
"label" => "\\Label",
"group" => "\\Group",
"choose" => "\\Choose\\Choose",
"if" => "\\Choose\\ChooseIf",
"else-if" => "\\Choose\\ChooseElseIf",
"else" => "\\Choose\\ChooseElse",
'date' => "\\Date\\Date",
"date-part" => "\\Date\\DatePart",
"names" => "\\Name\\Names",
"name" => "\\Name\\Name",
"name-part" => "\\Name\\NamePart",
"substitute" => "\\Name\\Substitute",
"et-al" => "\\Name\\EtAl"
];
/**
* @param SimpleXMLElement $node
* @param mixed $param
* @return mixed
* @throws InvalidStylesheetException
*/
public static function create($node, $param = null)
{
$nodeClass = self::CITE_PROC_NODE_NAMESPACE.self::$nodes[$node->getName()];
if (!class_exists($nodeClass)) {
throw new InvalidStylesheetException("For node {$node->getName()} ".
"does not exist any counterpart class \"".$nodeClass.
"\". The given stylesheet seems to be invalid.");
}
if ($param != null) {
return new $nodeClass($node, $param);
}
return new $nodeClass($node);
}
}
@@ -0,0 +1,145 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use Seboettg\CiteProc\CiteProc;
use Seboettg\CiteProc\Exception\CiteProcException;
use stdClass;
/**
* Class NameHelper
* @package Seboettg\CiteProc\Util
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class NameHelper
{
/**
* @param stdClass $precedingItem
* @param array $currentAuthor
* @return bool
* @throws CiteProcException
*/
public static function identicalAuthors($precedingItem, $currentAuthor)
{
if (!property_exists($precedingItem, "author")) {
throw new CiteProcException("No author to present");
}
if (count($precedingItem->author) !== count($currentAuthor)) {
return false;
}
foreach ($currentAuthor as $current) {
if (self::precedingHasAuthor($precedingItem, $current)) {
continue;
}
return false;
}
return true;
}
/**
* @param stdClass $preceding
* @param stdClass $name
* @return bool
*/
public static function precedingHasAuthor($preceding, $name)
{
foreach ($preceding->author as $author) {
if ($author->family === $name->family && $author->given === $name->given) {
return true;
}
}
return false;
}
/**
* removes the field $particle from $data and appends its content to the $namePart field of $data
* @param stdClass $data
* @param string $namePart
* @param string $particle
*/
public static function appendParticleTo(&$data, $namePart, $particle)
{
if (isset($data->{$particle}) && isset($data->{$namePart})) {
$data->{$namePart} = $data->{$namePart}." ".$data->{$particle}; // append $particle to $namePart
unset($data->{$particle}); //remove particle from $data
}
}
/**
* removes the field $particle from $data and prepends its content to the $namePart field of $data
* @param stdClass $data
* @param string $namePart ("given"|"family")
* @param string $particle
*/
public static function prependParticleTo(&$data, $namePart, $particle)
{
if (isset($data->{$particle}) && isset($data->{$namePart})) {
$data->{$namePart} = $data->{$particle}." ".$data->{$namePart}; //prepend $particle to $namePart
unset($data->{$particle}); //remove particle from $data
}
}
/**
* @param array $persons1
* @param array $persons2
* @return bool
*/
public static function sameNames($persons1, $persons2)
{
$same = count($persons1) === count($persons2);
if (!$same) {
return false;
}
array_walk($persons1, function ($name, $key) use ($persons2, &$same) {
$family1 = $name->family;
$family2 = $persons2[$key]->family;
$same = $same && ($family1 === $family2);
});
return (bool) $same;
}
/**
* @param $data
* @return string
* @throws CiteProcException
*/
public static function normalizeName($data)
{
if (empty($data->family)) {
throw new CiteProcException("Illegal argument. Name has no family name.");
}
return $data->family.(isset($data->given) ? $data->given : "");
}
public static function addExtendedMarkup($nameVar, $nameItem, $formattedName)
{
$markupExtension = CiteProc::getContext()->getMarkupExtension();
if (array_key_exists($nameVar, $markupExtension)) {
$function = $markupExtension[$nameVar];
if (is_callable($function)) {
return $function($nameItem, $formattedName);
}
} elseif (array_key_exists($mode = CiteProc::getContext()->getMode(), $markupExtension)) {
if (array_key_exists($nameVar, $markupExtension[$mode])) {
$function = $markupExtension[$mode][$nameVar];
if (is_callable($function)) {
return $function($nameItem, $formattedName);
}
}
}
return $formattedName;
}
}
@@ -0,0 +1,198 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use Closure;
/**
* Class Number
* @package Seboettg\CiteProc\Util
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class NumberHelper
{
const PATTERN_ORDINAL = "/\d+((st|nd|rd|th)\.?|\.)$/";
const PATTERN_ROMAN = "/^[ivxlcdm]+\.?$/i";
const PATTERN_ROMAN_RANGE = "/^([ivxlcdm]+\.*)\s*([*\\-&+,;])\s*([ivxlcdm]+\.?)$/i";
const PATTERN_AFFIXES = "/^[a-z]?\d+[a-z]?$/i";
const PATTERN_COMMA_AMPERSAND_RANGE = "/\d+([\s?\-&+,;\s])+\d+/";
const ROMAN_NUMERALS = [
["", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix"],
["", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc"],
["", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm"],
["", "m", "mm", "mmm", "mmmm", "mmmmm"]
];
const ROMAN_DIGITS = [
"M" => 1000,
"D" => 500,
"C" => 100,
"L" => 50,
"X" => 10,
"V" => 5,
"I" => 1
];
/**
* @return Closure
*/
public static function getCompareNumber()
{
return function ($numA, $numB, $order) {
if (is_numeric($numA) && is_numeric($numB)) {
$ret = $numA - $numB;
} else {
$ret = strcasecmp($numA, $numB);
}
if ("descending" === $order) {
return $ret > 0 ? -1 : 1;
}
return $ret > 0 ? 1 : -1;
};
}
/**
* @param $num
* @return string
*/
public static function dec2roman($num)
{
$ret = "";
if ($num < 6000) {
$numStr = strrev($num);
$len = strlen($numStr);
for ($pos = 0; $pos < $len; $pos++) {
$n = $numStr[$pos];
$ret = self::ROMAN_NUMERALS[$pos][$n] . $ret;
}
}
return $ret;
}
/**
* @param $romanNumber
* @return int|mixed
*/
public static function roman2Dec($romanNumber)
{
$romanNumber = trim($romanNumber);
if (is_numeric($romanNumber)) {
return 0;
}
$values = [];
// Convert the string to an array of roman values:
for ($i = 0; $i < mb_strlen($romanNumber); ++$i) {
$char = mb_strtoupper($romanNumber[$i]);
if (isset(self::ROMAN_DIGITS[$char])) {
$values[] = self::ROMAN_DIGITS[$char];
}
}
$sum = 0;
while ($current = current($values)) {
$next = next($values);
$next > $current ? $sum += $next - $current + 0 * next($values) : $sum += $current;
}
return $sum;
}
/**
* @param $str
* @return bool
*/
public static function isRomanNumber($str)
{
$number = trim($str);
for ($i = 0; $i < mb_strlen($number); ++$i) {
$char = mb_strtoupper($number[$i]);
if (!in_array($char, array_keys(self::ROMAN_DIGITS))) {
return false;
}
}
return true;
}
/**
* @param $str
* @return string
*/
public static function evaluateStringPluralism($str)
{
$plural = 'single';
if (!empty($str)) {
$isRange = self::isRange($str);
if ($isRange) {
return 'multiple';
} else {
if (is_numeric($str) || NumberHelper::isRomanNumber($str)) {
return 'single';
}
}
}
return $plural;
}
/**
* @param $string
* @return mixed
*/
public static function extractNumber($string)
{
if (preg_match("/(\d+)[^\d]*$/", $string, $match)) {
return $match[1];
}
return $string;
}
/**
* @param $str
* @return array[]|false|string[]
*/
public static function splitByRangeDelimiter($str)
{
return preg_split("/[-&,]/", $str);
}
/**
* @param string $str
* @return bool
*/
private static function isRange($str)
{
$rangeParts = self::splitByRangeDelimiter($str);
$isRange = false;
if (count($rangeParts) > 1) {
$isRange = true;
foreach ($rangeParts as $range) {
if (NumberHelper::isRomanNumber(trim($range)) || is_numeric(trim($range))) {
$isRange = $isRange && true;
}
}
}
return $isRange;
}
/**
* @param int|string $number
* @return bool
*/
public static function isRomanRange($number)
{
return preg_match(self::PATTERN_ROMAN_RANGE, $number);
}
}
@@ -0,0 +1,87 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2017 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use Seboettg\CiteProc\Style\Options\PageRangeFormats;
/**
* Class PageHelper
* @package Seboettg\CiteProc\Util
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class PageHelper
{
/**
* @param array $ranges
* @param string $pageRangeFormat
* @return string
*/
public static function processPageRangeFormats($ranges, $pageRangeFormat)
{
list($from, $to) = $ranges;
if (!empty($pageRangeFormat)) {
switch ($pageRangeFormat) {
case PageRangeFormats::MINIMAL:
$resTo = self::renderMinimal($from, $to, 0);
break;
case PageRangeFormats::MINIMAL_TWO:
if (strlen($to) > 2) {
$resTo = self::renderMinimal($from, $to, strlen($to) - 2);
} else {
$resTo = $to;
}
break;
case PageRangeFormats::CHICAGO:
$resTo = self::renderChicago($from, $to);
break;
case PageRangeFormats::EXPANDED:
default:
$resTo = $to;
}
return "$from-$resTo";
}
return "$from-$to";
}
/**
*
* @param $from
* @param $to
* @param int $limit
* @return string
*/
private static function renderMinimal($from, $to, $limit = 1)
{
$resTo = "";
if (strlen($from) == strlen($to)) {
for ($i = strlen($to) - 1; $i >= $limit; --$i) {
$digitTo = $to[$i];
$digitFrom = $from[$i];
if ($digitTo !== $digitFrom) {
$resTo = $digitTo.$resTo;
}
}
return $resTo;
}
return $to;
}
private static function renderChicago($from, $to)
{
if ($from > 100 && ($from % 100 > 0) && intval(($from / 100), 10) === intval(($to / 100), 10)) {
return "".($to % 100);
} elseif ($from >= 10000) {
return "".($to % 1000);
}
return $to;
}
}
@@ -0,0 +1,317 @@
<?php /** @noinspection PhpInternalEntityUsedInspection */
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use Seboettg\CiteProc\CiteProc;
use Seboettg\Collection\ArrayList;
/**
* Class StringHelper
* @package Seboettg\CiteProc\Util
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class StringHelper
{
const PREPOSITIONS = [
'on', 'in', 'at', 'since', 'for', 'ago', 'before', 'to', 'past', 'till', 'until', 'by', 'under', 'below',
'over', 'above', 'across', 'through', 'into', 'towards', 'onto', 'from', 'of', 'off', 'about', 'via'
];
const ARTICLES = [
'a', 'an', 'the'
];
const ADVERBS = [
'yet', 'so', 'just', 'only'
];
const CONJUNCTIONS = [
'nor', 'so', 'and', 'or'
];
const ADJECTIVES = [
'down', 'up'
];
const ISO_ENCODINGS = [
'ISO-8859-1',
'ISO-8859-2',
'ISO-8859-3',
'ISO-8859-4',
'ISO-8859-5',
'ISO-8859-6',
'ISO-8859-7',
'ISO-8859-8',
'ISO-8859-9',
'ISO-8859-10',
'ISO-8859-13',
'ISO-8859-14',
'ISO-8859-15',
'ISO-8859-16'
];
/**
* opening quote sign
*/
const OPENING_QUOTE = "";
/**
* closing quote sign
*/
const CLOSING_QUOTE = "";
/**
* @param $text
* @return string
*/
public static function capitalizeAll($text)
{
$wordArray = explode(" ", $text);
array_walk($wordArray, function (&$word) {
$word = ucfirst($word);
});
return implode(" ", array_filter($wordArray));
}
/**
* @param $titleString
* @return string
*/
public static function capitalizeForTitle($titleString)
{
if (strlen($titleString) == 0) {
return "";
}
if (preg_match('/(.+[^\<\>][\.:\/;\?\!]\s?)([a-z])(.+)/', $titleString, $match)) {
$titleString = $match[1].StringHelper::mb_ucfirst($match[2]).$match[3];
}
$pattern = "/(\s|\/)/";
if (!preg_match($pattern, $titleString, $matches)) {
return StringHelper::mb_ucfirst($titleString);
}
$delimiter = $matches[1];
$wordArray = preg_split($pattern, $titleString); //explode(" ", $titleString);
$wordList = new ArrayList(...$wordArray);
return $wordList
->map(function(string $word) {
$wordParts = explode("-", $word);
if (count($wordParts) > 1) {
$casedWordParts = [];
foreach ($wordParts as $w) {
$casedWordParts[] = StringHelper::keepLowerCase($w) ? $w : StringHelper::mb_ucfirst($w);
}
$word = implode("-", $casedWordParts);
}
return StringHelper::keepLowerCase($word) ? $word : StringHelper::mb_ucfirst($word);
})
->collectToString($delimiter);
}
/**
* @param $word
* @return bool
*/
public static function keepLowerCase($word)
{
// keep lower case if the first char is not an utf-8 letter
return in_array($word, self::PREPOSITIONS) ||
in_array($word, self::ARTICLES) ||
in_array($word, self::CONJUNCTIONS) ||
in_array($word, self::ADJECTIVES) ||
(bool) preg_match("/[^\p{L}].+/", $word);
}
/**
* @param $string
* @param string $encoding
* @return string
*/
// phpcs:disable
public static function mb_ucfirst($string, $encoding = 'UTF-8')
{// phpcs:enable
$strlen = mb_strlen($string, $encoding);
if ($strlen == 0) return '';
$firstChar = mb_substr($string, 0, 1, $encoding);
$then = mb_substr($string, 1, $strlen - 1, $encoding);
/** @noinspection PhpInternalEntityUsedInspection */
// We can not rely on mb_detect_encoding. See https://www.php.net/manual/en/function.mb-detect-encoding.php.
// We need to double-check if the first char is not a multibyte char otherwise mb_strtoupper() process it
// incorrectly, and it causes issues later. For example 'こ' transforms to 'Á'.
$original_ord = mb_ord($firstChar, $encoding);
$encoding = mb_detect_encoding($firstChar, self::ISO_ENCODINGS, true);
$new_ord = mb_ord($firstChar, $encoding);
return $original_ord === $new_ord && in_array($encoding, self::ISO_ENCODINGS) ?
mb_strtoupper($firstChar, $encoding).$then : $firstChar.$then;
}
// phpcs:disable
public static function mb_strrev($string)
{// phpcs:enable
$result = '';
for ($i = mb_strlen($string); $i >= 0; --$i) {
$result .= mb_substr($string, $i, 1);
}
return $result;
}
/**
* @param string $delimiter
* @param string[] $arrayOfStrings
* @return string;
*/
public static function implodeAndPreventConsecutiveChars($delimiter, $arrayOfStrings)
{
$delim = trim($delimiter);
if (!empty($delim)) {
foreach ($arrayOfStrings as $key => $textPart) {
$pos = mb_strpos(StringHelper::mb_strrev($textPart), StringHelper::mb_strrev($delim));
if ($pos === 0) {
$length = mb_strlen($textPart) - mb_strlen($delim);
$textPart = mb_substr($textPart, 0, $length);
$arrayOfStrings[$key] = $textPart;
}
}
}
return implode($delimiter, array_filter($arrayOfStrings));
}
/**
* @param $string
* @param $initializeSign
* @return string
*/
public static function initializeBySpaceOrHyphen($string, $initializeSign)
{
$initializeWithHyphen = CiteProc::getContext()->getGlobalOptions()->isInitializeWithHyphen();
$res = "";
$exploded = explode("-", $string);
$i = 0;
foreach ($exploded as $explode) {
$spaceExploded = explode(" ", $explode);
foreach ($spaceExploded as $givenPart) {
$firstLetter = mb_substr($givenPart, 0, 1, "UTF-8");
if (StringHelper::isLatinString($firstLetter)) {
$res .= ctype_upper($firstLetter) ? $firstLetter.$initializeSign : " ".$givenPart." ";
} else {
$res .= $firstLetter.$initializeSign;
}
}
if ($i < count($exploded) - 1 && $initializeWithHyphen) {
$res = rtrim($res)."-";
}
++$i;
}
return $res;
}
/**
* @param $string
* @return mixed|string
*/
public static function camelCase2Hyphen($string)
{
$hyphenated = preg_replace("/([A-Z])/", "-$1", $string);
$hyphenated = substr($hyphenated, 0, 1) === "-" ? substr($hyphenated, 1) : $hyphenated;
return mb_strtolower($hyphenated);
}
/**
* @param $string
* @return bool
*/
public static function checkLowerCaseString($string)
{
return ($string === mb_strtolower($string));
}
/**
* @param $string
* @return bool
*/
public static function checkUpperCaseString($string)
{
return ($string === mb_strtoupper($string));
}
/**
* @param $string
* @return mixed
*/
public static function clearApostrophes($string)
{
return preg_replace("/\'/", "", $string);
}
/**
* replaces outer quotes of $text by given inner quotes
*
* @param $text
* @param $outerOpenQuote
* @param $outerCloseQuote
* @param $innerOpenQuote
* @param $innerCloseQuote
* @return string
*/
public static function replaceOuterQuotes(
$text,
$outerOpenQuote,
$outerCloseQuote,
$innerOpenQuote,
$innerCloseQuote
) {
if (preg_match("/(.*)$outerOpenQuote(.+)$outerCloseQuote(.*)/u", $text, $match)) {
return $match[1].$innerOpenQuote.$match[2].$innerCloseQuote.$match[3];
}
return $text;
}
/**
* @param $string
* @return bool
*/
public static function isLatinString($string)
{
return boolval(preg_match_all("/^[\p{Latin}\p{Common}]+$/u", $string));
//return !$noLatin;
}
/**
* @param $string
* @return bool
*/
public static function isCyrillicString($string)
{
return boolval(preg_match("/^[\p{Cyrillic}\p{Common}]+$/u", $string));
}
/**
* @param $string
* @return bool
*/
public static function isAsianString($string)
{
return boolval(preg_match("/^[\p{Han}\s\p{P}]*$/u", $string));
}
/**
* removes all kind of brackets from a given string
* @param $datePart
* @return mixed
*/
public static function removeBrackets($datePart)
{
return str_replace(["[", "]", "(", ")", "{", "}"], "", $datePart);
}
}
@@ -0,0 +1,179 @@
<?php
/*
* citeproc-php
*
* @link http://github.com/seboettg/citeproc-php for the source repository
* @copyright Copyright (c) 2016 Sebastian Böttger.
* @license https://opensource.org/licenses/MIT
*/
namespace Seboettg\CiteProc\Util;
use InvalidArgumentException;
use Seboettg\CiteProc\Exception\CiteProcException;
use Seboettg\CiteProc\Exception\InvalidStylesheetException;
use Seboettg\CiteProc\Rendering\Name\Names;
use SimpleXMLElement;
use stdClass;
/**
* Class Variables
* @package Seboettg\CiteProc\Util
*
* @author Sebastian Böttger <seboettg@gmail.com>
*/
class Variables
{
const NAME_VARIABLES = [
'author', // author
'collection-editor', // editor of the collection holding the item (e.g. the series editor for a book)
'composer', // composer (e.g. of a musical score)
'container-author', // author of the container holding the item (e.g. the book author for a book chapter)
'director', // director (e.g. of a film)
'editor', // editor
'editorial-director', // managing editor (“Directeur de la Publication” in French)
'illustrator', // illustrator (e.g. of a childrens book)
'interviewer', // interviewer (e.g. of an interview)
'original-author', //
'recipient', // recipient (e.g. of a letter)
'reviewed-author' // author of the item reviewed by the current item
];
const NUMBER_VARIABLES = [
'chapter-number', // chapter number
'collection-number', // number identifying the collection holding the item (e.g. the series number for a book)
'edition', // (container) edition holding the item (e.g. “3” when citing a chapter in the third
// edition of a book)
'issue', // (container) issue holding the item (e.g. “5” when citing a journal article from
// journal volume 2, issue 5)
'number', // number identifying the item (e.g. a report number)
'number-of-pages', // total number of pages of the cited item
'number-of-volumes', // total number of volumes, usable for citing multi-volume books and such
'volume' // (container) volume holding the item (e.g. “2” when citing a chapter from book volume 2)
];
const DATE_VARIABLES = [
'accessed', // date the item has been accessed
'container',
'event-date', // date the related event took place
'issued', // date the item was issued/published
'original-date', // (issue) date of the original version
'submitted' // date the item (e.g. a manuscript) has been submitted for publication
];
const STANDARD_VARIABLE = [
'abstract', //abstract of the item (e.g. the abstract of a journal article)
'annote', //readers notes about the item content
'archive', //archive storing the item
'archive-location', //storage location within an archive (e.g. a box and folder number)
'archive-place', //geographic location of the archive
'authority', //issuing or judicial authority (e.g. “USPTO” for a patent, “Fairfax Circuit Court” for
//a legal case)
'call-number', //call number (to locate the item in a library)
'citation-label', //label identifying the item in in-text citations of label styles (e.g. “Ferr78”). May
//be assigned by the CSL processor based on item metadata.
'citation-number', //index (starting at 1) of the cited reference in the bibliography (generated by the CSL
//processor)
'collection-title', //title of the collection holding the item (e.g. the series title for a book)
'container-title', //title of the container holding the item (e.g. the book title for a book chapter, the
//journal title for a journal article)
'container-title-short', //short/abbreviated form of “container-title” (also accessible through the “short” form
//of the “container-title” variable)
'dimensions', //physical (e.g. size) or temporal (e.g. running time) dimensions of the item
'DOI', //Digital Object Identifier (e.g. “10.1128/AEM.02591-07”)
'event', //name of the related event (e.g. the conference name when citing a conference paper)
'event-place', //geographic location of the related event (e.g. “Amsterdam, the Netherlands”)
'first-reference-note-number', //number of a preceding note containing the first reference to the item. Assigned
// by the CSL processor. The variable holds no value for non-note-based styles, or when
// the item hasnt been cited in any preceding notes.
'genre', //class, type or genre of the item (e.g. “adventure” for an adventure movie,
//“PhD dissertation” for a PhD thesis),
'ISBN', //International Standard Book Number
'ISSN', //International Standard Serial Number
'jurisdiction', //geographic scope of relevance (e.g. “US” for a US patent)
'keyword', //keyword(s) or tag(s) attached to the item
'locator', //a cite-specific pinpointer within the item (e.g. a page number within a book, or a
//volume in a multi-volume work). Must be accompanied in the input data by a label
//indicating the locator type (see the Locators term list), which determines which term
//is rendered by cs:label when the “locator” variable is selected.
'medium', //medium description (e.g. “CD”, “DVD”, etc.)
'note', //(short) inline note giving additional item details (e.g. a concise summary or commentary)
'original-publisher', //original publisher, for items that have been republished by a different publisher
'original-publisher-place', //geographic location of the original publisher (e.g. “London, UK”)
'original-title', //title of the original version (e.g. “Война и мир”, the untranslated Russian title of
// “War and Peace”)
'page', //range of pages the item (e.g. a journal article) covers in a container (e.g. a journal
// issue)
'page-first', //first page of the range of pages the item (e.g. a journal article) covers in a
//container (e.g. a journal issue)
'PMCID', //PubMed Central reference number
'PMID', //PubMed reference number
'publisher', //publisher
'publisher-place', //geographic location of the publisher
'references', //resources related to the procedural history of a legal case
'reviewed-title', //title of the item reviewed by the current item
'scale', //scale of e.g. a map
'section', //container section holding the item (e.g. “politics” for a newspaper article)
'source', //from whence the item originates (e.g. a library catalog or database)
'status', //(publication) status of the item (e.g. “forthcoming”)
'title', //primary title of the item
'title-short', //short/abbreviated form of “title” (also accessible through the “short” form of the
//“title” variable)
'URL', //Uniform Resource Locator (e.g. “http://aem.asm.org/cgi/content/full/74/9/2766”)
'version', //version of the item (e.g. “2.0.9” for a software program)
'year-suffix', //disambiguating year suffix in author-date styles (e.g. “a” in “Doe, 1999a”)
];
/**
* @param $name
* @return bool
*/
public static function isDateVariable($name)
{
return in_array($name, self::DATE_VARIABLES);
}
/**
* @param $name
* @return bool
*/
public static function isNumberVariable($name)
{
return in_array($name, self::NUMBER_VARIABLES);
}
/**
* @param $name
* @return bool
*/
public static function isNameVariable($name)
{
return in_array($name, self::NAME_VARIABLES);
}
/**
* @param stdClass $data
* @param string $variable
* @return string
* @throws InvalidStylesheetException
* @throws CiteProcException
*/
public static function nameHash(stdClass $data, $variable)
{
if (!self::isNameVariable($variable)) {
throw new InvalidArgumentException("\"$variable\" is not a valid name variable.");
}
$parent = null;
$names = new Names(
new SimpleXMLElement(
"<names variable=\"$variable\" delimiter=\"-\">".
"<name form=\"long\" sort-separator=\",\" name-as-sort-order=\"all\"/></names>"
),
$parent
);
return $names->render($data);
}
}

Some files were not shown because too many files have changed in this diff Show More