Skip to content

Commit 27fe9fd

Browse files
authored
Merge pull request #103 from veewee/feature/promote-namespaces
Add promote_namespaces function
2 parents 9a77451 + cb54ad3 commit 27fe9fd

6 files changed

Lines changed: 385 additions & 0 deletions

File tree

docs/dom.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,22 @@ Document::fromUnsafeDocument(
545545
);
546546
```
547547

548+
#### promote_namespaces
549+
550+
This configurator moves all prefixed namespace declarations from child elements to the document root element.
551+
Unlike `optimize_namespaces`, it preserves the original prefix names.
552+
This is useful when dealing with servers that require all namespace declarations on the root element.
553+
554+
```php
555+
use VeeWee\Xml\Dom\Document;
556+
use function VeeWee\Xml\Dom\Configurator\promote_namespaces;
557+
558+
Document::fromUnsafeDocument(
559+
$document,
560+
promote_namespaces()
561+
);
562+
```
563+
548564
#### pretty_print
549565

550566
Makes the output of the DOM document human-readable.
@@ -1008,6 +1024,23 @@ $doc->manipulate(
10081024
);
10091025
```
10101026

1027+
#### promote_namespaces
1028+
1029+
Moves all prefixed namespace declarations from child elements to the document root element, preserving the original prefix names.
1030+
1031+
```php
1032+
use \Dom\XMLDocument;
1033+
use VeeWee\Xml\Dom\Document;
1034+
use function VeeWee\Xml\Dom\Manipulator\Document\promote_namespaces;
1035+
1036+
$doc = Document::empty();
1037+
$doc->manipulate(
1038+
static function (XMLDocument $document): void {
1039+
promote_namespaces($document);
1040+
}
1041+
);
1042+
```
1043+
10111044
### Element
10121045

