From c5c3c5bcac49241a6240918d1b4656e71f2e5698 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Fri, 23 Feb 2024 15:39:26 +0100 Subject: [PATCH] New namspaces system --- src/Xml/Dom/Configurator/pretty_print.php | 16 +++++++++----- src/Xml/Dom/Configurator/trim_spaces.php | 15 +++++++++---- .../Attribute/xmlns_attributes_list.php | 15 ++++--------- .../Element/copy_named_xmlns_attributes.php | 6 ++---- src/Xml/Dom/Manipulator/Element/rename.php | 3 +-- .../Dom/Manipulator/Node/remove_namespace.php | 8 +++---- src/Xml/Dom/Manipulator/Xmlns/rename.php | 2 +- src/Xml/Dom/Manipulator/append.php | 10 +++++++++ .../Predicate/is_default_xmlns_attribute.php | 3 +-- src/Xml/Dom/Predicate/is_document_element.php | 3 +-- src/Xml/Dom/Predicate/is_xmlns_attribute.php | 8 +++---- .../Traverser/Visitor/RemoveNamespaces.php | 21 +++++++++---------- .../Dom/Traverser/Visitor/SortAttributes.php | 6 ++++-- .../Internal/Decoder/Builder/attributes.php | 4 +++- .../Internal/Decoder/Builder/namespaces.php | 7 ++++--- .../Xml/Dom/Configurator/PrettyPrintTest.php | 7 +++---- tests/Xml/Dom/Configurator/TrimSpacesTest.php | 7 +++---- .../Xml/Dom/Manipulator/Xmlns/RenameTest.php | 2 +- tests/Xml/Encoding/EncodingTest.php | 4 ++-- 19 files changed, 79 insertions(+), 68 deletions(-) diff --git a/src/Xml/Dom/Configurator/pretty_print.php b/src/Xml/Dom/Configurator/pretty_print.php index 71384406..f1493018 100644 --- a/src/Xml/Dom/Configurator/pretty_print.php +++ b/src/Xml/Dom/Configurator/pretty_print.php @@ -5,7 +5,8 @@ namespace VeeWee\Xml\Dom\Configurator; use Closure; -use \DOM\XMLDocument; +use VeeWee\Xml\Dom\Document; +use function VeeWee\Xml\Dom\Loader\xml_string_loader; /** * @return Closure(\DOM\XMLDocument): \DOM\XMLDocument @@ -13,10 +14,15 @@ function pretty_print(): Closure { return static function (\DOM\XMLDocument $document): \DOM\XMLDocument { - // TODO : not fully implemented yet in the new API - //$document->preserveWhiteSpace = false; - $document->formatOutput = true; + $trimmed = Document::fromLoader( + xml_string_loader( + Document::fromUnsafeDocument($document)->toXmlString(), + LIBXML_NOBLANKS + ) + )->toUnsafeDocument(); - return $document; + $trimmed->formatOutput = true; + + return $trimmed; }; } diff --git a/src/Xml/Dom/Configurator/trim_spaces.php b/src/Xml/Dom/Configurator/trim_spaces.php index 9b23089a..ca225227 100644 --- a/src/Xml/Dom/Configurator/trim_spaces.php +++ b/src/Xml/Dom/Configurator/trim_spaces.php @@ -6,6 +6,8 @@ use Closure; use \DOM\XMLDocument; +use VeeWee\Xml\Dom\Document; +use function VeeWee\Xml\Dom\Loader\xml_string_loader; /** * @return Closure(\DOM\XMLDocument): \DOM\XMLDocument @@ -13,10 +15,15 @@ function trim_spaces(): Closure { return static function (\DOM\XMLDocument $document): \DOM\XMLDocument { - // TODO : not fully implemented yet in the new API - //$document->preserveWhiteSpace = false; - $document->formatOutput = false; + $trimmed = Document::fromLoader( + xml_string_loader( + Document::fromUnsafeDocument($document)->toXmlString(), + LIBXML_NOBLANKS + ) + )->toUnsafeDocument(); - return $document; + $trimmed->formatOutput = false; + + return $trimmed; }; } diff --git a/src/Xml/Dom/Locator/Attribute/xmlns_attributes_list.php b/src/Xml/Dom/Locator/Attribute/xmlns_attributes_list.php index e82545ba..5ac1d143 100644 --- a/src/Xml/Dom/Locator/Attribute/xmlns_attributes_list.php +++ b/src/Xml/Dom/Locator/Attribute/xmlns_attributes_list.php @@ -4,23 +4,16 @@ namespace VeeWee\Xml\Dom\Locator\Attribute; -use \DOM\NameSpaceNode; -use \DOM\Node; use VeeWee\Xml\Dom\Collection\NodeList; use VeeWee\Xml\Exception\RuntimeException; -use function VeeWee\Xml\Dom\Locator\Xmlns\linked_namespaces; -use function VeeWee\Xml\Dom\Predicate\is_element; +use function VeeWee\Xml\Dom\Predicate\is_xmlns_attribute; /** - * @return NodeList<\DOM\NameSpaceNode> + * @return NodeList<\DOM\Attr> * @throws RuntimeException */ function xmlns_attributes_list(\DOM\Node $node): NodeList { - if (! is_element($node)) { - return NodeList::empty(); - } - - return linked_namespaces($node) - ->filter(static fn (\DOM\NameSpaceNode $namespace): bool => $node->hasAttribute($namespace->nodeName)); + return attributes_list($node) + ->filter(static fn (\DOM\Attr $attribute): bool => is_xmlns_attribute($attribute)); } diff --git a/src/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php b/src/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php index 43921231..cd96ac13 100644 --- a/src/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php +++ b/src/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php @@ -4,18 +4,16 @@ namespace VeeWee\Xml\Dom\Manipulator\Element; -use \DOM\Element; -use \DOM\NameSpaceNode; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\Dom\Builder\xmlns_attribute; -use function VeeWee\Xml\Dom\Locator\Xmlns\linked_namespaces; +use function VeeWee\Xml\Dom\Locator\Attribute\xmlns_attributes_list; /** * @throws RuntimeException */ function copy_named_xmlns_attributes(\DOM\Element $target, \DOM\Element $source): void { - linked_namespaces($source)->forEach(static function (\DOM\NameSpaceNode $xmlns) use ($target) { + xmlns_attributes_list($source)->forEach(static function (\DOM\Attr $xmlns) use ($target) { if ($xmlns->prefix && !$target->hasAttribute($xmlns->nodeName)) { xmlns_attribute($xmlns->prefix, $xmlns->namespaceURI)($target); } diff --git a/src/Xml/Dom/Manipulator/Element/rename.php b/src/Xml/Dom/Manipulator/Element/rename.php index dca0f633..8e1a8f40 100644 --- a/src/Xml/Dom/Manipulator/Element/rename.php +++ b/src/Xml/Dom/Manipulator/Element/rename.php @@ -4,7 +4,6 @@ namespace VeeWee\Xml\Dom\Manipulator\Element; -use \DOM\NameSpaceNode; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\Dom\Builder\element; use function VeeWee\Xml\Dom\Builder\namespaced_element; @@ -33,7 +32,7 @@ function rename(\DOM\Element $target, string $newQName, ?string $newNamespaceURI append(...children($target))($newElement); xmlns_attributes_list($target)->forEach( - static function (\DOM\NameSpaceNode $attribute) use ($target, $newElement): void { + static function (\DOM\Attr $attribute) use ($target, $newElement): void { if (is_default_xmlns_attribute($attribute) || $target->prefix === $attribute->prefix) { return; } diff --git a/src/Xml/Dom/Manipulator/Node/remove_namespace.php b/src/Xml/Dom/Manipulator/Node/remove_namespace.php index 169a8d1e..a9c7f756 100644 --- a/src/Xml/Dom/Manipulator/Node/remove_namespace.php +++ b/src/Xml/Dom/Manipulator/Node/remove_namespace.php @@ -4,8 +4,6 @@ namespace VeeWee\Xml\Dom\Manipulator\Node; -use \DOM\Element; -use \DOM\NameSpaceNode; use VeeWee\Xml\Exception\RuntimeException; use function VeeWee\Xml\ErrorHandling\disallow_issues; use function VeeWee\Xml\ErrorHandling\disallow_libxml_false_returns; @@ -13,15 +11,15 @@ /** * @throws RuntimeException */ -function remove_namespace(\DOM\NameSpaceNode $target, \DOM\Element $parent): \DOM\NameSpaceNode +function remove_namespace(\DOM\Attr $target, \DOM\Element $parent): \DOM\Attr { return disallow_issues( /** * @throws RuntimeException */ - static function () use ($target, $parent): \DOM\NameSpaceNode { + static function () use ($target, $parent): \DOM\Attr { disallow_libxml_false_returns( - $parent->removeAttributeNS($target->namespaceURI, $target->prefix), + $parent->removeAttributeNode($target), 'Could not remove xmlns attribute from dom element' ); diff --git a/src/Xml/Dom/Manipulator/Xmlns/rename.php b/src/Xml/Dom/Manipulator/Xmlns/rename.php index 086a455e..fd1f9fae 100644 --- a/src/Xml/Dom/Manipulator/Xmlns/rename.php +++ b/src/Xml/Dom/Manipulator/Xmlns/rename.php @@ -86,7 +86,7 @@ static function (\DOM\Node $node) use ($namespaceURI, $newPrefix, $predicate, $r // Remove old xmlns declarations: $namespaceNodes = xmlns_attributes_list($node) ->filter( - static fn (\DOM\NameSpaceNode $xmlns): bool + static fn (\DOM\Attr $xmlns): bool => $xmlns->namespaceURI === $namespaceURI && $xmlns->prefix !== $newPrefix ); diff --git a/src/Xml/Dom/Manipulator/append.php b/src/Xml/Dom/Manipulator/append.php index 922af7c3..e25c5f47 100644 --- a/src/Xml/Dom/Manipulator/append.php +++ b/src/Xml/Dom/Manipulator/append.php @@ -7,7 +7,10 @@ use Closure; use \DOM\Node; use VeeWee\Xml\Exception\RuntimeException; +use function VeeWee\Xml\Dom\Predicate\is_attribute; +use function VeeWee\Xml\Dom\Predicate\is_element; use function VeeWee\Xml\ErrorHandling\disallow_issues; +use function VeeWee\Xml\ErrorHandling\disallow_libxml_false_returns; /** * @no-named-arguments @@ -19,6 +22,13 @@ function append(\DOM\Node ... $nodes): Closure return static fn (\DOM\Node $target): \DOM\Node => disallow_issues( static function () use ($target, $nodes) { foreach ($nodes as $node) { + // Attributes cannot be appended with appendChild. + // Setting the attribute node to the element is the correct way to append an attribute. + if (is_attribute($node) && is_element($target)) { + $target->setAttributeNode($node); + continue; + } + $target->appendChild($node); } diff --git a/src/Xml/Dom/Predicate/is_default_xmlns_attribute.php b/src/Xml/Dom/Predicate/is_default_xmlns_attribute.php index 7aac7fe9..b58b386b 100644 --- a/src/Xml/Dom/Predicate/is_default_xmlns_attribute.php +++ b/src/Xml/Dom/Predicate/is_default_xmlns_attribute.php @@ -4,10 +4,9 @@ namespace VeeWee\Xml\Dom\Predicate; -use \DOM\NameSpaceNode; use \DOM\Node; -function is_default_xmlns_attribute(\DOM\Node|\DOM\NameSpaceNode $node): bool +function is_default_xmlns_attribute(\DOM\Node $node): bool { return is_xmlns_attribute($node) && $node->prefix === ''; } diff --git a/src/Xml/Dom/Predicate/is_document_element.php b/src/Xml/Dom/Predicate/is_document_element.php index 1b5b22cf..be8694ab 100644 --- a/src/Xml/Dom/Predicate/is_document_element.php +++ b/src/Xml/Dom/Predicate/is_document_element.php @@ -4,11 +4,10 @@ namespace VeeWee\Xml\Dom\Predicate; -use \DOM\NameSpaceNode; use \DOM\Node; use function VeeWee\Xml\Dom\Locator\Node\detect_document; -function is_document_element(\DOM\Node|\DOM\NameSpaceNode $node): bool +function is_document_element(\DOM\Node $node): bool { return is_element($node) && detect_document($node)->documentElement === $node; } diff --git a/src/Xml/Dom/Predicate/is_xmlns_attribute.php b/src/Xml/Dom/Predicate/is_xmlns_attribute.php index 422ad07f..ac752caf 100644 --- a/src/Xml/Dom/Predicate/is_xmlns_attribute.php +++ b/src/Xml/Dom/Predicate/is_xmlns_attribute.php @@ -4,13 +4,13 @@ namespace VeeWee\Xml\Dom\Predicate; -use \DOM\NameSpaceNode; use \DOM\Node; +use VeeWee\Xml\Xmlns\Xmlns; /** - * @psalm-assert-if-true \DOM\NameSpaceNode $node + * @psalm-assert-if-true \DOM\Attr $node */ -function is_xmlns_attribute(\DOM\Node|\DOM\NameSpaceNode $node): bool +function is_xmlns_attribute(\DOM\Node $node): bool { - return $node instanceof \DOM\NameSpaceNode; + return is_attribute($node) && $node->namespaceURI === Xmlns::xmlns()->value(); } diff --git a/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php b/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php index 4551ad07..226c61ad 100644 --- a/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php +++ b/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php @@ -3,23 +3,24 @@ namespace VeeWee\Xml\Dom\Traverser\Visitor; -use \DOM\NameSpaceNode; +use \DOM\Attr; use \DOM\Node; use VeeWee\Xml\Dom\Traverser\Action; use VeeWee\Xml\Exception\RuntimeException; use function Psl\Iter\contains; use function VeeWee\Xml\Dom\Locator\Attribute\xmlns_attributes_list; use function VeeWee\Xml\Dom\Predicate\is_element; +use function VeeWee\Xml\Dom\Predicate\is_xmlns_attribute; final class RemoveNamespaces extends AbstractVisitor { /** - * @var null | callable(\DOM\NameSpaceNode): bool + * @var null | callable(\DOM\Attr): bool */ private $filter; /** - * @param null | callable(\DOM\NameSpaceNode): bool $filter + * @param null | callable(\DOM\Attr): bool $filter */ public function __construct( ?callable $filter = null @@ -35,14 +36,14 @@ public static function all(): self public static function prefixed(): self { return new self( - static fn (\DOM\NameSpaceNode $node): bool => $node->prefix !== '' + static fn (\DOM\Attr $node): bool => $node->prefix !== null ); } public static function unprefixed(): self { return new self( - static fn (\DOM\NameSpaceNode $node): bool => $node->prefix === '' + static fn (\DOM\Attr $node): bool => $node->prefix === null ); } @@ -52,7 +53,7 @@ public static function unprefixed(): self public static function byPrefixNames(array $prefixes): self { return new self( - static fn (\DOM\NameSpaceNode $node): bool => contains($prefixes, $node->prefix) + static fn (\DOM\Attr $node): bool => contains($prefixes, $node->localName) ); } @@ -62,7 +63,7 @@ public static function byPrefixNames(array $prefixes): self public static function byNamespaceURIs(array $URIs): self { return new self( - static fn (\DOM\NameSpaceNode $node): bool => contains($URIs, $node->namespaceURI) + static fn (\DOM\Attr $node): bool => contains($URIs, $node->value) ); } @@ -80,11 +81,9 @@ public function onNodeLeave(\DOM\Node $node): Action $namespaces = $namespaces->filter($this->filter); } + // TODO : convert to is_xmlns_attribute based visitor instead of acting on element level foreach ($namespaces as $namespace) { - $node->removeAttributeNS( - $namespace->namespaceURI, - $namespace->prefix - ); + $node->removeAttributeNode($namespace); } return new Action\Noop(); diff --git a/src/Xml/Dom/Traverser/Visitor/SortAttributes.php b/src/Xml/Dom/Traverser/Visitor/SortAttributes.php index ef166ab1..95066f3d 100644 --- a/src/Xml/Dom/Traverser/Visitor/SortAttributes.php +++ b/src/Xml/Dom/Traverser/Visitor/SortAttributes.php @@ -6,6 +6,8 @@ use VeeWee\Xml\Dom\Traverser\Action; use function VeeWee\Xml\Dom\Locator\Attribute\attributes_list; +use function VeeWee\Xml\Dom\Manipulator\append; +use function VeeWee\Xml\Dom\Manipulator\Node\remove; use function VeeWee\Xml\Dom\Predicate\is_element; use function VeeWee\Xml\ErrorHandling\disallow_issues; @@ -22,8 +24,8 @@ public function onNodeEnter(\DOM\Node $node): Action ->forEach( static function (\DOM\Attr $attr) use ($node): void { disallow_issues(static function () use ($node, $attr) { - $node->removeAttributeNode($attr); - $node->setAttributeNode($attr); + remove($attr); + append($attr)($node); }); } ); diff --git a/src/Xml/Encoding/Internal/Decoder/Builder/attributes.php b/src/Xml/Encoding/Internal/Decoder/Builder/attributes.php index 0eaa804d..af1f859d 100644 --- a/src/Xml/Encoding/Internal/Decoder/Builder/attributes.php +++ b/src/Xml/Encoding/Internal/Decoder/Builder/attributes.php @@ -9,6 +9,8 @@ use function Psl\Dict\filter; use function Psl\Dict\merge; use function Psl\Iter\reduce; +use function VeeWee\Xml\Dom\Locator\Attribute\attributes_list; +use function VeeWee\Xml\Dom\Predicate\is_xmlns_attribute; /** * @psalm-internal VeeWee\Xml\Encoding @@ -17,7 +19,7 @@ function attributes(\DOM\Element $element): array { return filter([ '@attributes' => reduce( - $element->attributes, + attributes_list($element)->filter(static fn(\DOM\Attr $attr): bool => !is_xmlns_attribute($attr)), static fn (array $attributes, \DOM\Attr $attr): array => merge($attributes, attribute($attr)), [] diff --git a/src/Xml/Encoding/Internal/Decoder/Builder/namespaces.php b/src/Xml/Encoding/Internal/Decoder/Builder/namespaces.php index 54dd1c86..5c70b485 100644 --- a/src/Xml/Encoding/Internal/Decoder/Builder/namespaces.php +++ b/src/Xml/Encoding/Internal/Decoder/Builder/namespaces.php @@ -5,7 +5,6 @@ namespace VeeWee\Xml\Encoding\Internal\Decoder\Builder; use \DOM\Element; -use \DOM\NameSpaceNode; use VeeWee\Xml\Exception\RuntimeException; use function Psl\Dict\filter; use function Psl\Dict\merge; @@ -20,9 +19,11 @@ function namespaces(\DOM\Element $element): array { return filter([ '@namespaces' => xmlns_attributes_list($element)->reduce( - static fn (array $namespaces, \DOM\NameSpaceNode $node) + static fn (array $namespaces, \DOM\Attr $node) => $node->namespaceURI - ? merge($namespaces, [(string) $node->prefix => $node->namespaceURI]) + ? merge($namespaces, [ + ($node->prefix ? $node->localName : '') => $node->value + ]) : $namespaces, [] ), diff --git a/tests/Xml/Dom/Configurator/PrettyPrintTest.php b/tests/Xml/Dom/Configurator/PrettyPrintTest.php index 29337608..05a87d66 100644 --- a/tests/Xml/Dom/Configurator/PrettyPrintTest.php +++ b/tests/Xml/Dom/Configurator/PrettyPrintTest.php @@ -25,9 +25,8 @@ public function test_it_can_trim_contents(): void EOXML; - static::assertSame($doc, $result); - // TODO : static::assertFalse($doc->preserveWhiteSpace); - static::assertTrue($doc->formatOutput); - static::assertSame($expected, xml_string()($doc->documentElement)); + static::assertNotSame($doc, $result); + static::assertTrue($result->formatOutput); + static::assertSame($expected, xml_string()($result->documentElement)); } } diff --git a/tests/Xml/Dom/Configurator/TrimSpacesTest.php b/tests/Xml/Dom/Configurator/TrimSpacesTest.php index 9fd1e130..960e4373 100644 --- a/tests/Xml/Dom/Configurator/TrimSpacesTest.php +++ b/tests/Xml/Dom/Configurator/TrimSpacesTest.php @@ -18,9 +18,8 @@ public function test_it_can_trim_contents(): void $configurator = trim_spaces(); $result = $configurator($doc); - static::assertSame($doc, $result); - // TODO : static::assertFalse($doc->preserveWhiteSpace); - static::assertFalse($doc->formatOutput); - static::assertSame('', xml_string()($doc->documentElement)); + static::assertNotSame($doc, $result); + static::assertFalse($result->formatOutput); + static::assertSame('', xml_string()($result->documentElement)); } } diff --git a/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php b/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php index 09b161af..98486623 100644 --- a/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php +++ b/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php @@ -24,7 +24,7 @@ public function test_it_can_rename_namespaces(string $input, string $expected): static::assertSame($expected, $actual); } - + public function test_it_can_not_rename_existing_prefix_to_other_uri(): void { $document = Document::fromXmlString('')->toUnsafeDocument(); diff --git a/tests/Xml/Encoding/EncodingTest.php b/tests/Xml/Encoding/EncodingTest.php index 3b923c0d..30a961fa 100644 --- a/tests/Xml/Encoding/EncodingTest.php +++ b/tests/Xml/Encoding/EncodingTest.php @@ -19,7 +19,7 @@ final class EncodingTest extends TestCase { - private const XML_HEADER = ''; + private const XML_HEADER = ''; /** * @dataProvider provideBidirectionalCases @@ -263,8 +263,8 @@ public function provideRiskyBidirectionalCases() EOXML, 'data' => ['root' => [ '@namespaces' => [ - 'test' => 'http://testy.test', '' => 'http://rooty.root', + 'test' => 'http://testy.test', ], 'test:item' => [ 'id:int' => [