Skip to content

Commit

Permalink
Add xpath method
Browse files Browse the repository at this point in the history
  • Loading branch information
tomas-novotny committed Oct 17, 2023
1 parent 66e368a commit 53c0718
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 57 deletions.
12 changes: 6 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased](https://github.com/inspirum/xml-php/compare/v3.0.0...master)


## [v3.0.0 (2023-10-16)](https://github.com/inspirum/xml-php/compare/v2.3.1...v3.0.0)
## [v3.0.0 (2023-10-17)](https://github.com/inspirum/xml-php/compare/v2.3.1...v3.0.0)
### Changed
- Support only **PHP 8.2+**
- Make [`Config`](./src/Formatter/Config.php) interface instead of readonly class

### Added
- [`Node`](./src/Builder/Node.php) implements `\Inspirum\Arrayable\Model` interface
- Added [`Node::xpath`](./src/Builder/Node.php) method using internally [`DOMXPath`](https://www.php.net/manual/en/class.domxpath.php)
- Added option cast node to flatten (one-dimensional) array
- Added [`DefaultConfig`](./src/Formatter/DefaultConfig.php) config class
- Added [`FullResponseConfig`](./src/Formatter/FullResponseConfig.php) config class
- Added [`FlattenConfig`](./src/Formatter/FlattenConfig.php) config class
- Added [`DefaultConfig`](./src/Formatter/DefaultConfig.php) config class
- Added [`FullResponseConfig`](./src/Formatter/FullResponseConfig.php) config class
- Added [`FlattenConfig`](./src/Formatter/FlattenConfig.php) config class


## [v2.3.1 (2023-08-08)](https://github.com/inspirum/xml-php/compare/v2.3.0...v2.3.1)
Expand Down Expand Up @@ -56,7 +56,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- [`Inspirum\XML\Builder\Node`](./src/Builder/Node.php)
- [`Inspirum\XML\Reader\ReaderFactory`](./src/Reader/ReaderFactory.php)
- [`Inspirum\XML\Reader\Reader`](./src/Reader/Reader.php)
- Factories for [XML builder](./src/Builder/Document.php) and [XML Reader](./src/Reader/Reader.php)
- Factories for [**XML builder**](./src/Builder/Document.php) and [**XML Reader**](./src/Reader/Reader.php)
- Publicly available [`Formatter::nodeToArray`](./src/Formatter/Formatter.php) method


Expand Down
71 changes: 65 additions & 6 deletions src/Builder/BaseNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use DOMElement;
use DOMException;
use DOMNode;
use DOMXpath;
use Inspirum\XML\Exception\Handler;
use Inspirum\XML\Formatter\Config;
use Inspirum\XML\Formatter\DefaultConfig;
Expand Down Expand Up @@ -63,6 +64,11 @@ public function addTextElement(string $name, mixed $value, array $attributes = [
return $this->createNode($element);
}

public function addElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): Node
{
return $this->addTextElement($node->nodeName, $node->textContent, $this->getAttributesFromNode($node), $forcedEscape, $withNamespaces);
}

public function append(Node $element): void
{
if ($element->getNode() !== null) {
Expand All @@ -88,6 +94,11 @@ public function createTextElement(string $name, mixed $value, array $attributes
return $this->createNode($element);
}

public function createElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): Node
{
return $this->createTextElement($node->nodeName, $node->textContent, $this->getAttributesFromNode($node), $forcedEscape, $withNamespaces);
}

public function addXMLData(string $content): ?Node
{
if ($content === '') {
Expand Down Expand Up @@ -177,8 +188,6 @@ private function setDOMElementValue(DOMElement $element, mixed $value, bool $for

/**
* Create new DOM attribute with namespace if exists
*
* @return void
*/
private function setDOMAttributeNS(DOMElement $element, string $name, mixed $value, bool $withNamespaces): void
{
Expand All @@ -199,7 +208,7 @@ private function setDOMAttributeNS(DOMElement $element, string $name, mixed $val
*/
private function appendChild(DOMNode $element): void
{
$node = $this->node ?? $this->document;
$node = $this->resolveNode();
$node->appendChild($element);
}

Expand All @@ -221,7 +230,7 @@ private function registerNamespaces(array $attributes): void

public function getTextContent(): ?string
{
$node = $this->node ?? $this->document;
$node = $this->resolveNode();

return $node->textContent;
}
Expand All @@ -231,7 +240,18 @@ public function getTextContent(): ?string
*/
public function getAttributes(bool $autoCast = false): array
{
$node = $this->node ?? $this->document;
$node = $this->resolveNode();

return $this->getAttributesFromNode($node, $autoCast);
}

/**
* Get attributes from \DOMNode
*
* @return ($autoCast is true ? array<string,mixed> : array<string,string>)
*/
private function getAttributesFromNode(DOMNode $node, bool $autoCast = false): array
{
$attributes = [];

if ($node->hasAttributes()) {
Expand All @@ -245,6 +265,45 @@ public function getAttributes(bool $autoCast = false): array
return $attributes;
}

/**
* @inheritDoc
*/
public function xpath(string $expression): ?array
{
$xpath = new DOMXpath($this->toDOMDocument());

$nodes = $xpath->query($expression);
if ($nodes === false) {
return null;
}

$results = [];
foreach ($nodes as $node) {
$results[] = $this->createElementFromNode($node);
}

return $results;
}

/**
* Copy current node to new \DOMDocument
*/
private function toDOMDocument(): DOMDocument
{
$doc = new DOMDocument($this->document->xmlVersion ?? '1.0', $this->document->encoding ?? 'UTF-8');
$doc->loadXML($this->toString());

return $doc;
}

/**
* Resolve current node
*/
private function resolveNode(): DOMNode
{
return $this->node ?? $this->document;
}

public function toString(bool $formatOutput = false): string
{
return Handler::withErrorHandlerForDOMDocument(function () use ($formatOutput): string {
Expand All @@ -269,7 +328,7 @@ public function __toString(): string
*/
public function toArray(?Config $config = null): array
{
$result = Formatter::nodeToArray($this->node ?? $this->document, $config ?? new DefaultConfig());
$result = Formatter::nodeToArray($this->resolveNode(), $config ?? new DefaultConfig());

if (is_array($result) === false) {
$result = [$result];
Expand Down
19 changes: 19 additions & 0 deletions src/Builder/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ public function addElement(string $name, array $attributes = [], bool $withNames
*/
public function addTextElement(string $name, mixed $value, array $attributes = [], bool $forcedEscape = false, bool $withNamespaces = true): Node;

/**
* Add element from \DOMNode
*
* @throws \DOMException
*/
public function addElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): Node;

/**
* Append node to parent node.
*/
Expand All @@ -55,6 +62,13 @@ public function createElement(string $name, array $attributes = [], bool $withNa
*/
public function createTextElement(string $name, mixed $value, array $attributes = [], bool $forcedEscape = false, bool $withNamespaces = true): Node;

/**
* Create new (unconnected) element from \DOMNode
*
* @throws \DOMException
*/
public function createElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): Node;

/**
* Add XML data
*/
Expand All @@ -72,6 +86,11 @@ public function getTextContent(): ?string;
*/
public function getAttributes(bool $autoCast = false): array;

/**
* @return list<\Inspirum\XML\Builder\Node>|null
*/
public function xpath(string $expression): ?array;

/**
* Get connected \DOMDocument
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Formatter/BaseConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
private const NODES = '@nodes';
private const VALUE = '@value';
private const FLATTEN_NODES = '/';
private const FLATTEN_ATTRIBUTES = '#';
private const FLATTEN_ATTRIBUTES = '@';

/**
* @param list<string>|true $alwaysArray
Expand Down
40 changes: 20 additions & 20 deletions tests/Builder/NodeToArrayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -516,13 +516,13 @@ public function testWithFlattenConfig(): void

self::assertSame(
[
'a/b/c1#test' => ['true', 'cc'],
'a/b/c1#a' => '1.4',
'a/b/c1@test' => ['true', 'cc'],
'a/b/c1@a' => '1.4',
'a/b/c1' => ['1', '0'],
'a/b/c2' => 'false',
'a/b/c3' => 'test',
'a/b/c1#b' => '2',
'a#version' => '1.0',
'a/b/c1@b' => '2',
'a@version' => '1.0',
],
$xml->toArray($config),
);
Expand All @@ -546,13 +546,13 @@ public function testWithFlattenAutocastConfig(): void

self::assertSame(
[
'a/b/c1#test' => [true, 'cc'],
'a/b/c1#a' => 1.4,
'a/b/c1@test' => [true, 'cc'],
'a/b/c1@a' => 1.4,
'a/b/c1' => [1, 0],
'a/b/c2' => false,
'a/b/c3' => 'test',
'a/b/c1#b' => 2,
'a#version' => 1.0,
'a/b/c1@b' => 2,
'a@version' => 1.0,
],
$xml->toArray($config),
);
Expand All @@ -576,13 +576,13 @@ public function testWithFlattenAlwaysArrayConfig(): void

self::assertSame(
[
'a/b/c1#test' => ['true', 'cc'],
'a/b/c1#a' => ['1.4'],
'a/b/c1@test' => ['true', 'cc'],
'a/b/c1@a' => ['1.4'],
'a/b/c1' => ['1', '0'],
'a/b/c2' => ['false'],
'a/b/c3' => ['test'],
'a/b/c1#b' => ['2'],
'a#version' => ['1.0'],
'a/b/c1@b' => ['2'],
'a@version' => ['1.0'],
],
$xml->toArray($config),
);
Expand All @@ -602,17 +602,17 @@ public function testWithFlattenCustomConfig(): void
$bE = $aE->addElement('b');
$bE->addTextElement('c1', 0, ['test' => 'cc', 'b' => 2]);

$config = new FlattenConfig(flattenNodes: '|', flattenAttributes: '@');
$config = new FlattenConfig(flattenNodes: '.', flattenAttributes: '#');

self::assertSame(
[
'a|b|c1@test' => ['true', 'cc'],
'a|b|c1@a' => '1.4',
'a|b|c1' => ['1', '0'],
'a|b|c2' => 'false',
'a|b|c3' => 'test',
'a|b|c1@b' => '2',
'a@version' => '1.0',
'a.b.c1#test' => ['true', 'cc'],
'a.b.c1#a' => '1.4',
'a.b.c1' => ['1', '0'],
'a.b.c2' => 'false',
'a.b.c3' => 'test',
'a.b.c1#b' => '2',
'a#version' => '1.0',
],
$xml->toArray($config),
);
Expand Down
Loading

0 comments on commit 53c0718

Please sign in to comment.