diff --git a/docs/reader.md b/docs/reader.md
index d853c8c..c89df63 100644
--- a/docs/reader.md
+++ b/docs/reader.md
@@ -146,31 +146,211 @@ $reader = Reader::configure($yourLoader, ...$configurators);
#### all
-All provided matchers need to match in order for this matcher to succceed:
+All provided matchers need to match in order for this matcher to succeed:
```php
+use \VeeWee\Xml\Reader\Matcher;
+
Matcher\all(
Matcher\node_name('item'),
Matcher\node_attribute('locale', 'nl-BE')
);
```
-#### node_attribute
+#### any
+
+One of the provided matchers need to match in order for this matcher to succeed:
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\any(
+ Matcher\node_name('item'),
+ Matcher\node_name('product'),
+);
+```
+
+#### attribute_local_name
+
+Matches current element based on attribute exists: `locale`.
+Also prefixed attributes will be matched `some:locale`.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\attribute_local_name('locale');
+```
+
+#### attribute_local_value
+
+Matches current element based on attribute value `locale="nl-BE"`.
+Also prefixed attributes will be matched `some:locale="nl-BE"`.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\attribute_local_value('locale', 'nl-BE');
+```
+
+#### attribute_name
+
+Matches current element based on attribute exists: `locale`.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\attribute_name('locale');
+// OR
+Matcher\attribute_name('prefixed:locale');
+```
+
+#### attribute_value
+
+Matches current element based on attribute value `locale="nl-BE"`.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\attribute_value('locale', 'nl-BE');
+// OR
+Matcher\attribute_value('prefixed:locale', 'nl-BE');
+```
+
+#### document_element
-Matches current element on attribute `locale="nl-BE"`.
+Matches on the root document element only.
```php
-Matcher\node_attribute('locale', 'nl-BE');
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\document_element();
```
-#### node_name
+#### element_local_name
Matches current element on node name ` `.
+Also prefixed elements will be matched: ``.
```php
-Matcher\node_name('item');
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\element_local_name('item');
```
+#### element_name
+
+Matches current element on full node name ` `.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\element_name('item');
+// OR
+Matcher\element_name('some:item');
+```
+
+#### element_position
+
+Matches current element on the position of the element in the XML tree.
+Given following example:
+
+```xml
+
+
+
+
+
+```
+
+Only the middle ` ` will be matched.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\element_position(1);
+```
+
+#### namespaced_attribute
+
+Matches current element based on attribute XMLNS namespace `https://some` and attribute key `locale`.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\namespaced_attribute('https://some', 'locale');
+```
+
+#### namespaced_attribute_value
+
+Matches current element based on attribute namespace `https://some` and value `locale="nl-BE"`.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\namespaced_attribute_value('https://some', 'locale', 'nl-BE');
+```
+
+#### namespaced_element
+
+Matches current element on namespace and element name ` `.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\namespaced_element('https://some', 'item');
+```
+
+#### not
+
+Inverses a matcher's result.
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\not(
+ Matcher\element_name('item')
+);
+```
+
+#### sequence
+
+Provide a sequence of matchers that represents the XML tree.
+Only the items that are described by the sequence will match.
+
+Given:
+
+```xml
+
+
+ Jos
+ Bos
+ Mos
+
+
+```
+
+This matcher will grab the `user` element with `locale="nl"`
+
+```php
+use \VeeWee\Xml\Reader\Matcher;
+
+Matcher\not(
+ // Level 0:
+ Matcher\document_element(),
+ // Level 1:
+ // all() Acts as a wildcard to grab any element under document element.
+ // You could also go for the more exact element_name('users')
+ Matcher\all(),
+ // Level 2: Jos
+ // Searches for all elements that matches `` and attribute `locale="nl"`
+ Matcher\all(
+ element_name('user'),
+ attribute_value('locale', 'nl')
+ )
+);
+```
+
+
#### Writing your own matcher
A matcher can be any `callable` that takes a `NodeSequence` as input and returns a `bool` that specifies if it matches or not:
@@ -182,7 +362,7 @@ use VeeWee\Xml\Reader\Node\NodeSequence;
interface Matcher
{
- publict function __invoke(NodeSequence $sequence): bool;
+ public function __invoke(NodeSequence $sequence): bool;
}
```
diff --git a/psalm.xml b/psalm.xml
index 5a7bd22..95604c8 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -32,6 +32,11 @@
+
+
+
+
+
diff --git a/src/Xml/Dom/Loader/xml_file_loader.php b/src/Xml/Dom/Loader/xml_file_loader.php
index 28e00ad..06d560e 100644
--- a/src/Xml/Dom/Loader/xml_file_loader.php
+++ b/src/Xml/Dom/Loader/xml_file_loader.php
@@ -18,7 +18,7 @@ function xml_file_loader(string $file, int $options = 0): Closure
load(
static function () use ($document, $file, $options): bool {
Assert::fileExists($file);
- return (bool) $document->load($file, $options);
+ return $document->load($file, $options);
}
);
};
diff --git a/src/Xml/Dom/Loader/xml_string_loader.php b/src/Xml/Dom/Loader/xml_string_loader.php
index 1dfa2fc..333a335 100644
--- a/src/Xml/Dom/Loader/xml_string_loader.php
+++ b/src/Xml/Dom/Loader/xml_string_loader.php
@@ -15,6 +15,6 @@
function xml_string_loader(string $xml, int $options = 0): Closure
{
return static function (DOMDocument $document) use ($xml, $options): void {
- load(static fn (): bool => (bool) $document->loadXML($xml, $options));
+ load(static fn (): bool => $document->loadXML($xml, $options));
};
}
diff --git a/src/Xml/Reader/Matcher/any.php b/src/Xml/Reader/Matcher/any.php
new file mode 100644
index 0000000..f88aac7
--- /dev/null
+++ b/src/Xml/Reader/Matcher/any.php
@@ -0,0 +1,25 @@
+ $matchers
+ *
+ * @return \Closure(NodeSequence): bool
+ */
+function any(callable ... $matchers): Closure
+{
+ return static fn (NodeSequence $sequence): bool => Iter\any(
+ $matchers,
+ /**
+ * @param callable(NodeSequence): bool $matcher
+ */
+ static fn (callable $matcher): bool => $matcher($sequence)
+ );
+}
diff --git a/src/Xml/Reader/Matcher/attribute_local_name.php b/src/Xml/Reader/Matcher/attribute_local_name.php
new file mode 100644
index 0000000..ab1eb3a
--- /dev/null
+++ b/src/Xml/Reader/Matcher/attribute_local_name.php
@@ -0,0 +1,23 @@
+current()->attributes(),
+ static fn (AttributeNode $attribute): bool => $attribute->localName() === $localName
+ );
+ };
+}
diff --git a/src/Xml/Reader/Matcher/attribute_local_value.php b/src/Xml/Reader/Matcher/attribute_local_value.php
new file mode 100644
index 0000000..2a038c5
--- /dev/null
+++ b/src/Xml/Reader/Matcher/attribute_local_value.php
@@ -0,0 +1,23 @@
+current()->attributes(),
+ static fn (AttributeNode $attribute): bool => $attribute->localName() === $localName && $attribute->value() === $value
+ );
+ };
+}
diff --git a/src/Xml/Reader/Matcher/attribute_name.php b/src/Xml/Reader/Matcher/attribute_name.php
new file mode 100644
index 0000000..813a269
--- /dev/null
+++ b/src/Xml/Reader/Matcher/attribute_name.php
@@ -0,0 +1,23 @@
+current()->attributes(),
+ static fn (AttributeNode $attribute): bool => $attribute->name() === $name
+ );
+ };
+}
diff --git a/src/Xml/Reader/Matcher/attribute_value.php b/src/Xml/Reader/Matcher/attribute_value.php
new file mode 100644
index 0000000..ef00ba4
--- /dev/null
+++ b/src/Xml/Reader/Matcher/attribute_value.php
@@ -0,0 +1,23 @@
+current()->attributes(),
+ static fn (AttributeNode $attribute): bool => $attribute->name() === $name && $attribute->value() === $value
+ );
+ };
+}
diff --git a/src/Xml/Reader/Matcher/document_element.php b/src/Xml/Reader/Matcher/document_element.php
new file mode 100644
index 0000000..0b81a8e
--- /dev/null
+++ b/src/Xml/Reader/Matcher/document_element.php
@@ -0,0 +1,18 @@
+parent();
+ };
+}
diff --git a/src/Xml/Reader/Matcher/element_local_name.php b/src/Xml/Reader/Matcher/element_local_name.php
new file mode 100644
index 0000000..a72d7bc
--- /dev/null
+++ b/src/Xml/Reader/Matcher/element_local_name.php
@@ -0,0 +1,18 @@
+current()->localName() === $localName;
+ };
+}
diff --git a/src/Xml/Reader/Matcher/element_name.php b/src/Xml/Reader/Matcher/element_name.php
new file mode 100644
index 0000000..05b345b
--- /dev/null
+++ b/src/Xml/Reader/Matcher/element_name.php
@@ -0,0 +1,18 @@
+current()->name() === $name;
+ };
+}
diff --git a/src/Xml/Reader/Matcher/element_position.php b/src/Xml/Reader/Matcher/element_position.php
new file mode 100644
index 0000000..6dd0309
--- /dev/null
+++ b/src/Xml/Reader/Matcher/element_position.php
@@ -0,0 +1,18 @@
+current()->position() === $position;
+ };
+}
diff --git a/src/Xml/Reader/Matcher/namespaced_attribute.php b/src/Xml/Reader/Matcher/namespaced_attribute.php
new file mode 100644
index 0000000..ceb3103
--- /dev/null
+++ b/src/Xml/Reader/Matcher/namespaced_attribute.php
@@ -0,0 +1,23 @@
+current()->attributes(),
+ static fn (AttributeNode $attribute): bool => $attribute->localName() === $localName && $attribute->namespace() === $namespace
+ );
+ };
+}
diff --git a/src/Xml/Reader/Matcher/namespaced_attribute_value.php b/src/Xml/Reader/Matcher/namespaced_attribute_value.php
new file mode 100644
index 0000000..eeab1f4
--- /dev/null
+++ b/src/Xml/Reader/Matcher/namespaced_attribute_value.php
@@ -0,0 +1,26 @@
+current()->attributes(),
+ static fn (AttributeNode $attribute): bool =>
+ $attribute->localName() === $localName
+ && $attribute->namespace() === $namespace
+ && $attribute->value() === $value
+ );
+ };
+}
diff --git a/src/Xml/Reader/Matcher/namespaced_element.php b/src/Xml/Reader/Matcher/namespaced_element.php
new file mode 100644
index 0000000..052f124
--- /dev/null
+++ b/src/Xml/Reader/Matcher/namespaced_element.php
@@ -0,0 +1,20 @@
+current();
+
+ return $current->localName() === $localName && $current->namespace() === $namespace;
+ };
+}
diff --git a/src/Xml/Reader/Matcher/node_attribute.php b/src/Xml/Reader/Matcher/node_attribute.php
index f6e0cd2..5e55671 100644
--- a/src/Xml/Reader/Matcher/node_attribute.php
+++ b/src/Xml/Reader/Matcher/node_attribute.php
@@ -10,6 +10,7 @@
use function Psl\Iter\any;
/**
+ * @deprecated Use attribute_value instead! This will be removed in next major version
* @return \Closure(NodeSequence): bool
*/
function node_attribute(string $key, string $value): Closure
diff --git a/src/Xml/Reader/Matcher/node_name.php b/src/Xml/Reader/Matcher/node_name.php
index ceec497..645fb01 100644
--- a/src/Xml/Reader/Matcher/node_name.php
+++ b/src/Xml/Reader/Matcher/node_name.php
@@ -8,6 +8,7 @@
use VeeWee\Xml\Reader\Node\NodeSequence;
/**
+ * @deprecated Use element_name instead! This will be removed in next major version
* @return \Closure(NodeSequence): bool
*/
function node_name(string $name): Closure
diff --git a/src/Xml/Reader/Matcher/not.php b/src/Xml/Reader/Matcher/not.php
new file mode 100644
index 0000000..e067305
--- /dev/null
+++ b/src/Xml/Reader/Matcher/not.php
@@ -0,0 +1,18 @@
+ !$matcher($sequence);
+}
diff --git a/src/Xml/Reader/Matcher/sequence.php b/src/Xml/Reader/Matcher/sequence.php
new file mode 100644
index 0000000..4fe6e3d
--- /dev/null
+++ b/src/Xml/Reader/Matcher/sequence.php
@@ -0,0 +1,41 @@
+ $matcherSequence
+ *
+ * @return \Closure(NodeSequence): bool
+ */
+function sequence(callable ... $matcherSequence): Closure
+{
+ return static function (NodeSequence $sequence) use ($matcherSequence) : bool {
+ $nodeSequence = $sequence->sequence();
+ if (count($matcherSequence) !== count($nodeSequence)) {
+ return false;
+ }
+
+ $currentSequence = new NodeSequence();
+ foreach ($nodeSequence as $i => $node) {
+ $currentSequence = $currentSequence->append($node);
+ $matcher = $matcherSequence[$i];
+ if (!$matcher($currentSequence)) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+}
diff --git a/src/bootstrap.php b/src/bootstrap.php
index 607c4ca..202424b 100644
--- a/src/bootstrap.php
+++ b/src/bootstrap.php
@@ -124,8 +124,22 @@
require_once __DIR__.'/Xml/Reader/Loader/xml_file_loader.php';
require_once __DIR__.'/Xml/Reader/Loader/xml_string_loader.php';
require_once __DIR__.'/Xml/Reader/Matcher/all.php';
+require_once __DIR__.'/Xml/Reader/Matcher/any.php';
+require_once __DIR__.'/Xml/Reader/Matcher/attribute_local_name.php';
+require_once __DIR__.'/Xml/Reader/Matcher/attribute_local_value.php';
+require_once __DIR__.'/Xml/Reader/Matcher/attribute_name.php';
+require_once __DIR__.'/Xml/Reader/Matcher/attribute_value.php';
+require_once __DIR__.'/Xml/Reader/Matcher/document_element.php';
+require_once __DIR__.'/Xml/Reader/Matcher/element_local_name.php';
+require_once __DIR__.'/Xml/Reader/Matcher/element_name.php';
+require_once __DIR__.'/Xml/Reader/Matcher/element_position.php';
+require_once __DIR__.'/Xml/Reader/Matcher/namespaced_attribute.php';
+require_once __DIR__.'/Xml/Reader/Matcher/namespaced_attribute_value.php';
+require_once __DIR__.'/Xml/Reader/Matcher/namespaced_element.php';
require_once __DIR__.'/Xml/Reader/Matcher/node_attribute.php';
require_once __DIR__.'/Xml/Reader/Matcher/node_name.php';
+require_once __DIR__.'/Xml/Reader/Matcher/not.php';
+require_once __DIR__.'/Xml/Reader/Matcher/sequence.php';
require_once __DIR__.'/Xml/Writer/Builder/attribute.php';
require_once __DIR__.'/Xml/Writer/Builder/attributes.php';
require_once __DIR__.'/Xml/Writer/Builder/children.php';
diff --git a/tests/Xml/Reader/Matcher/AbstractMatcherTest.php b/tests/Xml/Reader/Matcher/AbstractMatcherTest.php
new file mode 100644
index 0000000..17ae66a
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/AbstractMatcherTest.php
@@ -0,0 +1,42 @@
+ $expected
+ */
+ public function test_real_xml_cases(Closure $matcher, string $xml, array $expected)
+ {
+ $reader = Reader::fromXmlString($xml);
+ $actual = [...$reader->provide($matcher)];
+
+ static::assertSame($actual, $expected);
+ }
+
+ /**
+ * @dataProvider provideMatcherCases
+ *
+ * @param \Closure(NodeSequence): bool $matcher
+ */
+ public function test_matcher_cases(Closure $matcher, NodeSequence $sequence, bool $expected)
+ {
+ $actual = $matcher($sequence);
+
+ static::assertSame($actual, $expected);
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/AllTest.php b/tests/Xml/Reader/Matcher/AllTest.php
index 1de4bc7..bdf824a 100644
--- a/tests/Xml/Reader/Matcher/AllTest.php
+++ b/tests/Xml/Reader/Matcher/AllTest.php
@@ -4,42 +4,76 @@
namespace VeeWee\Tests\Xml\Reader\Matcher;
-use PHPUnit\Framework\TestCase;
+use Generator;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function VeeWee\Xml\Reader\Matcher\all;
+use function VeeWee\Xml\Reader\Matcher\element_name;
-final class AllTest extends TestCase
+final class AllTest extends AbstractMatcherTest
{
- public function test_it_returns_true_if_all_matchers_agree(): void
+ public static function provideRealXmlCases(): Generator
{
- $matcher = all(
- static fn () => true,
- static fn () => true,
- static fn () => true
- );
- static::assertTrue($matcher($this->createSequence()));
+ yield 'all' => [
+ all(),
+ $xml = <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ $xml,
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ yield 'users' => [
+ all(element_name('user')),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
}
-
- public function test_it_returns_false_if__not_all_matchers_agree(): void
+ public static function provideMatcherCases(): Generator
{
- $matcher = all(
- static fn () => true,
- static fn () => true,
- static fn () => false
- );
- static::assertFalse($matcher($this->createSequence()));
- }
+ $sequence = new NodeSequence();
-
- public function test_it_returns_true_if_there_are_no_matchers(): void
- {
- $matcher = all();
- static::assertTrue($matcher($this->createSequence()));
- }
+ yield 'it_returns_true_if_all_matchers_agree' => [
+ all(
+ static fn () => true,
+ static fn () => true,
+ static fn () => true
+ ),
+ $sequence,
+ true
+ ];
- private function createSequence(): NodeSequence
- {
- return new NodeSequence();
+ yield 'it_returns_false_if_not_all_matchers_agree' => [
+ all(
+ static fn () => true,
+ static fn () => true,
+ static fn () => false
+ ),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_true_if_there_are_no_matchers' => [
+ all(),
+ $sequence,
+ true
+ ];
}
}
diff --git a/tests/Xml/Reader/Matcher/AnyTest.php b/tests/Xml/Reader/Matcher/AnyTest.php
new file mode 100644
index 0000000..3fb9aff
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/AnyTest.php
@@ -0,0 +1,84 @@
+ [
+ any(),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ []
+ ];
+ yield 'users' => [
+ any(element_name('user')),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence();
+
+ yield 'it_returns_true_if_all_matchers_agree' => [
+ any(
+ static fn () => true,
+ static fn () => true,
+ static fn () => true
+ ),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_true_if_any_matchers_agree' => [
+ any(
+ static fn () => false,
+ static fn () => true,
+ static fn () => false
+ ),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_no_matchers_agree' => [
+ any(
+ static fn () => false,
+ static fn () => false,
+ static fn () => false
+ ),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_there_are_no_matchers' => [
+ any(),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/AttributeLocalNameTest.php b/tests/Xml/Reader/Matcher/AttributeLocalNameTest.php
new file mode 100644
index 0000000..6c0e635
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/AttributeLocalNameTest.php
@@ -0,0 +1,68 @@
+ [
+ attribute_local_name('country'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos',
+ ]
+ ];
+ yield 'namespaced' => [
+ attribute_local_name('country'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'x:item', 'item', 'https://x', 'x', [
+ new AttributeNode('x:locale', 'locale', 'x', 'https://x', 'nl')
+ ])
+ );
+
+ yield 'it_returns_true_if_local_attribute_name_matches' => [
+ attribute_local_name('locale'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_local_attribute_name_does_not_match' => [
+ attribute_local_name('unknown'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/AttributeLocalValueTest.php b/tests/Xml/Reader/Matcher/AttributeLocalValueTest.php
new file mode 100644
index 0000000..a3d9edc
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/AttributeLocalValueTest.php
@@ -0,0 +1,73 @@
+ [
+ attribute_local_value('country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos',
+ ]
+ ];
+ yield 'namespaced' => [
+ attribute_local_value('country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'x:item', 'item', 'https://x', 'x', [
+ new AttributeNode('x:locale', 'locale', 'x', 'https://x', 'nl')
+ ])
+ );
+
+ yield 'it_returns_true_if_local_attribute_value_matches' => [
+ attribute_local_value('locale', 'nl'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_local_attribute_value_does_not_match' => [
+ attribute_local_value('locale', 'en'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_local_attribute_value_is_not_available' => [
+ attribute_local_value('unkown', 'en'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/AttributeNameTest.php b/tests/Xml/Reader/Matcher/AttributeNameTest.php
new file mode 100644
index 0000000..6753c8f
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/AttributeNameTest.php
@@ -0,0 +1,67 @@
+ [
+ attribute_name('country'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos',
+ ]
+ ];
+ yield 'namespaced' => [
+ attribute_name('u:country'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [
+ new AttributeNode('locale', 'locale', '', '', 'nl')
+ ])
+ );
+
+ yield 'it_returns_true_if_attribute_name_matches' => [
+ attribute_name('locale'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_attribute_name_is_not_available' => [
+ attribute_name('unkown'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/AttributeValueTest.php b/tests/Xml/Reader/Matcher/AttributeValueTest.php
new file mode 100644
index 0000000..faf0523
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/AttributeValueTest.php
@@ -0,0 +1,73 @@
+ [
+ attribute_value('country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos',
+ ]
+ ];
+ yield 'namespaced' => [
+ attribute_value('u:country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [
+ new AttributeNode('locale', 'locale', '', '', 'nl')
+ ])
+ );
+
+ yield 'it_returns_true_if_attribute_value_matches' => [
+ attribute_value('locale', 'nl'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_attribute_value_does_not_match' => [
+ attribute_value('locale', 'en'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_attribute_value_is_not_available' => [
+ attribute_value('unkown', 'en'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/DocumentElementTest.php b/tests/Xml/Reader/Matcher/DocumentElementTest.php
new file mode 100644
index 0000000..02d16b0
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/DocumentElementTest.php
@@ -0,0 +1,58 @@
+ [
+ document_element(),
+ $xml = <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ $xml,
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ yield 'it_returns_true_if_its_the_document_element' => [
+ document_element(),
+ new NodeSequence(
+ new ElementNode(1, 'root', 'root', '', '', [])
+ ),
+ true
+ ];
+
+ yield 'it_returns_false_if_its_not_the_document_element' => [
+ document_element(),
+ new NodeSequence(
+ new ElementNode(1, 'root', 'root', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ ),
+ false
+ ];
+
+ yield 'it_solves_code_coverage_issue...?' => [
+ static fn (NodeSequence $sequence): bool => document_element()($sequence),
+ new NodeSequence(
+ new ElementNode(1, 'root', 'root', '', '', [])
+ ),
+ true
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/ElementLocalNameTest.php b/tests/Xml/Reader/Matcher/ElementLocalNameTest.php
new file mode 100644
index 0000000..53c0d76
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/ElementLocalNameTest.php
@@ -0,0 +1,66 @@
+ [
+ element_local_name('user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ yield 'namespaced' => [
+ element_local_name('user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'x:item', 'item', 'https://x', 'x', [])
+ );
+
+ yield 'it_returns_true_if_local_element_name_matches' => [
+ element_local_name('item'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_local_element_name_does_not_match' => [
+ element_local_name('other'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/ElementNameTest.php b/tests/Xml/Reader/Matcher/ElementNameTest.php
new file mode 100644
index 0000000..a806a16
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/ElementNameTest.php
@@ -0,0 +1,66 @@
+ [
+ element_name('user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ yield 'namespaced' => [
+ element_name('u:user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [])
+ );
+
+ yield 'it_returns_true_if_element_name_matches' => [
+ element_name('item'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_element_name_does_not_match' => [
+ element_name('other'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/ElementPositionTest.php b/tests/Xml/Reader/Matcher/ElementPositionTest.php
new file mode 100644
index 0000000..3d298f4
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/ElementPositionTest.php
@@ -0,0 +1,49 @@
+ [
+ element_position(2),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Bos',
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ yield 'it_returns_true_if_element_position_matches' => [
+ element_position(1),
+ new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [])
+ ),
+ true
+ ];
+
+ yield 'it_returns_false_if_element_name_does_not_match' => [
+ element_position(1),
+ new NodeSequence(
+ new ElementNode(2, 'item', 'item', '', '', [])
+ ),
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/NamespacedAttributeTest.php b/tests/Xml/Reader/Matcher/NamespacedAttributeTest.php
new file mode 100644
index 0000000..5c3218e
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/NamespacedAttributeTest.php
@@ -0,0 +1,59 @@
+ [
+ namespaced_attribute('https://users', 'country'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [
+ new AttributeNode('locale', 'locale', 'https://x', 'x', 'nl')
+ ])
+ );
+
+ yield 'it_returns_true_if_attribute_name_matches' => [
+ namespaced_attribute('https://x', 'locale'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_attribute_name_is_not_available' => [
+ namespaced_attribute('https://x', 'unknown'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_namespace_does_not_match' => [
+ namespaced_attribute('https://invalid', 'locale'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/NamespacedAttributeValueTest.php b/tests/Xml/Reader/Matcher/NamespacedAttributeValueTest.php
new file mode 100644
index 0000000..acf5aa8
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/NamespacedAttributeValueTest.php
@@ -0,0 +1,64 @@
+ [
+ namespaced_attribute_value('https://users', 'country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [
+ new AttributeNode('locale', 'locale', 'https://x', 'x', 'nl')
+ ])
+ );
+
+ yield 'it_returns_true_if_attribute_name_matches' => [
+ namespaced_attribute_value('https://x', 'locale', 'nl'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_attribute_name_is_not_available' => [
+ namespaced_attribute_value('https://x', 'unknown', 'nl'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_namespace_does_not_match' => [
+ namespaced_attribute_value('https://invalid', 'locale', 'nl'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_value_does_not_match' => [
+ namespaced_attribute_value('https://x', 'locale', 'other'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/NamespacedElementNameTest.php b/tests/Xml/Reader/Matcher/NamespacedElementNameTest.php
new file mode 100644
index 0000000..eff410b
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/NamespacedElementNameTest.php
@@ -0,0 +1,56 @@
+ [
+ namespaced_element('https://users', 'user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence(
+ new ElementNode(1, 'x:item', 'item', 'https://x', 'x', [])
+ );
+
+ yield 'it_returns_true_if_element_name_matches' => [
+ namespaced_element('https://x', 'item'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_element_name_does_not_match' => [
+ namespaced_element('https://x', 'other'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_element_namespace_does_not_match' => [
+ namespaced_element('https://invalid', 'item'),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/NodeAttributeTest.php b/tests/Xml/Reader/Matcher/NodeAttributeTest.php
index 02e1d30..63a92fc 100644
--- a/tests/Xml/Reader/Matcher/NodeAttributeTest.php
+++ b/tests/Xml/Reader/Matcher/NodeAttributeTest.php
@@ -4,40 +4,73 @@
namespace VeeWee\Tests\Xml\Reader\Matcher;
-use PHPUnit\Framework\TestCase;
+use Generator;
use VeeWee\Xml\Reader\Node\AttributeNode;
use VeeWee\Xml\Reader\Node\ElementNode;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function VeeWee\Xml\Reader\Matcher\node_attribute;
-final class NodeAttributeTest extends TestCase
+/**
+ * @deprecated Use attribute_value instead! This will be removed in next major version
+ */
+final class NodeAttributeTest extends AbstractMatcherTest
{
- public function test_it_returns_true_if_node_attribute_matches(): void
+ public static function provideRealXmlCases(): Generator
{
- $matcher = node_attribute('locale', 'nl');
- static::assertTrue($matcher($this->createSequence()));
+ yield 'users' => [
+ node_attribute('country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos',
+ ]
+ ];
+ yield 'namespaced' => [
+ node_attribute('u:country', 'BE'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Mos'
+ ]
+ ];
}
-
- public function test_it_returns_false_if_node_attribute_does_not_match(): void
+ public static function provideMatcherCases(): Generator
{
- $matcher = node_attribute('locale', 'en');
- static::assertFalse($matcher($this->createSequence()));
- }
-
-
- public function test_it_returns_false_if_node_attribute_is_not_available(): void
- {
- $matcher = node_attribute('unkown', 'en');
- static::assertFalse($matcher($this->createSequence()));
- }
-
- private function createSequence(): NodeSequence
- {
- return new NodeSequence(
+ $sequence = new NodeSequence(
new ElementNode(1, 'item', 'item', '', '', [
new AttributeNode('locale', 'locale', '', '', 'nl')
])
);
+
+ yield 'it_returns_true_if_node_attribute_matches' => [
+ node_attribute('locale', 'nl'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_node_attribute_does_not_match' => [
+ node_attribute('locale', 'en'),
+ $sequence,
+ false
+ ];
+
+ yield 'it_returns_false_if_node_attribute_is_not_available' => [
+ node_attribute('unkown', 'en'),
+ $sequence,
+ false
+ ];
}
}
diff --git a/tests/Xml/Reader/Matcher/NodeNameTest.php b/tests/Xml/Reader/Matcher/NodeNameTest.php
index a21bc94..f98b69c 100644
--- a/tests/Xml/Reader/Matcher/NodeNameTest.php
+++ b/tests/Xml/Reader/Matcher/NodeNameTest.php
@@ -4,30 +4,66 @@
namespace VeeWee\Tests\Xml\Reader\Matcher;
-use PHPUnit\Framework\TestCase;
+use Generator;
use VeeWee\Xml\Reader\Node\ElementNode;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function VeeWee\Xml\Reader\Matcher\node_name;
-final class NodeNameTest extends TestCase
+/**
+ * @deprecated Use element_name instead! This will be removed in next major version
+ */
+final class NodeNameTest extends AbstractMatcherTest
{
- public function test_it_returns_true_if_node_name_matches(): void
+ public static function provideRealXmlCases(): Generator
{
- $matcher = node_name('item');
- static::assertTrue($matcher($this->createSequence()));
+ yield 'users' => [
+ node_name('user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ yield 'namespaced' => [
+ node_name('u:user'),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
}
-
- public function test_it_returns_false_if_node_name_does_not_match(): void
+ public static function provideMatcherCases(): Generator
{
- $matcher = node_name('other');
- static::assertFalse($matcher($this->createSequence()));
- }
-
- private function createSequence(): NodeSequence
- {
- return new NodeSequence(
+ $sequence = new NodeSequence(
new ElementNode(1, 'item', 'item', '', '', [])
);
+
+ yield 'it_returns_true_if_element_name_matches' => [
+ node_name('item'),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_element_name_does_not_match' => [
+ node_name('other'),
+ $sequence,
+ false
+ ];
}
}
diff --git a/tests/Xml/Reader/Matcher/NotTest.php b/tests/Xml/Reader/Matcher/NotTest.php
new file mode 100644
index 0000000..f7edd21
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/NotTest.php
@@ -0,0 +1,53 @@
+ [
+ not(element_name('root')),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ 'Bos',
+ 'Mos'
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ $sequence = new NodeSequence();
+
+ yield 'it_returns_true_if_the_inner_matcher_returns_false' => [
+ not(
+ static fn () => false,
+ ),
+ $sequence,
+ true
+ ];
+
+ yield 'it_returns_false_if_the_inner_matcher_returns_true' => [
+ not(
+ static fn () => true,
+ ),
+ $sequence,
+ false
+ ];
+ }
+}
diff --git a/tests/Xml/Reader/Matcher/SequenceTest.php b/tests/Xml/Reader/Matcher/SequenceTest.php
new file mode 100644
index 0000000..b12a00a
--- /dev/null
+++ b/tests/Xml/Reader/Matcher/SequenceTest.php
@@ -0,0 +1,119 @@
+ [
+ sequence(
+ document_element(),
+ all(
+ element_name('user'),
+ attribute_value('locale', 'nl')
+ )
+ ),
+ <<<'EOXML'
+
+ Jos
+ Bos
+ Mos
+
+ EOXML,
+ [
+ 'Jos',
+ ]
+ ];
+ }
+
+ public static function provideMatcherCases(): Generator
+ {
+ yield 'it_returns_true_if_no_sequence_ant_matcher' => [
+ sequence(),
+ new NodeSequence(),
+ true
+ ];
+
+ yield 'it_returns_false_on_invalid_count' => [
+ sequence(static fn () => true),
+ new NodeSequence(),
+ false
+ ];
+
+ yield 'it_returns_false_on_invalid_step' => [
+ sequence(static fn () => false),
+ new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', [])
+ ),
+ false
+ ];
+
+ yield 'it_returns_false_on_invalid_step_in_between' => [
+ sequence(
+ static fn () => true,
+ static fn () => false,
+ static fn () => true
+ ),
+ new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', [])
+ ),
+ false
+ ];
+
+ yield 'it_returns_true_if_full_sequence_matches' => [
+ sequence(
+ static fn () => true,
+ static fn () => true,
+ static fn () => true
+ ),
+ new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', [])
+ ),
+ true
+ ];
+
+ yield 'it_returns_false_if_elements_dont_go_deep_enough' => [
+ sequence(
+ static fn () => true,
+ static fn () => true,
+ static fn () => true
+ ),
+ new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ ),
+ false
+ ];
+
+ yield 'it_returns_false_if_elements_go_deeper' => [
+ sequence(
+ static fn () => true,
+ static fn () => true,
+ static fn () => true
+ ),
+ new NodeSequence(
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ new ElementNode(1, 'item', 'item', '', '', []),
+ ),
+ false
+ ];
+ }
+}