diff --git a/src/Constraint/AbstractConstraint.php b/src/Constraint/AbstractConstraint.php index a3fa3aba..e1246011 100644 --- a/src/Constraint/AbstractConstraint.php +++ b/src/Constraint/AbstractConstraint.php @@ -1,19 +1,16 @@ - - * created at 26.10.19, 14:12 + * @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; -/** - * Class AbstractConstraint - * @package Seboettg\CiteProc\Constraint - * @noinspection PhpUnused - */ abstract class AbstractConstraint implements Constraint { @@ -23,7 +20,7 @@ abstract class AbstractConstraint implements Constraint protected $match; /** - * @var array + * @var ArrayList\ArrayListInterface */ protected $conditionVariables; @@ -32,53 +29,70 @@ abstract class AbstractConstraint implements Constraint * @param stdClass $data; * @return bool */ - abstract protected function matchForVariable($variable, $data); + abstract protected function matchForVariable(string $variable, stdClass $data): bool; /** * Variable constructor. - * @param string $value + * @param string $variableValues * @param string $match */ - /** @noinspection PhpUnused */ - public function __construct($value, $match = "any") + public function __construct(string $variableValues, string $match = "any") { - $this->conditionVariables = explode(" ", $value); + $this->conditionVariables = new ArrayList(...explode(" ", $variableValues)); $this->match = $match; } /** - * @param $value + * @param stdClass $data * @param int|null $citationNumber * @return bool */ - public function validate($value, $citationNumber = null) + public function validate(stdClass $data, int $citationNumber = null): bool { switch ($this->match) { case Constraint::MATCH_ALL: - return $this->matchAll($value); + return $this->matchAll($data); case Constraint::MATCH_NONE: - return !$this->matchAny($value); //no match for any value + return $this->matchNone($data); //no match for any value case Constraint::MATCH_ANY: default: - return $this->matchAny($value); + return $this->matchAny($data); } } - private function matchAny($value) + private function matchAny(stdClass $data): bool { - $conditionMatched = false; - foreach ($this->conditionVariables as $variable) { - $conditionMatched |= $this->matchForVariable($variable, $value); - } - return (bool)$conditionMatched; + 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($value) + private function matchAll(stdClass $data): bool { - $conditionMatched = true; - foreach ($this->conditionVariables as $variable) { - $conditionMatched &= $this->matchForVariable($variable, $value); - } - return (bool)$conditionMatched; + 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(); } } diff --git a/src/Constraint/Constraint.php b/src/Constraint/Constraint.php index 09ad72e3..bc9f3b02 100644 --- a/src/Constraint/Constraint.php +++ b/src/Constraint/Constraint.php @@ -1,4 +1,5 @@ - * */ -/** @noinspection PhpUnused */ class Disambiguate implements Constraint { /** - * @codeCoverageIgnore - * @param $value + * @param stdClass $data * @param int|null $citationNumber * @return bool */ - public function validate($value, $citationNumber = null) + public function validate(stdClass $data, $citationNumber = null): bool { return false; } diff --git a/src/Constraint/Factory.php b/src/Constraint/Factory.php index 58464181..b982f225 100644 --- a/src/Constraint/Factory.php +++ b/src/Constraint/Factory.php @@ -1,4 +1,5 @@ - */ class Factory extends \Seboettg\CiteProc\Util\Factory { const NAMESPACE_CONSTRAINTS = "Seboettg\\CiteProc\\Constraint\\"; /** - * @param string $name - * @param string $value - * @param string $match - * @return mixed * @throws ClassNotFoundException */ - public static function createConstraint(string $name, string $value, string $match) + public static function createConstraint(string $name, string $value, string $match): Constraint { $parts = explode("-", $name); $className = implode("", array_map(function ($part) { diff --git a/src/Constraint/IsNumeric.php b/src/Constraint/IsNumeric.php index 7d8cbd66..965f2c44 100644 --- a/src/Constraint/IsNumeric.php +++ b/src/Constraint/IsNumeric.php @@ -1,4 +1,5 @@ {$variable})) { return $this->parseValue($data->{$variable}); @@ -46,7 +47,7 @@ protected function matchForVariable($variable, $data) * @param $evalValue * @return bool */ - private function parseValue($evalValue) + private function parseValue($evalValue): bool { if (is_numeric($evalValue)) { return true; diff --git a/src/Constraint/IsUncertainDate.php b/src/Constraint/IsUncertainDate.php index b6698425..b40060f4 100644 --- a/src/Constraint/IsUncertainDate.php +++ b/src/Constraint/IsUncertainDate.php @@ -1,4 +1,5 @@ {$variable})) { if (isset($data->{$variable}->{'circa'})) { diff --git a/src/Constraint/Jurisdiction.php b/src/Constraint/Jurisdiction.php index 9368bd38..25b00dbe 100644 --- a/src/Constraint/Jurisdiction.php +++ b/src/Constraint/Jurisdiction.php @@ -1,4 +1,5 @@ */ -/** @noinspection PhpUnused */ class Jurisdiction implements Constraint { /** * @codeCoverageIgnore - * @param $value + * @param stdClass $data * @param int|null $citationNumber * @return bool */ - public function validate($value, $citationNumber = null) + public function validate(stdClass $data, int $citationNumber = null): bool { return false; } diff --git a/src/Constraint/Locator.php b/src/Constraint/Locator.php index 08325ad0..2ae8e665 100644 --- a/src/Constraint/Locator.php +++ b/src/Constraint/Locator.php @@ -1,4 +1,5 @@ */ -/** @noinspection PhpUnused */ class Locator extends AbstractConstraint { /** * @inheritDoc */ - protected function matchForVariable($variable, $data) + protected function matchForVariable(string $variable, stdClass $data): bool { if (!empty($data->id)) { $citationItem = CiteProc::getContext()->getCitationItemById($data->id); diff --git a/src/Constraint/Position.php b/src/Constraint/Position.php index 69a9a8b6..79e622eb 100644 --- a/src/Constraint/Position.php +++ b/src/Constraint/Position.php @@ -1,4 +1,5 @@ */ class Position implements Constraint { @@ -51,42 +49,42 @@ class Position implements Constraint const SUBSEQUENT = "subsequent"; const NEAR_NOTE = "near-note"; - private $value; + private $position; private $match; - public function __construct($value, $match = "all") + public function __construct(string $position, string $match = "all") { - $this->value = $value; + $this->position = $position; $this->match = $match; } /** * @codeCoverageIgnore - * @param stdClass $object + * @param stdClass $data * @param int|null $citationNumber * @return bool */ - public function validate($object, $citationNumber = null) + public function validate(stdClass $data, $citationNumber = null): bool { if (CiteProc::getContext()->isModeBibliography()) { return false; } - switch ($this->value) { + switch ($this->position) { case self::FIRST: - return $this->getPosition($object) === null; + return $this->getPosition($data) === null; case self::IBID: case self::IBID_WITH_LOCATOR: case self::SUBSEQUENT: - return $this->isOnLastPosition($object); + return $this->isOnLastPosition($data); } return true; } - private function getPosition($object) + private function getPosition(stdClass $data): ?string { foreach (CiteProc::getContext()->getCitedItems() as $key => $value) { - if (!empty($value->{'id'}) && $value->{'id'} === $object->{'id'}) { + if (!empty($value->{'id'}) && $value->{'id'} === $data->{'id'}) { return $key; } } @@ -94,12 +92,12 @@ private function getPosition($object) } /** - * @param stdClass $object + * @param stdClass $data * @return bool */ - private function isOnLastPosition($object): bool + private function isOnLastPosition(stdClass $data): bool { $lastCitedItem = CiteProc::getContext()->getCitedItems()->last(); - return !empty($lastCitedItem) ? $lastCitedItem->{'id'} === $object->{'id'} : false; + return !empty($lastCitedItem) && $lastCitedItem->{'id'} === $data->{'id'}; } } diff --git a/src/Constraint/Type.php b/src/Constraint/Type.php index d3c76883..1cd1b42e 100644 --- a/src/Constraint/Type.php +++ b/src/Constraint/Type.php @@ -1,4 +1,5 @@ - */ -/** @noinspection PhpUnused */ class Type extends AbstractConstraint { @@ -26,8 +20,8 @@ class Type extends AbstractConstraint * @param stdClass $data ; * @return bool */ - protected function matchForVariable($variable, $data) + protected function matchForVariable(string $variable, stdClass $data): bool { - return in_array($data->type, $this->conditionVariables); + return in_array($data->type, $this->conditionVariables->toArray()); } } diff --git a/src/Constraint/Variable.php b/src/Constraint/Variable.php index a310e5d6..7e5af880 100644 --- a/src/Constraint/Variable.php +++ b/src/Constraint/Variable.php @@ -1,4 +1,5 @@ - */ -/** @noinspection PhpUnused */ class Variable extends AbstractConstraint { /** @@ -26,15 +20,15 @@ class Variable extends AbstractConstraint * @param stdClass $data * @return bool */ - protected function matchForVariable($variable, $data) + protected function matchForVariable(string $variable, stdClass $data): bool { - $variableExistInCitationItem = false; + $variableExistsInCitationItem = false; if (CiteProc::getContext()->isModeCitation() && isset($data->id)) { $citationItem = CiteProc::getContext()->getCitationItemById($data->id); if (!empty($citationItem)) { - $variableExistInCitationItem = !empty($citationItem->{$variable}); + $variableExistsInCitationItem = !empty($citationItem->{$variable}); } } - return !empty($data->{$variable}) || $variableExistInCitationItem; + return !empty($data->{$variable}) || $variableExistsInCitationItem; } } diff --git a/src/Rendering/Choose/Choose.php b/src/Rendering/Choose/Choose.php index b87ee4f2..18c1506f 100644 --- a/src/Rendering/Choose/Choose.php +++ b/src/Rendering/Choose/Choose.php @@ -46,57 +46,65 @@ public function __construct(SimpleXMLElement $node, $parent) { $this->parent = $parent; $this->children = new ArrayList(); - $elseIf = []; + $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[] = new ChooseElseIf($child, $this); + $elseIf->append(new ChooseElseIf($child, $this)); break; case 'else': $this->children->add("else", new ChooseElse($child, $this)); break; } } - if (!empty($elseIf)) { + if ($elseIf->count() > 0) { $this->children->add("elseif", $elseIf); } } /** - * @param array|DataList $data - * @param null|int $citationNumber + * @param array|DataList $data + * @param null|int $citationNumber * @return string + * @throws ArrayList\NotConvertibleToStringException */ public function render($data, $citationNumber = null) { - $arr = []; + $result = new ArrayList(); + $matchedIfs = false; - // IF - if ($prevCondition = $this->children->get("if")->match($data)) { - $arr[] = $this->children->get("if")->render($data); - } elseif (!$prevCondition && $this->children->hasKey("elseif")) { // ELSEIF - /** - * @var ChooseElseIf $child - */ - foreach ($this->children->get("elseif") as $child) { - $condition = $child->match($data); - if ($condition && !$prevCondition) { - $arr[] = $child->render($data); - $prevCondition = true; - break; //break loop as soon as condition matches - } - $prevCondition = $condition; + $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) + ); } } - //ELSE - if (!$prevCondition && $this->children->hasKey("else")) { - $arr[] = $this->children->get("else")->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 implode("", $arr); + return $result->collectToString(""); } /** diff --git a/src/Rendering/Choose/ChooseElse.php b/src/Rendering/Choose/ChooseElse.php index 3245c0ce..08c39aaa 100644 --- a/src/Rendering/Choose/ChooseElse.php +++ b/src/Rendering/Choose/ChooseElse.php @@ -1,4 +1,5 @@ - */ class ChooseElse extends ChooseIf { //render function is inherited from ChooseIf diff --git a/src/Rendering/Choose/ChooseElseIf.php b/src/Rendering/Choose/ChooseElseIf.php index 383ce2f3..10f2d668 100644 --- a/src/Rendering/Choose/ChooseElseIf.php +++ b/src/Rendering/Choose/ChooseElseIf.php @@ -1,5 +1,6 @@ - */ class ChooseElseIf extends ChooseElse { diff --git a/src/Rendering/Choose/ChooseIf.php b/src/Rendering/Choose/ChooseIf.php index a956fe89..ecaa78b2 100644 --- a/src/Rendering/Choose/ChooseIf.php +++ b/src/Rendering/Choose/ChooseIf.php @@ -1,4 +1,5 @@ - */ class ChooseIf implements Rendering, HasParent { /** - * @var ArrayList + * @var ArrayList|Constraint[] */ private $constraints; @@ -53,7 +48,7 @@ class ChooseIf implements Rendering, HasParent * @throws InvalidStylesheetException * @throws ClassNotFoundException */ - public function __construct(SimpleXMLElement $node, $parent) + public function __construct(SimpleXMLElement $node, Choose $parent) { $this->parent = $parent; $this->constraints = new ArrayList(); @@ -76,7 +71,7 @@ public function __construct(SimpleXMLElement $node, $parent) * @param null|int $citationNumber * @return string */ - public function render($data, $citationNumber = null) + public function render($data, $citationNumber = null): string { $ret = []; /** @var Rendering $child */ @@ -95,26 +90,40 @@ public function render($data, $citationNumber = null) * @param null|int $citationNumber * @return bool */ - public function match($data, $citationNumber = null) + public function match($data, int $citationNumber = null): bool { if ($this->constraints->count() === 1) { return $this->constraints->current()->validate($data); } - $result = true; - /** @var Constraint $constraint */ - foreach ($this->constraints as $constraint) { - if ($this->match === "any") { - if ($constraint->validate($data, $citationNumber)) { - return true; - } - } else { - $result &= $constraint->validate($data, $citationNumber); - } - } - if ($this->constraints->count() > 1 && $this->match === "all") { - return (bool) $result; - } elseif ($this->match === "none") { - return !((bool) $result); + + 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; } @@ -124,7 +133,7 @@ public function match($data, $citationNumber = null) * @noinspection PhpUnused * @return Choose */ - public function getParent() + public function getParent(): Choose { return $this->parent; } diff --git a/src/Rendering/Choose/Tuple.php b/src/Rendering/Choose/Tuple.php new file mode 100644 index 00000000..8c811667 --- /dev/null +++ b/src/Rendering/Choose/Tuple.php @@ -0,0 +1,23 @@ +first = $first; + $this->second = $second; + } +} diff --git a/src/Util/CiteProcHelper.php b/src/Util/CiteProcHelper.php index f2c8e536..b7f2034f 100644 --- a/src/Util/CiteProcHelper.php +++ b/src/Util/CiteProcHelper.php @@ -27,21 +27,21 @@ public static function applyAdditionMarkupFunction($dataItem, $valueToRender, $r { $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_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_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); } @@ -63,26 +63,26 @@ public static function cloneArray(array $array) return $newArray; } - /** - * @param stdClass $dataItem the actual item - * @param string $valueToRender value the has to apply on - * @return bool - */ + /** + * @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']; - } - } - } + { + $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; - } + return false; + } } diff --git a/src/functions.php b/src/functions.php index 9be4d45c..905116e5 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,4 +1,5 @@ >===== MODE =====>> +bibliography +<<===== MODE =====<< + +>>===== RESULT =====>> +
+
Abramson, Daniel. “Bank of England”. In John Soane Architect: Master of Space and Light, edited by Margaret Richardson and Mary Anne Stevens, 208-51. London: Royal Academy of Arts, 1999.
+
+<<===== RESULT =====<< + +>>===== CSL =====>> + + +<<===== CSL =====<< + +>>===== INPUT =====>> +[ + { + "author": [ + { + "family": "Abramson", + "given": "Daniel" + } + ], + "citation-label": "abramson_bank_1999", + "container-title": "John Soane Architect: Master of Space and Light", + "editor": [ + { + "family": "Richardson", + "given": "Margaret" + }, + { + "family": "Stevens", + "given": "Mary Anne" + } + ], + "id": "abramson_bank_1999", + "issued": { + "date-parts": [ + [ + 1999 + ] + ] + }, + "page": "208-251", + "publisher": "Royal Academy of Arts", + "publisher-place": "London", + "title": "Bank of England", + "type": "chapter" + } +] +<<===== INPUT =====<< diff --git a/tests/src/BugfixTest.php b/tests/src/BugfixTest.php index 943f54f4..95eb5457 100644 --- a/tests/src/BugfixTest.php +++ b/tests/src/BugfixTest.php @@ -149,6 +149,11 @@ public function testBugfixGithub106() $this->runTestSuite('bugfix-github-106'); } + public function testBugfixGithub116() + { + $this->runTestSuite('bugfix-github-116'); + } + public function testBugfixGithub117() { $this->runTestSuite('bugfix-github-117'); diff --git a/tests/src/Rendering/Choose/ChooseIfTest.php b/tests/src/Rendering/Choose/ChooseIfTest.php new file mode 100644 index 00000000..d18779c3 --- /dev/null +++ b/tests/src/Rendering/Choose/ChooseIfTest.php @@ -0,0 +1,73 @@ + + + + + + + + + EOT; + + $dataString = <<mockContext(); + $expectedResult = "false"; // since match="none" + $choose = new Choose(new SimpleXMLElement($csl), null); + $this->assertEquals($expectedResult, $choose->render(json_decode($dataString))); + } + + private function mockContext(): void + { + $mockedContext = $this->createMock(Context::class); + $mockedContext + ->method('isModeBibliography') + ->willReturn(true); + $mockedContext + ->method('isModeCitation') + ->willReturn(false); + $mockedContext + ->method('getMode') + ->willReturn('bibliography'); + $mockedContext + ->method('getMarkupExtension') + ->willReturn([]); + + CiteProc::setContext($mockedContext); + } +}