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 + ]; + } +}