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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions features/SkipNode.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
Feature: run XMLProcessor with TextNodeProcessor

Scenario: run XMLProcessor
Given initialize XMLProcessor with "Netlogix\XmlProcessor\Behat\NodeProcessor\ArrayNodeProcessor"
When set skipNode to:
"""
category
"""
When process xml with current XMLProcessor instance:
"""
<root name="main">
<product id="1">foo</product>
<category id="1">
<category id="2">
<product id="3">baz</product>
</category>
<category><bar/></category>
</category>
<product id="2">bar</product>
</root>
"""
Then NodeProcessor "Netlogix\XmlProcessor\Behat\NodeProcessor\ArrayNodeProcessor" should return:
"""
[
{
"node": "root",
"level": 1,
"attributes": {
"name": "main"
},
"children": [
{
"node": "product",
"level": 2,
"attributes": {
"id": "1"
},
"children": [],
"text": "foo"
},
{
"node": "category",
"level": 2,
"attributes": {
"id": "1"
},
"children": []
},
{
"node": "product",
"level": 2,
"attributes": {
"id": "2"
},
"children": [],
"text": "bar"
}
]
}
]
"""
10 changes: 9 additions & 1 deletion features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ public function iRunXmlProcessor(PyStringNode $content): void
$this->xmlProcessor->processFile($fileName);
}

/**
* @When set skipNode to:
*/
public function iSetSkipNode(PyStringNode $skipNode): void
{
$this->xmlProcessor->setSkipNodes($skipNode->getStrings());
}

