1 | <?php |
||
12 | class XMLReader extends \XMLReader { |
||
13 | |||
14 | /** |
||
15 | * @var Namespaces |
||
16 | */ |
||
17 | private $_namespaces; |
||
18 | |||
19 | /** |
||
20 | * Store last used document to avoid early GC |
||
21 | * @var Document |
||
22 | */ |
||
23 | private $_document; |
||
24 | |||
25 | 15 | public function __construct() { |
|
26 | 15 | $this->_namespaces = new Namespaces(); |
|
27 | 15 | } |
|
28 | |||
29 | /** |
||
30 | * register a namespace prefix for the xml reader, it will be used in |
||
31 | * next() and other methods with a tag name argument |
||
32 | * |
||
33 | * @param string $prefix |
||
34 | * @param string $namespaceURI |
||
35 | * @throws \LogicException |
||
36 | */ |
||
37 | 4 | public function registerNamespace(string $prefix, string $namespaceURI) { |
|
38 | 4 | $this->_namespaces[$prefix] = $namespaceURI; |
|
39 | 4 | } |
|
40 | |||
41 | /** |
||
42 | * Positions cursor on the next node skipping all subtrees. If $name contains |
||
43 | * a namespace prefix it will be resolved using the registered namespaces. |
||
44 | * |
||
45 | * @param NULL|string $name The name of the next node to move to. |
||
46 | * @param NULL|string $namespaceURI |
||
47 | * @param callable|NULL $filter |
||
48 | * @return bool |
||
49 | */ |
||
50 | 5 | public function next($name = NULL, string $namespaceURI = NULL, callable $filter = NULL): bool { |
|
51 | 5 | if (NULL !== $name) { |
|
52 | 3 | list($localName, $namespaceURI, $ignoreNamespace) = $this->prepareCondition($name, $namespaceURI); |
|
53 | } else { |
||
54 | 2 | $ignoreNamespace = NULL === $namespaceURI || '' === $namespaceURI; |
|
55 | 2 | $localName = $name; |
|
56 | 2 | $namespaceURI = ''; |
|
57 | } |
||
58 | 5 | if ($ignoreNamespace && !$filter) { |
|
59 | 2 | return NULL !== $name ? parent::next($name) : parent::next(); |
|
60 | } |
||
61 | 3 | $found = empty($localName) ? parent::next() : parent::next($localName); |
|
62 | 3 | while ($found) { |
|
63 | if ( |
||
64 | 3 | ($ignoreNamespace || $this->namespaceURI === $namespaceURI) && |
|
65 | 3 | (!$filter || $filter($this)) |
|
66 | ) { |
||
67 | 3 | return TRUE; |
|
68 | } |
||
69 | 3 | $found = empty($localName) ? parent::next() : parent::next($localName); |
|
70 | } |
||
71 | 3 | return FALSE; |
|
72 | } |
||
73 | |||
74 | /** |
||
75 | * Move to next node in document, including subtrees. If $name contains |
||
76 | * a namespace prefix it will be resolved using the registered namespaces. |
||
77 | * |
||
78 | * @param NULL|string $name The name of the next node to move to. |
||
79 | * @param NULL|string $namespaceURI |
||
80 | * @param callable|NULL $filter |
||
81 | * @return bool |
||
82 | */ |
||
83 | 14 | public function read(string $name = NULL, string $namespaceURI = NULL, callable $filter = NULL): bool { |
|
84 | 14 | if (NULL !== $name) { |
|
85 | 8 | list($localName, $namespaceURI, $ignoreNamespace) = $this->prepareCondition($name, $namespaceURI); |
|
86 | 8 | while (parent::read()) { |
|
87 | if ( |
||
88 | 8 | $this->nodeType === XML_ELEMENT_NODE && |
|
89 | 8 | $this->localName === $localName && |
|
90 | ( |
||
91 | 8 | ($ignoreNamespace || ($this->namespaceURI === $namespaceURI)) && |
|
92 | 8 | (!$filter || $filter($this)) |
|
93 | ) |
||
94 | ) { |
||
95 | 8 | return TRUE; |
|
96 | } |
||
97 | } |
||
98 | 4 | return FALSE; |
|
99 | } |
||
100 | 6 | if ($filter) { |
|
101 | 2 | while (parent::read()) { |
|
102 | 2 | if ($filter($this)) { |
|
103 | 2 | return TRUE; |
|
104 | } |
||
105 | } |
||
106 | 1 | return FALSE; |
|
107 | } |
||
108 | 4 | return parent::read(); |
|
109 | } |
||
110 | |||
111 | /** |
||
112 | * Return attribute by name, resolve namespace prefix if included. |
||
113 | * |
||
114 | * @param string $name |
||
115 | * @return NULL|string |
||
116 | */ |
||
117 | 11 | public function getAttribute($name) { |
|
118 | 11 | list($prefix, $localName) = QualifiedName::split($name); |
|
119 | 11 | if (empty($prefix)) { |
|
120 | 10 | return parent::getAttribute($name); |
|
121 | } |
||
122 | 1 | return parent::getAttributeNs($localName, $this->_namespaces->resolveNamespace($prefix)); |
|
123 | } |
||
124 | |||
125 | /** |
||
126 | * @param \DOMNode $baseNode |
||
127 | * @return Node|\DOMNode |
||
128 | * @throws \LogicException |
||
129 | */ |
||
130 | 2 | public function expand($baseNode = NULL) { |
|
131 | 2 | if (NULL !== $baseNode) { |
|
132 | 1 | return parent::expand($baseNode); |
|
133 | } |
||
134 | 1 | $this->_document = $document = new Document(); |
|
135 | 1 | $document->namespaces($this->_namespaces); |
|
136 | 1 | return parent::expand($document); |
|
137 | } |
||
138 | |||
139 | /** |
||
140 | * @param $name |
||
141 | * @param $namespaceURI |
||
142 | * @return array |
||
143 | */ |
||
144 | 8 | private function prepareCondition($name, $namespaceURI): array { |
|
156 | |||
157 | /** |
||
158 | * @param resource $stream |
||
159 | * @param string $encoding |
||
160 | * @param int $options |
||
161 | * @return bool |
||
162 | * @throws \FluentDOM\Exceptions\InvalidArgument |
||
163 | */ |
||
164 | 2 | public function attachStream($stream, string $encoding = NULL, int $options = 0): bool { |
|
172 | } |
||
173 | } |
||
174 |