Skip to content

Commit 4f03a1e

Browse files
authored
add skipNodes to XmlProcessor (#6)
1 parent e86dc23 commit 4f03a1e

15 files changed

Lines changed: 307 additions & 43 deletions

File tree

features/SkipNode.feature

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Feature: run XMLProcessor with TextNodeProcessor
2+
3+
Scenario: run XMLProcessor
4+
Given initialize XMLProcessor with "Netlogix\XmlProcessor\Behat\NodeProcessor\ArrayNodeProcessor"
5+
When set skipNode to:
6+
"""
7+
category
8+
"""
9+
When process xml with current XMLProcessor instance:
10+
"""
11+
<root name="main">
12+
<product id="1">foo</product>
13+
<category id="1">
14+
<category id="2">
15+
<product id="3">baz</product>
16+
</category>
17+
<category><bar/></category>
18+
</category>
19+
<product id="2">bar</product>
20+
</root>
21+
"""
22+
Then NodeProcessor "Netlogix\XmlProcessor\Behat\NodeProcessor\ArrayNodeProcessor" should return:
23+
"""
24+
[
25+
{
26+
"node": "root",
27+
"level": 1,
28+
"attributes": {
29+
"name": "main"
30+
},
31+
"children": [
32+
{
33+
"node": "product",
34+
"level": 2,
35+
"attributes": {
36+
"id": "1"
37+
},
38+
"children": [],
39+
"text": "foo"
40+
},
41+
{
42+
"node": "category",
43+
"level": 2,
44+
"attributes": {
45+
"id": "1"
46+
},
47+
"children": []
48+
},
49+
{
50+
"node": "product",
51+
"level": 2,
52+
"attributes": {
53+
"id": "2"
54+
},
55+
"children": [],
56+
"text": "bar"
57+
}
58+
]
59+
}
60+
]
61+
"""

features/bootstrap/FeatureContext.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ public function iRunXmlProcessor(PyStringNode $content): void
4040
$this->xmlProcessor->processFile($fileName);
4141
}
4242