/**
* @Then NodeProcessor :nodeProcessorClass should return:
*/
Expand All @@ -52,7 +60,7 @@ function nodeProcessorShouldReturn(string $nodeProcessorClass, PyStringNode $con
throw new \Exception(sprintf('Class %s does not extend %s', $nodeProcessorClass, NodeProcessorInterface::class));
}
/** @var InvokeNodeProcessorInterface $nodeProcessor */
$nodeProcessor = $this->xmlProcessor->getProcessorContext()->getProcessor($nodeProcessorClass);
$nodeProcessor = $this->xmlProcessor->getProcessor($nodeProcessorClass);
$expected = json_decode($content->getRaw(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(sprintf('Could not decode expected result: %s', json_last_error_msg()));
Expand Down
13 changes: 2 additions & 11 deletions src/NodeProcessor/AbstractNodeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Netlogix\XmlProcessor\NodeProcessor;

use Netlogix\XmlProcessor\XmlProcessor;
use Netlogix\XmlProcessor\XmlProcessorContext;

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

public function isNode(string $nodePath): bool
{
$expected = $this->getNodePath();
if ($expected === '/' . $nodePath) {
return true;
}

return $nodePath === $this->getNodePath()
|| (
function_exists('str_end_with')
? str_end_with($nodePath, $expected) :
substr_compare($nodePath, $expected, -strlen($expected)) === 0
);
return XmlProcessor::checkNodePath($nodePath, $this->getNodePath());
}
}
73 changes: 65 additions & 8 deletions src/XmlProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,57 @@ class XmlProcessor
private array $nodePath = [];
private string $currentValue = '';

private ?array $skipNodes = NULL;

private \XMLReader $xml;
private XmlProcessorContext $context;

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

/** @var iterable<bool> */
private iterable $parserProperties;

/**
* @param iterable<NodeProcessorInterface> $processors
* @param iterable<bool> $options
* @param iterable<bool> $parserProperties
*/
public function __construct(
iterable $processors,
iterable $options = []
iterable $parserProperties = []
)
{
$this->xml = new \XMLReader();
foreach ($options as $option => $value) {
$this->xml->setParserProperty($option, $value);
}
$this->processors = $processors;
$this->context = new XmlProcessorContext($this->xml, $this->processors);
$this->parserProperties = $parserProperties;
$this->context = new XmlProcessorContext(
$this->xml,
$this->processors,
fn() => $this->skipNode()
);
}

function getProcessorContext(): XmlProcessorContext
function setSkipNodes(?array $skipNodes = NULL): void
{
return $this->context;
$this->skipNodes = $skipNodes;
}

function getSkipNodes(): ?array
{
return $this->skipNodes;
}

function getProcessor(string $processorName): ?NodeProcessorInterface
{
return $this->context->getProcessor($processorName);
}

public function processFile(string $filename): void
{
$this->xml->open($filename);
foreach ($this->parserProperties as $parserProperty => $value) {
$this->xml->setParserProperty($parserProperty, $value);
}
$this->getProcessorEvents(self::EVENT_OPEN_FILE);
while ($this->xml->read()) {
switch ($this->xml->nodeType) {
Expand All @@ -57,6 +77,10 @@ public function processFile(string $filename): void
case \XMLReader::ELEMENT:
$selfClosing = $this->xml->isEmptyElement;
$this->eventOpenElement();
if ($this->shouldSkipNode()) {
$this->skipNode();
break;
}
if ($selfClosing) {
$this->eventCloseElement();
}
Expand All @@ -73,6 +97,28 @@ public function processFile(string $filename): void
$this->xml->close();
}

private function skipNode(): bool
{
$result = $this->xml->next();
$this->eventCloseElement();
return $result;
}

private function shouldSkipNode(): bool
{
if ($this->skipNodes === NULL) {
return false;
}
$nodePath = implode('/', $this->nodePath);
foreach ($this->skipNodes as $skipNode) {
if (self::checkNodePath($nodePath, $skipNode)) {
return true;
}
}

return false;
}

private function eventOpenElement(): void
{
$this->pushNodePath();
Expand Down Expand Up @@ -148,4 +194,15 @@ private function createContext(string $contextClass): NodeProcessorContext
}
return $context;
}

static function checkNodePath(string $nodePath, string $expected): bool
{
return
$expected === '/' . $nodePath ||
$nodePath === $expected || (
function_exists('str_end_with')
? str_end_with($nodePath, $expected) :
substr_compare($nodePath, $expected, -strlen($expected)) === 0
);
}
}
13 changes: 11 additions & 2 deletions src/XmlProcessorContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Netlogix\XmlProcessor;

use Netlogix\XmlProcessor\NodeProcessor\NamedNodeProcessorInterface;
use Netlogix\XmlProcessor\NodeProcessor\NodeProcessorInterface;

class XmlProcessorContext
Expand All @@ -13,16 +14,24 @@ class XmlProcessorContext
*/
private iterable $processors;

public function __construct(\XMLReader $xml, iterable $processors)
private \Closure $skipNode;

public function __construct(\XMLReader $xml, iterable $processors, \Closure $skipNode)
{
$this->xml = $xml;
$this->processors = $processors;
$this->skipNode = $skipNode;
}

public function skipCurrentNode(): bool
{
return ($this->skipNode)();
}

public function getProcessor(string $class): ?NodeProcessorInterface
{
foreach ($this->processors as $processor) {
if ($processor instanceof $class) {
if (class_exists($class) && $processor instanceof $class) {
return $processor;
}
}
Expand Down
20 changes: 19 additions & 1 deletion tests/Fixtures/AbstractNodeProcessorTest/TestNodeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,26 @@
namespace Netlogix\XmlProcessor\Tests\Fixtures\AbstractNodeProcessorTest;

use Netlogix\XmlProcessor\NodeProcessor\AbstractNodeProcessor;
use Netlogix\XmlProcessor\NodeProcessor\CloseNodeProcessorInterface;
use Netlogix\XmlProcessor\NodeProcessor\Context\CloseContext;
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
use Netlogix\XmlProcessor\NodeProcessor\Context\TextContext;
use Netlogix\XmlProcessor\NodeProcessor\OpenNodeProcessorInterface;
use Netlogix\XmlProcessor\NodeProcessor\TextNodeProcessorInterface;

class TestNodeProcessor extends AbstractNodeProcessor
class TestNodeProcessor extends AbstractNodeProcessor implements OpenNodeProcessorInterface, TextNodeProcessorInterface, CloseNodeProcessorInterface
{
const NODE_PATH = 'test';

function openElement(OpenContext $context): void
{
}

function textElement(TextContext $context): void
{
}

function closeElement(CloseContext $context): void
{
}
}
6 changes: 5 additions & 1 deletion tests/Fixtures/XmlProcessorTest/test.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<?xml version='1.0'?>
<root foo="bar">
hallo
<foo>
<bar>text</bar>
</foo>
<baz>hallo</baz>
<br/>
</root>
11 changes: 11 additions & 0 deletions tests/Unit/Behat/NodeProcessor/ArrayNodeProcessorTest.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?php
declare(strict_types=1);

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

use Netlogix\XmlProcessor\Behat\NodeProcessor\ArrayNodeProcessor;
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
use Netlogix\XmlProcessor\NodeProcessor\Context\TextContext;
use Netlogix\XmlProcessor\XmlProcessorContext;
use PHPUnit\Framework\TestCase;

Expand Down Expand Up @@ -39,10 +41,12 @@ function testOpenElement(): void
[
'nodePath' => ['foo', 'bar'],
'attributes' => ['name' => 'me'],
'text' => 'test'
],
[
'nodePath' => ['foo', 'bar'],
'attributes' => ['name' => 'you'],
'text' => 'test2'
],
[
'nodePath' => ['foo'],
Expand All @@ -57,6 +61,11 @@ function testOpenElement(): void
$context = new OpenContext($xmlProcessorContext, $item['nodePath']);
$context->setAttributes($item['attributes']);
$nodeProcessor->openElement($context);
if (isset($item['text'])) {
$textContext = new TextContext($xmlProcessorContext, $item['nodePath']);
$textContext->setText($item['text']);
$nodeProcessor->textElement($textContext);
}
}

self::assertEquals([
Expand All @@ -70,12 +79,14 @@ function testOpenElement(): void
'level' => 2,
'attributes' => ['name' => 'me'],
'children' => [],
'text' => 'test'
],
[
'node' => 'bar',
'level' => 2,
'attributes' => ['name' => 'you'],
'children' => [],
'text' => 'test2'
],
],

Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Behat/NodeProcessor/TextNodeProcessorTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
declare(strict_types=1);

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

Expand Down
Loading