1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @package s9e\SweetDOM |
5
|
|
|
* @copyright Copyright (c) 2019-2020 The s9e authors |
6
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php The MIT License |
7
|
|
|
*/ |
8
|
|
|
namespace s9e\SweetDOM; |
9
|
|
|
|
10
|
|
|
use BadMethodCallException; |
11
|
|
|
use DOMElement; |
12
|
|
|
use DOMNode; |
13
|
|
|
use DOMNodeList; |
14
|
|
|
use InvalidArgumentException; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @method self appendElement(string $nodeName, $text = '') |
18
|
|
|
* @method self appendElementSibling(string $nodeName, $text = '') |
19
|
|
|
* @method void appendText(string $text) |
20
|
|
|
* @method void appendTextSibling(string $text) |
21
|
|
|
* @method self appendXslApplyTemplates(string $select = null) |
22
|
|
|
* @method self appendXslApplyTemplatesSibling(string $select = null) |
23
|
|
|
* @method self appendXslAttribute(string $name, string $text = '') |
24
|
|
|
* @method self appendXslAttributeSibling(string $name, string $text = '') |
25
|
|
|
* @method self appendXslChoose() |
26
|
|
|
* @method self appendXslChooseSibling() |
27
|
|
|
* @method self appendXslComment(string $text = '') |
28
|
|
|
* @method self appendXslCommentSibling(string $text = '') |
29
|
|
|
* @method self appendXslCopyOf(string $select) |
30
|
|
|
* @method self appendXslCopyOfSibling(string $select) |
31
|
|
|
* @method self appendXslIf(string $test, string $text = '') |
32
|
|
|
* @method self appendXslIfSibling(string $test, string $text = '') |
33
|
|
|
* @method self appendXslOtherwise(string $text = '') |
34
|
|
|
* @method self appendXslOtherwiseSibling(string $text = '') |
35
|
|
|
* @method self appendXslText(string $text = '') |
36
|
|
|
* @method self appendXslTextSibling(string $text = '') |
37
|
|
|
* @method self appendXslValueOf(string $select) |
38
|
|
|
* @method self appendXslValueOfSibling(string $select) |
39
|
|
|
* @method self appendXslVariable(string $name, string $select = null) |
40
|
|
|
* @method self appendXslVariableSibling(string $name, string $select = null) |
41
|
|
|
* @method self appendXslWhen(string $test, string $text = '') |
42
|
|
|
* @method self appendXslWhenSibling(string $test, string $text = '') |
43
|
|
|
* @method self prependElement(string $nodeName, $text = '') |
44
|
|
|
* @method self prependElementSibling(string $nodeName, $text = '') |
45
|
|
|
* @method void prependText(string $text) |
46
|
|
|
* @method void prependTextSibling(string $text) |
47
|
|
|
* @method self prependXslApplyTemplates(string $select = null) |
48
|
|
|
* @method self prependXslApplyTemplatesSibling(string $select = null) |
49
|
|
|
* @method self prependXslAttribute(string $name, string $text = '') |
50
|
|
|
* @method self prependXslAttributeSibling(string $name, string $text = '') |
51
|
|
|
* @method self prependXslChoose() |
52
|
|
|
* @method self prependXslChooseSibling() |
53
|
|
|
* @method self prependXslComment(string $text = '') |
54
|
|
|
* @method self prependXslCommentSibling(string $text = '') |
55
|
|
|
* @method self prependXslCopyOf(string $select) |
56
|
|
|
* @method self prependXslCopyOfSibling(string $select) |
57
|
|
|
* @method self prependXslIf(string $test, string $text = '') |
58
|
|
|
* @method self prependXslIfSibling(string $test, string $text = '') |
59
|
|
|
* @method self prependXslOtherwise(string $text = '') |
60
|
|
|
* @method self prependXslOtherwiseSibling(string $text = '') |
61
|
|
|
* @method self prependXslText(string $text = '') |
62
|
|
|
* @method self prependXslTextSibling(string $text = '') |
63
|
|
|
* @method self prependXslValueOf(string $select) |
64
|
|
|
* @method self prependXslValueOfSibling(string $select) |
65
|
|
|
* @method self prependXslVariable(string $name, string $select = null) |
66
|
|
|
* @method self prependXslVariableSibling(string $name, string $select = null) |
67
|
|
|
* @method self prependXslWhen(string $test, string $text = '') |
68
|
|
|
* @method self prependXslWhenSibling(string $test, string $text = '') |
69
|
|
|
*/ |
70
|
|
|
class Element extends DOMElement |
71
|
|
|
{ |
72
|
|
|
public function __call(string $name, array $arguments) |
73
|
|
|
{ |
74
|
|
|
$name = strtolower($name); |
75
|
|
|
$positions = [ |
76
|
|
|
'append' => 'beforeend', |
77
|
|
|
'appendsibling' => 'afterend', |
78
|
|
|
'prepend' => 'afterbegin', |
79
|
|
|
'prependsibling' => 'beforebegin' |
80
|
|
|
]; |
81
|
|
|
|
82
|
|
|
if (preg_match('(^(append|prepend)(xsl\\w+?)(sibling|)$)', $name, $m)) |
83
|
|
|
{ |
84
|
|
|
$localName = $m[2]; |
85
|
|
|
$where = $positions[$m[1] . $m[3]]; |
86
|
|
|
|
87
|
|
|
return $this->insertXslElement($localName, $where, $arguments); |
88
|
|
|
} |
89
|
|
|
if (preg_match('(^(append|prepend)element(sibling|)$)', $name, $m)) |
90
|
|
|
{ |
91
|
|
|
$nodeName = $arguments[0]; |
92
|
|
|
$text = $arguments[1] ?? ''; |
93
|
|
|
$where = $positions[$m[1] . $m[2]]; |
94
|
|
|
|
95
|
|
|
return $this->insertElement($nodeName, $where, $text); |
96
|
|
|
} |
97
|
|
|
if (preg_match('(^(append|prepend)text(sibling|)$)', $name, $m)) |
98
|
|
|
{ |
99
|
|
|
$text = $arguments[0]; |
100
|
|
|
$where = $positions[$m[1] . $m[2]]; |
101
|
|
|
|
102
|
|
|
return $this->insertAdjacentText($where, $text); |
|
|
|
|
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
throw new BadMethodCallException; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* |
110
|
|
|
* |
111
|
|
|
* @return self |
112
|
|
|
*/ |
113
|
|
|
protected function insertElement(string $nodeName, string $where, string $text): self |
114
|
|
|
{ |
115
|
|
|
$pos = strpos($nodeName, ':'); |
116
|
|
|
if ($pos === false) |
117
|
|
|
{ |
118
|
|
|
$element = $this->ownerDocument->createElement($nodeName, $text); |
119
|
|
|
} |
120
|
|
|
else |
121
|
|
|
{ |
122
|
|
|
$prefix = substr($nodeName, 0, $pos); |
123
|
|
|
$namespaceURI = $this->ownerDocument->lookupNamespaceURI($prefix); |
124
|
|
|
$element = $this->ownerDocument->createElementNS($namespaceURI, $nodeName, $text); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return $this->insertAdjacentElement($where, $element); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* |
132
|
|
|
* |
133
|
|
|
* @return self |
134
|
|
|
*/ |
135
|
|
|
protected function insertXslElement(string $localName, string $where, array $arguments): self |
136
|
|
|
{ |
137
|
|
|
$callback = [$this->ownerDocument, 'create' . $localName]; |
138
|
|
|
if (!is_callable($callback)) |
139
|
|
|
{ |
140
|
|
|
throw new BadMethodCallException; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
$element = call_user_func_array($callback, $arguments); |
144
|
|
|
|
145
|
|
|
return $this->insertAdjacentElement($where, $element); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Evaluate and return the result of a given XPath expression using this element as context node |
150
|
|
|
* |
151
|
|
|
* @param string $expr XPath expression |
152
|
|
|
* @return mixed |
153
|
|
|
*/ |
154
|
|
|
public function evaluate(string $expr) |
155
|
|
|
{ |
156
|
|
|
return $this->ownerDocument->evaluate($expr, $this); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Evaluate and return the first element of a given XPath query using this element as context node |
161
|
|
|
* |
162
|
|
|
* @param string $expr XPath expression |
163
|
|
|
* @return DOMNode|null |
164
|
|
|
*/ |
165
|
|
|
public function firstOf(string $expr): ?DOMNode |
166
|
|
|
{ |
167
|
|
|
return $this->ownerDocument->firstOf($expr, $this); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Insert given element relative to this element's position |
172
|
|
|
* |
173
|
|
|
* @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' |
174
|
|
|
* @param self $element |
175
|
|
|
* @return self |
176
|
|
|
*/ |
177
|
|
|
public function insertAdjacentElement(string $where, self $element): self |
178
|
|
|
{ |
179
|
|
|
$this->insertAdjacentNode($where, $element); |
180
|
|
|
|
181
|
|
|
return $element; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* Insert given text relative to this element's position |
186
|
|
|
* |
187
|
|
|
* @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' |
188
|
|
|
* @param string $text |
189
|
|
|
* @return void |
190
|
|
|
*/ |
191
|
|
|
public function insertAdjacentText(string $where, string $text): void |
192
|
|
|
{ |
193
|
|
|
$this->insertAdjacentNode($where, $this->ownerDocument->createTextNode($text)); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Insert given XML relative to this element's position |
198
|
|
|
* |
199
|
|
|
* @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' |
200
|
|
|
* @param string $xml |
201
|
|
|
* @return void |
202
|
|
|
*/ |
203
|
|
|
public function insertAdjacentXML(string $where, string $xml): void |
204
|
|
|
{ |
205
|
|
|
$fragment = $this->ownerDocument->createDocumentFragment(); |
206
|
|
|
$fragment->appendXML($this->addMissingNamespaceDeclarations($xml)); |
207
|
|
|
|
208
|
|
|
$this->insertAdjacentNode($where, $fragment); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Evaluate and return the result of a given XPath query using this element as context node |
213
|
|
|
* |
214
|
|
|
* @param string $expr XPath expression |
215
|
|
|
* @return DOMNodeList |
216
|
|
|
*/ |
217
|
|
|
public function query(string $expr): DOMNodeList |
218
|
|
|
{ |
219
|
|
|
return $this->ownerDocument->query($expr, $this); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Remove this element from the document |
224
|
|
|
* |
225
|
|
|
* @return void |
226
|
|
|
*/ |
227
|
|
|
public function remove(): void |
228
|
|
|
{ |
229
|
|
|
$this->parentNode->removeChild($this); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Replace this element with given nodes/text |
234
|
|
|
* |
235
|
|
|
* @param DOMNode|string $nodes |
236
|
|
|
* @return void |
237
|
|
|
*/ |
238
|
|
|
public function replaceWith(...$nodes): void |
239
|
|
|
{ |
240
|
|
|
foreach ($nodes as $node) |
241
|
|
|
{ |
242
|
|
|
if (!($node instanceof DOMNode)) |
243
|
|
|
{ |
244
|
|
|
$node = $this->ownerDocument->createTextNode((string) $node); |
245
|
|
|
} |
246
|
|
|
$this->parentNode->insertBefore($node, $this); |
247
|
|
|
} |
248
|
|
|
$this->parentNode->removeChild($this); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* Add namespace declarations that may be missing in given XML |
253
|
|
|
* |
254
|
|
|
* @param string $xml Original XML |
255
|
|
|
* @return string Modified XML |
256
|
|
|
*/ |
257
|
|
|
protected function addMissingNamespaceDeclarations(string $xml): string |
258
|
|
|
{ |
259
|
|
|
preg_match_all('(xmlns:\\K[-\\w]++(?==))', $xml, $m); |
260
|
|
|
$prefixes = array_flip($m[0]); |
261
|
|
|
|
262
|
|
|
return preg_replace_callback( |
263
|
|
|
'(<([-\\w]++):[^>]*?\\K\\s*/?>)', |
264
|
|
|
function ($m) use ($prefixes) |
265
|
|
|
{ |
266
|
|
|
$return = $m[0]; |
267
|
|
|
$prefix = $m[1]; |
268
|
|
|
if (!isset($prefixes[$prefix])) |
269
|
|
|
{ |
270
|
|
|
$nsURI = $this->lookupNamespaceURI($prefix); |
271
|
|
|
$return = ' xmlns:' . $prefix . '="' . htmlspecialchars($nsURI, ENT_XML1) . '"' . $return; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
return $return; |
275
|
|
|
}, |
276
|
|
|
$xml |
277
|
|
|
); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* Insert given node relative to this element's position |
282
|
|
|
* |
283
|
|
|
* @param string $where One of 'beforebegin', 'afterbegin', 'beforeend', 'afterend' |
284
|
|
|
* @param DOMNode $node |
285
|
|
|
* @return void |
286
|
|
|
*/ |
287
|
|
|
protected function insertAdjacentNode(string $where, DOMNode $node): void |
288
|
|
|
{ |
289
|
|
|
$where = strtolower($where); |
290
|
|
|
if ($where === 'beforebegin') |
291
|
|
|
{ |
292
|
|
|
$this->parentNode->insertBefore($node, $this); |
293
|
|
|
} |
294
|
|
|
elseif ($where === 'beforeend') |
295
|
|
|
{ |
296
|
|
|
$this->appendChild($node); |
297
|
|
|
} |
298
|
|
|
elseif ($where === 'afterend') |
299
|
|
|
{ |
300
|
|
|
$this->parentNode->insertBefore($node, $this->nextSibling); |
301
|
|
|
} |
302
|
|
|
elseif ($where === 'afterbegin') |
303
|
|
|
{ |
304
|
|
|
$this->insertBefore($node, $this->firstChild); |
305
|
|
|
} |
306
|
|
|
else |
307
|
|
|
{ |
308
|
|
|
throw new InvalidArgumentException; |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
} |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.