Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,18 @@ element('foo',
<foo hello="world" bar="baz" />
```

#### default_xmlns_attribute

Operates on a `Dom\Element` and adds a default xmlns attribute.
Given how XML serialization works in PHP, this function only works on already prefixed + namespaced elements:

```php
use function VeeWee\Xml\Dom\Builder\namespaced_element;
use function VeeWee\Xml\Dom\Builder\default_xmlns_attribute;

namespaced_element('uri://x', x:hello', default_xmlns_attribute(http://default'));
```

#### cdata

Operates on a `Dom\Node` and creates a `Dom\CDATASection`.
Expand Down Expand Up @@ -1289,6 +1301,19 @@ if (is_non_empty_text($someNode)) {
}
```

#### is_prefixed_node_name

Checks if a given node name is prefixed or not.
This will validate for pattern `^[^:]+:[^:]+$` to make sure that there are no multiple colons in the node name and that all parts are set.

```php
use function VeeWee\Xml\Dom\Predicate\is_prefixed_node_name;

if (is_prefixed_node_name('prefixed:nodeName')) {
// ...
}
```

#### is_text

Checks if a node is of type `Dom\Text`.
Expand Down
21 changes: 21 additions & 0 deletions src/Xml/Dom/Builder/default_xmlns_attribute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Builder;

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

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

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

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

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

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

declare(strict_types=1);

namespace VeeWee\Xml\Dom\Predicate;

function is_prefixed_node_name(string $nodeName): bool
{
return (bool)preg_match('/^[^:]+:[^:]+$/', $nodeName);
}
9 changes: 3 additions & 6 deletions src/Xml/Encoding/Internal/Decoder/Builder/namespaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@ function namespaces(Element $element): array
{
return filter([
'@namespaces' => xmlns_attributes_list($element)->reduce(
static fn (array $namespaces, Attr $node)
=> $node->value
? merge($namespaces, [
($node->prefix !== null ? $node->localName : '') => $node->value
])
: $namespaces,
static fn (array $namespaces, Attr $node) => merge($namespaces, [
($node->prefix !== null ? $node->localName : '') => $node->value
]),
[]
),
]);
Expand Down
3 changes: 1 addition & 2 deletions src/Xml/Encoding/Internal/Encoder/Builder/children.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use Dom\Element;
use function Psl\Dict\map;
use function VeeWee\Xml\Dom\Builder\children as buildChildren;
use function VeeWee\Xml\Dom\Builder\element as elementBuilder;
use function VeeWee\Xml\Dom\Builder\value;

/**
Expand All @@ -28,7 +27,7 @@ function children(string $name, array $children): Closure
*/
static fn (array|string $data): Closure => is_array($data)
? element($name, $data)
: elementBuilder($name, value($data))
: xmlns_inheriting_element($name, [value($data)])
)
);
}
7 changes: 1 addition & 6 deletions src/Xml/Encoding/Internal/Encoder/Builder/element.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
use function VeeWee\Xml\Dom\Builder\attributes;
use function VeeWee\Xml\Dom\Builder\cdata;
use function VeeWee\Xml\Dom\Builder\children as childrenBuilder;
use function VeeWee\Xml\Dom\Builder\element as elementBuilder;
use function VeeWee\Xml\Dom\Builder\escaped_value;
use function VeeWee\Xml\Dom\Builder\namespaced_element as namespacedElementBuilder;
use function VeeWee\Xml\Dom\Builder\xmlns_attributes;

/**
Expand All @@ -46,7 +44,6 @@ function element(string $name, array $data): Closure
static fn (string $key): bool => !in_array($key, ['@attributes', '@namespaces', '@value', '@cdata'], true)
);

$currentNamespace = $namespaces[''] ?? null;
$namedNamespaces = filter_keys($namespaces ?? []);

/** @var list<Closure(Element): Element> $children */
Expand All @@ -66,7 +63,5 @@ function element(string $name, array $data): Closure
)),
]);

return $currentNamespace !== null
? namespacedElementBuilder($currentNamespace, $name, ...$children)
: elementBuilder($name, ...$children);
return xmlns_inheriting_element($name, $children, $namespaces);
}
3 changes: 1 addition & 2 deletions src/Xml/Encoding/Internal/Encoder/Builder/parent_node.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Psl\Exception\InvariantViolationException;
use Psl\Type\Exception\AssertException;
use function VeeWee\Xml\Dom\Builder\children as buildChildren;
use function VeeWee\Xml\Dom\Builder\element as elementBuilder;
use function VeeWee\Xml\Dom\Builder\escaped_value;

