diff --git a/docs/dom.md b/docs/dom.md index 56bcc62..8742683 100644 --- a/docs/dom.md +++ b/docs/dom.md @@ -896,7 +896,7 @@ This function returns a list of all namespaces that are linked to a specific DOM use VeeWee\Xml\Dom\Collection\NodeList; use function VeeWee\Xml\Dom\Locator\Xmlns\linked_namespaces; -/** @var NodeList $namespaces */ +/** @var array $namespaces - A lookup of prefix -> namespace */ $namespaces = linked_namespaces($element); ``` @@ -908,7 +908,7 @@ This function returns a list of all namespaces that are linked to a specific DOM use VeeWee\Xml\Dom\Collection\NodeList; use function VeeWee\Xml\Dom\Locator\Xmlns\recursive_linked_namespaces; -/** @var NodeList $namespaces */ +/** @var array $namespaces - A lookup of prefix -> namespace */ $namespaces = recursive_linked_namespaces($element); ``` diff --git a/src/Xml/Dom/Builder/value.php b/src/Xml/Dom/Builder/value.php index 7533d0c..50c7167 100644 --- a/src/Xml/Dom/Builder/value.php +++ b/src/Xml/Dom/Builder/value.php @@ -6,7 +6,6 @@ use Closure; use \DOM\Element; -use function VeeWee\Xml\Dom\Locator\Node\detect_document; /** * @return Closure(\DOM\Element): \DOM\Element @@ -14,9 +13,7 @@ function value(string $value): Closure { return static function (\DOM\Element $node) use ($value): \DOM\Element { - $document = detect_document($node); - $text = $document->createTextNode($value); - $node->appendChild($text); + $node->substitutedNodeValue = $value; return $node; }; diff --git a/src/Xml/Dom/Configurator/pretty_print.php b/src/Xml/Dom/Configurator/pretty_print.php index 7138440..f149301 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 9b23089..ca22522 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 e82545b..5ac1d14 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/Locator/Node/value.php b/src/Xml/Dom/Locator/Node/value.php index c82e77d..9ee53e9 100644 --- a/src/Xml/Dom/Locator/Node/value.php +++ b/src/Xml/Dom/Locator/Node/value.php @@ -19,8 +19,5 @@ */ function value(\DOM\Node $node, TypeInterface $type) { - // TODO : nodeValue did entity substitution - // TODO : nodeValue returns null for elements - // TODO : How to best deal with this? - return $type->coerce($node->textContent ?? ''); + return $type->coerce($node->substitutedNodeValue ?? ''); } diff --git a/src/Xml/Dom/Locator/Xmlns/linked_namespaces.php b/src/Xml/Dom/Locator/Xmlns/linked_namespaces.php index 8b7ef71..1879d71 100644 --- a/src/Xml/Dom/Locator/Xmlns/linked_namespaces.php +++ b/src/Xml/Dom/Locator/Xmlns/linked_namespaces.php @@ -5,21 +5,29 @@ namespace VeeWee\Xml\Dom\Locator\Xmlns; use \DOM\NameSpaceNode; -use \DOM\Node; use InvalidArgumentException; -use VeeWee\Xml\Dom\Collection\NodeList; -use VeeWee\Xml\Dom\Xpath; use VeeWee\Xml\Exception\RuntimeException; +use function Psl\Dict\merge; +use function VeeWee\Xml\Dom\Locator\Attribute\xmlns_attributes_list; /** - * @return NodeList<\DOM\NameSpaceNode> + * @return array - A list of prefix => namespace of all directly linked namespaces. * * @throws RuntimeException * @throws InvalidArgumentException */ -function linked_namespaces(\DOM\Node $node): NodeList +function linked_namespaces(\DOM\Element $node): array { - $xpath = Xpath::fromUnsafeNode($node); - - return $xpath->query('./namespace::*', $node)->expectAllOfType(\DOM\NameSpaceNode::class); + return xmlns_attributes_list($node) + ->reduce( + /** + * @param array $result + * @return array + */ + static fn (array $result, \DOM\Attr $attribute): array => merge( + $result, + [($attribute->prefix !== null ? $attribute->localName : '') => $attribute->value] + ), + [] + ); } diff --git a/src/Xml/Dom/Locator/Xmlns/recursive_linked_namespaces.php b/src/Xml/Dom/Locator/Xmlns/recursive_linked_namespaces.php index 2f3c766..25ab846 100644 --- a/src/Xml/Dom/Locator/Xmlns/recursive_linked_namespaces.php +++ b/src/Xml/Dom/Locator/Xmlns/recursive_linked_namespaces.php @@ -5,21 +5,16 @@ namespace VeeWee\Xml\Dom\Locator\Xmlns; use \DOM\NameSpaceNode; -use \DOM\Node; use InvalidArgumentException; -use VeeWee\Xml\Dom\Collection\NodeList; -use VeeWee\Xml\Dom\Xpath; use VeeWee\Xml\Exception\RuntimeException; /** - * @return NodeList<\DOM\NameSpaceNode> + * @return array * * @throws RuntimeException * @throws InvalidArgumentException */ -function recursive_linked_namespaces(\DOM\Node $node): NodeList +function recursive_linked_namespaces(\DOM\Element $node): array { - $xpath = Xpath::fromUnsafeNode($node); - - return $xpath->query('.//namespace::*', $node)->expectAllOfType(\DOM\NameSpaceNode::class); + return $node->getInScopeNamespaces(); } diff --git a/src/Xml/Dom/Manipulator/Attribute/rename.php b/src/Xml/Dom/Manipulator/Attribute/rename.php index 9fa2cc1..e3b2ea7 100644 --- a/src/Xml/Dom/Manipulator/Attribute/rename.php +++ b/src/Xml/Dom/Manipulator/Attribute/rename.php @@ -6,40 +6,18 @@ use \DOM\Attr; use VeeWee\Xml\Exception\RuntimeException; -use function VeeWee\Xml\Dom\Builder\attribute; -use function VeeWee\Xml\Dom\Builder\namespaced_attribute; -use function VeeWee\Xml\Dom\Locator\Element\parent_element; -use function VeeWee\Xml\Dom\Manipulator\Node\remove; -use function VeeWee\Xml\Dom\Predicate\is_attribute; +use function Psl\Fun\tap; +use function VeeWee\Xml\Dom\Manipulator\Xmlns\rename as rename_xmlns_attribute; +use function VeeWee\Xml\Dom\Predicate\is_xmlns_attribute; +use function VeeWee\Xml\ErrorHandling\disallow_issues; /** * @throws RuntimeException */ function rename(\DOM\Attr $target, string $newQName, ?string $newNamespaceURI = null): \DOM\Attr { - $element = parent_element($target); - $namespace = $newNamespaceURI ?? $target->namespaceURI; - $value = $target->nodeValue ?? ''; - - $builder = $namespace !== null - ? namespaced_attribute($namespace, $newQName, $value) - : attribute($newQName, $value); - - remove($target); - $builder($element); - - // If the namespace prefix of the target still exists, PHP will fallback into using that prefix. - // In that case it is not possible to fully rename an attribute. - // If you want to rename a prefix, you'll have to remove the xmlns first - // or make sure the new prefix is found first for the given namespace URI. - $result = $element->getAttributeNode($newQName); - - /** @psalm-suppress TypeDoesNotContainType - It can actually be null if the exact node name is not found. */ - if (!$result || !is_attribute($result)) { - throw RuntimeException::withMessage( - 'Unable to rename attribute '.$target->nodeName.' into '.$newQName.'. You might need to swap xmlns prefix first!' - ); - } - - return $result; + return disallow_issues(static fn (): \DOM\Attr => match(true) { + is_xmlns_attribute($target) => rename_xmlns_attribute($target, $newQName), + default => tap(fn () => $target->rename($newNamespaceURI, $newQName))($target) + }); } 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 4392123..b2a32f7 100644 --- a/src/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php +++ b/src/Xml/Dom/Manipulator/Element/copy_named_xmlns_attributes.php @@ -4,20 +4,18 @@ 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); + xmlns_attribute($xmlns->localName, $xmlns->value)($target); } }); } diff --git a/src/Xml/Dom/Manipulator/Element/rename.php b/src/Xml/Dom/Manipulator/Element/rename.php index dca0f63..58b4aff 100644 --- a/src/Xml/Dom/Manipulator/Element/rename.php +++ b/src/Xml/Dom/Manipulator/Element/rename.php @@ -4,51 +4,31 @@ 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; -use function VeeWee\Xml\Dom\Builder\xmlns_attribute; -use function VeeWee\Xml\Dom\Locator\Attribute\attributes_list; -use function VeeWee\Xml\Dom\Locator\Attribute\xmlns_attributes_list; -use function VeeWee\Xml\Dom\Locator\Element\parent_element; -use function VeeWee\Xml\Dom\Locator\Node\children; -use function VeeWee\Xml\Dom\Manipulator\append; -use function VeeWee\Xml\Dom\Predicate\is_default_xmlns_attribute; +use function VeeWee\Xml\ErrorHandling\disallow_issues; /** * @throws RuntimeException */ function rename(\DOM\Element $target, string $newQName, ?string $newNamespaceURI = null): \DOM\Element { - $isRootElement = $target === $target->ownerDocument->documentElement; - $parent = $isRootElement ? $target->ownerDocument : parent_element($target); - $namespace = $newNamespaceURI ?? $target->namespaceURI; - $builder = $namespace !== null - ? namespaced_element($namespace, $newQName) - : element($newQName); - - $newElement = $builder($parent); - - append(...children($target))($newElement); - - xmlns_attributes_list($target)->forEach( - static function (\DOM\NameSpaceNode $attribute) use ($target, $newElement): void { - if (is_default_xmlns_attribute($attribute) || $target->prefix === $attribute->prefix) { - return; - } - xmlns_attribute($attribute->prefix, $attribute->namespaceURI)($newElement); - } - ); - - attributes_list($target)->forEach( - static function (\DOM\Attr $attribute) use ($target, $newElement): void { - $target->removeAttributeNode($attribute); - $newElement->setAttributeNode($attribute); - } - ); - - $parent->replaceChild($newElement, $target); - - return $newElement; + $parts = explode(':', $newQName, 2); + $newPrefix = $parts[0] ?? ''; + + /* + * To make sure the new namespace prefix is being used, we need to apply an additional xmlns declaration chech: + * This is due to a particular rule in the XML serialization spec, + * that enforces that a namespaceURI on an element is only associated with exactly one prefix. + * See the note of bullet point 2 of https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml. + * + * If you rename a:xx to b:xx an xmlns:b="xx" attribute gets added at the end, but prefix a: will still be serialized. + * So in this case, we need to remove the xmlns declaration first. + */ + if ($newPrefix && $newPrefix !== $target->prefix && $target->hasAttribute('xmlns:'.$target->prefix)) { + $target->removeAttribute('xmlns:'.$target->prefix); + } + + disallow_issues(static fn () => $target->rename($newNamespaceURI, $newQName)); + + return $target; } diff --git a/src/Xml/Dom/Manipulator/Node/remove_namespace.php b/src/Xml/Dom/Manipulator/Node/remove_namespace.php index 169a8d1..a9c7f75 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/Node/rename.php b/src/Xml/Dom/Manipulator/Node/rename.php index 1265175..a5694d8 100644 --- a/src/Xml/Dom/Manipulator/Node/rename.php +++ b/src/Xml/Dom/Manipulator/Node/rename.php @@ -18,13 +18,9 @@ */ function rename(\DOM\Node $target, string $newQName, ?string $newNamespaceURI = null): \DOM\Node { - if (is_attribute($target)) { - return rename_attribute($target, $newQName, $newNamespaceURI); - } - - if (is_element($target)) { - return rename_element($target, $newQName, $newNamespaceURI); - } - - throw RuntimeException::withMessage('Can not rename dom node with type ' . get_class($target)); + return match(true) { + is_attribute($target) => rename_attribute($target, $newQName, $newNamespaceURI), + is_element($target) => rename_element($target, $newQName, $newNamespaceURI), + default => throw RuntimeException::withMessage('Can not rename dom node with type ' . get_class($target)) + }; } diff --git a/src/Xml/Dom/Manipulator/Xmlns/rename.php b/src/Xml/Dom/Manipulator/Xmlns/rename.php index 086a455..1580aeb 100644 --- a/src/Xml/Dom/Manipulator/Xmlns/rename.php +++ b/src/Xml/Dom/Manipulator/Xmlns/rename.php @@ -4,111 +4,16 @@ namespace VeeWee\Xml\Dom\Manipulator\Xmlns; -use \DOM\XMLDocument; -use \DOM\Element; -use \DOM\NameSpaceNode; -use \DOM\Node; -use VeeWee\Xml\Dom\Collection\NodeList; -use VeeWee\Xml\Dom\Xpath; use VeeWee\Xml\Exception\RuntimeException; -use function Psl\invariant; -use function Psl\Type\non_empty_string; -use function sprintf; -use function VeeWee\Xml\Dom\Builder\xmlns_attribute; -use function VeeWee\Xml\Dom\Locator\Attribute\attributes_list; -use function VeeWee\Xml\Dom\Locator\Attribute\xmlns_attributes_list; -use function VeeWee\Xml\Dom\Manipulator\Node\remove_namespace; -use function VeeWee\Xml\Dom\Manipulator\Node\rename as rename_node; -use function VeeWee\Xml\Dom\Predicate\is_attribute; -use function VeeWee\Xml\Dom\Predicate\is_element; +use VeeWee\Xml\Xmlns\Xmlns; +use function VeeWee\Xml\ErrorHandling\disallow_issues; /** * @throws RuntimeException */ -function rename(\DOM\XMLDocument $document, string $namespaceURI, string $newPrefix): void +function rename(\DOM\Attr $target, string $newQName): \DOM\Attr { - // Check for prefix collisions - $existingUri = $document->lookupNamespaceURI($newPrefix); - if ($existingUri !== null && $existingUri !== $namespaceURI) { - throw RuntimeException::withMessage( - sprintf( - 'Cannot rename the namespace uri %s because the prefix %s is already linked to uri %s', - $namespaceURI, - $newPrefix, - $existingUri - ) - ); - } + disallow_issues(static fn () => $target->rename(Xmlns::xmlns()->value(), $newQName)); - $xpath = Xpath::fromUnsafeNode($document); - $predicate = static fn (\DOM\Node $node): bool - => $node->namespaceURI === $namespaceURI && $node->prefix !== $newPrefix; - - // Fetch all nodes (attributes and elements) linked to the given namespace and the nodes that declare namespaces. - // The sort order is important: - // Make sure to set the deepest DOM element first in the list - // The attributes need to be dealt with as last, - // otherwise XMLNS namespace will be removed again after dealing with the elements that declare the xmlns. - $linkedNodes = $xpath->query( - sprintf('//*[namespace-uri()=\'%1$s\' or @*[namespace-uri()=\'%1$s\'] or namespace::*]', $namespaceURI) - )->expectAllOfType(\DOM\Element::class)->reduce( - static fn (NodeList $list, \DOM\Element $element): NodeList - => new NodeList( - ...[$element], - ...$list, - ...attributes_list($element)->filter($predicate), - ), - new NodeList() - ); - - // Add new xmlns to root node - $root = $document->documentElement; - xmlns_attribute($newPrefix, $namespaceURI)($root); - - // Go through the linked nodes and remove all matching xmlns attributes - // Finally rename the node in order to use the new prefix. - $linkedNodes->forEach( - static function (\DOM\Node $node) use ($namespaceURI, $newPrefix, $predicate, $root): void { - // Wrapped in a closure so that psalm knows it all... - $newQname = static fn (\DOM\Node $node): string => $newPrefix.':'.non_empty_string()->assert($node->localName); - - if (is_attribute($node)) { - rename_node($node, $newQname($node), $namespaceURI); - return; - } - - // @codeCoverageIgnoreStart - if (!is_element($node)) { - return; - } - // @codeCoverageIgnoreEnd - - // Remove old xmlns declarations: - $namespaceNodes = xmlns_attributes_list($node) - ->filter( - static fn (\DOM\NameSpaceNode $xmlns): bool - => $xmlns->namespaceURI === $namespaceURI && $xmlns->prefix !== $newPrefix - ); - - foreach ($namespaceNodes as $xmlns) { - // Before removing the default xmlns on the root node - // We need to make sure to rename it to the new namespace - // Otherwise the namespace will be lost! - if ($node === $root && $predicate($node)) { - // The root node renaming can result in a new \DOM\Node. - // Make sure to use this new node to avoid issues with e.g. duplicate namespace declarations. - $node = rename_node($node, $newQname($node), $namespaceURI); - invariant(is_element($node), 'Expected the root node to be a DOM element'); - } - - remove_namespace($xmlns, $node); - } - - // If the DOM element is part of the namespace URI, rename it! - // (Remember: this function also accepts regular DOM elements with xmlns declarations that are not linked to the namespace) - if ($predicate($node)) { - rename_node($node, $newQname($node), $namespaceURI); - } - } - ); + return $target; } diff --git a/src/Xml/Dom/Manipulator/Xmlns/rename_element_namespace.php b/src/Xml/Dom/Manipulator/Xmlns/rename_element_namespace.php new file mode 100644 index 0000000..27444be --- /dev/null +++ b/src/Xml/Dom/Manipulator/Xmlns/rename_element_namespace.php @@ -0,0 +1,33 @@ +childNodes as $child) { + if (is_element($child)) { + rename_element_namespace($child, $namespaceURI, $newPrefix); + } + } + + foreach ($root->attributes as $attr) { + match (true) { + $attr->namespaceURI === $namespaceURI => $attr->rename($namespaceURI, $newPrefix . ':' . $attr->localName), + is_xmlns_attribute($attr) && $attr->value === $namespaceURI => $attr->rename($attr->namespaceURI, 'xmlns:' . $newPrefix), + default => null, + }; + } + + if ($root->namespaceURI === $namespaceURI) { + $root->rename($namespaceURI, $newPrefix . ':' . $root->localName); + } +} diff --git a/src/Xml/Dom/Manipulator/append.php b/src/Xml/Dom/Manipulator/append.php index 922af7c..e25c5f4 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 7aac7fe..3deffed 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 === ''; + return is_xmlns_attribute($node) && $node->prefix === null; } diff --git a/src/Xml/Dom/Predicate/is_document_element.php b/src/Xml/Dom/Predicate/is_document_element.php index 1b5b22c..be8694a 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 422ad07..ac752ca 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/Action/RenameNode.php b/src/Xml/Dom/Traverser/Action/RenameNode.php index 919685d..03bc1e6 100644 --- a/src/Xml/Dom/Traverser/Action/RenameNode.php +++ b/src/Xml/Dom/Traverser/Action/RenameNode.php @@ -12,7 +12,8 @@ final class RenameNode implements Action { public function __construct( - private string $newQName + private string $newQName, + private ?string $newNamespaceURI = null, ) { } @@ -21,6 +22,6 @@ public function __construct( */ public function __invoke(\DOM\Node $currentNode): void { - rename($currentNode, $this->newQName); + rename($currentNode, $this->newQName, $this->newNamespaceURI); } } diff --git a/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php b/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php index 4551ad0..0da7c9a 100644 --- a/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php +++ b/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php @@ -3,23 +3,22 @@ namespace VeeWee\Xml\Dom\Traverser\Visitor; -use \DOM\NameSpaceNode; -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_attribute; 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 | \DOM\Element): 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 +34,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 | \DOM\Element $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 | \DOM\Element $node): bool => $node->prefix === null ); } @@ -52,7 +51,10 @@ 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 | \DOM\Element $node): bool => match(true) { + is_xmlns_attribute($node) => contains($prefixes, $node->prefix !== null ? $node->localName : ''), + default => contains($prefixes, $node->prefix ?? '') + } ); } @@ -62,31 +64,56 @@ 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 | \DOM\Element $node): bool => match(true) { + is_xmlns_attribute($node) => contains($URIs, $node->value), + default => contains($URIs, $node->namespaceURI), + } ); } + public function onNodeEnter(\DOM\Node $node): Action + { + if (is_xmlns_attribute($node)) { + return new Action\Noop(); + } + + if (!$this->shouldDealWithNode($node)) { + return new Action\Noop(); + } + + return new Action\RenameNode($node->localName, null); + } + /** * @throws RuntimeException */ public function onNodeLeave(\DOM\Node $node): Action { - if (!is_element($node)) { + if (!is_xmlns_attribute($node)) { return new Action\Noop(); } - $namespaces = xmlns_attributes_list($node); - if ($this->filter !== null) { - $namespaces = $namespaces->filter($this->filter); + if (!$this->shouldDealWithNode($node)) { + return new Action\Noop(); + } + + return new Action\RemoveNode(); + } + + private function shouldDealWithNode(\DOM\Node $node): bool + { + if (!is_element($node) && !is_attribute($node)) { + return false; + } + + if ($node->namespaceURI === null) { + return false; } - foreach ($namespaces as $namespace) { - $node->removeAttributeNS( - $namespace->namespaceURI, - $namespace->prefix - ); + if ($this->filter === null) { + return true; } - return new Action\Noop(); + return ($this->filter)($node); } } diff --git a/src/Xml/Dom/Traverser/Visitor/SortAttributes.php b/src/Xml/Dom/Traverser/Visitor/SortAttributes.php index ef166ab..95066f3 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 0eaa804..af1f859 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 54dd1c8..5c70b48 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/src/bootstrap.php b/src/bootstrap.php index ce099e0..c11c580 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -66,6 +66,7 @@ 'Xml\Dom\Manipulator\Node\replace_by_external_node' => __DIR__.'/Xml/Dom/Manipulator/Node/replace_by_external_node.php', 'Xml\Dom\Manipulator\Node\replace_by_external_nodes' => __DIR__.'/Xml/Dom/Manipulator/Node/replace_by_external_nodes.php', 'Xml\Dom\Manipulator\Xmlns\rename' => __DIR__.'/Xml/Dom/Manipulator/Xmlns/rename.php', + 'Xml\Dom\Manipulator\Xmlns\rename_element_namespace' => __DIR__.'/Xml/Dom/Manipulator/Xmlns/rename_element_namespace.php', 'Xml\Dom\Manipulator\append' => __DIR__.'/Xml/Dom/Manipulator/append.php', 'Xml\Dom\Mapper\xml_string' => __DIR__.'/Xml/Dom/Mapper/xml_string.php', 'Xml\Dom\Mapper\xslt_template' => __DIR__.'/Xml/Dom/Mapper/xslt_template.php', diff --git a/stubs/DOM.phpstub b/stubs/DOM.phpstub index 9fe7fc4..3b736f0 100644 --- a/stubs/DOM.phpstub +++ b/stubs/DOM.phpstub @@ -509,7 +509,7 @@ namespace public function replaceChildren(...$nodes): void {} } - class DOMNodeList implements \IteratorAggregate, \Countable + class DOMNodeList implements IteratorAggregate, Countable { /** @readonly */ public int $length; @@ -919,7 +919,7 @@ namespace public function splitText(int $offset) {} } - class DOMNamedNodeMap implements \IteratorAggregate, \Countable + class DOMNamedNodeMap implements IteratorAggregate, Countable { /** @readonly */ public int $length; @@ -1017,6 +1017,8 @@ namespace public function registerPhpFunctions(string|array|null $restrict = null): void {} public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {} + + public static function quote(string $str): string {} } #endif @@ -1122,6 +1124,19 @@ namespace DOM public function replaceWith(Node|string ...$nodes): void; } + /** + * @strict-properties + * @not-serializable + */ + class Implementation + { + public function createDocumentType(string $qualifiedName, string $publicId, string $systemId): DocumentType {} + + public function createDocument(?string $namespace, string $qualifiedName, ?DocumentType $doctype = null): XMLDocument {} + + public function createHTMLDocument(?string $title = null): HTMLDocument {} + } + /** @strict-properties */ class Node { @@ -1203,7 +1218,7 @@ namespace DOM public function __wakeup(): void {} } - class NodeList implements \IteratorAggregate, \Countable + class NodeList implements IteratorAggregate, Countable { /** @readonly */ public int $length; @@ -1221,7 +1236,7 @@ namespace DOM public function item(int $index): ?Node {} } - class NamedNodeMap implements \IteratorAggregate, \Countable + class NamedNodeMap implements IteratorAggregate, Countable { /** @readonly */ public int $length; @@ -1243,7 +1258,7 @@ namespace DOM public function getIterator(): \Iterator {} } - class DTDNamedNodeMap implements \IteratorAggregate, \Countable + class DTDNamedNodeMap implements IteratorAggregate, Countable { /** @readonly */ public int $length; @@ -1265,7 +1280,7 @@ namespace DOM public function getIterator(): \Iterator {} } - class HTMLCollection implements \IteratorAggregate, \Countable + class HTMLCollection implements IteratorAggregate, Countable { /** @readonly */ public int $length; @@ -1370,6 +1385,12 @@ namespace DOM public function prepend(Node|string ...$nodes): void {} /** @implementation-alias DOMElement::replaceChildren */ public function replaceChildren(Node|string ...$nodes): void {} + + public string $substitutedNodeValue; + + public function getInScopeNamespaces(): array {} + + public function rename(?string $namespaceURI, string $qualifiedName): void {} } class Attr extends Node @@ -1392,6 +1413,9 @@ namespace DOM /** @implementation-alias DOMAttr::isId */ public function isId(): bool {} + + /** @implementation-alias DOM\Element::rename */ + public function rename(?string $namespaceURI, string $qualifiedName): void {} } class CharacterData extends Node implements ChildNode @@ -1510,6 +1534,8 @@ namespace DOM abstract class Document extends Node implements ParentNode { + /** @readonly */ + public Implementation $implementation; /** @readonly */ public string $URL; /** @readonly */ @@ -1654,6 +1680,9 @@ namespace DOM /** @implementation-alias DOMXPath::registerPhpFunctionNS */ public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {} + + /** @implementation-alias DOMXPath::quote */ + public static function quote(string $str): string {} } #endif diff --git a/tests/Xml/Dom/Configurator/PrettyPrintTest.php b/tests/Xml/Dom/Configurator/PrettyPrintTest.php index 2933760..05a87d6 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 9fd1e13..960e437 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/Locator/Attribute/AttributesListTest.php b/tests/Xml/Dom/Locator/Attribute/AttributesListTest.php index 87c2503..7e6d3b4 100644 --- a/tests/Xml/Dom/Locator/Attribute/AttributesListTest.php +++ b/tests/Xml/Dom/Locator/Attribute/AttributesListTest.php @@ -35,6 +35,8 @@ public function provideTestCases() yield [$doc->documentElement, [ $doc->documentElement->getAttributeNode('attr1'), $doc->documentElement->getAttributeNode('attr2'), + $doc->documentElement->getAttributeNode('xmlns'), + $doc->documentElement->getAttributeNode('xmlns:yo'), ]]; yield [$doc->documentElement->firstElementChild, [ $doc->documentElement->firstElementChild->getAttributeNode('attr'), diff --git a/tests/Xml/Dom/Locator/Element/ParentElementTest.php b/tests/Xml/Dom/Locator/Element/ParentElementTest.php index 664e741..4536dac 100644 --- a/tests/Xml/Dom/Locator/Element/ParentElementTest.php +++ b/tests/Xml/Dom/Locator/Element/ParentElementTest.php @@ -34,7 +34,7 @@ public function test_it_can_detect_parents(): void static::assertSame($hello, $domdoc->documentElement); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Can not find parent element for \DOM\Element hello'); + $this->expectExceptionMessage('Can not find parent element for DOM\Element hello'); parent_element($hello); } } diff --git a/tests/Xml/Dom/Locator/Xmlns/LinkedNamespacesTest.php b/tests/Xml/Dom/Locator/Xmlns/LinkedNamespacesTest.php index 60fa809..c0e7a9e 100644 --- a/tests/Xml/Dom/Locator/Xmlns/LinkedNamespacesTest.php +++ b/tests/Xml/Dom/Locator/Xmlns/LinkedNamespacesTest.php @@ -6,10 +6,7 @@ use \DOM\NameSpaceNode; use PHPUnit\Framework\TestCase; -use VeeWee\Xml\Dom\Collection\NodeList; use VeeWee\Xml\Dom\Document; -use function Psl\Dict\merge; -use function Psl\Iter\reduce; use function VeeWee\Xml\Dom\Locator\document_element; use function VeeWee\Xml\Dom\Locator\Xmlns\linked_namespaces; @@ -24,20 +21,13 @@ public function test_it_can_detect_linked_namespaces(): void XML; $element = Document::fromXmlString($xml)->locate(document_element()); - $parse = static fn (NodeList $list): array => reduce( - [...$list], - static fn (array $map, \DOM\NameSpaceNode $node) => merge($map, [$node->localName => $node->namespaceURI]), - [] - ); - static::assertSame( [ - 'xml' => 'http://www.w3.org/XML/1998/namespace', + '' => 'http://hello.com', 'world' => 'http://world.com', - 'xmlns' => 'http://hello.com', ], - $parse(linked_namespaces($element)) + linked_namespaces($element) ); - static::assertSame([], $parse(linked_namespaces($element->childNodes->item(0)))); + static::assertSame([], linked_namespaces($element->firstElementChild)); } } diff --git a/tests/Xml/Dom/Locator/Xmlns/RecursiveLinkedNamespacesTest.php b/tests/Xml/Dom/Locator/Xmlns/RecursiveLinkedNamespacesTest.php index 35fab90..407cc08 100644 --- a/tests/Xml/Dom/Locator/Xmlns/RecursiveLinkedNamespacesTest.php +++ b/tests/Xml/Dom/Locator/Xmlns/RecursiveLinkedNamespacesTest.php @@ -22,29 +22,21 @@ public function test_it_can_detect_recursively_linked_namespaces(): void 1 XML; - $element = Document::fromXmlString($xml)->locate(document_element()); - - $parse = static fn (NodeList $list): array => reduce( - [...$list], - static fn (array $map, \DOM\NameSpaceNode $node) => merge($map, [$node->localName => $node->namespaceURI]), - [] - ); + $element = Document::fromXmlString($xml)->locateDocumentElement(); static::assertSame( [ - 'xml' => 'http://www.w3.org/XML/1998/namespace', - 'xmlns' => 'http://hello.com', + '' => 'http://hello.com', 'world' => 'http://world.com', ], - $parse(recursive_linked_namespaces($element)) + recursive_linked_namespaces($element) ); static::assertSame( [ - 'xml' => 'http://www.w3.org/XML/1998/namespace', - 'xmlns' => 'http://hello.com', + '' => 'http://hello.com', 'world' => 'http://world.com', ], - $parse(recursive_linked_namespaces($element->firstElementChild)) + recursive_linked_namespaces($element->firstElementChild) ); } } diff --git a/tests/Xml/Dom/Manipulator/Node/RenameTest.php b/tests/Xml/Dom/Manipulator/Node/RenameTest.php index 310e98c..7c912f7 100644 --- a/tests/Xml/Dom/Manipulator/Node/RenameTest.php +++ b/tests/Xml/Dom/Manipulator/Node/RenameTest.php @@ -64,11 +64,14 @@ public function test_it_can_rename_an_element_with_attributes(): void public function test_it_can_rename_an_element_with_namespace(): void { $doc = Document::fromXmlString(''); - $node = $doc->xpath(namespaces(['ok' => 'http://ok']))->querySingle('//ok:item'); + $node = $doc->locateDocumentElement()->firstElementChild; - $result = rename($node, 'thing'); + $result = rename($node, 'thing', $node->namespaceURI); - static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); + static::assertXmlStringEqualsXmlString( + '', + $doc->toXmlString() + ); static::assertSame($doc->xpath(namespaces(['ok' => 'http://ok']))->querySingle('//ok:thing'), $result); } @@ -88,7 +91,7 @@ public function test_it_can_rename_an_element_with_prefixed_namespace(): void $doc = Document::fromXmlString(''); $node = $doc->xpath(namespaces(['a' => 'http://ok']))->querySingle('//a:item'); - $result = rename($node, 'a:thing'); + $result = rename($node, 'a:thing', $node->namespaceURI); static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); static::assertSame($doc->xpath(namespaces(['a' => 'http://ok']))->querySingle('//a:thing'), $result); @@ -108,28 +111,28 @@ public function test_it_can_rename_an_element_with_prefixed_namespace_and_new_ur public function test_it_can_rename_an_element_and_drop_prefix(): void { $doc = Document::fromXmlString(''); - $node = $doc->xpath(namespaces(['a' => 'http://ok']))->querySingle('//a:item'); + $node = $doc->locateDocumentElement()->firstElementChild; $result = rename($node, 'thing'); static::assertSame( - $doc->reconfigure(comparable())->toXmlString(), - Document::fromXmlString( - '', - comparable() - )->toXmlString(), + '', + $doc->stringifyDocumentElement(), ); - static::assertSame($doc->xpath(namespaces(['a' => 'http://ok']))->querySingle('//a:thing'), $result); + static::assertSame($doc->xpath()->querySingle('thing'), $result); } public function test_it_can_rename_an_element_prefix(): void { $doc = Document::fromXmlString(''); - $node = $doc->xpath(namespaces(['a' => 'http://ok']))->querySingle('//a:item'); + $node = $doc->locateDocumentElement()->firstElementChild; - $result = rename($node, 'b:thing'); + $result = rename($node, 'b:thing', $node->namespaceURI); - static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); + static::assertSame( + '', + $doc->stringifyDocumentElement(), + ); static::assertSame($doc->xpath(namespaces(['b' => 'http://ok']))->querySingle('//b:thing'), $result); } @@ -151,7 +154,7 @@ public function test_it_can_rename_namespaced_attributes(): void $root = $doc->map(document_element()); $node = $root->getAttributeNode('a:who'); - $result = rename($node, 'a:you'); + $result = rename($node, 'a:you', $node->namespaceURI); static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); static::assertSame($root->getAttributeNode('a:you'), $result); @@ -168,7 +171,7 @@ public function test_it_can_not_rename_namespaced_attribute_prefix_when_the_xmln $node = $root->getAttributeNode('a:who'); $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Unable to rename attribute a:who into b:you'); + $this->expectExceptionMessage('Namespace Error'); rename($node, 'b:you'); } @@ -178,13 +181,10 @@ public function test_it_can_rename_namespaced_attribute_prefix(): void $doc = Document::fromXmlString(''); $root = $doc->map(document_element()); $node = $root->getAttributeNode('a:who'); - - // You'll need to manually rename the namespace $ns = $root->getAttributeNode('xmlns:a'); - remove_namespace($ns, $root); - xmlns_attribute('b', $ns->namespaceURI)($root); - $result = rename($node, 'b:you'); + rename($ns, 'xmlns:b', $ns->value); + $result = rename($node, 'b:you', $node->namespaceURI); static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); static::assertSame($root->getAttributeNode('b:you'), $result); @@ -223,7 +223,7 @@ public function test_it_can_rename_root_element(): void static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); static::assertSame($doc->map(document_element()), $result); - static::assertNotSame($root, $result); + static::assertSame($root, $result); } public function test_it_can_rename_root_element_with_namespace(): void @@ -235,7 +235,7 @@ public function test_it_can_rename_root_element_with_namespace(): void static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); static::assertSame($doc->map(document_element()), $result); - static::assertNotSame($root, $result); + static::assertSame($root, $result); } public function test_it_can_rename_root_element_with_default_namespace(): void @@ -245,8 +245,8 @@ public function test_it_can_rename_root_element_with_default_namespace(): void $result = rename($root, 'goodbye', 'https://foo'); - static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); + static::assertXmlStringEqualsXmlString($doc->toXmlString(), ''); static::assertSame($doc->map(document_element()), $result); - static::assertNotSame($root, $result); + static::assertSame($root, $result); } } diff --git a/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php b/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php index 09b161a..c94c0f0 100644 --- a/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php +++ b/tests/Xml/Dom/Manipulator/Xmlns/RenameTest.php @@ -6,41 +6,30 @@ use PHPUnit\Framework\TestCase; use VeeWee\Xml\Dom\Document; -use VeeWee\Xml\Exception\RuntimeException; -use function VeeWee\Xml\Dom\Manipulator\Xmlns\rename; -use function VeeWee\Xml\Dom\Mapper\xml_string; +use function VeeWee\Xml\Dom\Manipulator\Xmlns\rename_element_namespace; final class RenameTest extends TestCase { /** * @dataProvider provideXmls + * @param callable(): \Dom\Attr $targetLocator */ public function test_it_can_rename_namespaces(string $input, string $expected): void { - $document = Document::fromXmlString($input)->toUnsafeDocument(); - rename($document, 'http://replace', 'foo'); + $document = Document::fromXmlString($input); + $target = $document->locateDocumentElement(); + rename_element_namespace($target, 'http://replace', 'foo'); - $actual = xml_string()($document->documentElement); + $actual = $document->stringifyDocumentElement(); static::assertSame($expected, $actual); } - - public function test_it_can_not_rename_existing_prefix_to_other_uri(): void - { - $document = Document::fromXmlString('')->toUnsafeDocument(); - - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Cannot rename the namespace uri http://replace because the prefix ns1 is already linked to uri http://ok'); - rename($document, 'http://replace', 'ns1'); - } - public function provideXmls() { - yield 'no-action' => [ - '', + yield 'simple' => [ + '', '', ]; - yield 'namespaced-root-and-child' => [ << @@ -52,7 +41,7 @@ public function provideXmls() << - + EOXML, @@ -69,7 +58,7 @@ public function provideXmls() << - + EOXML, @@ -84,8 +73,8 @@ public function provideXmls() EOXML, << - + + @@ -99,8 +88,8 @@ public function provideXmls() EOXML, << - + + EOXML, ]; @@ -128,7 +117,7 @@ public function provideXmls() EOXML, << - + diff --git a/tests/Xml/Encoding/EncodingTest.php b/tests/Xml/Encoding/EncodingTest.php index 3b923c0..30a961f 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' => [