43+
/**
44+
* @When set skipNode to:
45+
*/
46+
public function iSetSkipNode(PyStringNode $skipNode): void
47+
{
48+
$this->xmlProcessor->setSkipNodes($skipNode->getStrings());
49+
}
50+
4351
/**
4452
* @Then NodeProcessor :nodeProcessorClass should return:
4553
*/
@@ -52,7 +60,7 @@ function nodeProcessorShouldReturn(string $nodeProcessorClass, PyStringNode $con
5260
throw new \Exception(sprintf('Class %s does not extend %s', $nodeProcessorClass, NodeProcessorInterface::class));
5361
}
5462
/** @var InvokeNodeProcessorInterface $nodeProcessor */
55-
$nodeProcessor = $this->xmlProcessor->getProcessorContext()->getProcessor($nodeProcessorClass);
63+
$nodeProcessor = $this->xmlProcessor->getProcessor($nodeProcessorClass);
5664
$expected = json_decode($content->getRaw(), true);
5765
if (json_last_error() !== JSON_ERROR_NONE) {
5866
throw new \Exception(sprintf('Could not decode expected result: %s', json_last_error_msg()));

src/NodeProcessor/AbstractNodeProcessor.php

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace Netlogix\XmlProcessor\NodeProcessor;
55

6+
use Netlogix\XmlProcessor\XmlProcessor;
67
use Netlogix\XmlProcessor\XmlProcessorContext;
78

89
class AbstractNodeProcessor implements NodeProcessorInterface
@@ -34,16 +35,6 @@ public function getSubscribedEvents(string $nodePath, XmlProcessorContext $conte
3435

3536
public function isNode(string $nodePath): bool
3637
{
37-
$expected = $this->getNodePath();
38-
if ($expected === '/' . $nodePath) {
39-
return true;
40-
}
41-
42-
return $nodePath === $this->getNodePath()
43-
|| (
44-
function_exists('str_end_with')
45-
? str_end_with($nodePath, $expected) :
46-
substr_compare($nodePath, $expected, -strlen($expected)) === 0
47-
);
38+
return XmlProcessor::checkNodePath($nodePath, $this->getNodePath());
4839
}
4940
}

src/XmlProcessor.php

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,57 @@ class XmlProcessor
1717
private array $nodePath = [];
1818
private string $currentValue = '';
1919

20+
private ?array $skipNodes = NULL;
21+
2022
private \XMLReader $xml;
2123
private XmlProcessorContext $context;
2224

2325
/** @var iterable<NodeProcessorInterface> */
2426
private iterable $processors;
2527

28+
/** @var iterable<bool> */
29+
private iterable $parserProperties;
30+
2631
/**
2732
* @param iterable<NodeProcessorInterface> $processors
28-
* @param iterable<bool> $options
33+
* @param iterable<bool> $parserProperties
2934
*/
3035
public function __construct(
3136
iterable $processors,
32-
iterable $options = []
37+
iterable $parserProperties = []
3338
)
3439
{
3540
$this->xml = new \XMLReader();
36-
foreach ($options as $option => $value) {
37-
$this->xml->setParserProperty($option, $value);
38-
}
3941
$this->processors = $processors;
40-
$this->context = new XmlProcessorContext($this->xml, $this->processors);
42+
$this->parserProperties = $parserProperties;
43+
$this->context = new XmlProcessorContext(
44+
$this->xml,
45+
$this->processors,
46+
fn() => $this->skipNode()
47+
);
4148
}
4249

43-
function getProcessorContext(): XmlProcessorContext
50+
function setSkipNodes(?array $skipNodes = NULL): void
4451
{
45-
return $this->context;
52+
$this->skipNodes = $skipNodes;
53+
}
54+
55+
function getSkipNodes(): ?array
56+
{
57+
return $this->skipNodes;
58+
}
59+
60+
function getProcessor(string $processorName): ?NodeProcessorInterface
61+
{
62+
return $this->context->getProcessor($processorName);
4663
}
4764

4865
public function processFile(string $filename): void
4966
{
5067
$this->xml->open($filename);
68+
foreach ($this->parserProperties as $parserProperty => $value) {
69+
$this->xml->setParserProperty($parserProperty, $value);
70+
}
5171
$this->getProcessorEvents(self::EVENT_OPEN_FILE);
5272
while ($this->xml->read()) {
5373
switch ($this->xml->nodeType) {
@@ -57,6 +77,10 @@ public function processFile(string $filename): void
5777
case \XMLReader::ELEMENT:
5878
$selfClosing = $this->xml->isEmptyElement;
5979
$this->eventOpenElement();
80+
if ($this->shouldSkipNode()) {
81+
$this->skipNode();
82+
break;
83+
}
6084
if ($selfClosing) {
6185
$this->eventCloseElement();
6286
}
@@ -73,6 +97,28 @@ public function processFile(string $filename): void
7397
$this->xml->close();
7498
}
7599

100+
private function skipNode(): bool
101+
{
102+
$result = $this->xml->next();
103+
$this->eventCloseElement();
104+
return $result;
105+
}
106+
107+
private function shouldSkipNode(): bool
108+
{
109+
if ($this->skipNodes === NULL) {
110+
return false;
111+
}
112+
$nodePath = implode('/', $this->nodePath);
113+
foreach ($this->skipNodes as $skipNode) {
114+
if (self::checkNodePath($nodePath, $skipNode)) {
115+
return true;
116+
}
117+
}
118+
119+
return false;
120+
}
121+
76122
private function eventOpenElement(): void
77123
{
78124
$this->pushNodePath();
@@ -148,4 +194,15 @@ private function createContext(string $contextClass): NodeProcessorContext
148194
}
149195
return $context;
150196
}
197+
198+
static function checkNodePath(string $nodePath, string $expected): bool
199+
{
200+
return
201+
$expected === '/' . $nodePath ||
202+
$nodePath === $expected || (
203+
function_exists('str_end_with')
204+
? str_end_with($nodePath, $expected) :
205+
substr_compare($nodePath, $expected, -strlen($expected)) === 0
206+
);
207+
}
151208
}

src/XmlProcessorContext.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace Netlogix\XmlProcessor;
55

6+
use Netlogix\XmlProcessor\NodeProcessor\NamedNodeProcessorInterface;
67
use Netlogix\XmlProcessor\NodeProcessor\NodeProcessorInterface;
78

89
class XmlProcessorContext
@@ -13,16 +14,24 @@ class XmlProcessorContext
1314
*/
1415
private iterable $processors;
1516

16-
public function __construct(\XMLReader $xml, iterable $processors)
17+
private \Closure $skipNode;
18+
19+
public function __construct(\XMLReader $xml, iterable $processors, \Closure $skipNode)
1720
{
1821
$this->xml = $xml;
1922
$this->processors = $processors;
23+
$this->skipNode = $skipNode;
24+
}
25+
26+
public function skipCurrentNode(): bool
27+
{
28+
return ($this->skipNode)();
2029
}
2130

2231
public function getProcessor(string $class): ?NodeProcessorInterface
2332
{
2433
foreach ($this->processors as $processor) {
25-
if ($processor instanceof $class) {
34+
if (class_exists($class) && $processor instanceof $class) {
2635
return $processor;
2736
}
2837
}

tests/Fixtures/AbstractNodeProcessorTest/TestNodeProcessor.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,26 @@
33
namespace Netlogix\XmlProcessor\Tests\Fixtures\AbstractNodeProcessorTest;
44

55
use Netlogix\XmlProcessor\NodeProcessor\AbstractNodeProcessor;
6+
use Netlogix\XmlProcessor\NodeProcessor\CloseNodeProcessorInterface;
7+
use Netlogix\XmlProcessor\NodeProcessor\Context\CloseContext;
8+
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
9+
use Netlogix\XmlProcessor\NodeProcessor\Context\TextContext;
10+
use Netlogix\XmlProcessor\NodeProcessor\OpenNodeProcessorInterface;
11+
use Netlogix\XmlProcessor\NodeProcessor\TextNodeProcessorInterface;
612

7-
class TestNodeProcessor extends AbstractNodeProcessor
13+
class TestNodeProcessor extends AbstractNodeProcessor implements OpenNodeProcessorInterface, TextNodeProcessorInterface, CloseNodeProcessorInterface
814
{
915
const NODE_PATH = 'test';
16+
17+
function openElement(OpenContext $context): void
18+
{
19+
}
20+
21+
function textElement(TextContext $context): void
22+
{
23+
}
24+
25+
function closeElement(CloseContext $context): void
26+
{
27+
}
1028
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
<?xml version='1.0'?>
22
<root foo="bar">
3-
hallo
3+
<foo>
4+
<bar>text</bar>
5+
</foo>
6+
<baz>hallo</baz>
7+
<br/>
48
</root>

tests/Unit/Behat/NodeProcessor/ArrayNodeProcessorTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<?php
2+
declare(strict_types=1);
23

34
namespace Netlogix\XmlProcessor\Tests\Unit\Behat\NodeProcessor;
45

56
use Netlogix\XmlProcessor\Behat\NodeProcessor\ArrayNodeProcessor;
67
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
8+
use Netlogix\XmlProcessor\NodeProcessor\Context\TextContext;
79
use Netlogix\XmlProcessor\XmlProcessorContext;
810
use PHPUnit\Framework\TestCase;
911

@@ -39,10 +41,12 @@ function testOpenElement(): void
3941
[
4042
'nodePath' => ['foo', 'bar'],
4143
'attributes' => ['name' => 'me'],
44+
'text' => 'test'
4245
],
4346
[
4447
'nodePath' => ['foo', 'bar'],
4548
'attributes' => ['name' => 'you'],
49+
'text' => 'test2'
4650
],
4751
[
4852
'nodePath' => ['foo'],
@@ -57,6 +61,11 @@ function testOpenElement(): void
5761
$context = new OpenContext($xmlProcessorContext, $item['nodePath']);
5862
$context->setAttributes($item['attributes']);
5963
$nodeProcessor->openElement($context);
64+
if (isset($item['text'])) {
65+
$textContext = new TextContext($xmlProcessorContext, $item['nodePath']);
66+
$textContext->setText($item['text']);
67+
$nodeProcessor->textElement($textContext);
68+
}
6069
}
6170

6271
self::assertEquals([
@@ -70,12 +79,14 @@ function testOpenElement(): void
7079
'level' => 2,
7180
'attributes' => ['name' => 'me'],
7281
'children' => [],
82+
'text' => 'test'
7383
],
7484
[
7585
'node' => 'bar',
7686
'level' => 2,
7787
'attributes' => ['name' => 'you'],
7888
'children' => [],
89+
'text' => 'test2'
7990
],
8091
],
8192

tests/Unit/Behat/NodeProcessor/TextNodeProcessorTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
2+
declare(strict_types=1);
23

34
namespace Netlogix\XmlProcessor\Tests\Unit\Behat\NodeProcessor;
45

0 commit comments

Comments
 (0)