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..cb131e6 100644 --- a/src/Xml/Dom/Manipulator/Attribute/rename.php +++ b/src/Xml/Dom/Manipulator/Attribute/rename.php @@ -11,35 +11,14 @@ 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 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 ?? ''; + disallow_issues(static fn () => $target->rename($newNamespaceURI, $newQName)); - $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 $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..cd96ac1 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 dca0f63..e4ce032 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; @@ -15,40 +14,15 @@ 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); + disallow_issues(static fn () => $target->rename($newNamespaceURI, $newQName)); - 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; + 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/Xmlns/rename.php b/src/Xml/Dom/Manipulator/Xmlns/rename.php index 086a455..fd1f9fa 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 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..b58b386 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 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/Visitor/RemoveNamespaces.php b/src/Xml/Dom/Traverser/Visitor/RemoveNamespaces.php index 4551ad0..226c61a 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 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/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/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..da5c147 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); } @@ -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..9848662 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 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' => [