Skip to content

Commit 2406405

Browse files
committed
Fix xpath for parent nodes
1 parent 063aaa5 commit 2406405

File tree

6 files changed

+108
-35
lines changed

6 files changed

+108
-35
lines changed

composer-require-checker.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"symbol-whitelist" : [
3+
"DOMXpath"
4+
]
5+
}

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
"phpstan": "./vendor/bin/phpstan analyse -c phpstan.neon",
9494
"phpcbf": "./vendor/bin/phpcbf -p --extensions=php",
9595
"infection": "./tools/infection",
96-
"composerRequireChecker": "./tools/composer-require-checker check",
96+
"composerRequireChecker": "./tools/composer-require-checker check --config-file=$PWD/composer-require-checker.json",
9797
"composerUnused": "./tools/composer-unused"
9898
}
9999
}

phive.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phive xmlns="https://phar.io/phive">
3-
<phar name="composer-unused" version="^0.8.9" location="./tools/composer-unused" copy="false" installed="0.8.9"/>
4-
<phar name="composer-require-checker" version="^4.6.0" location="./tools/composer-require-checker" copy="false" installed="4.6.0"/>
3+
<phar name="composer-unused" version="^0.8.9" location="./tools/composer-unused" copy="false" installed="0.8.10"/>
4+
<phar name="composer-require-checker" version="^4.6.0" location="./tools/composer-require-checker" copy="false" installed="4.7.1"/>
55
<phar name="infection" version="^0.26.21" location="./tools/infection" copy="false" installed="0.26.21"/>
66
</phive>

