Skip to content

Commit

Permalink
Inherit XMLNS during encoding like we did in v3
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Jan 24, 2025
1 parent 86a8497 commit 31a9821
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 18 deletions.
21 changes: 21 additions & 0 deletions src/Xml/Dom/Builder/default_xmlns_attribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Builder;

use Closure;
use Dom\Element;
use VeeWee\Xml\Xmlns\Xmlns;

/**
* @return Closure(Element): Element
*/
function default_xmlns_attribute(string $namespaceURI): Closure
{
return static function (Element $node) use ($namespaceURI): Element {
$node->setAttributeNS(Xmlns::xmlns()->value(), 'xmlns', $namespaceURI);

return $node;
};
}
7 changes: 3 additions & 4 deletions src/Xml/Dom/Builder/nodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@

use Closure;
use Dom\Node;
use Dom\XMLDocument;
use function is_array;
use function Psl\Iter\reduce;
use function VeeWee\Xml\Dom\Locator\Node\detect_document;

/**
* @param list<callable(XMLDocument): (list<Node>|Node)> $builders
* @param list<callable(Node): (list<Node>|Node)> $builders
*
* @return Closure(XMLDocument): list<Node>
* @return Closure(Node): list<Node>
*/
function nodes(callable ... $builders): Closure
{
Expand All @@ -27,7 +26,7 @@ function nodes(callable ... $builders): Closure
$builders,
/**
* @param list<Node> $builds
* @param callable(XMLDocument): (Node|list<Node>) $builder
* @param callable(Node): (Node|list<Node>) $builder
* @return list<Node>
*/
static function (array $builds, callable $builder) use ($node): array {
Expand Down
2 changes: 2 additions & 0 deletions src/Xml/Dom/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ public function manipulate(callable $manipulator): self
}

/**
* @psalm-suppress ArgumentTypeCoercion - nodes() works on node but we provide the parent type XMLDocument.
*
* @param list<callable(XMLDocument): (list<Node>|Node)> $builders
*
* @return list<Node>
Expand Down
12 changes: 12 additions & 0 deletions src/Xml/Dom/Predicate/is_prefixed_node_name.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Predicate;

use function Psl\Regex\matches;

function is_prefixed_node_name(string $nodeName): bool
{
return matches($nodeName, '/^[^:]+:[^:]+$/');

Check failure on line 11 in src/Xml/Dom/Predicate/is_prefixed_node_name.php

View workflow job for this annotation

GitHub Actions / PHP 8.4 @ ubuntu-latest

MissingThrowsDocblock

src/Xml/Dom/Predicate/is_prefixed_node_name.php:11:12: MissingThrowsDocblock: Psl\Regex\Exception\RuntimeException is thrown but not caught - please either catch or add a @throws annotation (see https://psalm.dev/169)

Check failure on line 11 in src/Xml/Dom/Predicate/is_prefixed_node_name.php

View workflow job for this annotation

GitHub Actions / PHP 8.4 @ ubuntu-latest

MissingThrowsDocblock

src/Xml/Dom/Predicate/is_prefixed_node_name.php:11:12: MissingThrowsDocblock: Psl\Regex\Exception\RuntimeException is thrown but not caught - please either catch or add a @throws annotation (see https://psalm.dev/169)
}
9 changes: 3 additions & 6 deletions src/Xml/Encoding/Internal/Decoder/Builder/namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@ function namespaces(Element $element): array
{
return filter([
'@namespaces' => xmlns_attributes_list($element)->reduce(
static fn (array $namespaces, Attr $node)
=> $node->value
? merge($namespaces, [
($node->prefix !== null ? $node->localName : '') => $node->value
])
: $namespaces,
static fn (array $namespaces, Attr $node) => merge($namespaces, [
($node->prefix !== null ? $node->localName : '') => $node->value
]),
[]
),
]);
Expand Down
2 changes: 1 addition & 1 deletion src/Xml/Encoding/Internal/Encoder/Builder/children.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function children(string $name, array $children): Closure
*/
static fn (array|string $data): Closure => is_array($data)
? element($name, $data)
: elementBuilder($name, value($data))
: xmlns_inheriting_element($name, [value($data)])
)
);
}
6 changes: 1 addition & 5 deletions src/Xml/Encoding/Internal/Encoder/Builder/element.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
use function VeeWee\Xml\Dom\Builder\attributes;
use function VeeWee\Xml\Dom\Builder\cdata;
use function VeeWee\Xml\Dom\Builder\children as childrenBuilder;
use function VeeWee\Xml\Dom\Builder\element as elementBuilder;
use function VeeWee\Xml\Dom\Builder\escaped_value;
use function VeeWee\Xml\Dom\Builder\namespaced_element as namespacedElementBuilder;
use function VeeWee\Xml\Dom\Builder\xmlns_attributes;

