From 4ebe9d78f42a8e770a77ae2ef3f4f52936a746a5 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Wed, 11 Dec 2024 19:41:58 -0600 Subject: [PATCH 01/11] Allow PHP 8.4 --- .github/workflows/phpunit.yml | 2 + .run/PSalm.run.xml | 5 ++ .run/Tests.run.xml | 6 +++ composer.json | 7 +-- phpunit.xml.dist | 48 +++++++++---------- psalm.xml | 18 ++++++++ src/XmlDataset.php | 6 +-- src/XmlIterator.php | 86 ++++++++++++++++++++++------------- tests/XmlDatasetTest.php | 35 ++++++++------ 9 files changed, 136 insertions(+), 77 deletions(-) create mode 100644 .run/PSalm.run.xml create mode 100644 .run/Tests.run.xml create mode 100644 psalm.xml diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index a781440..2743e1c 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -16,6 +16,7 @@ jobs: strategy: matrix: php-version: + - "8.4" - "8.3" - "8.2" - "8.1" @@ -23,6 +24,7 @@ jobs: steps: - uses: actions/checkout@v4 - run: composer install + - run: ./vendor/bin/psalm - run: ./vendor/bin/phpunit Documentation: diff --git a/.run/PSalm.run.xml b/.run/PSalm.run.xml new file mode 100644 index 0000000..bd119ce --- /dev/null +++ b/.run/PSalm.run.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/.run/Tests.run.xml b/.run/Tests.run.xml new file mode 100644 index 0000000..591853e --- /dev/null +++ b/.run/Tests.run.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json index c2e41ab..2c6f88a 100644 --- a/composer.json +++ b/composer.json @@ -14,12 +14,13 @@ "prefer-stable": true, "minimum-stability": "dev", "require": { - "php": ">=8.1 <8.4", + "php": ">=8.1 <8.5", "ext-dom": "*", - "byjg/anydataset": "^5.0" + "byjg/anydataset": "^5.1" }, "require-dev": { - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "^10.5|^11.5", + "vimeo/psalm": "^6.0" }, "provide": { "byjg/anydataset-implementation": "1.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f01d221..584610f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,33 +4,33 @@ To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates and open the template in the editor. --> - - + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnTestsThatTriggerErrors="true" + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerWarnings="true" + displayDetailsOnPhpunitDeprecations="true" + stopOnFailure="false" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> + + + + + - - - - - + + + ./src/ + + - - - ./src - - - - - - ./tests - - - + + + ./tests/ + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..ebabb1a --- /dev/null +++ b/psalm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/XmlDataset.php b/src/XmlDataset.php index c80d6df..69e4d63 100644 --- a/src/XmlDataset.php +++ b/src/XmlDataset.php @@ -3,13 +3,12 @@ namespace ByJG\AnyDataset\Xml; use ByJG\AnyDataset\Core\GenericIterator; -use ByJG\AnyDataset\Core\Exception\DatasetException; +use ByJG\XmlUtil\Exception\FileException; use ByJG\XmlUtil\Exception\XmlUtilException; use ByJG\XmlUtil\File; use ByJG\XmlUtil\XmlDocument; use ByJG\XmlUtil\XmlNode; use DOMDocument; -use InvalidArgumentException; class XmlDataset { @@ -42,9 +41,10 @@ class XmlDataset /** * @param XmlNode|DOMDocument|string|File $xml * @param string $rowNode - * @param string[] $colNode + * @param array $colNode * @param array $registerNS * @throws XmlUtilException + * @throws FileException */ public function __construct(XmlNode|DOMDocument|string|File $xml, string $rowNode, array $colNode, array $registerNS = []) { diff --git a/src/XmlIterator.php b/src/XmlIterator.php index 6cd697b..cc753f9 100644 --- a/src/XmlIterator.php +++ b/src/XmlIterator.php @@ -3,12 +3,13 @@ namespace ByJG\AnyDataset\Xml; use ByJG\AnyDataset\Core\GenericIterator; -use ByJG\AnyDataset\Core\Exception\IteratorException; use ByJG\AnyDataset\Core\Row; +use ByJG\AnyDataset\Core\RowArray; +use ByJG\AnyDataset\Core\RowInterface; use ByJG\XmlUtil\Exception\XmlUtilException; use ByJG\XmlUtil\XmlNode; use DOMNodeList; -use InvalidArgumentException; +use ReturnTypeWillChange; class XmlIterator extends GenericIterator { @@ -18,35 +19,34 @@ class XmlIterator extends GenericIterator * * @var DOMNodeList|null */ - private ?DOMNodeList $nodeList = null; + private ?DOMNodeList $nodeList; /** * Enter description here... * * @var string[] */ - private ?array $colNodes = null; + private ?array $colNodes; /** * Enter description here... * - * @var int + * @var array */ - private int $current = 0; + private array $current; + protected array $registerNS; - public function __construct(DOMNodeList $nodeList, array $colNodes, array $registerNS = null) + public function __construct(DOMNodeList $nodeList, array $colNodes, ?array $registerNS = null) { $this->registerNS = $registerNS; $this->nodeList = $nodeList; $this->colNodes = $colNodes; - $this->current = 0; - } - - public function count(): int - { - return $this->nodeList->length; + $this->current = [ + 'row' => null, + 'i' => 0, + ]; } /** @@ -55,55 +55,77 @@ public function count(): int */ public function hasNext(): bool { - if ($this->current < $this->count()) { - return true; - } - - return false; + return ($this->current["i"] < count($this->nodeList)); } /** - * @access public - * @return Row|null - * @throws IteratorException * @throws XmlUtilException */ - public function moveNext(): ?Row + protected function parseXmlNode(bool $next): ?RowInterface { + if ($this->current["row"] !== null && !$next) { + return $this->current["row"]; + } + if (!$this->hasNext()) { return null; } - $node = $this->nodeList->item($this->current++); + $rowNumber = $this->current["i"]; + $node = $this->nodeList->item($rowNumber); - $row = new Row(); + $row = new RowArray(); $callables = []; - foreach ($this->colNodes as $key => $colxpath) { - if (is_callable($colxpath)) { - $callables[$key] = $colxpath; + foreach ($this->colNodes as $key => $colXpath) { + if (is_callable($colXpath)) { + $callables[$key] = $colXpath; continue; } - $nodeCol = XmlNode::instance($node)->selectNodes($colxpath, $this->registerNS); + $nodeCol = XmlNode::instance($node)->selectNodes($colXpath, $this->registerNS); if ($nodeCol->count() == 0) { - $row->addField(strtolower($key), ""); + $row->set(strtolower($key), ""); } else { foreach ($nodeCol as $col) { - $row->addField(strtolower($key), $col->nodeValue); + $row->set(strtolower($key), $col->nodeValue, append: true); } } } foreach ($callables as $key => $callable) { - $row->addField(strtolower($key), $callable($row)); + $row->set(strtolower($key), $callable($row), append: true); } + $this->current = [ + 'row' => $next ? null : $row, + 'i' => $rowNumber + ($next ? 1 : 0), + ]; + return $row; } + /** + * @access public + * @return Row|null + * @throws XmlUtilException + */ + public function moveNext(): ?RowInterface + { + return $this->parseXmlNode(next: true); + } + public function key(): int { - return $this->current; + return $this->current["i"]; + } + + /** + * @throws XmlUtilException + */ + #[ReturnTypeWillChange] + public function current(): ?RowInterface + { + return $this->parseXmlNode(next: false); } } diff --git a/tests/XmlDatasetTest.php b/tests/XmlDatasetTest.php index 96e4318..9d448d7 100644 --- a/tests/XmlDatasetTest.php +++ b/tests/XmlDatasetTest.php @@ -4,6 +4,7 @@ use ByJG\AnyDataset\Core\IteratorInterface; use ByJG\AnyDataset\Core\Row; +use ByJG\AnyDataset\Core\RowInterface; use ByJG\AnyDataset\Xml\XmlDataset; use ByJG\XmlUtil\Exception\XmlUtilException; use PHPUnit\Framework\TestCase; @@ -58,9 +59,8 @@ public function testcreateXMLDataset() $xmlDataset = new XmlDataset(XmlDatasetTest::XML_OK, $this->rootNode, $this->arrColumn); $xmlIterator = $xmlDataset->getIterator(); - $this->assertTrue($xmlIterator instanceof IteratorInterface); $this->assertTrue($xmlIterator->hasNext()); - $this->assertEquals($xmlIterator->Count(), 3); + $this->assertCount(3, $xmlIterator->toArray()); } public function testnavigateXMLIterator() @@ -100,7 +100,7 @@ public function testwrongNodeRoot() $xmlDataset = new XmlDataset(XmlDatasetTest::XML_OK, "wrong", $this->arrColumn); $xmlIterator = $xmlDataset->getIterator(); - $this->assertEquals($xmlIterator->count(), 0); + $this->assertCount(0, $xmlIterator->toArray()); } public function testwrongColumn() @@ -108,7 +108,7 @@ public function testwrongColumn() $xmlDataset = new XmlDataset(XmlDatasetTest::XML_OK, $this->rootNode, array("title" => "aaaa")); $xmlIterator = $xmlDataset->getIterator(); - $this->assertEquals($xmlIterator->count(), 3); + $this->assertCount(3, $xmlIterator->toArray()); } public function testrepeatedNodes() @@ -126,19 +126,17 @@ public function testrepeatedNodes() $xmlDataset = new XmlDataset($xml, $this->rootNode, array("author" => "author")); $xmlIterator = $xmlDataset->getIterator(); - $this->assertTrue($xmlIterator instanceof IteratorInterface); $this->assertTrue($xmlIterator->hasNext()); - $this->assertEquals(1, $xmlIterator->Count()); $sr = $xmlIterator->moveNext(); - $authors = $sr->getAsArray('author'); + $authors = $sr->get('author'); $this->assertEquals(2, count($authors)); $this->assertEquals('Giada De Laurentiis', $authors[0]); $this->assertEquals('Another Author', $authors[1]); } - public function testatomXml() + public function testAtomXml() { $xml = ' myId @@ -187,13 +185,19 @@ public function testatomXml() "gd" => "http://schemas.google.com/g/2005" ); $rootNode = 'fake:entry'; - $colNode = array("id" => "fake:id", "updated" => "fake:updated", "name" => "fake:title", "email" => "gd:email/@address", "item" => function($row) { return $row->get("name") . " - " . $row->get("email"); }); + $colNode = [ + "id" => "fake:id", + "updated" => "fake:updated", + "name" => "fake:title", + "email" => "gd:email/@address", + "item" => function($row) { return $row->get("name") . " - " . $row->get("email"); } + ]; $xmlDataset = new XmlDataset($xml, $rootNode, $colNode, $namespace); $xmlIterator = $xmlDataset->getIterator(); - $this->assertTrue($xmlIterator instanceof IteratorInterface); $this->assertTrue($xmlIterator->hasNext()); - $this->assertEquals(2, $xmlIterator->Count()); + + $rowCur = $xmlIterator->current(); $row = $xmlIterator->moveNext(); $this->assertEquals("http://www.google.com/m8/feeds/contacts/my%40gmail.com/base/0", $row->get("id")); @@ -201,6 +205,7 @@ public function testatomXml() $this->assertEquals("Person 1", $row->get("name")); $this->assertEquals("p1@gmail.com", $row->get("email")); $this->assertEquals("Person 1 - p1@gmail.com", $row->get("item")); + $this->assertEquals($row, $rowCur); $row = $xmlIterator->moveNext(); $this->assertEquals("http://www.google.com/m8/feeds/contacts/my%40gmail.com/base/1", $row->get("id")); @@ -212,13 +217,13 @@ public function testatomXml() /** - * @param Row $sr + * @param RowInterface $sr */ public function assertSingleRow($sr, $count) { - $this->assertEquals($sr->get("category"), $this->arrTest[$count]["category"]); - $this->assertEquals($sr->get("title"), $this->arrTest[$count]["title"]); - $this->assertEquals($sr->get("lang"), $this->arrTest[$count]["lang"]); + $this->assertEquals($this->arrTest[$count]["category"], $sr->get("category"), "Row $count"); + $this->assertEquals($this->arrTest[$count]["title"], $sr->get("title"), "Row $count"); + $this->assertEquals($this->arrTest[$count]["lang"], $sr->get("lang"), "Row $count"); } } From 191eb715916d9b7779697a9e73719daa4976ffd7 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Fri, 13 Dec 2024 12:19:00 -0600 Subject: [PATCH 02/11] Allow PHP 8.4 --- src/XmlIterator.php | 48 ++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/XmlIterator.php b/src/XmlIterator.php index cc753f9..74fb4d3 100644 --- a/src/XmlIterator.php +++ b/src/XmlIterator.php @@ -47,27 +47,16 @@ public function __construct(DOMNodeList $nodeList, array $colNodes, ?array $regi 'row' => null, 'i' => 0, ]; - } - /** - * @access public - * @return bool - */ - public function hasNext(): bool - { - return ($this->current["i"] < count($this->nodeList)); + $this->parseXmlNode(); } /** * @throws XmlUtilException */ - protected function parseXmlNode(bool $next): ?RowInterface + protected function parseXmlNode(): ?RowInterface { - if ($this->current["row"] !== null && !$next) { - return $this->current["row"]; - } - - if (!$this->hasNext()) { + if (!$this->valid()) { return null; } @@ -97,24 +86,11 @@ protected function parseXmlNode(bool $next): ?RowInterface $row->set(strtolower($key), $callable($row), append: true); } - $this->current = [ - 'row' => $next ? null : $row, - 'i' => $rowNumber + ($next ? 1 : 0), - ]; + $this->current["row"] = $row; return $row; } - /** - * @access public - * @return Row|null - * @throws XmlUtilException - */ - public function moveNext(): ?RowInterface - { - return $this->parseXmlNode(next: true); - } - public function key(): int { return $this->current["i"]; @@ -126,6 +102,20 @@ public function key(): int #[ReturnTypeWillChange] public function current(): ?RowInterface { - return $this->parseXmlNode(next: false); + return $this->current["row"]; + } + + #[ReturnTypeWillChange] + public function next(): void + { + $this->current["i"]++; + $this->current["row"] = null; + $this->parseXmlNode(); + } + + #[ReturnTypeWillChange] + public function valid(): bool + { + return ($this->current["i"] < count($this->nodeList)); } } From ad5727c6b2bc0b4bc6d665ec26116fe6d6f9da6e Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Thu, 13 Mar 2025 18:18:01 -0500 Subject: [PATCH 03/11] Update Documentation --- README.md | 147 +++++++++----------------------------- docs/XPath-Expressions.md | 95 ++++++++++++++++++++++++ docs/XmlDataset.md | 122 +++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 115 deletions(-) create mode 100644 docs/XPath-Expressions.md create mode 100644 docs/XmlDataset.md diff --git a/README.md b/README.md index 652d019..efc1030 100644 --- a/README.md +++ b/README.md @@ -6,47 +6,38 @@ [![GitHub license](https://img.shields.io/github/license/byjg/php-anydataset-xml.svg)](https://opensource.byjg.com/opensource/licensing.html) [![GitHub release](https://img.shields.io/github/release/byjg/php-anydataset-xml.svg)](https://github.com/byjg/php-anydataset-xml/releases/) -XML abstraction dataset. Anydataset is an agnostic data source abstraction layer in PHP. - -See more about Anydataset [here](https://opensource.byjg.com/php/anydataset). - -## Examples - -### Simple Manipulation - -example1.xml -```xml - - - - Everyday Italian - Giada De Laurentiis - 2005 - 30.00 - - - Harry Potter - J K. Rowling - 2005 - 29.99 - - - Learning XML - Erik T. Ray - 2003 - 39.95 - - +XML abstraction dataset for the AnyDataset library. AnyDataset is an agnostic data source abstraction layer in PHP. + +## Features + +- **Simple XML Processing**: Process XML data with a clean, consistent API +- **XPath Support**: Use XPath expressions to select nodes and attributes +- **Namespace Support**: Full support for XML namespaces +- **Repeated Node Handling**: Automatically handles repeated nodes by returning arrays +- **Custom Field Processing**: Define custom field mappings with closures/callbacks +- **Flexible Input**: Accept XML as string, DOMDocument, XmlNode, or File object +- **AnyDataset Integration**: Compatible with the AnyDataset abstraction layer + +## Documentation + +- [XmlDataset](docs/XmlDataset.md) - Main class for working with XML data +- [XPath Expressions](docs/XPath-Expressions.md) - Guide to using XPath in the library + +## Installation + +```bash +composer require "byjg/anydataset-xml" ``` -example1.php +## Quick Example + ```php "@category", "title" => "title", @@ -66,93 +57,19 @@ foreach ($iterator as $row) { } ``` -### Xml with namespaces - -example2.xml -```xml - - - myId - 2014-09-15T19:35:55.795Z - - Title - - - - - - - - My Name - My Email - - Contacts - 2107 - 1 - 20 - - http://www.google.com/m8/feeds/contacts/my%40gmail.com/base/0 - 2013-10-05T22:16:03.564Z - - Person 1 - - - - - - - - http://www.google.com/m8/feeds/contacts/my%40gmail.com/base/1 - 2012-07-12T17:19:17.546Z - - Person 2 - - - - - - -``` - -example2.php -```php - "http://www.w3.org/2005/Atom", - "gd" => "http://schemas.google.com/g/2005" -); - -$rootNode = 'fake:entry'; -$colNode = array("id" => "fake:id", "updated" => "fake:updated", "name" => "fake:title", "email" => "gd:email/@address"); -$xmlDataset = new \ByJG\AnyDataset\Xml\XmlDataset( - $xml, - $rootNode, - $colNode, - $namespace -); -$xmlIterator = $xmlDataset->getIterator(); -``` - -## Install +## Dependencies -``` -composer require "byjg/anydataset-xml" +```mermaid +flowchart TD + byjg/anydataset-xml --> byjg/anydataset + byjg/anydataset-xml --> ext-dom ``` -## Running the Unit tests +## Running the Unit Tests ```bash vendor/bin/phpunit ``` -## Dependencies - -```mermaid -flowchart TD - byjg/anydataset-xml --> byjg/anydataset - byjg/anydataset-xml --> ext-dom -``` ---- [Open source ByJG](http://opensource.byjg.com) diff --git a/docs/XPath-Expressions.md b/docs/XPath-Expressions.md new file mode 100644 index 0000000..5acadf3 --- /dev/null +++ b/docs/XPath-Expressions.md @@ -0,0 +1,95 @@ +# XPath Expressions in AnyDataset-Xml + +This library uses XPath expressions to select nodes and attributes from XML documents. Understanding XPath is essential for effectively using the AnyDataset-Xml library. + +## Basic XPath Syntax + +Here are some common XPath expressions used in the library: + +| Expression | Description | Example | +|----------------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| `element` | Selects all elements with the given name | `"title"` selects all `` elements | +| `@attribute` | Selects the attribute with the given name | `"@category"` selects the category attribute | +| `element/@attribute` | Selects an attribute of an element | `"title/@lang"` selects the lang attribute of title elements | +| `parent/child` | Selects all child elements of the parent | `"book/author"` selects all author elements that are children of book elements | +| `//element` | Selects all elements with the given name, regardless of position | `"//title"` selects all title elements anywhere in the document | +| `*` | Selects all elements | `"book/*"` selects all child elements of book | +| `element[n]` | Selects the nth element | `"author[1]"` selects the first author element | +| `element[condition]` | Selects elements that satisfy the condition | `"book[@category='WEB']"` selects book elements with category attribute equal to 'WEB' | + +## Examples in AnyDataset-Xml + +### Basic Element Selection + +```php +$colNodes = [ + "title" => "title", // Selects the <title> element + "author" => "author", // Selects the <author> element + "year" => "year", // Selects the <year> element + "price" => "price" // Selects the <price> element +]; +``` + +### Attribute Selection + +```php +$colNodes = [ + "category" => "@category", // Selects the category attribute of the current node + "lang" => "title/@lang" // Selects the lang attribute of the title element +]; +``` + +### Using Namespaces + +When working with namespaced XML, you need to register the namespaces and use them in your XPath expressions: + +```php +$namespace = [ + "atom" => "http://www.w3.org/2005/Atom", + "gd" => "http://schemas.google.com/g/2005" +]; + +$colNodes = [ + "id" => "atom:id", // Selects the id element in the atom namespace + "email" => "gd:email/@address" // Selects the address attribute of the email element in the gd namespace +]; + +$dataset = new \ByJG\AnyDataset\Xml\XmlDataset( + $xml, + "atom:entry", // Selects entry elements in the atom namespace + $colNodes, + $namespace +); +``` + +## Advanced Usage + +### Custom Field Processing + +You can use callback functions to process field values: + +```php +$colNodes = [ + "title" => "title", + "lang" => "title/@lang", + "shortLang" => function ($row) { + return substr($row->get('lang'), 0, 2); + } +]; +``` + +### Handling Repeated Nodes + +When an XPath expression matches multiple nodes, the values are returned as an array: + +```php +// For XML like: +// <book> +// <author>Author 1</author> +// <author>Author 2</author> +// </book> + +$colNodes = [ + "authors" => "author" // Will return an array of all author elements +]; +``` \ No newline at end of file diff --git a/docs/XmlDataset.md b/docs/XmlDataset.md new file mode 100644 index 0000000..1cb84c5 --- /dev/null +++ b/docs/XmlDataset.md @@ -0,0 +1,122 @@ +# XmlDataset + +The `XmlDataset` class is the main entry point for working with XML data in the AnyDataset library. + +## Basic Usage + +```php +$dataset = new \ByJG\AnyDataset\Xml\XmlDataset( + $xml, // The XML string, can also be an XmlNode, DOMDocument, or File object + "book", // The node that represents a row + [ + "category" => "@category", + "title" => "title", + "lang" => "title/@lang", + ] // Mapping columns +); + +$iterator = $dataset->getIterator(); +foreach ($iterator as $row) { + echo $row->get('category'); + echo $row->get('title'); + echo $row->get('lang'); +} +``` + +## Constructor Parameters + +```php +public function __construct( + XmlNode|DOMDocument|string|File $xml, + string $rowNode, + array $colNode, + array $registerNS = [] +) +``` + +- **$xml**: The XML source. Can be: + - A string containing XML + - An XmlNode object + - A DOMDocument object + - A File object + +- **$rowNode**: XPath expression that identifies the nodes to be treated as rows + +- **$colNode**: Associative array mapping field names to XPath expressions + - Keys: The field names that will be accessible in the iterator + - Values: XPath expressions relative to the row node, or callback functions + +- **$registerNS**: Optional array of namespace prefixes and URIs + +## Methods + +### getIterator() + +```php +public function getIterator(): GenericIterator +``` + +Returns an `XmlIterator` instance that can be used to iterate through the XML data. + +## Examples + +### Basic Example + +```php +$xml = file_get_contents('example.xml'); + +$dataset = new \ByJG\AnyDataset\Xml\XmlDataset( + $xml, + "book", + [ + "category" => "@category", + "title" => "title", + "lang" => "title/@lang" + ] +); + +$iterator = $dataset->getIterator(); +foreach ($iterator as $row) { + echo $row->get('category'); // Print attribute values + echo $row->get('title'); // Print element values + echo $row->get('lang'); // Print attribute of an element +} +``` + +### With Custom Field Processing + +```php +$dataset = new \ByJG\AnyDataset\Xml\XmlDataset( + $xml, + "book", + [ + "category" => "@category", + "title" => "title", + "lang" => "title/@lang", + "lang2" => function ($row) { + return substr($row->get('lang'), 0, 2); + } + ] +); +``` + +### With Namespaces + +```php +$namespace = [ + "atom" => "http://www.w3.org/2005/Atom", + "gd" => "http://schemas.google.com/g/2005" +]; + +$dataset = new \ByJG\AnyDataset\Xml\XmlDataset( + $xml, + "atom:entry", + [ + "id" => "atom:id", + "updated" => "atom:updated", + "name" => "atom:title", + "email" => "gd:email/@address" + ], + $namespace +); +``` \ No newline at end of file From 91ff2d2602aa7a9ff7a17050164f5b0035d41e69 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Fri, 14 Mar 2025 08:20:31 -0500 Subject: [PATCH 04/11] Psalm updates --- src/XmlIterator.php | 8 +++++++- tests/XmlDatasetTest.php | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/XmlIterator.php b/src/XmlIterator.php index 74fb4d3..8ea7ebc 100644 --- a/src/XmlIterator.php +++ b/src/XmlIterator.php @@ -9,6 +9,7 @@ use ByJG\XmlUtil\Exception\XmlUtilException; use ByJG\XmlUtil\XmlNode; use DOMNodeList; +use Override; use ReturnTypeWillChange; class XmlIterator extends GenericIterator @@ -91,21 +92,25 @@ protected function parseXmlNode(): ?RowInterface return $row; } + #[ReturnTypeWillChange] + #[Override] public function key(): int { return $this->current["i"]; } /** - * @throws XmlUtilException + * @return RowInterface|null */ #[ReturnTypeWillChange] + #[Override] public function current(): ?RowInterface { return $this->current["row"]; } #[ReturnTypeWillChange] + #[Override] public function next(): void { $this->current["i"]++; @@ -114,6 +119,7 @@ public function next(): void } #[ReturnTypeWillChange] + #[Override] public function valid(): bool { return ($this->current["i"] < count($this->nodeList)); diff --git a/tests/XmlDatasetTest.php b/tests/XmlDatasetTest.php index 9d448d7..5c40fb7 100644 --- a/tests/XmlDatasetTest.php +++ b/tests/XmlDatasetTest.php @@ -7,6 +7,7 @@ use ByJG\AnyDataset\Core\RowInterface; use ByJG\AnyDataset\Xml\XmlDataset; use ByJG\XmlUtil\Exception\XmlUtilException; +use Override; use PHPUnit\Framework\TestCase; class XmlDatasetTest extends TestCase @@ -41,6 +42,7 @@ class XmlDatasetTest extends TestCase protected $arrTest2 = array(); // Run before each test case + #[Override] public function setUp(): void { $this->arrTest = array(); From f3cb4a713e5956eb58c970af83998bc04ede01f3 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Sat, 22 Mar 2025 20:45:38 -0500 Subject: [PATCH 05/11] Adjusts --- README.md | 8 ++++---- composer.json | 4 ++-- phpunit.xml.dist | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index efc1030..8e84f1b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # AnyDataset-Xml -[![Build Status](https://github.com/byjg/php-anydataset-xml/actions/workflows/phpunit.yml/badge.svg?branch=master)](https://github.com/byjg/php-anydataset-xml/actions/workflows/phpunit.yml) -[![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com) -[![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-anydataset-xml/) -[![GitHub license](https://img.shields.io/github/license/byjg/php-anydataset-xml.svg)](https://opensource.byjg.com/opensource/licensing.html) +[![Build Status](https://github.com/byjg/php-anydataset-xml/actions/workflows/phpunit.yml/badge.svg?branch=master)](https://github.com/byjg/php-anydataset-xml/actions/workflows/phpunit.yml) +[![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com) +[![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-anydataset-xml/) +[![GitHub license](https://img.shields.io/github/license/byjg/php-anydataset-xml.svg)](https://opensource.byjg.com/opensource/licensing.html) [![GitHub release](https://img.shields.io/github/release/byjg/php-anydataset-xml.svg)](https://github.com/byjg/php-anydataset-xml/releases/) XML abstraction dataset for the AnyDataset library. AnyDataset is an agnostic data source abstraction layer in PHP. diff --git a/composer.json b/composer.json index 2c6f88a..5a6aebf 100644 --- a/composer.json +++ b/composer.json @@ -16,11 +16,11 @@ "require": { "php": ">=8.1 <8.5", "ext-dom": "*", - "byjg/anydataset": "^5.1" + "byjg/anydataset": "^6.0" }, "require-dev": { "phpunit/phpunit": "^10.5|^11.5", - "vimeo/psalm": "^6.0" + "vimeo/psalm": "^5.9|^6.2" }, "provide": { "byjg/anydataset-implementation": "1.0" diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 584610f..711a9b0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -15,7 +15,7 @@ and open the template in the editor. displayDetailsOnTestsThatTriggerWarnings="true" displayDetailsOnPhpunitDeprecations="true" stopOnFailure="false" - xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"> <php> <ini name="display_errors" value="On"/> <ini name="display_startup_errors" value="On"/> From cdb95b4cf96a391aa4357b104ba938c0fa76a4f2 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Sat, 22 Mar 2025 20:53:12 -0500 Subject: [PATCH 06/11] Fix Documentation --- .vscode/launch.json | 35 +++++++++++++++++++++++++++++++++++ docs/XPath-Expressions.md | 32 ++++++++++++++++++++++++++++++-- docs/XmlDataset.md | 12 +++++++++++- tests/XmlDatasetTest.php | 20 +++++++++++--------- 4 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a8c1b2a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug current Script in Console", + "type": "php", + "request": "launch", + "program": "${file}", + "cwd": "${fileDirname}", + "port": 9003, + "runtimeArgs": [ + "-dxdebug.start_with_request=yes" + ], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + }, + { + "name": "PHPUnit Debug", + "type": "php", + "request": "launch", + "program": "${workspaceFolder}/vendor/bin/phpunit", + "cwd": "${workspaceFolder}", + "port": 9003, + "runtimeArgs": [ + "-dxdebug.start_with_request=yes" + ], + "env": { + "XDEBUG_MODE": "debug,develop", + "XDEBUG_CONFIG": "client_port=${port}" + } + } + ] +} \ No newline at end of file diff --git a/docs/XPath-Expressions.md b/docs/XPath-Expressions.md index 5acadf3..10ddf6c 100644 --- a/docs/XPath-Expressions.md +++ b/docs/XPath-Expressions.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 2 +--- + # XPath Expressions in AnyDataset-Xml This library uses XPath expressions to select nodes and attributes from XML documents. Understanding XPath is essential for effectively using the AnyDataset-Xml library. @@ -17,6 +21,12 @@ Here are some common XPath expressions used in the library: | `element[n]` | Selects the nth element | `"author[1]"` selects the first author element | | `element[condition]` | Selects elements that satisfy the condition | `"book[@category='WEB']"` selects book elements with category attribute equal to 'WEB' | +## Implementation Details + +- All field names in the resulting data are converted to lowercase +- If an XPath expression doesn't match any nodes, an empty string is returned for that field +- If an XPath expression matches multiple nodes, all values are collected automatically in an array + ## Examples in AnyDataset-Xml ### Basic Element Selection @@ -80,7 +90,7 @@ $colNodes = [ ### Handling Repeated Nodes -When an XPath expression matches multiple nodes, the values are returned as an array: +When an XPath expression matches multiple nodes, the values are automatically collected in an array: ```php // For XML like: @@ -90,6 +100,24 @@ When an XPath expression matches multiple nodes, the values are returned as an a // </book> $colNodes = [ - "authors" => "author" // Will return an array of all author elements + "authors" => "author" // Will automatically return an array of all author values: ["Author 1", "Author 2"] +]; + +// Access as: +$authorArray = $row->get('authors'); +``` + +### Accessing Field Values + +Remember that all field names are converted to lowercase when accessed: + +```php +$colNodes = [ + "Title" => "title", + "AUTHOR" => "author" ]; + +// Access using lowercase: +$title = $row->get('title'); // Not $row->get('Title') +$author = $row->get('author'); // Not $row->get('AUTHOR') ``` \ No newline at end of file diff --git a/docs/XmlDataset.md b/docs/XmlDataset.md index 1cb84c5..31e573b 100644 --- a/docs/XmlDataset.md +++ b/docs/XmlDataset.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 1 +--- + # XmlDataset The `XmlDataset` class is the main entry point for working with XML data in the AnyDataset library. @@ -43,7 +47,7 @@ public function __construct( - **$rowNode**: XPath expression that identifies the nodes to be treated as rows - **$colNode**: Associative array mapping field names to XPath expressions - - Keys: The field names that will be accessible in the iterator + - Keys: The field names that will be accessible in the iterator (will be converted to lowercase) - Values: XPath expressions relative to the row node, or callback functions - **$registerNS**: Optional array of namespace prefixes and URIs @@ -58,6 +62,12 @@ public function getIterator(): GenericIterator Returns an `XmlIterator` instance that can be used to iterate through the XML data. +## Field Values Handling + +- When an XPath expression matches multiple nodes, the values are automatically collected in an array +- All field names are converted to lowercase when accessed through the iterator +- If no nodes match an XPath expression, an empty string is returned for that field + ## Examples ### Basic Example diff --git a/tests/XmlDatasetTest.php b/tests/XmlDatasetTest.php index 5c40fb7..fce54ab 100644 --- a/tests/XmlDatasetTest.php +++ b/tests/XmlDatasetTest.php @@ -61,7 +61,7 @@ public function testcreateXMLDataset() $xmlDataset = new XmlDataset(XmlDatasetTest::XML_OK, $this->rootNode, $this->arrColumn); $xmlIterator = $xmlDataset->getIterator(); - $this->assertTrue($xmlIterator->hasNext()); + $this->assertTrue($xmlIterator->valid()); $this->assertCount(3, $xmlIterator->toArray()); } @@ -71,8 +71,9 @@ public function testnavigateXMLIterator() $xmlIterator = $xmlDataset->getIterator(); $count = 0; - while ($xmlIterator->hasNext()) { - $this->assertSingleRow($xmlIterator->moveNext(), $count++); + while ($xmlIterator->valid()) { + $this->assertSingleRow($xmlIterator->current(), $count++); + $xmlIterator->next(); } $this->assertEquals($count, 3); @@ -128,9 +129,9 @@ public function testrepeatedNodes() $xmlDataset = new XmlDataset($xml, $this->rootNode, array("author" => "author")); $xmlIterator = $xmlDataset->getIterator(); - $this->assertTrue($xmlIterator->hasNext()); + $this->assertTrue($xmlIterator->valid()); - $sr = $xmlIterator->moveNext(); + $sr = $xmlIterator->current(); $authors = $sr->get('author'); $this->assertEquals(2, count($authors)); @@ -152,7 +153,7 @@ public function testAtomXml() <link rel="self" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/my%40gmail.com/full?max-results=20"/> <link rel="next" type="application/atom+xml" href="https://www.google.com/m8/feeds/contacts/my%40gmail.com/full?max-results=20&start-index=21"/> <author> - <name>My Name</name> + <n>My Name</n> <email>My Email</email> </author> <generator version="1.0" uri="http://www.google.com/m8/feeds">Contacts</generator> @@ -197,11 +198,11 @@ public function testAtomXml() $xmlDataset = new XmlDataset($xml, $rootNode, $colNode, $namespace); $xmlIterator = $xmlDataset->getIterator(); - $this->assertTrue($xmlIterator->hasNext()); + $this->assertTrue($xmlIterator->valid()); $rowCur = $xmlIterator->current(); - $row = $xmlIterator->moveNext(); + $row = $rowCur; $this->assertEquals("http://www.google.com/m8/feeds/contacts/my%40gmail.com/base/0", $row->get("id")); $this->assertEquals("2013-10-05T22:16:03.564Z", $row->get("updated")); $this->assertEquals("Person 1", $row->get("name")); @@ -209,7 +210,8 @@ public function testAtomXml() $this->assertEquals("Person 1 - p1@gmail.com", $row->get("item")); $this->assertEquals($row, $rowCur); - $row = $xmlIterator->moveNext(); + $xmlIterator->next(); + $row = $xmlIterator->current(); $this->assertEquals("http://www.google.com/m8/feeds/contacts/my%40gmail.com/base/1", $row->get("id")); $this->assertEquals("2012-07-12T17:19:17.546Z", $row->get("updated")); $this->assertEquals("Person 2", $row->get("name")); From b8bf8f4e7d1e80f79c69ae6529dd4aa7112b75f7 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Sat, 23 Aug 2025 16:56:27 -0400 Subject: [PATCH 07/11] Github action --- .github/workflows/phpunit.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 2743e1c..889ea93 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -34,5 +34,6 @@ jobs: with: folder: php project: ${{ github.event.repository.name }} - secrets: inherit + secrets: + DOC_TOKEN: ${{ secrets.DOC_TOKEN }} From 2f9755fb58b66f34f7336be5520e86140ba2f9e4 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Sat, 23 Aug 2025 17:20:16 -0400 Subject: [PATCH 08/11] Github action --- src/XmlIterator.php | 44 +++++++++++++++++----------------------- tests/XmlDatasetTest.php | 2 +- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/XmlIterator.php b/src/XmlIterator.php index 8ea7ebc..eb492c7 100644 --- a/src/XmlIterator.php +++ b/src/XmlIterator.php @@ -29,12 +29,8 @@ class XmlIterator extends GenericIterator */ private ?array $colNodes; - /** - * Enter description here... - * - * @var array - */ - private array $current; + private ?RowInterface $currentRow = null; + private int $currentIndex = 0; protected array $registerNS; @@ -43,13 +39,6 @@ public function __construct(DOMNodeList $nodeList, array $colNodes, ?array $regi $this->registerNS = $registerNS; $this->nodeList = $nodeList; $this->colNodes = $colNodes; - - $this->current = [ - 'row' => null, - 'i' => 0, - ]; - - $this->parseXmlNode(); } /** @@ -61,33 +50,36 @@ protected function parseXmlNode(): ?RowInterface return null; } - $rowNumber = $this->current["i"]; + $rowNumber = $this->currentIndex; $node = $this->nodeList->item($rowNumber); $row = new RowArray(); $callables = []; + $xmlNode = XmlNode::instance($node); + $lowercaseKeys = array_map('strtolower', array_keys($this->colNodes)); + $this->colNodes = array_combine($lowercaseKeys, array_values($this->colNodes)); foreach ($this->colNodes as $key => $colXpath) { if (is_callable($colXpath)) { $callables[$key] = $colXpath; continue; } - $nodeCol = XmlNode::instance($node)->selectNodes($colXpath, $this->registerNS); + $nodeCol = $xmlNode->selectNodes($colXpath, $this->registerNS); if ($nodeCol->count() == 0) { - $row->set(strtolower($key), ""); + $row->set($key, ""); } else { foreach ($nodeCol as $col) { - $row->set(strtolower($key), $col->nodeValue, append: true); + $row->set($key, $col->nodeValue, append: true); } } } foreach ($callables as $key => $callable) { - $row->set(strtolower($key), $callable($row), append: true); + $row->set($key, $callable($row), append: true); } - $this->current["row"] = $row; + $this->currentRow = $row; return $row; } @@ -96,7 +88,7 @@ protected function parseXmlNode(): ?RowInterface #[Override] public function key(): int { - return $this->current["i"]; + return $this->currentIndex; } /** @@ -106,22 +98,24 @@ public function key(): int #[Override] public function current(): ?RowInterface { - return $this->current["row"]; + if ($this->currentRow === null) { + $this->parseXmlNode(); + } + return $this->currentRow; } #[ReturnTypeWillChange] #[Override] public function next(): void { - $this->current["i"]++; - $this->current["row"] = null; - $this->parseXmlNode(); + $this->currentIndex++; + $this->currentRow = null; } #[ReturnTypeWillChange] #[Override] public function valid(): bool { - return ($this->current["i"] < count($this->nodeList)); + return ($this->currentIndex < count($this->nodeList)); } } diff --git a/tests/XmlDatasetTest.php b/tests/XmlDatasetTest.php index fce54ab..d33e0dd 100644 --- a/tests/XmlDatasetTest.php +++ b/tests/XmlDatasetTest.php @@ -126,7 +126,7 @@ public function testrepeatedNodes() <price>30.00</price> </book></bookstore>'; - $xmlDataset = new XmlDataset($xml, $this->rootNode, array("author" => "author")); + $xmlDataset = new XmlDataset($xml, $this->rootNode, array("AUTHOR" => "author")); // It will be converted to author $xmlIterator = $xmlDataset->getIterator(); $this->assertTrue($xmlIterator->valid()); From 9e02e73a3011207dd9d559900550acdad68e53b9 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Mon, 3 Nov 2025 18:45:11 -0500 Subject: [PATCH 09/11] Updated Documentation --- .run/{Tests.run.xml => PHPUnit.run.xml} | 2 +- .run/PSalm.run.xml | 5 ----- .run/psalm.run.xml | 8 ++++++++ README.md | 2 +- composer.json | 4 ++++ docs/XPath-Expressions.md | 7 ++++++- docs/XmlDataset.md | 3 +++ psalm.xml | 1 + 8 files changed, 24 insertions(+), 8 deletions(-) rename .run/{Tests.run.xml => PHPUnit.run.xml} (68%) delete mode 100644 .run/PSalm.run.xml create mode 100644 .run/psalm.run.xml diff --git a/.run/Tests.run.xml b/.run/PHPUnit.run.xml similarity index 68% rename from .run/Tests.run.xml rename to .run/PHPUnit.run.xml index 591853e..f81c245 100644 --- a/.run/Tests.run.xml +++ b/.run/PHPUnit.run.xml @@ -1,5 +1,5 @@ <component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Tests" type="PHPUnitRunConfigurationType" factoryName="PHPUnit"> + <configuration default="false" name="PHPUnit" type="PHPUnitRunConfigurationType" factoryName="PHPUnit"> <TestRunner configuration_file="$PROJECT_DIR$/phpunit.xml.dist" scope="XML" use_alternative_configuration_file="true" /> <method v="2" /> </configuration> diff --git a/.run/PSalm.run.xml b/.run/PSalm.run.xml deleted file mode 100644 index bd119ce..0000000 --- a/.run/PSalm.run.xml +++ /dev/null @@ -1,5 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="PSalm" type="PhpLocalRunConfigurationType" factoryName="PHP Console" path="$PROJECT_DIR$/vendor/bin/psalm"> - <method v="2" /> - </configuration> -</component> \ No newline at end of file diff --git a/.run/psalm.run.xml b/.run/psalm.run.xml new file mode 100644 index 0000000..d9c1b61 --- /dev/null +++ b/.run/psalm.run.xml @@ -0,0 +1,8 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="psalm" type="ComposerRunConfigurationType" factoryName="Composer Script"> + <option name="commandLineParameters" value="" /> + <option name="pathToComposerJson" value="$PROJECT_DIR$/composer.json" /> + <option name="script" value="psalm" /> + <method v="2" /> + </configuration> +</component> \ No newline at end of file diff --git a/README.md b/README.md index 8e84f1b..deef818 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ $dataset = new \ByJG\AnyDataset\Xml\XmlDataset( $iterator = $dataset->getIterator(); foreach ($iterator as $row) { echo $row->get('category'); // Print COOKING, CHILDREN, WEB - echo $row->get('title'); // Print Everyday Italian, Harry Potter, Learning Xml + echo $row->get('title'); // Print Everyday Italian, Harry Potter, Learning XML echo $row->get('lang'); // Print en-US, de-DE, pt-BR echo $row->get('lang2'); // Print en, de, pt } diff --git a/composer.json b/composer.json index 5a6aebf..5cfb99d 100644 --- a/composer.json +++ b/composer.json @@ -25,5 +25,9 @@ "provide": { "byjg/anydataset-implementation": "1.0" }, + "scripts": { + "test": "vendor/bin/phpunit", + "psalm": "vendor/bin/psalm" + }, "license": "MIT" } diff --git a/docs/XPath-Expressions.md b/docs/XPath-Expressions.md index 10ddf6c..14bd02d 100644 --- a/docs/XPath-Expressions.md +++ b/docs/XPath-Expressions.md @@ -1,5 +1,6 @@ --- sidebar_position: 2 +title: XPath Expressions --- # XPath Expressions in AnyDataset-Xml @@ -23,9 +24,11 @@ Here are some common XPath expressions used in the library: ## Implementation Details +:::warning Key Points - All field names in the resulting data are converted to lowercase - If an XPath expression doesn't match any nodes, an empty string is returned for that field - If an XPath expression matches multiple nodes, all values are collected automatically in an array +::: ## Examples in AnyDataset-Xml @@ -109,7 +112,9 @@ $authorArray = $row->get('authors'); ### Accessing Field Values -Remember that all field names are converted to lowercase when accessed: +:::caution Case Sensitivity +All field names are converted to lowercase when accessed through the iterator. +::: ```php $colNodes = [ diff --git a/docs/XmlDataset.md b/docs/XmlDataset.md index 31e573b..bb80ac5 100644 --- a/docs/XmlDataset.md +++ b/docs/XmlDataset.md @@ -1,5 +1,6 @@ --- sidebar_position: 1 +title: XmlDataset --- # XmlDataset @@ -64,9 +65,11 @@ Returns an `XmlIterator` instance that can be used to iterate through the XML da ## Field Values Handling +:::info Important Behavior - When an XPath expression matches multiple nodes, the values are automatically collected in an array - All field names are converted to lowercase when accessed through the iterator - If no nodes match an XPath expression, an empty string is returned for that field +::: ## Examples diff --git a/psalm.xml b/psalm.xml index ebabb1a..b208114 100644 --- a/psalm.xml +++ b/psalm.xml @@ -4,6 +4,7 @@ resolveFromConfigFile="true" findUnusedBaselineEntry="true" findUnusedCode="false" + cacheDirectory="/tmp/psalm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" From 27757488e44bb15e80204597533f442e524921d1 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Tue, 18 Nov 2025 18:40:40 -0500 Subject: [PATCH 10/11] Github action adjustments: update checkout action and container options --- .github/workflows/phpunit.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 889ea93..531c803 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -12,7 +12,9 @@ on: jobs: Build: runs-on: 'ubuntu-latest' - container: 'byjg/php:${{ matrix.php-version }}-cli' + container: + image: 'byjg/php:${{ matrix.php-version }}-cli' + options: --user root --privileged strategy: matrix: php-version: @@ -22,7 +24,7 @@ jobs: - "8.1" steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: composer install - run: ./vendor/bin/psalm - run: ./vendor/bin/phpunit From c3f20a67900498e8e3e3427d6c317510212eea38 Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes <joao@byjg.com.br> Date: Sat, 22 Nov 2025 16:06:21 -0500 Subject: [PATCH 11/11] Refactored type hints, adjusted Psalm error level, improved GitHub Actions workflow, updated composer.json requirements, and added sponsor badge to README. --- .github/workflows/phpunit.yml | 36 +++++++++++++++++++++++++++++++---- README.md | 1 + composer.json | 6 +++--- psalm.xml | 3 +-- src/XmlDataset.php | 2 +- src/XmlIterator.php | 12 ++++++++---- 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 531c803..fc5f29a 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -18,16 +18,44 @@ jobs: strategy: matrix: php-version: + - "8.5" - "8.4" - "8.3" - - "8.2" - - "8.1" steps: - uses: actions/checkout@v5 - run: composer install - - run: ./vendor/bin/psalm - - run: ./vendor/bin/phpunit + - run: composer test + + Psalm: + name: Psalm Static Analyzer + runs-on: ubuntu-latest + permissions: + # for github/codeql-action/upload-sarif to upload SARIF results + security-events: write + container: + image: byjg/php:8.4-cli + options: --user root --privileged + + steps: + - name: Git checkout + uses: actions/checkout@v4 + + - name: Composer + run: composer install + + - name: Psalm + # Note: Ignoring error code 2, which just signals that some + # flaws were found, not that Psalm itself failed to run. + run: ./vendor/bin/psalm + --show-info=true + --report=psalm-results.sarif || [ $? = 2 ] + + - name: Upload Analysis results to GitHub + uses: github/codeql-action/upload-sarif@v4 + if: github.ref == 'refs/heads/master' + with: + sarif_file: psalm-results.sarif Documentation: if: github.ref == 'refs/heads/master' diff --git a/README.md b/README.md index deef818..654a123 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # AnyDataset-Xml +[![Sponsor](https://img.shields.io/badge/Sponsor-%23ea4aaa?logo=githubsponsors&logoColor=white&labelColor=0d1117)](https://github.com/sponsors/byjg) [![Build Status](https://github.com/byjg/php-anydataset-xml/actions/workflows/phpunit.yml/badge.svg?branch=master)](https://github.com/byjg/php-anydataset-xml/actions/workflows/phpunit.yml) [![Opensource ByJG](https://img.shields.io/badge/opensource-byjg-success.svg)](http://opensource.byjg.com) [![GitHub source](https://img.shields.io/badge/Github-source-informational?logo=github)](https://github.com/byjg/php-anydataset-xml/) diff --git a/composer.json b/composer.json index 5cfb99d..734c410 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "byjg/anydataset-xml", - "description": "Anydataset Xml abstraction. Anydataset is an agnostic data source abstraction layer in PHP.", + "description": "XML abstraction dataset for the AnyDataset library. AnyDataset is an agnostic data source abstraction layer in PHP.", "autoload": { "psr-4": { "ByJG\\AnyDataset\\Xml\\": "src/" @@ -14,13 +14,13 @@ "prefer-stable": true, "minimum-stability": "dev", "require": { - "php": ">=8.1 <8.5", + "php": ">=8.3 <8.6", "ext-dom": "*", "byjg/anydataset": "^6.0" }, "require-dev": { "phpunit/phpunit": "^10.5|^11.5", - "vimeo/psalm": "^5.9|^6.2" + "vimeo/psalm": "^5.9|^6.13" }, "provide": { "byjg/anydataset-implementation": "1.0" diff --git a/psalm.xml b/psalm.xml index b208114..037de8b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ <?xml version="1.0"?> <psalm - errorLevel="4" + errorLevel="3" resolveFromConfigFile="true" findUnusedBaselineEntry="true" findUnusedCode="false" @@ -11,7 +11,6 @@ > <projectFiles> <directory name="src" /> - <directory name="tests" /> <ignoreFiles> <directory name="vendor" /> </ignoreFiles> diff --git a/src/XmlDataset.php b/src/XmlDataset.php index 69e4d63..858d465 100644 --- a/src/XmlDataset.php +++ b/src/XmlDataset.php @@ -25,7 +25,7 @@ class XmlDataset * * @var string[] */ - private ?array $colNodes; + private array $colNodes; /** * @var XmlDocument diff --git a/src/XmlIterator.php b/src/XmlIterator.php index eb492c7..4b5f24e 100644 --- a/src/XmlIterator.php +++ b/src/XmlIterator.php @@ -18,16 +18,16 @@ class XmlIterator extends GenericIterator /** * Enter description here... * - * @var DOMNodeList|null + * @var DOMNodeList */ - private ?DOMNodeList $nodeList; + private DOMNodeList $nodeList; /** * Enter description here... * * @var string[] */ - private ?array $colNodes; + private array $colNodes; private ?RowInterface $currentRow = null; private int $currentIndex = 0; @@ -36,7 +36,7 @@ class XmlIterator extends GenericIterator public function __construct(DOMNodeList $nodeList, array $colNodes, ?array $registerNS = null) { - $this->registerNS = $registerNS; + $this->registerNS = $registerNS ?? []; $this->nodeList = $nodeList; $this->colNodes = $colNodes; } @@ -53,6 +53,10 @@ protected function parseXmlNode(): ?RowInterface $rowNumber = $this->currentIndex; $node = $this->nodeList->item($rowNumber); + if ($node === null) { + return null; + } + $row = new RowArray(); $callables = [];