src/Builder/BaseNode.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use DOMElement;
1010
use DOMException;
1111
use DOMNode;
12+
use DOMText;
1213
use DOMXpath;
1314
use Inspirum\XML\Exception\Handler;
1415
use Inspirum\XML\Formatter\Config;
@@ -66,7 +67,11 @@ public function addTextElement(string $name, mixed $value, array $attributes = [
6667

6768
public function addElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): Node
6869
{
69-
return $this->addTextElement($node->nodeName, $node->textContent, $this->getAttributesFromNode($node), $forcedEscape, $withNamespaces);
70+
$element = $this->createFullDOMElementFromNode($node, $forcedEscape, $withNamespaces);
71+
72+
$this->appendChild($element);
73+
74+
return $this->createNode($element);
7075
}
7176

7277
public function append(Node $element): void
@@ -96,7 +101,9 @@ public function createTextElement(string $name, mixed $value, array $attributes
96101

97102
public function createElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): Node
98103
{
99-
return $this->createTextElement($node->nodeName, $node->textContent, $this->getAttributesFromNode($node), $forcedEscape, $withNamespaces);
104+
$element = $this->createFullDOMElementFromNode($node, $forcedEscape, $withNamespaces);
105+
106+
return $this->createNode($element);
100107
}
101108

102109
public function addXMLData(string $content): ?Node
@@ -134,6 +141,30 @@ private function createFullDOMElement(string $name, mixed $value, array $attribu
134141
return $element;
135142
}
136143

144+
private function createFullDOMElementFromNode(DOMNode $node, bool $forcedEscape = false, bool $withNamespaces = true): DOMElement
145+
{
146+
$value = null;
147+
$childElements = [];
148+
149+
/** @var \DOMNode $child */
150+
foreach ($node->childNodes as $child) {
151+
if ($child instanceof DOMText) {
152+
$value = $child->textContent;
153+
continue;
154+
}
155+
156+
$childElements[] = $this->createFullDOMElementFromNode($child, $forcedEscape, $withNamespaces);
157+
}
158+
159+
$element = $this->createFullDOMElement($node->nodeName, $value, $this->getAttributesFromNode($node), $forcedEscape, $withNamespaces);
160+
161+
foreach ($childElements as $childElement) {
162+
$element->appendChild($childElement);
163+
}
164+
165+
return $element;
166+
}
167+
137168
/**
138169
* Create new DOM fragment element
139170
*/

tests/Reader/DefaultReaderTest.php

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
use function is_array;
2222
use function is_numeric;
2323
use function is_string;
24+
use function preg_replace;
2425
use function simplexml_load_string;
26+
use function trim;
2527

2628
class DefaultReaderTest extends BaseTestCase
2729
{
@@ -132,7 +134,7 @@ public function testReadAllFile(): void
132134
$node = $reader->nextNode('feed');
133135

134136
self::assertSame(
135-
'<feed version="2.0"><updated>2020-08-25T13:53:38+00:00</updated><title>Test feed</title><errors id="1"/><errors2 id="2"/><items><item i="0"><id uuid="12345">1</id><name price="10.1">Test 1</name></item><item i="1"><id uuid="61648">2</id><name price="5">Test 2</name></item><item i="2"><id>3</id><name price="500">Test 3</name></item><item i="3"><id uuid="894654">4</id><name>Test 4</name></item><item i="4"><id uuid="78954">5</id><name price="0.99">Test 5</name></item></items></feed>',
137+
'<feed version="2.0"><updated>2020-08-25T13:53:38+00:00</updated><title>Test feed</title><errors id="1"/><errors2 id="2"/><items><item i="0"><id uuid="12345">1</id><name price="10.1">Test 1</name></item><item i="1"><id uuid="61648">2</id><name price="5">Test 2</name></item><item i="2"><id>3</id><name price="500"><![CDATA[Test 3 & 9]]></name></item><item i="3"><id uuid="894654">4</id><name>Test 4</name></item><item i="4"><id uuid="78954">5</id><name price="0.99">Test 5</name></item></items></feed>',
136138
$node?->toString(),
137139
);
138140
}
@@ -172,7 +174,7 @@ public function testNextNodes(): void
172174
[
173175
'<item i="0"><id uuid="12345">1</id><name price="10.1">Test 1</name></item>',
174176
'<item i="1"><id uuid="61648">2</id><name price="5">Test 2</name></item>',
175-
'<item i="2"><id>3</id><name price="500">Test 3</name></item>',
177+
'<item i="2"><id>3</id><name price="500"><![CDATA[Test 3 & 9]]></name></item>',
176178
'<item i="3"><id uuid="894654">4</id><name>Test 4</name></item>',
177179
],
178180
$output,
@@ -234,7 +236,7 @@ public function testIterateNodes(): void
234236
[
235237
'<item i="0"><id uuid="12345">1</id><name price="10.1">Test 1</name></item>',
236238
'<item i="1"><id uuid="61648">2</id><name price="5">Test 2</name></item>',
237-
'<item i="2"><id>3</id><name price="500">Test 3</name></item>',
239+
'<item i="2"><id>3</id><name price="500"><![CDATA[Test 3 & 9]]></name></item>',
238240
'<item i="3"><id uuid="894654">4</id><name>Test 4</name></item>',
239241
'<item i="4"><id uuid="78954">5</id><name price="0.99">Test 5</name></item>',
240242
],
@@ -359,14 +361,16 @@ public function testIteratePathMultipleNamespaces(): void
359361

360362
/**
361363
* @param array<list<string>|string> $expected
364+
* @param array<list<string>|string>|null $expectedOverride
362365
*/
363366
#[DataProvider('provideIterateXpath')]
364-
public function testIterateWithSimpleLoadString(string $file, bool $withNamespaces, string $path, array $expected): void
367+
public function testIterateWithSimpleLoadString(string $file, bool $withNamespaces, string $path, array $expected, ?array $expectedOverride = null): void
365368
{
366369
$reader = $this->newReader(self::getTestFilePath($file));
367370

368371
foreach ($reader->iterateNode('item', $withNamespaces) as $i => $item) {
369-
$elements = [];
372+
$elements = [];
373+
$expectedItem = $expectedOverride[$i] ?? $expected[$i];
370374

371375
try {
372376
self::withErrorHandler(static function () use ($item, $path, &$elements): void {
@@ -382,16 +386,16 @@ public function testIterateWithSimpleLoadString(string $file, bool $withNamespac
382386
throw new Exception('xpath: error');
383387
}
384388
} catch (Throwable $exception) {
385-
$expectedMessage = $expected[$i];
389+
$expectedMessage = $expectedItem;
386390
if (is_string($expectedMessage)) {
387-
self::assertMatchesRegularExpression($expectedMessage, $exception->getMessage());
391+
self::assertSame($expectedMessage, $exception->getMessage());
388392
continue;
389393
}
390394

391395
throw $exception;
392396
}
393397

394-
self::assertSame($expected[$i], array_map(static fn(SimpleXMLElement $element): string => (string) $element, $elements));
398+
self::assertSame($expectedItem, array_map(static fn(SimpleXMLElement $element): string => trim((string) preg_replace('/<\?xml[^>]*\?>/', '', (string) $element->asXML(), 1)), $elements));
395399
}
396400
}
397401

@@ -416,14 +420,14 @@ public function testIterateWithXpath(string $file, bool $withNamespaces, string
416420
} catch (Throwable $exception) {
417421
$expectedMessage = $expected[$i];
418422
if (is_string($expectedMessage)) {
419-
self::assertMatchesRegularExpression($expectedMessage, $exception->getMessage());
423+
self::assertSame($expectedMessage, $exception->getMessage());
420424
continue;
421425
}
422426

423427
throw $exception;
424428
}
425429

426-
self::assertSame($expected[$i], array_map(static fn(Node $element): ?string => $element->getTextContent(), $elements));
430+
self::assertSame($expected[$i], array_map(static fn(Node $element): string => $element->toString(), $elements));
427431
}
428432
}
429433

@@ -437,11 +441,24 @@ public static function provideIterateXpath(): iterable
437441
'withNamespaces' => false,
438442
'path' => '/item/id',
439443
'expected' => [
440-
['1'],
441-
['2'],
442-
['3'],
443-
['4'],
444-
['5'],
444+
['<id uuid="12345">1</id>'],
445+
['<id uuid="61648">2</id>'],
446+
['<id>3</id>'],
447+
['<id uuid="894654">4</id>'],
448+
['<id uuid="78954">5</id>'],
449+
],
450+
];
451+
452+
yield [
453+
'file' => 'sample_04.xml',
454+
'withNamespaces' => false,
455+
'path' => '/item',
456+
'expected' => [
457+
['<item i="0"><id uuid="12345">1</id><name price="10.1">Test 1</name></item>'],
458+
['<item i="1"><id uuid="61648">2</id><name price="5">Test 2</name></item>'],
459+
['<item i="2"><id>3</id><name price="500"><![CDATA[Test 3 & 9]]></name></item>'],
460+
['<item i="3"><id uuid="894654">4</id><name>Test 4</name></item>'],
461+
['<item i="4"><id uuid="78954">5</id><name price="0.99">Test 5</name></item>'],
445462
],
446463
];
447464

