From 75473eba55c83e31872399e8cac34eeab947c064 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 19 Apr 2024 13:58:03 +0200 Subject: [PATCH 1/6] Add `Query` class --- src/Mods/Utility/Query.php | 93 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/Mods/Utility/Query.php diff --git a/src/Mods/Utility/Query.php b/src/Mods/Utility/Query.php new file mode 100644 index 0000000..89896b3 --- /dev/null +++ b/src/Mods/Utility/Query.php @@ -0,0 +1,93 @@ + 'value'] + * @param string $value The value for metadata search + * + * @return void + */ + public function __construct(string $xpath, string $query = '', array $attributes = [], string $value = '') + { + if (!empty($query) && !empty($attributes) && !empty($value)) { + $xpath .= '[' . $query . '[' . $this->getParsedAttributes($attributes) .']="' . $value .'"]'; + } else if (empty($query) && !empty($attributes) && !empty($value)) { + $xpath .= '[' . $this->getParsedAttributes($attributes) .']="' . $value .'"'; + } else if (empty($query) && empty($attributes) && !empty($value)) { + $xpath .= '="' . $value.'"'; + } else if (empty($query) && !empty($attributes) && empty($value)) { + $xpath .= '[' . $this->getParsedAttributes($attributes) .']'; + } else if (!empty($query) && empty($attributes) && empty($value)) { + $xpath .= '[' . $query .']'; + } else if (!empty($query) && empty($attributes) && !empty($value)) { + $xpath .= '[' . $query .']="' . $value .'"'; + } else if (!empty($query) && !empty($attributes) && empty($value)) { + $xpath .= '[' . $query .'[' . $this->getParsedAttributes($attributes) . ']]'; + } + + $this->xPath = $xpath; + } + + public function getXPath(): string + { + var_dump($this->xPath); + return $this->xPath; + } + + private function getParsedAttributes(array $attributes): string + { + $parsedAttributes = ''; + + $amountAttributes = count($attributes); + + if ($amountAttributes == 1) { + foreach ($attributes as $key => $value) { + $parsedAttributes .= '@' . $key . '="' . $value . '"'; + } + } else { + $i = 0; + $lastIndex = $amountAttributes - 1; + + foreach ($attributes as $key => $value) { + $parsedAttributes .= '@' . $key . '="' . $value . '"'; + if ($i < $lastIndex) { + $parsedAttributes .= ' AND '; + } + $i++; + } + } + + return $parsedAttributes; + } +} From 35d9c2338ccfe8f78a884c735ef5051f92ed2df2 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 19 Apr 2024 13:58:26 +0200 Subject: [PATCH 2/6] Use `Query` class in `ModsReader` --- src/Mods/Reader/AbstractReader.php | 23 ++++++++++++++++++ src/Mods/Reader/AccessConditionReader.php | 24 +++++++++++++++++++ src/Mods/Reader/PhysicalDescriptionReader.php | 24 +++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/Mods/Reader/AbstractReader.php b/src/Mods/Reader/AbstractReader.php index db4a034..bbdd053 100644 --- a/src/Mods/Reader/AbstractReader.php +++ b/src/Mods/Reader/AbstractReader.php @@ -14,6 +14,7 @@ use Slub\Mods\Element\AbstractElement; use Slub\Mods\Element\Xml\Element; +use Slub\Mods\Utility\Query; /** * Trait for reading Abstract element @@ -40,4 +41,26 @@ public function getAbstract(string $query = ''): ?AbstractElement } return null; } + + /** + * Get the value of the element by given parameters. + * @see https://www.loc.gov/standards/mods/userguide/abstract.html + * + * @access public + * + * @param string $query The XPath query for metadata search + * @param array $attributes The array of attributes ['attribute' => 'value'] + * @param string $value The value for metadata search + * + * @return ?AbstractElement + */ + public function getAbstractByParameters(string $query = '', array $attributes = [], string $value = ''): ?AbstractElement + { + $query = new Query('./mods:abstract', $query, $attributes, $value); + $element = new Element($this->xml, $query->getXPath()); + if ($element->exists()) { + return new AbstractElement($element->getValues()[0]); + } + return null; + } } diff --git a/src/Mods/Reader/AccessConditionReader.php b/src/Mods/Reader/AccessConditionReader.php index df74a57..f55db54 100644 --- a/src/Mods/Reader/AccessConditionReader.php +++ b/src/Mods/Reader/AccessConditionReader.php @@ -13,6 +13,7 @@ namespace Slub\Mods\Reader; use Slub\Mods\Element\AccessCondition; +use Slub\Mods\Utility\Query; /** * Trait for reading AccessCondition element @@ -40,6 +41,29 @@ public function getAccessConditions(string $query = ''): array return $accessConditions; } + /** + * Get the the array of the elements by given parameters. + * @see https://www.loc.gov/standards/mods/userguide/accesscondition.html + * + * @access public + * + * @param string $query The XPath query for metadata search + * @param array $attributes The array of attributes ['attribute' => 'value'] + * @param string $value The value for metadata search + * + * @return AccessCondition[] + */ + public function getAccessConditionsByParameters(string $query = '', array $attributes = [], string $value = ''): array + { + $accessConditions = []; + $query = new Query('./mods:accessCondition', $query, $attributes, $value); + $values = $this->getValues($query->getXPath()); + foreach ($values as $value) { + $accessConditions[] = new AccessCondition($value); + } + return $accessConditions; + } + /** * Get the matching element. * @see https://www.loc.gov/standards/mods/userguide/accesscondition.html diff --git a/src/Mods/Reader/PhysicalDescriptionReader.php b/src/Mods/Reader/PhysicalDescriptionReader.php index 978526b..5120fa2 100644 --- a/src/Mods/Reader/PhysicalDescriptionReader.php +++ b/src/Mods/Reader/PhysicalDescriptionReader.php @@ -13,6 +13,7 @@ namespace Slub\Mods\Reader; use Slub\Mods\Element\PhysicalDescription; +use Slub\Mods\Utility\Query; /** * Trait for reading PhysicalDescription element @@ -40,6 +41,29 @@ public function getPhysicalDescriptions(string $query = ''): array return $physicalDescriptions; } + /** + * Get the the array of the elements by given parameters. + * @see https://www.loc.gov/standards/mods/userguide/physicaldescription.html + * + * @access public + * + * @param string $query The XPath query for metadata search + * @param array $attributes The array of attributes ['attribute' => 'value'] + * @param string $value The value for metadata search + * + * @return PhysicalDescription[] + */ + public function getPhysicalDescriptionsByParameters(string $query = '', array $attributes = [], string $value = ''): array + { + $physicalDescriptions = []; + $query = new Query('./mods:physicalDescription', $query, $attributes, $value); + $values = $this->getValues($query->getXPath()); + foreach ($values as $value) { + $physicalDescriptions[] = new PhysicalDescription($value); + } + return $physicalDescriptions; + } + /** * Get the matching element. * @see https://www.loc.gov/standards/mods/userguide/physicaldescription.html From d1b80d06982fbeb06f797608f93a3fd19870dde7 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 19 Apr 2024 14:00:52 +0200 Subject: [PATCH 3/6] Add test for querying by paramaters --- tests/Mods/Reader/PhysicalDescriptionReaderTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Mods/Reader/PhysicalDescriptionReaderTest.php b/tests/Mods/Reader/PhysicalDescriptionReaderTest.php index 03ccc31..8828825 100644 --- a/tests/Mods/Reader/PhysicalDescriptionReaderTest.php +++ b/tests/Mods/Reader/PhysicalDescriptionReaderTest.php @@ -59,6 +59,17 @@ public function getLastPhysicalDescriptionForBookDocument() self::assertPhysicalDescriptionForBookDocument($physicalDescription); } + /** + * @test + */ + public function gtPhysicalDescriptionsByParametersForBookDocument() + { + $physicalDescriptions = $this->bookReader->getPhysicalDescriptionsByParameters('./mods:form', ['authority' => 'marcform'], 'print'); + self::assertNotEmpty($physicalDescriptions); + self::assertEquals(1, count($physicalDescriptions)); + self::assertPhysicalDescriptionForBookDocument($physicalDescriptions[0]); + } + /** * @test */ From 463e0888c901dc02c9afdd44df48b2255d2dc0ee Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 19 Jun 2026 14:25:57 +0200 Subject: [PATCH 4/6] Apply Codacy hints Adds scalar type declaration to the `xPath` property and removes a debug `var_dump` statement. Includes minor whitespace adjustments and refactoring for consistency. --- src/Mods/Utility/Query.php | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Mods/Utility/Query.php b/src/Mods/Utility/Query.php index 89896b3..f205550 100644 --- a/src/Mods/Utility/Query.php +++ b/src/Mods/Utility/Query.php @@ -21,10 +21,9 @@ class Query { /** - * @access private * @var string The query **/ - private $xPath; + private string $xPath; /** * This creates the query eq. './xyz[@att="zty"]="vcx"'. @@ -41,19 +40,19 @@ class Query public function __construct(string $xpath, string $query = '', array $attributes = [], string $value = '') { if (!empty($query) && !empty($attributes) && !empty($value)) { - $xpath .= '[' . $query . '[' . $this->getParsedAttributes($attributes) .']="' . $value .'"]'; + $xpath .= '[' . $query . '[' . $this->getParsedAttributes($attributes) . ']="' . $value . '"]'; } else if (empty($query) && !empty($attributes) && !empty($value)) { - $xpath .= '[' . $this->getParsedAttributes($attributes) .']="' . $value .'"'; + $xpath .= '[' . $this->getParsedAttributes($attributes) . ']="' . $value . '"'; } else if (empty($query) && empty($attributes) && !empty($value)) { - $xpath .= '="' . $value.'"'; + $xpath .= '="' . $value . '"'; } else if (empty($query) && !empty($attributes) && empty($value)) { - $xpath .= '[' . $this->getParsedAttributes($attributes) .']'; + $xpath .= '[' . $this->getParsedAttributes($attributes) . ']'; } else if (!empty($query) && empty($attributes) && empty($value)) { - $xpath .= '[' . $query .']'; + $xpath .= '[' . $query . ']'; } else if (!empty($query) && empty($attributes) && !empty($value)) { - $xpath .= '[' . $query .']="' . $value .'"'; + $xpath .= '[' . $query . ']="' . $value . '"'; } else if (!empty($query) && !empty($attributes) && empty($value)) { - $xpath .= '[' . $query .'[' . $this->getParsedAttributes($attributes) . ']]'; + $xpath .= '[' . $query . '[' . $this->getParsedAttributes($attributes) . ']]'; } $this->xPath = $xpath; @@ -61,7 +60,6 @@ public function __construct(string $xpath, string $query = '', array $attributes public function getXPath(): string { - var_dump($this->xPath); return $this->xPath; } @@ -75,17 +73,19 @@ private function getParsedAttributes(array $attributes): string foreach ($attributes as $key => $value) { $parsedAttributes .= '@' . $key . '="' . $value . '"'; } - } else { - $i = 0; - $lastIndex = $amountAttributes - 1; - foreach ($attributes as $key => $value) { - $parsedAttributes .= '@' . $key . '="' . $value . '"'; - if ($i < $lastIndex) { - $parsedAttributes .= ' AND '; - } - $i++; + return $parsedAttributes; + } + + $index = 0; + $lastIndex = $amountAttributes - 1; + + foreach ($attributes as $key => $value) { + $parsedAttributes .= '@' . $key . '="' . $value . '"'; + if ($index < $lastIndex) { + $parsedAttributes .= ' AND '; } + $index++; } return $parsedAttributes; From ee97d78b1fa7befd9359baeb22aa071f1e8836f4 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 19 Jun 2026 14:50:45 +0200 Subject: [PATCH 5/6] Add unit tests for `Query` class XPath construction --- tests/Mods/Utility/QueryTest.php | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tests/Mods/Utility/QueryTest.php diff --git a/tests/Mods/Utility/QueryTest.php b/tests/Mods/Utility/QueryTest.php new file mode 100644 index 0000000..b1d0c8d --- /dev/null +++ b/tests/Mods/Utility/QueryTest.php @@ -0,0 +1,88 @@ + [ + 'query' => 'div', + 'attributes' => ['class' => 'test'], + 'value' => 'active', + 'expectedSuffix' => '[div[@class="test"]="active"]' + ], + 'No query, attributes and value present' => [ + 'query' => '', + 'attributes' => ['class' => 'test'], + 'value' => 'active', + 'expectedSuffix' => '[@class="test"]="active"' + ], + 'Only value present' => [ + 'query' => '', + 'attributes' => [], + 'value' => 'active', + 'expectedSuffix' => '="active"' + ], + 'Only attributes present' => [ + 'query' => '', + 'attributes' => ['class' => 'test'], + 'value' => '', + 'expectedSuffix' => '[@class="test"]' + ], + 'Only query present' => [ + 'query' => 'div', + 'attributes' => [], + 'value' => '', + 'expectedSuffix' => '[div]' + ], + 'Query and value present, no attributes' => [ + 'query' => 'div', + 'attributes' => [], + 'value' => 'active', + 'expectedSuffix' => '[div]="active"' + ], + 'Query and attributes present, no value' => [ + 'query' => 'div', + 'attributes' => ['class' => 'test'], + 'value' => '', + 'expectedSuffix' => '[div[@class="test"]]' + ], + 'All optional parameters empty' => [ + 'query' => '', + 'attributes' => [], + 'value' => '', + 'expectedSuffix' => '' + ], + 'Multiple attributes present with query and value' => [ + 'query' => 'div', + 'attributes' => ['class' => 'test', 'id' => 'main'], + 'value' => 'visible', + 'expectedSuffix' => '[div[@class="test" AND @id="main"]="visible"]' + ] + ]; + + /** + * @test + */ + public function testConstructorAppendsCorrectXpath() + { + $initialXPath = '/initial/xpath'; + + foreach (self::$testData as $testMessage => $testData) { + $resultQuery = new Query($initialXPath, $testData['query'], $testData['attributes'], $testData['value']); + self::assertSame($initialXPath . $testData['expectedSuffix'], $resultQuery->getXpath(), $testMessage); + } + } +} From e30575c9b7db32b19669b9c18cff8d2215511116 Mon Sep 17 00:00:00 2001 From: Beatrycze Volk Date: Fri, 19 Jun 2026 15:30:33 +0200 Subject: [PATCH 6/6] Simplify XPath predicate generation in Query constructor Consolidates complex `if/else if` conditions for `$query`, `$attributes`, and `$value` parameters. This improves readability and maintainability by streamlining the logic for building XPath predicates. --- src/Mods/Utility/Query.php | 21 ++++++++------------- tests/Mods/Utility/QueryTest.php | 3 +++ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Mods/Utility/Query.php b/src/Mods/Utility/Query.php index f205550..fe186ca 100644 --- a/src/Mods/Utility/Query.php +++ b/src/Mods/Utility/Query.php @@ -39,20 +39,15 @@ class Query */ public function __construct(string $xpath, string $query = '', array $attributes = [], string $value = '') { - if (!empty($query) && !empty($attributes) && !empty($value)) { - $xpath .= '[' . $query . '[' . $this->getParsedAttributes($attributes) . ']="' . $value . '"]'; - } else if (empty($query) && !empty($attributes) && !empty($value)) { - $xpath .= '[' . $this->getParsedAttributes($attributes) . ']="' . $value . '"'; - } else if (empty($query) && empty($attributes) && !empty($value)) { + $parsedAttributes = !empty($attributes) ? $this->getParsedAttributes($attributes) : ''; + + if (!empty($query) && !empty($parsedAttributes)) { + $xpath .= !empty($value) ? '[' . $query . '[' . $parsedAttributes . ']="' . $value . '"]' : '[' . $query . '[' . $parsedAttributes . ']]'; + } else if (!empty($query) || !empty($parsedAttributes)) { + $inner = !empty($query) ? $query : $parsedAttributes; + $xpath .= !empty($value) ? '[' . $inner . ']="' . $value . '"' : '[' . $inner . ']'; + } else if (!empty($value)) { $xpath .= '="' . $value . '"'; - } else if (empty($query) && !empty($attributes) && empty($value)) { - $xpath .= '[' . $this->getParsedAttributes($attributes) . ']'; - } else if (!empty($query) && empty($attributes) && empty($value)) { - $xpath .= '[' . $query . ']'; - } else if (!empty($query) && empty($attributes) && !empty($value)) { - $xpath .= '[' . $query . ']="' . $value . '"'; - } else if (!empty($query) && !empty($attributes) && empty($value)) { - $xpath .= '[' . $query . '[' . $this->getParsedAttributes($attributes) . ']]'; } $this->xPath = $xpath; diff --git a/tests/Mods/Utility/QueryTest.php b/tests/Mods/Utility/QueryTest.php index b1d0c8d..19f928c 100644 --- a/tests/Mods/Utility/QueryTest.php +++ b/tests/Mods/Utility/QueryTest.php @@ -16,6 +16,9 @@ class QueryTest extends TestCase { + /** + * @var mixed[] + */ private static array $testData = [ 'All parameters present' => [ 'query' => 'div',