diff --git a/src/NodeProcessor/Context/AbstractElementContext.php b/src/NodeProcessor/Context/AbstractElementContext.php new file mode 100644 index 0000000..0071998 --- /dev/null +++ b/src/NodeProcessor/Context/AbstractElementContext.php @@ -0,0 +1,18 @@ +selfClosing = $selfClosing; + } + + function getSelfClosing(): bool + { + return $this->selfClosing; + } +} diff --git a/src/NodeProcessor/Context/CloseContext.php b/src/NodeProcessor/Context/CloseContext.php index 6e4db29..d8516b1 100644 --- a/src/NodeProcessor/Context/CloseContext.php +++ b/src/NodeProcessor/Context/CloseContext.php @@ -3,6 +3,6 @@ namespace Netlogix\XmlProcessor\NodeProcessor\Context; -class CloseContext extends NodeProcessorContext +class CloseContext extends AbstractElementContext { } diff --git a/src/NodeProcessor/Context/OpenContext.php b/src/NodeProcessor/Context/OpenContext.php index 369410f..ee012a4 100644 --- a/src/NodeProcessor/Context/OpenContext.php +++ b/src/NodeProcessor/Context/OpenContext.php @@ -3,7 +3,7 @@ namespace Netlogix\XmlProcessor\NodeProcessor\Context; -class OpenContext extends NodeProcessorContext +class OpenContext extends AbstractElementContext { /** * @var array diff --git a/src/XmlProcessor.php b/src/XmlProcessor.php index 885c686..703cc3b 100644 --- a/src/XmlProcessor.php +++ b/src/XmlProcessor.php @@ -28,6 +28,9 @@ class XmlProcessor /** @var iterable */ private iterable $parserProperties; + private bool $skipCurrentNode = false; + private bool $selfClosing = false; + /** * @param iterable $processors * @param iterable $parserProperties @@ -43,7 +46,7 @@ public function __construct( $this->context = new XmlProcessorContext( $this->xml, $this->processors, - fn() => $this->skipNode() + fn() => $this->skipCurrentNode = true ); } @@ -75,13 +78,12 @@ public function processFile(string $filename): void $this->eventCloseElement(); break; case \XMLReader::ELEMENT: - $selfClosing = $this->xml->isEmptyElement; + $this->selfClosing = $this->xml->isEmptyElement; $this->eventOpenElement(); - if ($this->shouldSkipNode()) { - $this->skipNode(); - break; + if ($skip = $this->shouldSkipNode()) { + $this->xml->next(); } - if ($selfClosing) { + if ($skip || $this->selfClosing) { $this->eventCloseElement(); } break; @@ -106,6 +108,10 @@ private function skipNode(): bool private function shouldSkipNode(): bool { + if ($this->skipCurrentNode) { + $this->skipCurrentNode = false; + return true; + } if ($this->skipNodes === NULL) { return false; } @@ -186,6 +192,9 @@ private function popNodePath(): void private function createContext(string $contextClass): NodeProcessorContext { $context = new $contextClass($this->context, $this->nodePath); + if (method_exists($context, 'setSelfClosing')) { + $context->setSelfClosing($this->selfClosing); + } if (method_exists($context, 'setAttributes')) { $context->setAttributes($this->getAttributes()); } diff --git a/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php b/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php new file mode 100644 index 0000000..f6d3c5b --- /dev/null +++ b/tests/Unit/NodeProcessor/Context/AbstractElementContextTest.php @@ -0,0 +1,66 @@ +getMockBuilder(XmlProcessorContext::class) + ->disableOriginalConstructor() + ->getMock(), + $nodePath + ); + } + + public function test__construct(): void + { + $nodeProcessorContext = $this->getCloseContext(); + self::assertInstanceOf(AbstractElementContext::class, $nodeProcessorContext); + } + + /** + * @dataProvider setSelfClosingDataProvider + */ + function testSetSelfClosing($set, $expect): void + { + $nodeProcessorContext = $this->getCloseContext(); + $nodeProcessorContext->setSelfClosing($set); + self::assertEquals($expect, $nodeProcessorContext->getSelfClosing()); + } + + function setSelfClosingDataProvider(): \Generator + { + yield [true, true]; + yield [false, false]; + } + + /** + * @dataProvider getSelfClosingDataProvider + */ + function testGetSelfClosing($set, $expect): void + { + $nodeProcessorContext = $this->getCloseContext(); + if ($set !== NULL) { + $nodeProcessorContext->setSelfClosing($set); + } + self::assertEquals($expect, $nodeProcessorContext->getSelfClosing()); + } + + function getSelfClosingDataProvider(): \Generator + { + yield [NULL, false]; + yield [true, true]; + yield [false, false]; + } + +} diff --git a/tests/Unit/XmlProcessorTest.php b/tests/Unit/XmlProcessorTest.php index 73a8ee6..ec6ed6e 100644 --- a/tests/Unit/XmlProcessorTest.php +++ b/tests/Unit/XmlProcessorTest.php @@ -73,6 +73,36 @@ public function testProcessFile() [\XMLReader::SUBST_ENTITIES => true] ); $xmlProcessor->processFile(__DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'); + } + + public function testProcessFile_skipCurrentNode() + { + $nodeProcessor = $this->getMockForAbstractClass(NodeProcessorInterface::class); + + $nodeProcessor->method('getSubscribedEvents') + ->will( + $this->returnCallback(fn() => yield from [ + 'NodeType_' . \XMLReader::ELEMENT => function (OpenContext $context) { + $context->getXmlProcessorContext()->skipCurrentNode(); + self::assertNotEquals('bar', $context->getCurrentNodeName()); + }, + ]) + ); + + $xmlProcessor = new XmlProcessor([ + $nodeProcessor + ]); + + $xmlProcessor->processFile(__DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'); + + $xmlProcessor->setSkipNodes(['foo']); + $xmlProcessor->processFile(__DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'); + + $xmlProcessor = new XmlProcessor( + [$nodeProcessor], + [\XMLReader::SUBST_ENTITIES => true] + ); + $xmlProcessor->processFile(__DIR__ . '/../Fixtures/XmlProcessorTest/test.xml'); if(!function_exists('str_end_with')){ function str_end_with(string $nodePath, string $expected){ @@ -90,8 +120,9 @@ function testCheckNodePath(string $nodePath, string $expected, bool $result): vo self::assertSame(XmlProcessor::checkNodePath($nodePath, $expected), $result); } - public static function checkNodePathDataProvider(): iterable + public static function checkNodePathDataProvider(): \Generator { + yield ['', 'foo/bar', false]; yield ['/foo/bar', 'foo/bar', true]; yield ['/foo', 'foo/bar', false]; yield ['foo', 'foo/bar', false];