@@ -450,9 +467,9 @@ public static function provideIterateXpath(): iterable
450467
'withNamespaces' => false,
451468
'path' => '/item/name[@price>10]',
452469
'expected' => [
453-
['Test 1'],
470+
['<name price="10.1">Test 1</name>'],
454471
[],
455-
['Test 3'],
472+
['<name price="500"><![CDATA[Test 3 & 9]]></name>'],
456473
[],
457474
[],
458475
],
@@ -463,9 +480,14 @@ public static function provideIterateXpath(): iterable
463480
'withNamespaces' => false,
464481
'path' => '/item/id',
465482
'expected' => [
466-
['1/L1'],
467-
'/(simplexml_load_string\(\): namespace error |DOMDocument::loadXML\(\)): Namespace prefix h for test on id is not defined( in Entity)?/',
468-
'/(simplexml_load_string\(\): namespace error |DOMDocument::loadXML\(\)): Namespace prefix g on id is not defined/',
483+
['<id>1/L1</id>'],
484+
'DOMDocument::loadXML(): Namespace prefix h for test on id is not defined in Entity, line: 1',
485+
'DOMDocument::loadXML(): Namespace prefix g on id is not defined in Entity, line: 1',
486+
],
487+
'expectedOverride' => [
488+
['<id>1/L1</id>'],
489+
'simplexml_load_string(): namespace error : Namespace prefix h for test on id is not defined',
490+
'simplexml_load_string(): namespace error : Namespace prefix g on id is not defined',
469491
],
470492
];
471493

