Skip to content

Commit 5872f41

Browse files
committed
feat: optimize XmlProcessor
prevent function lookUp add whitelistEvents property
1 parent d55b6b4 commit 5872f41

1 file changed

Lines changed: 80 additions & 44 deletions

File tree

src/XmlProcessor.php

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

45
namespace Netlogix\XmlProcessor;
56

7+
use XMLReader;
8+
use Netlogix\XmlProcessor\NodeProcessor\Context\AbstractElementContext;
69
use Netlogix\XmlProcessor\NodeProcessor\Context\CloseContext;
710
use Netlogix\XmlProcessor\NodeProcessor\Context\NodeProcessorContext;
811
use Netlogix\XmlProcessor\NodeProcessor\Context\OpenContext;
@@ -13,14 +16,33 @@ class XmlProcessor
1316
{
1417
public const
1518
EVENT_OPEN_FILE = 'openFile',
16-
EVENT_END_OF_FILE = 'endOfFile';
19+
EVENT_END_OF_FILE = 'endOfFile',
20+
NODE_TYPE_NONE = 'NodeType_' . XMLReader::NONE,
21+
NODE_TYPE_ELEMENT = 'NodeType_' . XMLReader::ELEMENT,
22+
NODE_TYPE_ATTRIBUTE = 'NodeType_' . XMLReader::ATTRIBUTE,
23+
NODE_TYPE_TEXT = 'NodeType_' . XMLReader::TEXT,
24+
NODE_TYPE_CDATA = 'NodeType_' . XMLReader::CDATA,
25+
NODE_TYPE_ENTITY_REF = 'NodeType_' . XMLReader::ENTITY_REF,
26+
NODE_TYPE_ENTITY = 'NodeType_' . XMLReader::ENTITY,
27+
NODE_TYPE_PI = 'NodeType_' . XMLReader::PI,
28+
NODE_TYPE_COMMENT = 'NodeType_' . XMLReader::COMMENT,
29+
NODE_TYPE_DOC = 'NodeType_' . XMLReader::DOC,
30+
NODE_TYPE_DOC_TYPE = 'NodeType_' . XMLReader::DOC_TYPE,
31+
NODE_TYPE_DOC_FRAGMENT = 'NodeType_' . XMLReader::DOC_FRAGMENT,
32+
NODE_TYPE_NOTATION = 'NodeType_' . XMLReader::NOTATION,
33+
NODE_TYPE_WHITESPACE = 'NodeType_' . XMLReader::WHITESPACE,
34+
NODE_TYPE_SIGNIFICANT_WHITESPACE = 'NodeType_' . XMLReader::SIGNIFICANT_WHITESPACE,
35+
NODE_TYPE_END_ELEMENT = 'NodeType_' . XMLReader::END_ELEMENT,
36+
NODE_TYPE_END_ENTITY = 'NodeType_' . XMLReader::END_ENTITY,
37+
NODE_TYPE_XML_DECLARATION = 'NodeType_' . XMLReader::XML_DECLARATION;
38+
1739
private array $nodePath = [];
1840
private string $currentValue = '';
1941

20-
private ?array $skipNodes = NULL;
42+
private ?array $skipNodes = null;
2143
private array $eventCache = [];
2244

23-
private \XMLReader $xml;
45+
private XMLReader $xml;
2446
private XmlProcessorContext $context;
2547

2648
/** @var iterable<NodeProcessorInterface> */
@@ -29,6 +51,11 @@ class XmlProcessor
2951
/** @var iterable<bool> */
3052
private iterable $parserProperties;
3153

54+
/**
55+
* @var string[]
56+
*/
57+
private ?array $whitelistEvents = null;
58+
3259
private bool $skipCurrentNode = false;
3360
private bool $selfClosing = false;
3461

@@ -39,21 +66,16 @@ class XmlProcessor
3966
public function __construct(
4067
iterable $processors,
4168
iterable $parserProperties = [
42-
\XMLReader::VALIDATE => false
69+
XMLReader::VALIDATE => false
4370
]
44-
)
45-
{
46-
$this->xml = new \XMLReader();
71+
) {
72+
$this->xml = new XMLReader();
4773
$this->processors = $processors;
4874
$this->parserProperties = $parserProperties;
49-
$this->context = new XmlProcessorContext(
50-
$this->xml,
51-
$this->processors,
52-
fn() => $this->skipCurrentNode = true
53-
);
75+
$this->context = new XmlProcessorContext($this->xml, $this->processors, fn () => $this->skipCurrentNode = true);
5476
}
5577

56-
function setSkipNodes(?array $skipNodes = NULL): void
78+
function setSkipNodes(?array $skipNodes = null): void
5779
{
5880
$this->skipNodes = $skipNodes;
5981
}
@@ -63,6 +85,16 @@ function getSkipNodes(): ?array
6385
return $this->skipNodes;
6486
}
6587