/**
Expand All @@ -25,7 +24,7 @@
function parent_node(string $name, array|string $data): Closure
{
if (is_string($data)) {
return buildChildren(elementBuilder($name, escaped_value($data)));
return buildChildren(xmlns_inheriting_element($name, [escaped_value($data)]));
}

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

declare(strict_types=1);

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

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

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

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

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

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

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

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

$defaultXmlns = $defaultNamespace !== null ? [default_xmlns_attribute($defaultNamespace)] : [];
return namespacedElementBuilder(
$prefixedNamespace,
$name,
...$defaultXmlns,
...$children,
)($parent);
};
}
3 changes: 3 additions & 0 deletions src/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'Xml\Dom\Builder\attributes' => __DIR__.'/Xml/Dom/Builder/attributes.php',
'Xml\Dom\Builder\cdata' => __DIR__.'/Xml/Dom/Builder/cdata.php',
'Xml\Dom\Builder\children' => __DIR__.'/Xml/Dom/Builder/children.php',
'Xml\Dom\Builder\default_xmlns_attribute' => __DIR__.'/Xml/Dom/Builder/default_xmlns_attribute.php',
'Xml\Dom\Builder\element' => __DIR__.'/Xml/Dom/Builder/element.php',
'Xml\Dom\Builder\escaped_value' => __DIR__.'/Xml/Dom/Builder/escaped_value.php',
'Xml\Dom\Builder\namespaced_attribute' => __DIR__.'/Xml/Dom/Builder/namespaced_attribute.php',
Expand Down Expand Up @@ -79,6 +80,7 @@
'Xml\Dom\Predicate\is_document_element' => __DIR__.'/Xml/Dom/Predicate/is_document_element.php',
'Xml\Dom\Predicate\is_element' => __DIR__.'/Xml/Dom/Predicate/is_element.php',
'Xml\Dom\Predicate\is_non_empty_text' => __DIR__.'/Xml/Dom/Predicate/is_non_empty_text.php',
'Xml\Dom\Predicate\is_prefixed_node_name' => __DIR__.'/Xml/Dom/Predicate/is_prefixed_node_name.php',
'Xml\Dom\Predicate\is_text' => __DIR__.'/Xml/Dom/Predicate/is_text.php',
'Xml\Dom\Predicate\is_whitespace' => __DIR__.'/Xml/Dom/Predicate/is_whitespace.php',
'Xml\Dom\Predicate\is_xmlns_attribute' => __DIR__.'/Xml/Dom/Predicate/is_xmlns_attribute.php',
Expand Down Expand Up @@ -107,6 +109,7 @@
'Xml\Encoding\Internal\Encoder\Builder\normalize_data' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/normalize_data.php',
'Xml\Encoding\Internal\Encoder\Builder\parent_node' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/parent_node.php',
'Xml\Encoding\Internal\Encoder\Builder\root' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/root.php',
'Xml\Encoding\Internal\Encoder\Builder\xmlns_inheriting_element' => __DIR__.'/Xml/Encoding/Internal/Encoder/Builder/xmlns_inheriting_element.php',
'Xml\Encoding\Internal\wrap_exception' => __DIR__.'/Xml/Encoding/Internal/wrap_exception.php',
'Xml\Encoding\document_encode' => __DIR__.'/Xml/Encoding/document_encode.php',
'Xml\Encoding\element_decode' => __DIR__.'/Xml/Encoding/element_decode.php',
Expand Down
39 changes: 39 additions & 0 deletions tests/Xml/Dom/Builder/DefaultXmlnsAttributeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace VeeWee\Tests\Xml\Dom\Builder;

use PHPUnit\Framework\TestCase;
use VeeWee\Xml\Dom\Document;
use function VeeWee\Xml\Dom\Builder\default_xmlns_attribute;
use function VeeWee\Xml\Dom\Builder\element;
use function VeeWee\Xml\Dom\Builder\namespaced_element;

final class DefaultXmlnsAttributeTest extends TestCase
{
public function test_it_can_build_an_element_with_default_xmlns_on_namespaced_element(): void
{
$doc = Document::empty()->toUnsafeDocument();

$node = namespaced_element(
'uri://x',
'x:foo',
default_xmlns_attribute('uri://default')
)($doc);

static::assertSame('<x:foo xmlns:x="uri://x" xmlns="uri://default"/>', $doc->saveXml($node));
}

public function test_it_can_not_build_an_element_with_default_xmlns_on_regular_element(): void
{
$doc = Document::empty()->toUnsafeDocument();

$node = element(
'foo',
default_xmlns_attribute('uri://default')
)($doc);

static::assertSame('<foo/>', $doc->saveXml($node));
}
}
45 changes: 45 additions & 0 deletions tests/Xml/Dom/Predicate/IsPrefixedNodeNameTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

declare(strict_types=1);

namespace VeeWee\Tests\Xml\Dom\Predicate;

use PHPUnit\Framework\TestCase;
use function VeeWee\Xml\Dom\Predicate\is_prefixed_node_name;

final class IsPrefixedNodeNameTest extends TestCase
{
/**
*
* @dataProvider provideValidQNames
*/
public function test_it_does_nothing_on_valid_qnames(string $input): void
{
static::assertTrue(is_prefixed_node_name($input));
}

/**
*
* @dataProvider provideInvalidQNames
*/
public function test_it_throws_on_invalid_qnames(string $input): void
{
static::assertFalse(is_prefixed_node_name($input));
}

public static function provideValidQNames()
{
yield ['hello:world'];
yield ['a:b'];
yield ['---a----:----b---'];
}

public static function provideInvalidQNames()
{
yield [''];
yield ['aa'];
yield ['aa:'];
yield [':bb'];
yield [':b:c:cd:dz'];
}
}
Loading
Loading