@@ -474,7 +496,7 @@ public static function provideIterateXpath(): iterable
474496
'withNamespaces' => true,
475497
'path' => '/item/id',
476498
'expected' => [
477-
['1/L1'],
499+
['<id>1/L1</id>'],
478500
[],
479501
[],
480502
],
@@ -485,9 +507,14 @@ public static function provideIterateXpath(): iterable
485507
'withNamespaces' => false,
486508
'path' => '/item/g:id',
487509
'expected' => [
488-
'/(SimpleXMLElement::xpath\(\)|DOMXPath::query\(\))\: Undefined namespace prefix/',
489-
'/(simplexml_load_string\(\): namespace error |DOMDocument::loadXML\(\)): Namespace prefix h for test on id is not defined( in Entity)?/',
490-
'/(simplexml_load_string\(\): namespace error |DOMDocument::loadXML\(\)): Namespace prefix g on id is not defined/',
510+
'DOMXPath::query(): Undefined namespace prefix',
511+
'DOMDocument::loadXML(): Namespace prefix h for test on id is not defined in Entity, line: 1',
512+
'DOMDocument::loadXML(): Namespace prefix g on id is not defined in Entity, line: 1',
513+
],
514+
'expectedOverride' => [
515+
'SimpleXMLElement::xpath(): Undefined namespace prefix',
516+
'simplexml_load_string(): namespace error : Namespace prefix h for test on id is not defined',
517+
'simplexml_load_string(): namespace error : Namespace prefix g on id is not defined',
491518
],
492519
];
493520

@@ -496,9 +523,14 @@ public static function provideIterateXpath(): iterable
496523
'withNamespaces' => true,
497524
'path' => '/item/g:id',
498525
'expected' => [
499-
'/(SimpleXMLElement::xpath\(\)|DOMXPath::query\(\))\: Undefined namespace prefix/',
526+
'DOMXPath::query(): Undefined namespace prefix',
500527
[],
501-
['1/L3'],
528+
['<g:id xmlns:g="http://base.google.com/ns/1.0">1/L3</g:id>'],
529+
],
530+
'expectedOverride' => [
531+
'SimpleXMLElement::xpath(): Undefined namespace prefix',
532+
[],
533+
['<g:id>1/L3</g:id>'],
502534
],
503535
];
504536

@@ -507,8 +539,13 @@ public static function provideIterateXpath(): iterable
507539
'withNamespaces' => true,
508540
'path' => '/item/data/g:title',
509541
'expected' => [
510-
'/(SimpleXMLElement::xpath\(\)|DOMXPath::query\(\))\: Undefined namespace prefix/',
511-
['Title 2'],
542+
'DOMXPath::query(): Undefined namespace prefix',
543+
['<g:title xmlns:g="http://base.google.com/ns/1.0" test="bb">Title 2</g:title>'],
544+
[],
545+
],
546+
'expectedOverride' => [
547+
'SimpleXMLElement::xpath(): Undefined namespace prefix',
548+
['<g:title test="bb">Title 2</g:title>'],
512549
[],
513550
],
514551
];

tests/data/sample_04.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
<items>
88
<item i="0">
99
<id uuid="12345">1</id>
10-
<name price="10.1">Test 1</name>
10+
<name price="10.1"><![CDATA[Test 1]]></name>
1111
</item>
1212
<item i="1">
1313
<id uuid="61648">2</id>
1414
<name price="5">Test 2</name>
1515
</item>
1616
<item i="2">
1717
<id>3</id>
18-
<name price="500">Test 3</name>
18+
<name price="500"><![CDATA[Test 3 & 9]]></name>
1919
</item>
2020
<item i="3">
2121
<id uuid="894654">4</id>

0 commit comments

Comments
 (0)