Completed
Push — master ( 7c71fa...1dceb4 )
by Lars
01:30
created

SimpleHtmlDom::replaceChild()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.1979

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 14
cts 17
cp 0.8235
rs 8.7857
c 0
b 0
f 0
cc 6
nc 9
nop 1
crap 6.1979
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
use BadMethodCallException;
8
use DOMElement;
9
use DOMNode;
10
use RuntimeException;
11
12
/**
13
 * @property string      outerText <p>Get dom node's outer html (alias for "outerHtml").</p>
14
 * @property string      outerHtml <p>Get dom node's outer html.</p>
15
 * @property string      innerText <p>Get dom node's inner html (alias for "innerHtml").</p>
16
 * @property string      innerHtml <p>Get dom node's inner html.</p>
17
 * @property string      plaintext <p>Get dom node's plain text.</p>
18
 * @property-read string tag       <p>Get dom node name.</p>
19
 * @property-read string attr      <p>Get dom node attributes.</p>
20
 * @property-read string text      <p>Get dom node name.</p>
21
 * @property-read string html      <p>Get dom node's outer html.</p>
22
 *
23
 * @method SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNode|null children() children($idx = -1) <p>Returns children of node.</p>
24
 * @method SimpleHtmlDom|null first_child() <p>Returns the first child of node.</p>
25
 * @method SimpleHtmlDom|null last_child() <p>Returns the last child of node.</p>
26
 * @method SimpleHtmlDom|null next_sibling() <p>Returns the next sibling of node.</p>
27
 * @method SimpleHtmlDom|null prev_sibling() <p>Returns the previous sibling of node.</p>
28
 * @method SimpleHtmlDom|null parent() <p>Returns the parent of node.</p>
29
 * @method string outerText() <p>Get dom node's outer html (alias for "outerHtml()").</p>
30
 * @method string outerHtml() <p>Get dom node's outer html.</p>
31
 * @method string innerText() <p>Get dom node's inner html (alias for "innerHtml()").</p>
32
 */