88+
function setWhitelistEvents(?array $whitelistEvents = null): void
89+
{
90+
$this->whitelistEvents = $whitelistEvents;
91+
}
92+
93+
function getWhitelistEvents(): ?array
94+
{
95+
return $this->whitelistEvents;
96+
}
97+
6698
function getProcessor(string $processorName): ?NodeProcessorInterface
6799
{
68100
return $this->context->getProcessor($processorName);
@@ -74,51 +106,54 @@ public function processFile(string $filename): void
74106
foreach ($this->parserProperties as $parserProperty => $value) {
75107
$this->xml->setParserProperty($parserProperty, $value);
76108
}
77-
$this->getProcessorEvents(self::EVENT_OPEN_FILE);
109+
$this->callProcessorEvents(self::EVENT_OPEN_FILE);
78110
while ($this->xml->read()) {
79111
switch ($this->xml->nodeType) {
80-
case \XMLReader::END_ELEMENT:
112+
case XMLReader::END_ELEMENT:
81113
$this->eventCloseElement();
82114
break;
83-
case \XMLReader::ELEMENT:
115+
case XMLReader::ELEMENT:
84116
$this->selfClosing = $this->xml->isEmptyElement;
85117
$this->eventOpenElement();
86-
if ($skip = $this->shouldSkipNode()) {
118+
$skip = $this->shouldSkipNode();
119+
if ($skip) {
87120
$this->xml->next();
88121
}
89122
if ($skip || $this->selfClosing) {
90123
$this->eventCloseElement();
91124
}
92125
break;
93-
case \XMLReader::TEXT:
126+
case XMLReader::TEXT:
94127
$this->eventTextElement();
95128
break;
96129
default:
97-
$this->getProcessorEvents('NodeType_' . $this->xml->nodeType);
130+
$this->callProcessorEvents('NodeType_' . $this->xml->nodeType);
98131
break;
99132
}
100133
}
101-
$this->getProcessorEvents(self::EVENT_END_OF_FILE);
134+
$this->callProcessorEvents(self::EVENT_END_OF_FILE);
102135
$this->xml->close();
103136
}
104137

105138
private function skipNode(): bool
106139
{
107140
$result = $this->xml->next();
108141
$this->eventCloseElement();
142+
109143
return $result;
110144
}
111145

112146
private function shouldSkipNode(): bool
113147
{
114148
if ($this->skipCurrentNode) {
115149
$this->skipCurrentNode = false;
150+
116151
return true;
117152
}
118-
if ($this->skipNodes === NULL) {
153+
if ($this->skipNodes === null) {
119154
return false;
120155
}
121-
$nodePath = implode('/', $this->nodePath);
156+
$nodePath = \implode('/', $this->nodePath);
122157
foreach ($this->skipNodes as $skipNode) {
123158
if (self::checkNodePath($nodePath, $skipNode)) {
124159
return true;
@@ -131,26 +166,29 @@ private function shouldSkipNode(): bool
131166
private function eventOpenElement(): void
132167
{
133168
$this->pushNodePath();
134-
$this->getProcessorEvents('NodeType_' . \XMLReader::ELEMENT, OpenContext::class);
169+
$this->callProcessorEvents(XmlProcessor::NODE_TYPE_ELEMENT, OpenContext::class);
135170
}
136171

137172
private function eventTextElement(): void
138173
{
139174
$this->currentValue = $this->xml->value;
140-
$this->getProcessorEvents('NodeType_' . \XMLReader::TEXT, TextContext::class);
175+
$this->callProcessorEvents(XmlProcessor::NODE_TYPE_TEXT, TextContext::class);
141176
}
142177

143178
private function eventCloseElement(): void
144179
{
145-
$this->getProcessorEvents('NodeType_' . \XMLReader::END_ELEMENT, CloseContext::class);
180+
$this->callProcessorEvents(XmlProcessor::NODE_TYPE_END_ELEMENT, CloseContext::class);
146181
$this->popNodePath();
147182
}
148183

149-
private function getProcessorEvents(string $event, string $contextClass = NodeProcessorContext::class): void
184+
private function callProcessorEvents(string $event, string $contextClass = NodeProcessorContext::class): void
150185
{
151-
$context = NULL;
186+
if ($this->whitelistEvents !== null && !\in_array($event, $this->whitelistEvents, true)) {
187+
return;
188+
}
189+
$context = null;
152190
foreach ($this->getProcessorForEvent($event) as $action) {
153-
call_user_func($action, $context = $context ?? $this->createContext($contextClass));
191+
\call_user_func($action, $context ??= $this->createContext($contextClass));
154192
}
155193
unset($context);
156194
}
@@ -160,15 +198,16 @@ private function getProcessorEvents(string $event, string $contextClass = NodePr
160198
*/
161199
private function getProcessorForEvent(string $event): iterable
162200
{
163-
$nodePath = implode('/', $this->nodePath);
201+
$nodePath = \implode('/', $this->nodePath);
164202

165-
if (!is_array($this->eventCache[$nodePath][$event] ?? false)) {
203+
if (!\is_array($this->eventCache[$nodePath][$event] ?? false)) {
166204
$this->eventCache[$nodePath][$event] = [];
167205
foreach ($this->processors as $processor) {
168206
foreach ($processor->getSubscribedEvents($nodePath, $this->context) as $e => $action) {
169-
if ($e === $event) {
170-
$this->eventCache[$nodePath][$event][] = $action;
207+
if ($e !== $event) {
208+
continue;
171209
}
210+
$this->eventCache[$nodePath][$event][] = $action;
172211
}
173212
}
174213
}
@@ -185,6 +224,7 @@ private function getAttributes(): array
185224
while ($this->xml->moveToNextAttribute()) {
186225
$attributes[$this->xml->name] = $this->xml->value;
187226
}
227+
188228
return $attributes;
189229
}
190230

@@ -195,32 +235,28 @@ private function pushNodePath(): void
195235

196236
private function popNodePath(): void
197237
{
198-
array_pop($this->nodePath);
238+
\array_pop($this->nodePath);
199239
}
200240

201241
private function createContext(string $contextClass): NodeProcessorContext
202242
{
203243
$context = new $contextClass($this->context, $this->nodePath);
204-
if (method_exists($context, 'setSelfClosing')) {
244+
if ($context instanceof AbstractElementContext) {
205245
$context->setSelfClosing($this->selfClosing);
206246
}
207-
if (method_exists($context, 'setAttributes')) {
247+
if ($context instanceof OpenContext) {
208248
$context->setAttributes($this->getAttributes());
209-
}
210-
if (method_exists($context, 'setText')) {
249+
} elseif ($context instanceof TextContext) {
211250
$context->setText($this->currentValue);
212251
}
252+
213253
return $context;
214254
}
215255

216256
static function checkNodePath(string $nodePath, string $expected): bool
217257
{
218-
return
219-
$expected === '/' . $nodePath ||
220-
$nodePath === $expected || (
221-
function_exists('str_end_with')
222-
? str_end_with($nodePath, $expected) :
223-
substr_compare($nodePath, $expected, -strlen($expected)) === 0
224-
);
258+
return $nodePath === $expected
259+
|| '/' . $nodePath === $expected
260+
|| \str_ends_with($nodePath, $expected);
225261
}
226262
}

0 commit comments

Comments
 (0)