10131046
Element specific manipulators operate on `Dom\Element` instances.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace VeeWee\Xml\Dom\Configurator;
6+
7+
use Closure;
8+
use Dom\XMLDocument;
9+
use function VeeWee\Xml\Dom\Manipulator\Document\promote_namespaces as promote_namespaces_manipulator;
10+
11+
/**
12+
* @return Closure(XMLDocument): XMLDocument
13+
*/
14+
function promote_namespaces(): Closure
15+
{
16+
return static function (XMLDocument $document): XMLDocument {
17+
promote_namespaces_manipulator($document);
18+
19+
return $document;
20+
};
21+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace VeeWee\Xml\Dom\Manipulator\Document;
6+
7+
use Dom\Attr;
8+
use Dom\XMLDocument;
9+
use VeeWee\Xml\Exception\RuntimeException;
10+
use function Psl\Dict\pull;
11+
use function VeeWee\Xml\Dom\Builder\xmlns_attribute;
12+
use function VeeWee\Xml\Dom\Locator\Attribute\xmlns_attributes_list;
13+
use function VeeWee\Xml\Dom\Locator\document_element;
14+
use function VeeWee\Xml\Dom\Manipulator\Node\remove_namespace;
15+
16+
/**
17+
* @throws RuntimeException
18+
*/
19+
function promote_namespaces(XMLDocument $document): void
20+
{
21+
$documentElement = document_element()($document);
22+
23+
/** @var array<string, string> $promoted prefix => URI */
24+
$promoted = pull(
25+
xmlns_attributes_list($documentElement)->filter(static fn (Attr $attr): bool => $attr->prefix !== null),
26+
static fn (Attr $attr): string => $attr->value,
27+
static fn (Attr $attr): string => $attr->localName,
28+
);
29+
30+
foreach ($documentElement->getElementsByTagName('*') as $element) {
31+
$prefixedXmlns = xmlns_attributes_list($element)
32+
->filter(static fn (Attr $attr): bool => $attr->prefix !== null);
33+
34+
foreach ($prefixedXmlns as $attr) {
35+
$prefix = $attr->localName;
36+
$uri = $attr->value;
37+
38+
if (!array_key_exists($prefix, $promoted)) {
39+
xmlns_attribute($prefix, $uri)($documentElement);
40+
$promoted[$prefix] = $uri;
41+
}
42+
43+
if ($promoted[$prefix] === $uri) {
44+
remove_namespace($attr, $element);
45+
}
46+
}
47+
}
48+
}

src/bootstrap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
'Xml\Dom\Configurator\normalize' => __DIR__.'/Xml/Dom/Configurator/normalize.php',
3030
'Xml\Dom\Configurator\optimize_namespaces' => __DIR__.'/Xml/Dom/Configurator/optimize_namespaces.php',
3131
'Xml\Dom\Configurator\pretty_print' => __DIR__.'/Xml/Dom/Configurator/pretty_print.php',
32+
'Xml\Dom\Configurator\promote_namespaces' => __DIR__.'/Xml/Dom/Configurator/promote_namespaces.php',
3233
'Xml\Dom\Configurator\traverse' => __DIR__.'/Xml/Dom/Configurator/traverse.php',
3334
'Xml\Dom\Configurator\trim_spaces' => __DIR__.'/Xml/Dom/Configurator/trim_spaces.php',
3435
'Xml\Dom\Configurator\utf8' => __DIR__.'/Xml/Dom/Configurator/utf8.php',
@@ -59,6 +60,7 @@
5960
'Xml\Dom\Locator\root_namespace' => __DIR__.'/Xml/Dom/Locator/root_namespace.php',
6061
'Xml\Dom\Manipulator\Attribute\rename' => __DIR__.'/Xml/Dom/Manipulator/Attribute/rename.php',
6162
'Xml\Dom\Manipulator\Document\optimize_namespaces' => __DIR__.'/Xml/Dom/Manipulator/Document/optimize_namespaces.php',
63+
'Xml\Dom\Manipulator\Document\promote_namespaces' => __DIR__.'/Xml/Dom/Manipulator/Document/promote_namespaces.php',
6264
'Xml\Dom\Manipulator\Element\copy_named_xmlns_attributes' => __DIR__.'/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php',
6365
'Xml\Dom\Manipulator\Element\rename' => __DIR__.'/Xml/Dom/Manipulator/Element/rename.php',
6466
'Xml\Dom\Manipulator\Node\append_external_node' => __DIR__.'/Xml/Dom/Manipulator/Node/append_external_node.php',
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace VeeWee\Tests\Xml\Dom\Configurator;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\TestCase;
9+
use VeeWee\Xml\Dom\Document;
10+
use function VeeWee\Xml\Dom\Configurator\promote_namespaces;
11+
use function VeeWee\Xml\Dom\Locator\document_element;
12+
use function VeeWee\Xml\Dom\Mapper\xml_string;
13+
14+
final class PromoteNamespacesTest extends TestCase
15+
{
16+
#[DataProvider('provideXmls')]
17+
public function test_it_can_promote_namespaces(string $input, string $expected): void
18+
{
19+
$doc = Document::fromXmlString($input, promote_namespaces());
20+
$actual = xml_string()($doc->map(document_element()));
21+
22+
static::assertSame($expected, $actual);
23+
}
24+
25+
public static function provideXmls(): iterable
26+
{
27+
yield 'no-action' => [
28+
'<hello/>',
29+
'<hello/>',
30+
];
31+
32+
yield 'child-to-root' => [
33+
<<<EOXML
34+
<foo>
35+
<bar xmlns:a="http://a"><a:baz/></bar>
36+
</foo>
37+
EOXML,
38+
<<<EOXML
39+
<foo xmlns:a="http://a">
40+
<bar><a:baz/></bar>
41+
</foo>
42+
EOXML,
43+
];
44+
45+
yield 'mixed-namespaces' => [
46+
<<<EOXML
47+
<foo>
48+
<bar xmlns:a="http://a"><a:x/></bar>
49+
<baz xmlns:b="http://b"><b:y/></baz>
50+
</foo>
51+
EOXML,
52+
<<<EOXML
53+
<foo xmlns:a="http://a" xmlns:b="http://b">
54+
<bar><a:x/></bar>
55+
<baz><b:y/></baz>
56+
</foo>
57+
EOXML,
58+
];
59+
60+
yield 'soap-like' => [
61+
<<<EOXML
62+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
63+
<SOAP-ENV:Body>
64+
<tns:getUser xmlns:tns="https://example.com">
65+
<tns:id xmlns:tns="https://example.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:int" xmlns:xsd="http://www.w3.org/2001/XMLSchema">1</tns:id>
66+
</tns:getUser>
67+
</SOAP-ENV:Body>
68+
</SOAP-ENV:Envelope>
69+
EOXML,
70+
<<<EOXML
71+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="https://example.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
72+
<SOAP-ENV:Body>
73+
<tns:getUser>
74+
<tns:id xsi:type="xsd:int">1</tns:id>
75+
</tns:getUser>
76+
</SOAP-ENV:Body>
77+
</SOAP-ENV:Envelope>
78+
EOXML,
79+
];
80+
}
81+
}

0 commit comments

Comments
 (0)