33
class SimpleHtmlDom implements \IteratorAggregate
34
{
35
    /**
36
     * @var array
37
     */
38
    protected static $functionAliases = [
39
        'children'     => 'childNodes',
40
        'first_child'  => 'firstChild',
41
        'last_child'   => 'lastChild',
42
        'next_sibling' => 'nextSibling',
43
        'prev_sibling' => 'previousSibling',
44
        'parent'       => 'parentNode',
45
        'outertext'    => 'html',
46
        'outerhtml'    => 'html',
47
        'innertext'    => 'innerHtml',
48
        'innerhtml'    => 'innerHtml',
49
    ];
50
51
    /**
52
     * @var DOMElement
53
     */
54
    protected $node;
55
56
    /**
57
     * SimpleHtmlDom constructor.
58
     *
59
     * @param DOMNode $node
60
     */
61 102
    public function __construct(DOMNode $node)
62
    {
63 102
        $this->node = $node;
0 ignored issues
show
Documentation Bug introduced by
$node is of type object<DOMNode>, but the property $node was declared to be of type object<DOMElement>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
64 102
    }
65
66
    /**
67
     * @param string $name
68
     * @param array  $arguments
69
     *
70
     * @throws \BadMethodCallException
71
     *
72
     * @return SimpleHtmlDom|string|null
73
     */
74 9 View Code Duplication
    public function __call($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...
75
    {
76 9
        $name = \strtolower($name);
77
78 9
        if (isset(self::$functionAliases[$name])) {
79 9
            return \call_user_func_array([$this, self::$functionAliases[$name]], $arguments);
80
        }
81
82
        throw new BadMethodCallException('Method does not exist');
83
    }
84
85
    /**
86
     * @param string $name
87
     *
88
     * @return array|string|null
89
     */
90 46
    public function __get($name)
91
    {
92 46
        $nameOrig = $name;
93 46
        $name = \strtolower($name);
94
95
        switch ($name) {
96 46
            case 'outerhtml':
97 42
            case 'outertext':
98 36
            case 'html':
99 19
                return $this->html();
100 36
            case 'innerhtml':
101 30
            case 'innertext':
102 11
                return $this->innerHtml();
103 27
            case 'text':
104 22
            case 'plaintext':
105 17
                return $this->text();
106 12
            case 'tag':
107 5
                return $this->node->nodeName;
108 10
            case 'attr':
109
                return $this->getAllAttributes();
110 View Code Duplication
            default:
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...
111 10
                if (\property_exists($this->node, $nameOrig) === true) {
112 1
                    return $this->node->{$nameOrig};
113
                }
114
115 10
                return $this->getAttribute($name);
116
        }
117
    }
118
119
    /**
120
     * @param string $selector
121
     * @param int    $idx
122
     *
123
     * @return SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNodeInterface
124
     */
125 12
    public function __invoke($selector, $idx = null)
126
    {
127 12
        return $this->find($selector, $idx);
128
    }
129
130
    /**
131
     * @param $name
132
     *
133
     * @return bool
134
     */
135 1
    public function __isset($name)
136
    {
137 1
        $nameOrig = $name;
138 1
        $name = \strtolower($name);
139
140
        switch ($name) {
141 1
            case 'outertext':
142 1
            case 'outerhtml':
143 1
            case 'innertext':
144 1
            case 'innerhtml':
145 1
            case 'plaintext':
146 1
            case 'text':
147 1
            case 'tag':
148
                return true;
149 View Code Duplication
            default:
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...
150 1
                if (\property_exists($this->node, $nameOrig) === true) {
151
                    return isset($this->node->{$nameOrig});
152
                }
153
154 1
                return $this->hasAttribute($name);
155
        }
156
    }
157
158
    /**
159
     * @param $name
160
     * @param $value
161
     *
162
     * @return SimpleHtmlDom
163
     */
164 16
    public function __set($name, $value)
165
    {
166 16
        $nameOrig = $name;
167 16
        $name = \strtolower($name);
168
169
        switch ($name) {
170 16
            case 'outerhtml':
171 14
            case 'outertext':
172 4
                return $this->replaceNodeWithString($value);
173 12
            case 'innertext':
174 10
            case 'innerhtml':
175 7
                return $this->replaceChildWithString($value);
176 9
            case 'plaintext':
177 1
                return $this->replaceTextWithString($value);
178 View Code Duplication
            default:
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...
179 8
                if (\property_exists($this->node, $nameOrig) === true) {
180
                    return $this->node->{$nameOrig} = $value;
181
                }
182
183 8
                return $this->setAttribute($name, $value);
184
        }
185
    }
186
187
    /**
188
     * @return string
189
     */
190 2
    public function __toString()
191
    {
192 2
        return $this->html();
193
    }
194
195
    /**
196
     * @param $name
197
     *
198
     * @return SimpleHtmlDom
199
     */
200
    public function __unset($name)
201
    {
202
        return $this->removeAttribute($name);
203
    }
204
205
    /**
206
     * Returns children of node.
207
     *
208
     * @param int $idx
209
     *
210
     * @return SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNode|null
211
     */
212 2
    public function childNodes(int $idx = -1)
213
    {
214 2
        $nodeList = $this->getIterator();
215
216 2
        if ($idx === -1) {
217 2
            return $nodeList;
218
        }
219
220 2
        return $nodeList[$idx] ?? null;
221
    }
222
223
    /**
224
     * Find list of nodes with a CSS selector.
225
     *
226
     * @param string   $selector
227
     * @param int|null $idx
228
     *
229
     * @return SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNodeInterface
230
     */
231 26
    public function find(string $selector, $idx = null)
232
    {
233 26
        return $this->getHtmlDomParser()->find($selector, $idx);
234
    }
235
236
    /**
237
     * Find one node with a CSS selector.
238
     *
239
     * @param string $selector
240
     *
241
     * @return SimpleHtmlDom|SimpleHtmlDomNodeInterface
242
     */
243
    public function findOne(string $selector)
244
    {
245
        return $this->find($selector, 0);
246
    }
247
248
    /**
249
     * Returns the first child of node.
250
     *
251
     * @return SimpleHtmlDom|null
252
     */
253 4
    public function firstChild()
254
    {
255 4
        $node = $this->node->firstChild;
256
257 4
        if ($node === null) {
258 1
            return null;
259
        }
260
261 4
        return new self($node);
262
    }
263
264
    /**
265
     * Returns an array of attributes.
266
     *
267
     * @return array|null
268
     */
269 2
    public function getAllAttributes()
270
    {
271 2
        if ($this->node->hasAttributes()) {
272 2
            $attributes = [];
273 2
            foreach ($this->node->attributes as $attr) {
274 2
                $attributes[$attr->name] = HtmlDomParser::putReplacedBackToPreserveHtmlEntities($attr->value);
275
            }
276
277 2
            return $attributes;
278
        }
279
280 1
        return null;
281
    }
282
283
    /**
284
     * Return attribute value.
285
     *
286
     * @param string $name
287
     *
288
     * @return string
289
     */
290 14
    public function getAttribute(string $name): string
291
    {
292 14
        $html = $this->node->getAttribute($name);
293
294 14
        return HtmlDomParser::putReplacedBackToPreserveHtmlEntities($html);
295
    }
296
297
    /**
298
     * Return element by #id.
299
     *
300
     * @param string $id
301
     *
302
     * @return SimpleHtmlDom|SimpleHtmlDomNodeInterface
303
     */
304 1
    public function getElementById(string $id)
305
    {
306 1
        return $this->find("#${id}", 0);
307
    }
308
309
    /**
310
     * Return element by tag name.
311
     *
312
     * @param string $name
313
     *
314
     * @return SimpleHtmlDom|SimpleHtmlDomNodeBlank
315
     */
316
    public function getElementByTagName(string $name)
317
    {
318 1
        $node = $this->node->getElementsByTagName($name)->item(0);
319
320 1
        if ($node === null) {
321
            return new SimpleHtmlDomNodeBlank();
322
        }
323
324 1
        return new self($node);
325
    }
326
327
    /**
328
     * Returns elements by #id.
329
     *
330
     * @param string   $id
331
     * @param int|null $idx
332
     *
333
     * @return SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNodeInterface
334
     */
335
    public function getElementsById(string $id, $idx = null)
336
    {
337
        return $this->find("#${id}", $idx);
338
    }
339
340
    /**
341
     * Returns elements by tag name.
342
     *
343
     * @param string   $name
344
     * @param int|null $idx
345
     *
346
     * @return SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNode|SimpleHtmlDomNodeBlank
347
     */
348 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...
349
    {
350 1
        $nodesList = $this->node->getElementsByTagName($name);
351
352 1
        $elements = new SimpleHtmlDomNode();
353
354 1
        foreach ($nodesList as $node) {
355 1
            $elements[] = new self($node);
356
        }
357
358
        // return all elements
359 1
        if ($idx === null) {
360 1
            return $elements;
361
        }
362
363
        // handle negative values
364
        if ($idx < 0) {
365
            $idx = \count($elements) + $idx;
366
        }
367
368
        // return one element
369
        return $elements[$idx] ?? new SimpleHtmlDomNodeBlank();
370
    }
371
372
    /**
373
     * Create a new "HtmlDomParser"-object from the current context.
374
     *
375
     * @return HtmlDomParser
376
     */
377
    public function getHtmlDomParser(): HtmlDomParser
378
    {
379 66
        return new HtmlDomParser($this);
380
    }
381
382
    /**
383
     * Retrieve an external iterator.
384
     *
385
     * @see  http://php.net/manual/en/iteratoraggregate.getiterator.php
386
     *
387
     * @return SimpleHtmlDomNode An instance of an object implementing <b>Iterator</b> or
388
     * <b>Traversable</b>
389
     */
390
    public function getIterator(): SimpleHtmlDomNode
391
    {
392 2
        $elements = new SimpleHtmlDomNode();
393 2
        if ($this->node->hasChildNodes()) {
394 2
            foreach ($this->node->childNodes as $node) {
395 2
                $elements[] = new self($node);
396
            }
397
        }
398
399 2
        return $elements;
400
    }
401
402
    /**
403
     * @return DOMNode
404
     */
405
    public function getNode(): \DOMNode
406
    {
407 67
        return $this->node;
408
    }
409
410
    /**
411
     * Determine if an attribute exists on the element.
412
     *
413
     * @param string $name
414
     *
415
     * @return bool
416
     */
417
    public function hasAttribute(string $name): bool
418
    {
419 2
        return $this->node->hasAttribute($name);
420
    }
421
422
    /**
423
     * Get dom node's outer html.
424
     *
425
     * @param bool $multiDecodeNewHtmlEntity
426
     *
427
     * @return string
428
     */
429
    public function html(bool $multiDecodeNewHtmlEntity = false): string
430
    {
431 22
        return $this->getHtmlDomParser()->html($multiDecodeNewHtmlEntity);
432
    }
433
434
    /**
435
     * Get dom node's inner html.
436
     *
437
     * @param bool $multiDecodeNewHtmlEntity
438
     *
439
     * @return string
440
     */
441
    public function innerHtml(bool $multiDecodeNewHtmlEntity = false): string
442
    {
443 11
        return $this->getHtmlDomParser()->innerHtml($multiDecodeNewHtmlEntity);
444
    }
445
446
    /**
447
     * Returns the last child of node.
448
     *
449
     * @return SimpleHtmlDom|null
450
     */
451
    public function lastChild()
452
    {
453 4
        $node = $this->node->lastChild;
454
455 4
        if ($node === null) {
456 1
            return null;
457
        }
458
459 4
        return new self($node);
460
    }
461
462
    /**
463
     * Returns the next sibling of node.
464
     *
465
     * @return SimpleHtmlDom|null
466
     */
467
    public function nextSibling()
468
    {
469 1
        $node = $this->node->nextSibling;
470
471 1
        if ($node === null) {
472 1
            return null;
473
        }
474
475 1
        return new self($node);
476
    }
477
478
    /**
479
     * Returns the parent of node.
480
     *
481
     * @return SimpleHtmlDom
482
     */
483
    public function parentNode(): self
484
    {
485 1
        return new self($this->node->parentNode);
486
    }
487
488
    /**
489
     * Returns the previous sibling of node.
490
     *
491
     * @return SimpleHtmlDom|null
492
     */
493
    public function previousSibling()
494
    {
495 1
        $node = $this->node->previousSibling;
496
497 1
        if ($node === null) {
498 1
            return null;
499
        }
500
501 1
        return new self($node);
502
    }
503
504
    /**
505
     * Replace child node.
506
     *
507
     * @param string $string
508
     *
509
     * @throws \RuntimeException
510
     *
511
     * @return $this
512
     */
513
    protected function replaceChildWithString(string $string): self
514
    {
515 7
        if (!empty($string)) {
516 6
            $newDocument = new HtmlDomParser($string);
517
518 6
            $tmpDomString = $this->normalizeStringForComparision($newDocument);
519 6
            $tmpStr = $this->normalizeStringForComparision($string);
520 6
            if ($tmpDomString !== $tmpStr) {
521
                throw new RuntimeException(
522
                    'Not valid HTML fragment!' . "\n" .
523
                    $tmpDomString . "\n" .
524
                    $tmpStr
525
                );
526
            }
527
        }
528
529
        /** @noinspection PhpParamsInspection */
530 7
        if (\count($this->node->childNodes) > 0) {
531 7
            foreach ($this->node->childNodes as $node) {
532 7
                $this->node->removeChild($node);
533
            }
534
        }
535
536 7
        if (!empty($newDocument)) {
537 6
            $newDocument = $this->cleanHtmlWrapper($newDocument);
538 6
            $newNode = $this->node->ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
539
            /** @noinspection UnusedFunctionResultInspection */
540 6
            $this->node->appendChild($newNode);
541
        }
542
543 7
        return $this;
544
    }
545
546
    /**
547
     * Replace this node with text
548
     *
549
     * @param string $string
550
     *
551
     * @return $this|null
552
     */
553
    protected function replaceTextWithString($string)
554
    {
555 1
        if (empty($string)) {
556 1
            $this->node->parentNode->removeChild($this->node);
557
558 1
            return null;
559
        }
560
561 1
        $newElement = $this->node->ownerDocument->createTextNode($string);
562 1
        $newNode = $this->node->ownerDocument->importNode($newElement, true);
563 1
        $this->node->parentNode->replaceChild($newNode, $this->node);
564 1
        $this->node = $newNode;
565
566 1
        return $this;
567
    }
568
569
    /**
570
     * Replace this node.
571
     *
572
     * @param string $string
573
     *
574
     * @throws \RuntimeException
575
     *
576
     * @return $this|null
577
     */
578
    protected function replaceNodeWithString(string $string)
579
    {
580 4
        if (empty($string)) {
581 2
            $this->node->parentNode->removeChild($this->node);
582
583 2
            return null;
584
        }
585
586 3
        $newDocument = new HtmlDomParser($string);
587
588 3
        $tmpDomOuterTextString = $this->normalizeStringForComparision($newDocument);
589 3
        $tmpStr = $this->normalizeStringForComparision($string);
590 3
        if ($tmpDomOuterTextString !== $tmpStr) {
591
            throw new RuntimeException(
592
                'Not valid HTML fragment!' . "\n"
593
                . $tmpDomOuterTextString . "\n" .
594
                $tmpStr
595
            );
596
        }
597
598 3
        $newDocument = $this->cleanHtmlWrapper($newDocument, true);
599
600 3
        $newNode = $this->node->ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
601
602 3
        $this->node->parentNode->replaceChild($newNode, $this->node);
603 3
        $this->node = $newNode;
604
605
        // Remove head element, preserving child nodes. (again)
606 3 View Code Duplication
        if ($newDocument->getIsDOMDocumentCreatedWithoutHeadWrapper() === true) {
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...
607 3
            $html = $this->node->parentNode->getElementsByTagName('head')[0];
608 3
            if ($this->node->parentNode->ownerDocument !== null) {
609 2
                $fragment = $this->node->parentNode->ownerDocument->createDocumentFragment();
610 2
                if ($html !== null) {
611
                    /** @var DOMNode $html */
612 1
                    while ($html->childNodes->length > 0) {
613
                        /** @noinspection UnusedFunctionResultInspection */
614 1
                        $fragment->appendChild($html->childNodes->item(0));
615
                    }
616
                    /** @noinspection UnusedFunctionResultInspection */
617 1
                    $html->parentNode->replaceChild($fragment, $html);
618
                }
619
            }
620
        }
621
622 3
        return $this;
623
    }
624
625
    /**
626
     * Normalize the given input for comparision.
627
     *
628
     * @param HtmlDomParser|string $input
629
     *
630
     * @return string
631
     */
632
    private function normalizeStringForComparision($input): string
633
    {
634 9
        if ($input instanceof HtmlDomParser) {
635 9
            $string = $input->outerText();
636
637 9
            if ($input->getIsDOMDocumentCreatedWithoutHeadWrapper() === true) {
638
                /** @noinspection HtmlRequiredTitleElement */
639 9
                $string = \str_replace(['<head>', '</head>'], '', $string);
640
            }
641
        } else {
642 9
            $string = (string) $input;
643
        }
644
645
        return
646 9
            \urlencode(
647 9
                \urldecode(
648 9
                    \trim(
649 9
                        \str_replace(
650
                            [
651 9
                                ' ',
652
                                "\n",
653
                                "\r",
654
                                '/>',
655
                            ],
656
                            [
657 9
                                '',
658
                                '',
659
                                '',
660
                                '>',
661
                            ],
662 9
                            \strtolower($string)
663
                        )
664
                    )
665
                )
666
            );
667
    }
668
669
    /**
670
     * @param HtmlDomParser $newDocument
671
     * @param bool          $removeExtraHeadTag
672
     *
673
     * @return HtmlDomParser
674
     */
675
    protected function cleanHtmlWrapper(HtmlDomParser $newDocument, $removeExtraHeadTag = false): HtmlDomParser
676
    {
677
        if (
678 9
            $newDocument->getIsDOMDocumentCreatedWithoutHtml() === true
679
            ||
680 9
            $newDocument->getIsDOMDocumentCreatedWithoutHtmlWrapper() === true
681
        ) {
682
683
            // Remove doc-type node.
684 9
            if ($newDocument->getDocument()->doctype !== null) {
685
                /** @noinspection UnusedFunctionResultInspection */
686
                $newDocument->getDocument()->doctype->parentNode->removeChild($newDocument->getDocument()->doctype);
687
            }
688
689
            // Remove html element, preserving child nodes.
690 9
            $html = $newDocument->getDocument()->getElementsByTagName('html')->item(0);
691 9
            $fragment = $newDocument->getDocument()->createDocumentFragment();
692 9
            if ($html !== null) {
693 6
                while ($html->childNodes->length > 0) {
694
                    /** @noinspection UnusedFunctionResultInspection */
695 6
                    $fragment->appendChild($html->childNodes->item(0));
696
                }
697
                /** @noinspection UnusedFunctionResultInspection */
698 6
                $html->parentNode->replaceChild($fragment, $html);
699
            }
700
701
            // Remove body element, preserving child nodes.
702 9
            $body = $newDocument->getDocument()->getElementsByTagName('body')->item(0);
703 9
            $fragment = $newDocument->getDocument()->createDocumentFragment();
704 9
            if ($body instanceof \DOMElement) {
705 4
                while ($body->childNodes->length > 0) {
706
                    /** @noinspection UnusedFunctionResultInspection */
707 4
                    $fragment->appendChild($body->childNodes->item(0));
708
                }
709
                /** @noinspection UnusedFunctionResultInspection */
710 4
                $body->parentNode->replaceChild($fragment, $body);
711
712
                // At this point DOMDocument still added a "<p>"-wrapper around our string,
713
                // so we replace it with "<simpleHtmlDomP>" and delete this at the ending ...
714 4
                $item = $newDocument->getDocument()->getElementsByTagName('p')->item(0);
715 4
                if ($item !== null) {
716
                    /** @noinspection UnusedFunctionResultInspection */
717 4
                    $this->changeElementName($item, 'simpleHtmlDomP');
718
                }
719
            }
720
        }
721
722
        // Remove head element, preserving child nodes.
723 View Code Duplication
        if (
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...
724 9
            $removeExtraHeadTag === true
725
            &&
726 9
            $newDocument->getIsDOMDocumentCreatedWithoutHeadWrapper() === true
727
        ) {
728 3
            $html = $this->node->parentNode->getElementsByTagName('head')[0];
729 3
            if ($this->node->parentNode->ownerDocument !== null) {
730 2
                $fragment = $this->node->parentNode->ownerDocument->createDocumentFragment();
731 2
                if ($html !== null) {
732
                    /** @var DOMNode $html */
733
                    while ($html->childNodes->length > 0) {
734
                        /** @noinspection UnusedFunctionResultInspection */
735
                        $fragment->appendChild($html->childNodes->item(0));
736
                    }
737
                    /** @noinspection UnusedFunctionResultInspection */
738
                    $html->parentNode->replaceChild($fragment, $html);
739
                }
740
            }
741
        }
742
743 9
        return $newDocument;
744
    }
745
746
    /**
747
     * Change the name of a tag in a "DOMNode".
748
     *
749
     * @param DOMNode $node
750
     * @param string  $name
751
     *
752
     * @return DOMElement
753
     */
754
    protected function changeElementName(\DOMNode $node, string $name): \DOMElement
755
    {
756 4
        $newnode = $node->ownerDocument->createElement($name);
757
758 4
        foreach ($node->childNodes as $child) {
759 4
            $child = $node->ownerDocument->importNode($child, true);
760
            /** @noinspection UnusedFunctionResultInspection */
761 4
            $newnode->appendChild($child);
762
        }
763
764 4
        foreach ($node->attributes as $attrName => $attrNode) {
765
            /** @noinspection UnusedFunctionResultInspection */
766
            $newnode->setAttribute($attrName, $attrNode);
767
        }
768
769
        /** @noinspection UnusedFunctionResultInspection */
770 4
        $newnode->ownerDocument->replaceChild($newnode, $node);
771
772 4
        return $newnode;
773
    }
774
775
    /**
776
     * Set attribute value.
777
     *
778
     * @param string      $name       <p>The name of the html-attribute.</p>
779
     * @param string|null $value      <p>Set to NULL or empty string, to remove the attribute.</p>
780
     * @param bool        $strict     </p>
781
     *                                $value must be NULL, to remove the attribute,
782
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
783
     *                                </p>
784
     *
785
     * @return $this
786
     */
787
    public function setAttribute(string $name, $value = null, bool $strict = false): self
788
    {
789
        if (
790 10
            ($strict === true && $value === null)
791
            ||
792 10
            ($strict === false && empty($value))
793
        ) {
794 2
            $this->node->removeAttribute($name);
795
        } else {
796
            /** @noinspection UnusedFunctionResultInspection */
797 10
            $this->node->setAttribute($name, $value);
798
        }
799
800 10
        return $this;
801
    }
802
803
    /**
804
     * @param string|string[]|null $value <p>
805
     *                                    null === get the current input value
806
     *                                    text === set a new input value
807
     *                                    </p>
808
     *
809
     * @return string|string[]|null
810
     */
811
    public function val($value = null)
812
    {
813 1
        if ($value === null) {
814
            if (
815 1
                $this->tag === 'input'
816
                &&
817
                (
818 1
                    $this->getAttribute('type') === 'text'
819
                    ||
820 1
                    $this->hasAttribute('type') === false
821
                )
822
            ) {
823 1
                return $this->getAttribute('value');
824
            }
825
826
            if (
827 1
                $this->hasAttribute('checked') === true
828
                &&
829 1
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
830
            ) {
831 1
                return $this->getAttribute('value');
832
            }
833
834 1
            if ($this->node->nodeName === 'select') {
835
                $valuesFromDom = [];
836
                foreach ($this->getElementsByTagName('option') as $option) {
837
                    if ($this->hasAttribute('checked') === true) {
838
                        $valuesFromDom[] = $option->getAttribute('value');
839
                    }
840
                }
841
842
                if (\count($valuesFromDom) === 0) {
843
                    return null;
844
                }
845
846
                return $valuesFromDom;
847
            }
848
849 1
            if ($this->node->nodeName === 'textarea') {
850 1
                return $this->node->nodeValue;
851
            }
852
        } else {
853 1
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
854 1
                if ($value === $this->getAttribute('value')) {
855
                    /** @noinspection UnusedFunctionResultInspection */
856 1
                    $this->setAttribute('checked', 'checked');
857
                } else {
858 1
                    $this->removeAttribute('checked');
859
                }
860 1
            } elseif ($this->node->nodeName === 'select') {
861
                foreach ($this->node->getElementsByTagName('option') as $option) {
862
                    /** @var DOMElement $option */
863
                    if ($value === $option->getAttribute('value')) {
864
                        /** @noinspection UnusedFunctionResultInspection */
865
                        $option->setAttribute('selected', 'selected');
866
                    } else {
867
                        $option->removeAttribute('selected');
868
                    }
869
                }
870 1
            } elseif ($this->node->nodeName === 'input') {
871
                // Set value for input elements
872
                /** @noinspection UnusedFunctionResultInspection */
873 1
                $this->setAttribute('value', (string) $value);
874 1
            } elseif ($this->node->nodeName === 'textarea') {
875 1
                $this->node->nodeValue = (string) $value;
876
            }
877
        }
878
879 1
        return null;
880
    }
881
882
    /**
883
     * Remove attribute.
884
     *
885
     * @param string $name <p>The name of the html-attribute.</p>
886
     *
887
     * @return mixed
888
     */
889
    public function removeAttribute(string $name)
890
    {
891
        $this->node->removeAttribute($name);
892
893
        return $this;
894
    }
895
896
    /**
897
     * Get dom node's plain text.
898
     *
899
     * @return string
900
     */
901
    public function text(): string
902
    {
903 17
        return $this->getHtmlDomParser()->fixHtmlOutput($this->node->textContent);
904
    }
905
}
906