Completed
Pull Request — master (#55)
by Volodymyr
02:41
created

XmlDomParser::fixHtmlOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $parser->loadXmlF...guments0, $arguments1); (self) is incompatible with the return type declared by the abstract method voku\helper\AbstractDomParser::__callStatic of type voku\helper\AbstractDomParser.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
194
     */
195 1 View Code Duplication
    public function find(string $selector, $idx = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface[]|...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 49. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type false|SimpleXmlDomInterf...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 55. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface[]|...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 49. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
362
     */
363 View Code Duplication
    public function getElementsByTagName(string $name, $idx = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (voku\helper\XmlDomParser) is incompatible with the return type declared by the interface voku\helper\DomParserInterface::loadHtml of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->loadHtml($..., $libXMLExtraOptions); (voku\helper\XmlDomParser) is incompatible with the return type declared by the interface voku\helper\DomParserInterface::loadHtmlFile of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
464
    }
465
466
    /**
467
     * @param string $selector
468
     * @param int    $idx
469
     *
470
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface<SimpleXmlDomInterface>
0 ignored issues
show
Documentation introduced by
The doc-type SimpleXmlDomInterface|Si...<SimpleXmlDomInterface> could not be parsed: Expected "|" or "end of type", but got "<" at position 71. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
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
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
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;
0 ignored issues
show
Bug introduced by
Why assign $child to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
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