Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide better reader matchers #55

Merged
merged 1 commit into from
Oct 30, 2023
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
194 changes: 187 additions & 7 deletions docs/reader.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<item />`.
Also prefixed elements will be matched: `<some:item />`.

```php
Matcher\node_name('item');
use \VeeWee\Xml\Reader\Matcher;

Matcher\element_local_name('item');
```

#### element_name

Matches current element on full node name `<item />`.

```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
<items>
<item />
<item />
<item />
</items>
```

Only the middle `<item />` 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 `<item xmlns="https://some" />`.

```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
<root>
<users>
<user locale="nl">Jos</user>
<user>Bos</user>
<user>Mos</user>
</users>
</root>
```

This matcher will grab the `user` element with `locale="nl"`

```php
use \VeeWee\Xml\Reader\Matcher;

Matcher\not(
// Level 0: <root />
Matcher\document_element(),
// Level 1: <users />
// 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: <user locale="nl">Jos</user>
// Searches for all elements that matches `<user />` 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:
Expand All @@ -182,7 +362,7 @@ use VeeWee\Xml\Reader\Node\NodeSequence;

interface Matcher
{
publict function __invoke(NodeSequence $sequence): bool;
public function __invoke(NodeSequence $sequence): bool;
}
```

Expand Down
5 changes: 5 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
<directory name="tests"/>
</errorLevel>
</UndefinedClass>
<MissingDependency>
<errorLevel type="suppress">
<directory name="tests"/>
</errorLevel>
</MissingDependency>
<MixedArgumentTypeCoercion>
<errorLevel type="suppress">
<directory name="src/Xml/Encoding/Internal/Encoder/Builder" />
Expand Down
2 changes: 1 addition & 1 deletion src/Xml/Dom/Loader/xml_file_loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/Xml/Dom/Loader/xml_string_loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
};
}
25 changes: 25 additions & 0 deletions src/Xml/Reader/Matcher/any.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Reader\Matcher;

use Closure;
use Psl\Iter;
use VeeWee\Xml\Reader\Node\NodeSequence;

/**
* @param list<callable(NodeSequence): bool> $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)
);
}
23 changes: 23 additions & 0 deletions src/Xml/Reader/Matcher/attribute_local_name.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Reader\Matcher;

use Closure;
use VeeWee\Xml\Reader\Node\AttributeNode;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function Psl\Iter\any;

/**
* @return \Closure(NodeSequence): bool
*/
function attribute_local_name(string $localName): Closure
{
return static function (NodeSequence $sequence) use ($localName): bool {
return any(
$sequence->current()->attributes(),
static fn (AttributeNode $attribute): bool => $attribute->localName() === $localName
);
};
}
23 changes: 23 additions & 0 deletions src/Xml/Reader/Matcher/attribute_local_value.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Reader\Matcher;

use Closure;
use VeeWee\Xml\Reader\Node\AttributeNode;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function Psl\Iter\any;

/**
* @return \Closure(NodeSequence): bool
*/
function attribute_local_value(string $localName, string $value): Closure
{
return static function (NodeSequence $sequence) use ($localName, $value): bool {
return any(
$sequence->current()->attributes(),
static fn (AttributeNode $attribute): bool => $attribute->localName() === $localName && $attribute->value() === $value
);
};
}
23 changes: 23 additions & 0 deletions src/Xml/Reader/Matcher/attribute_name.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Reader\Matcher;

use Closure;
use VeeWee\Xml\Reader\Node\AttributeNode;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function Psl\Iter\any;

/**
* @return \Closure(NodeSequence): bool
*/
function attribute_name(string $name): Closure
{
return static function (NodeSequence $sequence) use ($name): bool {
return any(
$sequence->current()->attributes(),
static fn (AttributeNode $attribute): bool => $attribute->name() === $name
);
};
}
23 changes: 23 additions & 0 deletions src/Xml/Reader/Matcher/attribute_value.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Reader\Matcher;

use Closure;
use VeeWee\Xml\Reader\Node\AttributeNode;
use VeeWee\Xml\Reader\Node\NodeSequence;
use function Psl\Iter\any;

/**
* @return \Closure(NodeSequence): bool
*/
function attribute_value(string $name, string $value): Closure
{
return static function (NodeSequence $sequence) use ($name, $value): bool {
return any(
$sequence->current()->attributes(),
static fn (AttributeNode $attribute): bool => $attribute->name() === $name && $attribute->value() === $value
);
};
}
18 changes: 18 additions & 0 deletions src/Xml/Reader/Matcher/document_element.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace VeeWee\Xml\Reader\Matcher;

use Closure;
use VeeWee\Xml\Reader\Node\NodeSequence;

/**
* @return \Closure(NodeSequence): bool
*/
function document_element(): Closure
{
return static function (NodeSequence $sequence): bool {
return !$sequence->parent();
};
}
Loading