1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace voku\helper; |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* @property-read string $plaintext |
9
|
|
|
* <p>Get dom node's plain text.</p> |
10
|
|
|
* |
11
|
|
|
* @method static XmlDomParser file_get_xml($xml, $libXMLExtraOptions = null) |
12
|
|
|
* <p>Load XML from file.</p> |
13
|
|
|
* @method static XmlDomParser str_get_xml($xml, $libXMLExtraOptions = null) |
14
|
|
|
* <p>Load XML from string.</p> |
15
|
|
|
*/ |
16
|
|
|
class XmlDomParser extends AbstractDomParser |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* @param \DOMNode|SimpleXmlDomInterface|string $element HTML code or SimpleXmlDomInterface, \DOMNode |
20
|
|
|
*/ |
21
|
3 |
View Code Duplication |
public function __construct($element = null) |
|
|
|
|
22
|
|
|
{ |
23
|
3 |
|
$this->document = new \DOMDocument('1.0', $this->getEncoding()); |
24
|
|
|
|
25
|
|
|
// DOMDocument settings |
26
|
3 |
|
$this->document->preserveWhiteSpace = true; |
27
|
3 |
|
$this->document->formatOutput = true; |
28
|
|
|
|
29
|
3 |
|
if ($element instanceof SimpleXmlDomInterface) { |
30
|
|
|
$element = $element->getNode(); |
31
|
|
|
} |
32
|
|
|
|
33
|
3 |
|
if ($element instanceof \DOMNode) { |
34
|
|
|
$domNode = $this->document->importNode($element, true); |
35
|
|
|
|
36
|
|
|
if ($domNode instanceof \DOMNode) { |
37
|
|
|
/** @noinspection UnusedFunctionResultInspection */ |
38
|
|
|
$this->document->appendChild($domNode); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
return; |
42
|
|
|
} |
43
|
|
|
|
44
|
3 |
|
if ($element !== null) { |
45
|
|
|
/** @noinspection UnusedFunctionResultInspection */ |
46
|
|
|
$this->loadXml($element); |
47
|
|
|
} |
48
|
3 |
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @param string $name |
52
|
|
|
* @param array $arguments |
53
|
|
|
* |
54
|
|
|
* @throws \BadMethodCallException |
55
|
|
|
* @throws \RuntimeException |
56
|
|
|
* |
57
|
|
|
* @return XmlDomParser |
58
|
|
|
*/ |
59
|
3 |
View Code Duplication |
public static function __callStatic($name, $arguments) |
|
|
|
|
60
|
|
|
{ |
61
|
3 |
|
$arguments0 = $arguments[0] ?? ''; |
62
|
|
|
|
63
|
3 |
|
$arguments1 = $arguments[1] ?? null; |
64
|
|
|
|
65
|
3 |
|
if ($name === 'str_get_xml') { |
66
|
1 |
|
$parser = new static(); |
67
|
|
|
|
68
|
1 |
|
return $parser->loadXml($arguments0, $arguments1); |
69
|
|
|
} |
70
|
|
|
|
71
|
2 |
|
if ($name === 'file_get_xml') { |
72
|
2 |
|
$parser = new static(); |
73
|
|
|
|
74
|
2 |
|
return $parser->loadXmlFile($arguments0, $arguments1); |
|
|
|
|
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
throw new \BadMethodCallException('Method does not exist'); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** @noinspection MagicMethodsValidityInspection */ |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param string $name |
84
|
|
|
* |
85
|
|
|
* @return string|null |
86
|
|
|
*/ |
87
|
|
|
public function __get($name) |
88
|
|
|
{ |
89
|
|
|
$name = \strtolower($name); |
90
|
|
|
|
91
|
|
|
if ($name === 'plaintext') { |
92
|
|
|
return $this->text(); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
return null; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @return string |
100
|
|
|
*/ |
101
|
2 |
|
public function __toString() |
102
|
|
|
{ |
103
|
2 |
|
return $this->xml(false, false, true, 0); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Create DOMDocument from XML. |
108
|
|
|
* |
109
|
|
|
* @param string $xml |
110
|
|
|
* @param int|null $libXMLExtraOptions |
111
|
|
|
* |
112
|
|
|
* @return \DOMDocument |
113
|
|
|
*/ |
114
|
3 |
|
protected function createDOMDocument(string $xml, $libXMLExtraOptions = null): \DOMDocument |
115
|
|
|
{ |
116
|
|
|
// set error level |
117
|
3 |
|
$internalErrors = \libxml_use_internal_errors(true); |
118
|
3 |
|
$disableEntityLoader = \libxml_disable_entity_loader(true); |
119
|
3 |
|
\libxml_clear_errors(); |
120
|
|
|
|
121
|
3 |
|
$optionsXml = \LIBXML_DTDLOAD | \LIBXML_DTDATTR | \LIBXML_NONET; |
122
|
|
|
|
123
|
3 |
|
if (\defined('LIBXML_BIGLINES')) { |
124
|
3 |
|
$optionsXml |= \LIBXML_BIGLINES; |
125
|
|
|
} |
126
|
|
|
|
127
|
3 |
|
if (\defined('LIBXML_COMPACT')) { |
128
|
3 |
|
$optionsXml |= \LIBXML_COMPACT; |
129
|
|
|
} |
130
|
|
|
|
131
|
3 |
|
if ($libXMLExtraOptions !== null) { |
132
|
|
|
$optionsXml |= $libXMLExtraOptions; |
133
|
|
|
} |
134
|
|
|
|
135
|
3 |
|
$xml = self::replaceToPreserveHtmlEntities($xml); |
136
|
|
|
|
137
|
3 |
|
$documentFound = false; |
138
|
3 |
|
$sxe = \simplexml_load_string($xml, \SimpleXMLElement::class, $optionsXml); |
139
|
3 |
View Code Duplication |
if ($sxe !== false && \count(\libxml_get_errors()) === 0) { |
|
|
|
|
140
|
3 |
|
$domElementTmp = \dom_import_simplexml($sxe); |
141
|
|
|
if ( |
142
|
3 |
|
$domElementTmp |
143
|
|
|
&& |
144
|
3 |
|
$domElementTmp->ownerDocument !== null |
145
|
|
|
) { |
146
|
3 |
|
$documentFound = true; |
147
|
3 |
|
$this->document = $domElementTmp->ownerDocument; |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
|
151
|
3 |
View Code Duplication |
if ($documentFound === false) { |
|
|
|
|
152
|
|
|
|
153
|
|
|
// UTF-8 hack: http://php.net/manual/en/domdocument.loadhtml.php#95251 |
154
|
|
|
$xmlHackUsed = false; |
155
|
|
|
/** @noinspection StringFragmentMisplacedInspection */ |
156
|
|
|
if (\stripos('<?xml', $xml) !== 0) { |
157
|
|
|
$xmlHackUsed = true; |
158
|
|
|
$xml = '<?xml encoding="' . $this->getEncoding() . '" ?>' . $xml; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
$this->document->loadXML($xml, $optionsXml); |
162
|
|
|
|
163
|
|
|
// remove the "xml-encoding" hack |
164
|
|
|
if ($xmlHackUsed) { |
165
|
|
|
foreach ($this->document->childNodes as $child) { |
166
|
|
|
if ($child->nodeType === \XML_PI_NODE) { |
167
|
|
|
/** @noinspection UnusedFunctionResultInspection */ |
168
|
|
|
$this->document->removeChild($child); |
169
|
|
|
|
170
|
|
|
break; |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
// set encoding |
177
|
3 |
|
$this->document->encoding = $this->getEncoding(); |
178
|
|
|
|
179
|
|
|
// restore lib-xml settings |
180
|
3 |
|
\libxml_clear_errors(); |
181
|
3 |
|
\libxml_use_internal_errors($internalErrors); |
182
|
3 |
|
\libxml_disable_entity_loader($disableEntityLoader); |
183
|
|
|
|
184
|
3 |
|
return $this->document; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* Find list of nodes with a CSS selector. |
189
|
|
|
* |
190
|
|
|
* @param string $selector |
191
|
|
|
* @param int|null $idx |
192
|
|
|
* |
193
|
|
|
* @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
194
|
|
|
*/ |
195
|
1 |
View Code Duplication |
public function find(string $selector, $idx = null) |
|
|
|
|
196
|
|
|
{ |
197
|
1 |
|
$xPathQuery = SelectorConverter::toXPath($selector); |
198
|
|
|
|
199
|
1 |
|
$xPath = new \DOMXPath($this->document); |
200
|
1 |
|
$nodesList = $xPath->query($xPathQuery); |
201
|
1 |
|
$elements = new SimpleXmlDomNode(); |
202
|
|
|
|
203
|
1 |
|
if ($nodesList) { |
204
|
1 |
|
foreach ($nodesList as $node) { |
205
|
1 |
|
$elements[] = new SimpleXmlDom($node); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
// return all elements |
210
|
1 |
|
if ($idx === null) { |
211
|
1 |
|
if (\count($elements) === 0) { |
212
|
1 |
|
return new SimpleXmlDomNodeBlank(); |
213
|
|
|
} |
214
|
|
|
|
215
|
1 |
|
return $elements; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
// handle negative values |
219
|
1 |
|
if ($idx < 0) { |
220
|
|
|
$idx = \count($elements) + $idx; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
// return one element |
224
|
1 |
|
return $elements[$idx] ?? new SimpleXmlDomBlank(); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Find nodes with a CSS selector. |
229
|
|
|
* |
230
|
|
|
* @param string $selector |
231
|
|
|
* |
232
|
|
|
* @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
233
|
|
|
*/ |
234
|
1 |
|
public function findMulti(string $selector): SimpleXmlDomNodeInterface |
235
|
|
|
{ |
236
|
1 |
|
return $this->find($selector, null); |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* Find nodes with a CSS selector or false, if no element is found. |
241
|
|
|
* |
242
|
|
|
* @param string $selector |
243
|
|
|
* |
244
|
|
|
* @return false|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
245
|
|
|
*/ |
246
|
1 |
|
public function findMultiOrFalse(string $selector) |
247
|
|
|
{ |
248
|
1 |
|
$return = $this->find($selector, null); |
249
|
|
|
|
250
|
1 |
|
if ($return instanceof SimpleXmlDomNodeBlank) { |
251
|
1 |
|
return false; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
return $return; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Find one node with a CSS selector. |
259
|
|
|
* |
260
|
|
|
* @param string $selector |
261
|
|
|
* |
262
|
|
|
* @return SimpleXmlDomInterface |
263
|
|
|
*/ |
264
|
1 |
|
public function findOne(string $selector): SimpleXmlDomInterface |
265
|
|
|
{ |
266
|
1 |
|
return $this->find($selector, 0); |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Find one node with a CSS selector or false, if no element is found. |
271
|
|
|
* |
272
|
|
|
* @param string $selector |
273
|
|
|
* |
274
|
|
|
* @return false|SimpleXmlDomInterface |
275
|
|
|
*/ |
276
|
1 |
|
public function findOneOrFalse(string $selector) |
277
|
|
|
{ |
278
|
1 |
|
$return = $this->find($selector, 0); |
279
|
|
|
|
280
|
1 |
|
if ($return instanceof SimpleXmlDomBlank) { |
281
|
1 |
|
return false; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return $return; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* @param string $content |
289
|
|
|
* @param bool $multiDecodeNewHtmlEntity |
290
|
|
|
* |
291
|
|
|
* @return string |
292
|
|
|
*/ |
293
|
|
|
public function fixHtmlOutput(string $content, bool $multiDecodeNewHtmlEntity = false): string |
294
|
|
|
{ |
295
|
|
|
$content = $this->decodeHtmlEntity($content, $multiDecodeNewHtmlEntity); |
296
|
|
|
|
297
|
|
|
return self::putReplacedBackToPreserveHtmlEntities($content); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* Return elements by ".class". |
302
|
|
|
* |
303
|
|
|
* @param string $class |
304
|
|
|
* |
305
|
|
|
* @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
306
|
|
|
*/ |
307
|
|
|
public function getElementByClass(string $class): SimpleXmlDomNodeInterface |
308
|
|
|
{ |
309
|
|
|
return $this->findMulti(".${class}"); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Return element by #id. |
314
|
|
|
* |
315
|
|
|
* @param string $id |
316
|
|
|
* |
317
|
|
|
* @return SimpleXmlDomInterface |
318
|
|
|
*/ |
319
|
|
|
public function getElementById(string $id): SimpleXmlDomInterface |
320
|
|
|
{ |
321
|
|
|
return $this->findOne("#${id}"); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
/** |
325
|
|
|
* Return element by tag name. |
326
|
|
|
* |
327
|
|
|
* @param string $name |
328
|
|
|
* |
329
|
|
|
* @return SimpleXmlDomInterface |
330
|
|
|
*/ |
331
|
|
|
public function getElementByTagName(string $name): SimpleXmlDomInterface |
332
|
|
|
{ |
333
|
|
|
$node = $this->document->getElementsByTagName($name)->item(0); |
334
|
|
|
|
335
|
|
|
if ($node === null) { |
336
|
|
|
return new SimpleXmlDomBlank(); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
return new SimpleXmlDom($node); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Returns elements by "#id". |
344
|
|
|
* |
345
|
|
|
* @param string $id |
346
|
|
|
* @param int|null $idx |
347
|
|
|
* |
348
|
|
|
* @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
349
|
|
|
*/ |
350
|
|
|
public function getElementsById(string $id, $idx = null) |
351
|
|
|
{ |
352
|
|
|
return $this->find("#${id}", $idx); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* Returns elements by tag name. |
357
|
|
|
* |
358
|
|
|
* @param string $name |
359
|
|
|
* @param int|null $idx |
360
|
|
|
* |
361
|
|
|
* @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
362
|
|
|
*/ |
363
|
|
View Code Duplication |
public function getElementsByTagName(string $name, $idx = null) |
|
|
|
|
364
|
|
|
{ |
365
|
|
|
$nodesList = $this->document->getElementsByTagName($name); |
366
|
|
|
|
367
|
|
|
$elements = new SimpleXmlDomNode(); |
368
|
|
|
|
369
|
|
|
foreach ($nodesList as $node) { |
370
|
|
|
$elements[] = new SimpleXmlDom($node); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
// return all elements |
374
|
|
|
if ($idx === null) { |
375
|
|
|
if (\count($elements) === 0) { |
376
|
|
|
return new SimpleXmlDomNodeBlank(); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
return $elements; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
// handle negative values |
383
|
|
|
if ($idx < 0) { |
384
|
|
|
$idx = \count($elements) + $idx; |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
// return one element |
388
|
|
|
return $elements[$idx] ?? new SimpleXmlDomNodeBlank(); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Get dom node's outer html. |
393
|
|
|
* |
394
|
|
|
* @param bool $multiDecodeNewHtmlEntity |
395
|
|
|
* |
396
|
|
|
* @return string |
397
|
|
|
*/ |
398
|
|
|
public function html(bool $multiDecodeNewHtmlEntity = false): string |
399
|
|
|
{ |
400
|
|
|
if (static::$callback !== null) { |
401
|
|
|
\call_user_func(static::$callback, [$this]); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
$content = $this->document->saveHTML(); |
405
|
|
|
|
406
|
|
|
if ($content === false) { |
407
|
|
|
return ''; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
return $this->fixHtmlOutput($content, $multiDecodeNewHtmlEntity); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Load HTML from string. |
415
|
|
|
* |
416
|
|
|
* @param string $html |
417
|
|
|
* @param int|null $libXMLExtraOptions |
418
|
|
|
* |
419
|
|
|
* @return self |
420
|
|
|
*/ |
421
|
|
|
public function loadHtml(string $html, $libXMLExtraOptions = null): DomParserInterface |
422
|
|
|
{ |
423
|
|
|
$this->document = $this->createDOMDocument($html, $libXMLExtraOptions); |
424
|
|
|
|
425
|
|
|
return $this; |
|
|
|
|
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
/** |
429
|
|
|
* Load HTML from file. |
430
|
|
|
* |
431
|
|
|
* @param string $filePath |
432
|
|
|
* @param int|null $libXMLExtraOptions |
433
|
|
|
* |
434
|
|
|
* @throws \RuntimeException |
435
|
|
|
* |
436
|
|
|
* @return XmlDomParser |
437
|
|
|
*/ |
438
|
|
View Code Duplication |
public function loadHtmlFile(string $filePath, $libXMLExtraOptions = null): DomParserInterface |
|
|
|
|
439
|
|
|
{ |
440
|
|
|
if ( |
441
|
|
|
!\preg_match("/^https?:\/\//i", $filePath) |
442
|
|
|
&& |
443
|
|
|
!\file_exists($filePath) |
444
|
|
|
) { |
445
|
|
|
throw new \RuntimeException("File ${filePath} not found"); |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
try { |
449
|
|
|
if (\class_exists('\voku\helper\UTF8')) { |
450
|
|
|
/** @noinspection PhpUndefinedClassInspection */ |
451
|
|
|
$html = UTF8::file_get_contents($filePath); |
452
|
|
|
} else { |
453
|
|
|
$html = \file_get_contents($filePath); |
454
|
|
|
} |
455
|
|
|
} catch (\Exception $e) { |
456
|
|
|
throw new \RuntimeException("Could not load file ${filePath}"); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
if ($html === false) { |
460
|
|
|
throw new \RuntimeException("Could not load file ${filePath}"); |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
return $this->loadHtml($html, $libXMLExtraOptions); |
|
|
|
|
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* @param string $selector |
468
|
|
|
* @param int $idx |
469
|
|
|
* |
470
|
|
|
* @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface> |
|
|
|
|
471
|
|
|
*/ |
472
|
|
|
public function __invoke($selector, $idx = null) |
473
|
|
|
{ |
474
|
|
|
return $this->find($selector, $idx); |
475
|
|
|
} |
476
|
|
|
|
477
|
|
|
/** |
478
|
|
|
* Load XML from string. |
479
|
|
|
* |
480
|
|
|
* @param string $xml |
481
|
|
|
* @param int|null $libXMLExtraOptions |
482
|
|
|
* |
483
|
|
|
* @return XmlDomParser |
484
|
|
|
*/ |
485
|
3 |
|
public function loadXml(string $xml, $libXMLExtraOptions = null): self |
486
|
|
|
{ |
487
|
3 |
|
$this->document = $this->createDOMDocument($xml, $libXMLExtraOptions); |
488
|
|
|
|
489
|
3 |
|
return $this; |
490
|
|
|
} |
491
|
|
|
|
492
|
|
|
/** |
493
|
|
|
* Load XML from file. |
494
|
|
|
* |
495
|
|
|
* @param string $filePath |
496
|
|
|
* @param int|null $libXMLExtraOptions |
497
|
|
|
* |
498
|
|
|
* @throws \RuntimeException |
499
|
|
|
* |
500
|
|
|
* @return XmlDomParser |
501
|
|
|
*/ |
502
|
2 |
View Code Duplication |
public function loadXmlFile(string $filePath, $libXMLExtraOptions = null): self |
|
|
|
|
503
|
|
|
{ |
504
|
|
|
if ( |
505
|
2 |
|
!\preg_match("/^https?:\/\//i", $filePath) |
506
|
|
|
&& |
507
|
2 |
|
!\file_exists($filePath) |
508
|
|
|
) { |
509
|
|
|
throw new \RuntimeException("File ${filePath} not found"); |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
try { |
513
|
2 |
|
if (\class_exists('\voku\helper\UTF8')) { |
514
|
|
|
/** @noinspection PhpUndefinedClassInspection */ |
515
|
|
|
$xml = UTF8::file_get_contents($filePath); |
516
|
|
|
} else { |
517
|
2 |
|
$xml = \file_get_contents($filePath); |
518
|
|
|
} |
519
|
|
|
} catch (\Exception $e) { |
520
|
|
|
throw new \RuntimeException("Could not load file ${filePath}"); |
521
|
|
|
} |
522
|
|
|
|
523
|
2 |
|
if ($xml === false) { |
524
|
|
|
throw new \RuntimeException("Could not load file ${filePath}"); |
525
|
|
|
} |
526
|
|
|
|
527
|
2 |
|
return $this->loadXml($xml, $libXMLExtraOptions); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
/** |
531
|
|
|
* @param callable $callback |
532
|
|
|
* @param \DOMNode|null $domNode |
533
|
|
|
* |
534
|
|
|
* @return void |
535
|
|
|
*/ |
536
|
1 |
|
public function replaceTextWithCallback($callback, \DOMNode $domNode = null) |
537
|
|
|
{ |
538
|
1 |
|
if ($domNode === null) { |
539
|
1 |
|
$domNode = $this->document; |
540
|
|
|
} |
541
|
|
|
|
542
|
1 |
|
if ($domNode->hasChildNodes()) { |
543
|
1 |
|
$children = []; |
544
|
|
|
|
545
|
|
|
// since looping through a DOM being modified is a bad idea we prepare an array: |
546
|
1 |
|
foreach ($domNode->childNodes as $child) { |
547
|
1 |
|
$children[] = $child; |
548
|
|
|
} |
549
|
|
|
|
550
|
1 |
|
foreach ($children as $child) { |
551
|
1 |
|
if ($child->nodeType === \XML_TEXT_NODE) { |
552
|
|
|
/** @noinspection PhpSillyAssignmentInspection */ |
553
|
|
|
/** @var \DOMText $child */ |
554
|
1 |
|
$child = $child; |
|
|
|
|
555
|
|
|
|
556
|
1 |
|
$oldText = self::putReplacedBackToPreserveHtmlEntities($child->wholeText); |
557
|
1 |
|
$newText = $callback($oldText); |
558
|
1 |
|
if ($domNode->ownerDocument) { |
559
|
1 |
|
$newTextNode = $domNode->ownerDocument->createTextNode(self::replaceToPreserveHtmlEntities($newText)); |
560
|
1 |
|
$domNode->replaceChild($newTextNode, $child); |
561
|
|
|
} |
562
|
|
|
} else { |
563
|
1 |
|
$this->replaceTextWithCallback($callback, $child); |
564
|
|
|
} |
565
|
|
|
} |
566
|
|
|
} |
567
|
1 |
|
} |
568
|
|
|
} |
569
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.