/**
Expand Down Expand Up @@ -66,7 +64,5 @@ function element(string $name, array $data): Closure
)),
]);

return $currentNamespace !== null
? namespacedElementBuilder($currentNamespace, $name, ...$children)
: elementBuilder($name, ...$children);
return xmlns_inheriting_element($name, $children, $namespaces);
}
2 changes: 1 addition & 1 deletion src/Xml/Encoding/Internal/Encoder/Builder/parent_node.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
function parent_node(string $name, array|string $data): Closure
{
if (is_string($data)) {
return buildChildren(elementBuilder($name, escaped_value($data)));
return buildChildren(xmlns_inheriting_element($name, [escaped_value($data)]));
}

if (is_node_list($data)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Encoding\Internal\Encoder\Builder;

use Closure;
use Dom\Element;
use Dom\XMLDocument;
use Webmozart\Assert\Assert;
use function VeeWee\Xml\Dom\Builder\default_xmlns_attribute;
use function VeeWee\Xml\Dom\Builder\element as elementBuilder;
use function VeeWee\Xml\Dom\Builder\namespaced_element as namespacedElementBuilder;
use function VeeWee\Xml\Dom\Predicate\is_element;
use function VeeWee\Xml\Dom\Predicate\is_prefixed_node_name;

/**
* This function can create element nodes that inherit the local xmlns namespace of their parent if none is configured.
*
* @param list<Closure(Element): Element> $children
* @param array<string, string> $namespaces
*
* @return Closure(Element): Element
*/
function xmlns_inheriting_element(string $name, array $children, ?array $namespaces = []): Closure
{
return function (XMLDocument|Element $parent) use ($namespaces, $name, $children): Element {

$defaultNamespace = $namespaces[''] ?? null;

// These rules apply for non prefixed elements only:
// If no local namespace has been defined: lookup the default local namespace of the closest parent element.
// Use that specific local namespace to create the element if one could be found.
// Otherwise, just create a non-namespaced element.
if (!is_prefixed_node_name($name)) {
// Try to find the inherited default XMLNS for non prefixed elements without a desired local namespace.
if ($defaultNamespace === null && is_element($parent)) {
$defaultNamespace = $parent->lookupNamespaceURI('');
}

return $defaultNamespace !== null
? namespacedElementBuilder($defaultNamespace, $name, ...$children)($parent)
: elementBuilder($name, ...$children)($parent);
}

// Prefixed elements can be created as regular elements:
// The configured xmlns attributes will be added by the $children.
// If a local namespace is configured, make sure to register it on the node manually.
[$prefix] = explode(':', $name);
$prefixedNamespace = $namespaces[$prefix] ?? (is_element($parent) ? $parent->lookupNamespaceURI($prefix) : null);

Assert::notNull($prefixedNamespace, 'No namespace URI could be found for prefix: '.$prefix);

$defaultXmlns = $defaultNamespace !== null ? [default_xmlns_attribute($defaultNamespace)] : [];
return namespacedElementBuilder(
$prefixedNamespace,
$name,
...$defaultXmlns,
...$children,
)($parent);
};
}
3 changes: 3 additions & 0 deletions src/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'Xml\Dom\Builder\attributes' => __DIR__.'/Xml/Dom/Builder/attributes.php',
'Xml\Dom\Builder\cdata' => __DIR__.'/Xml/Dom/Builder/cdata.php',
'Xml\Dom\Builder\children' => __DIR__.'/Xml/Dom/Builder/children.php',
'Xml\Dom\Builder\default_xmlns_attribute' => __DIR__.'/Xml/Dom/Builder/default_xmlns_attribute.php',
'Xml\Dom\Builder\element' => __DIR__.'/Xml/Dom/Builder/element.php',
'Xml\Dom\Builder\escaped_value' => __DIR__.'/Xml/Dom/Builder/escaped_value.php',
'Xml\Dom\Builder\namespaced_attribute' => __DIR__.'/Xml/Dom/Builder/namespaced_attribute.php',
Expand Down Expand Up @@ -79,6 +80,7 @@
'Xml\Dom\Predicate\is_document_element' => __DIR__.'/Xml/Dom/Predicate/is_document_element.php',
'Xml\Dom\Predicate\is_element' => __DIR__.'/Xml/Dom/Predicate/is_element.php',
'Xml\Dom\Predicate\is_non_empty_text' => __DIR__.'/Xml/Dom/Predicate/is_non_empty_text.php',
'Xml\Dom\Predicate\is_prefixed_node_name' => __DIR__.'/Xml/Dom/Predicate/is_prefixed_node_name.php',
'Xml\Dom\Predicate\is_text' => __DIR__.'/Xml/Dom/Predicate/is_text.php',
'Xml\Dom\Predicate\is_whitespace' => __DIR__.'/Xml/Dom/Predicate/is_whitespace.php',
'Xml\Dom\Predicate\is_xmlns_attribute' => __DIR__.'/Xml/Dom/Predicate/is_xmlns_attribute.php',
Expand Down Expand Up @@ -107,6 +109,7 @@
'Xml\Encoding\Internal\Encoder\Builder\normalize_data' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/normalize_data.php',
'Xml\Encoding\Internal\Encoder\Builder\parent_node' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/parent_node.php',
'Xml\Encoding\Internal\Encoder\Builder\root' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/root.php',
'Xml\Encoding\Internal\Encoder\Builder\xmlns_inheriting_element' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/xmlns_inheriting_element.php',
'Xml\Encoding\Internal\wrap_exception' => __DIR__.'/Xml/Encoding/Internal/wrap_exception.php',
'Xml\Encoding\document_encode' => __DIR__.'/Xml/Encoding/document_encode.php',
'Xml\Encoding\element_decode' => __DIR__.'/Xml/Encoding/element_decode.php',
Expand Down
2 changes: 1 addition & 1 deletion tests/Xml/Encoding/EncodingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public static function provideRiskyBidirectionalCases()
yield 'namespaced' => [
'xml' => <<<EOXML
<root xmlns="http://rooty.root" xmlns:test="http://testy.test">
<test:item xmlns="">
<test:item>
<id:int xmlns:id="http://identity.id">1</id:int>
</test:item>
</root>
Expand Down

0 comments on commit 31a9821

Please sign in to comment.