Skip to content

Commit

Permalink
New namspaces system
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Mar 10, 2024
1 parent 74328c7 commit 26d0875
Show file tree
Hide file tree
Showing 29 changed files with 174 additions and 201 deletions.
4 changes: 2 additions & 2 deletions docs/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<DOMNameSpaceNode> $namespaces */
/** @var array<string, string> $namespaces - A lookup of prefix -> namespace */
$namespaces = linked_namespaces($element);
```

Expand All @@ -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<DOMNameSpaceNode> $namespaces */
/** @var array<string, string> $namespaces - A lookup of prefix -> namespace */
$namespaces = recursive_linked_namespaces($element);
```

Expand Down
5 changes: 1 addition & 4 deletions src/Xml/Dom/Builder/value.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@

use Closure;
use \DOM\Element;
use function VeeWee\Xml\Dom\Locator\Node\detect_document;

/**
* @return Closure(\DOM\Element): \DOM\Element
*/
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;
};
Expand Down
16 changes: 11 additions & 5 deletions src/Xml/Dom/Configurator/pretty_print.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@
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
*/
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;
};
}
15 changes: 11 additions & 4 deletions src/Xml/Dom/Configurator/trim_spaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@

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
*/
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;
};
}
15 changes: 4 additions & 11 deletions src/Xml/Dom/Locator/Attribute/xmlns_attributes_list.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
5 changes: 1 addition & 4 deletions src/Xml/Dom/Locator/Node/value.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '');
}
24 changes: 16 additions & 8 deletions src/Xml/Dom/Locator/Xmlns/linked_namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> - 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<string, string> $result
* @return array<string, string>
*/
static fn (array $result, \DOM\Attr $attribute): array => merge(
$result,
[($attribute->prefix !== null ? $attribute->localName : '') => $attribute->value]
),
[]
);
}
11 changes: 3 additions & 8 deletions src/Xml/Dom/Locator/Xmlns/recursive_linked_namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>
*
* @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();
}
27 changes: 3 additions & 24 deletions src/Xml/Dom/Manipulator/Attribute/rename.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
32 changes: 3 additions & 29 deletions src/Xml/Dom/Manipulator/Element/rename.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
8 changes: 3 additions & 5 deletions src/Xml/Dom/Manipulator/Node/remove_namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,22 @@

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;

/**
* @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'
);

Expand Down
2 changes: 1 addition & 1 deletion src/Xml/Dom/Manipulator/Xmlns/rename.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand Down
10 changes: 10 additions & 0 deletions src/Xml/Dom/Manipulator/append.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
3 changes: 1 addition & 2 deletions src/Xml/Dom/Predicate/is_default_xmlns_attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 === '';
}
3 changes: 1 addition & 2 deletions src/Xml/Dom/Predicate/is_document_element.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading

0 comments on commit 26d0875

Please sign in to comment.