Completed
Push — master ( 7c5d47...308454 )
by Lars
01:48 queued 12s
created

SimpleHtmlDom::html()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 103
    public function __construct(DOMNode $node)
62
    {
63 103
        $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 103
    }
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 47
    public function __get($name)
91
    {
92 47
        $nameOrig = $name;
93 47
        $name = \strtolower($name);
94
95
        switch ($name) {
96 47
            case 'outerhtml':
97 43
            case 'outertext':
98 36
            case 'html':
99 20
                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)) {
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)) {
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)) {
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 1
    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 1 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 67
    public function getHtmlDomParser(): HtmlDomParser
378
    {
379 67
        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 2
    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 68
    public function getNode(): \DOMNode
406
    {
407 68
        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 2
    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 23
    public function html(bool $multiDecodeNewHtmlEntity = false): string
430
    {
431 23
        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 11
    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 4
    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 1
    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 1
    public function parentNode(): self
484
    {
485 1
        return new self($this->node->parentNode);
486
    }
487
488
    /**
489
     * Nodes can get partially destroyed in which they're still an
490
     * actual DOM node (such as \DOMElement) but almost their entire
491
     * body is gone, including the `nodeType` attribute.
492
     *
493
     * @return bool true if node has been destroyed
494
     */
495
    public function isRemoved(): bool
496
    {
497
        return !isset($this->node->nodeType);
498
    }
499
500
    /**
501
     * Returns the previous sibling of node.
502
     *
503
     * @return SimpleHtmlDom|null
504
     */
505 1
    public function previousSibling()
506
    {
507 1
        $node = $this->node->previousSibling;
508
509 1
        if ($node === null) {
510 1
            return null;
511
        }
512
513 1
        return new self($node);
514
    }
515
516
    /**
517
     * Replace child node.
518
     *
519
     * @param string $string
520
     *
521
     * @throws \RuntimeException
522
     *
523
     * @return $this
524
     */
525 7
    protected function replaceChildWithString(string $string): self
526
    {
527 7
        if (!empty($string)) {
528 6
            $newDocument = new HtmlDomParser($string);
529
530 6
            $tmpDomString = $this->normalizeStringForComparision($newDocument);
531 6
            $tmpStr = $this->normalizeStringForComparision($string);
532 6
            if ($tmpDomString !== $tmpStr) {
533
                throw new RuntimeException(
534
                    'Not valid HTML fragment!' . "\n" .
535
                    $tmpDomString . "\n" .
536
                    $tmpStr
537
                );
538
            }
539
        }
540
541
        /** @noinspection PhpParamsInspection */
542 7
        if (\count($this->node->childNodes) > 0) {
543 7
            foreach ($this->node->childNodes as $node) {
544 7
                $this->node->removeChild($node);
545
            }
546
        }
547
548 7
        if (!empty($newDocument)) {
549 6
            $newDocument = $this->cleanHtmlWrapper($newDocument);
550 6
            $newNode = $this->node->ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
551
            /** @noinspection UnusedFunctionResultInspection */
552 6
            $this->node->appendChild($newNode);
553
        }
554
555 7
        return $this;
556
    }
557
558
    /**
559
     * Replace this node with text
560
     *
561
     * @param string $string
562
     *
563
     * @return $this|null
564
     */
565 1
    protected function replaceTextWithString($string)
566
    {
567 1
        if (empty($string)) {
568 1
            $this->node->parentNode->removeChild($this->node);
569
570 1
            return null;
571
        }
572
573 1
        $newElement = $this->node->ownerDocument->createTextNode($string);
574 1
        $newNode = $this->node->ownerDocument->importNode($newElement, true);
575 1
        $this->node->parentNode->replaceChild($newNode, $this->node);
576 1
        $this->node = $newNode;
577
578 1
        return $this;
579
    }
580
581
    /**
582
     * Replace this node.
583
     *
584
     * @param string $string
585
     *
586
     * @throws \RuntimeException
587
     *
588
     * @return $this|null
589
     */
590 4
    protected function replaceNodeWithString(string $string)
591
    {
592 4
        if (empty($string)) {
593 2
            $this->node->parentNode->removeChild($this->node);
594
595 2
            return null;
596
        }
597
598 3
        $newDocument = new HtmlDomParser($string);
599
600 3
        $tmpDomOuterTextString = $this->normalizeStringForComparision($newDocument);
601 3
        $tmpStr = $this->normalizeStringForComparision($string);
602 3
        if ($tmpDomOuterTextString !== $tmpStr) {
603
            throw new RuntimeException(
604
                'Not valid HTML fragment!' . "\n"
605
                . $tmpDomOuterTextString . "\n" .
606
                $tmpStr
607
            );
608
        }
609
610 3
        $newDocument = $this->cleanHtmlWrapper($newDocument, true);
611
612 3
        $newNode = $this->node->ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
613
614 3
        $this->node->parentNode->replaceChild($newNode, $this->node);
615 3
        $this->node = $newNode;
616
617
        // Remove head element, preserving child nodes. (again)
618 3 View Code Duplication
        if ($newDocument->getIsDOMDocumentCreatedWithoutHeadWrapper()) {
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...
619 3
            $html = $this->node->parentNode->getElementsByTagName('head')[0];
620 3
            if ($this->node->parentNode->ownerDocument !== null) {
621 2
                $fragment = $this->node->parentNode->ownerDocument->createDocumentFragment();
622 2
                if ($html !== null) {
623
                    /** @var DOMNode $html */
624 1
                    while ($html->childNodes->length > 0) {
625
                        /** @noinspection UnusedFunctionResultInspection */
626 1
                        $fragment->appendChild($html->childNodes->item(0));
627
                    }
628
                    /** @noinspection UnusedFunctionResultInspection */
629 1
                    $html->parentNode->replaceChild($fragment, $html);
630
                }
631
            }
632
        }
633
634 3
        return $this;
635
    }
636
637
    /**
638
     * Normalize the given input for comparision.
639
     *
640
     * @param HtmlDomParser|string $input
641
     *
642
     * @return string
643
     */
644 9
    private function normalizeStringForComparision($input): string
645
    {
646 9
        if ($input instanceof HtmlDomParser) {
647 9
            $string = $input->outerText();
648
649 9
            if ($input->getIsDOMDocumentCreatedWithoutHeadWrapper()) {
650
                /** @noinspection HtmlRequiredTitleElement */
651 9
                $string = \str_replace(['<head>', '</head>'], '', $string);
652
            }
653
        } else {
654 9
            $string = (string) $input;
655
        }
656
657
        return
658 9
            \urlencode(
659 9
                \urldecode(
660 9
                    \trim(
661 9
                        \str_replace(
662
                            [
663 9
                                ' ',
664
                                "\n",
665
                                "\r",
666
                                '/>',
667
                            ],
668
                            [
669 9
                                '',
670
                                '',
671
                                '',
672
                                '>',
673
                            ],
674 9
                            \strtolower($string)
675
                        )
676
                    )
677
                )
678
            );
679
    }
680
681
    /**
682
     * @param HtmlDomParser $newDocument
683
     * @param bool          $removeExtraHeadTag
684
     *
685
     * @return HtmlDomParser
686
     */
687 9
    protected function cleanHtmlWrapper(HtmlDomParser $newDocument, $removeExtraHeadTag = false): HtmlDomParser
688
    {
689
        if (
690 9
            $newDocument->getIsDOMDocumentCreatedWithoutHtml()
691
            ||
692 9
            $newDocument->getIsDOMDocumentCreatedWithoutHtmlWrapper()
693
        ) {
694
695
            // Remove doc-type node.
696 9
            if ($newDocument->getDocument()->doctype !== null) {
697
                /** @noinspection UnusedFunctionResultInspection */
698
                $newDocument->getDocument()->doctype->parentNode->removeChild($newDocument->getDocument()->doctype);
699
            }
700
701
            // Remove html element, preserving child nodes.
702 9
            $html = $newDocument->getDocument()->getElementsByTagName('html')->item(0);
703 9
            $fragment = $newDocument->getDocument()->createDocumentFragment();
704 9
            if ($html !== null) {
705 6
                while ($html->childNodes->length > 0) {
706
                    /** @noinspection UnusedFunctionResultInspection */
707 6
                    $fragment->appendChild($html->childNodes->item(0));
708
                }
709
                /** @noinspection UnusedFunctionResultInspection */
710 6
                $html->parentNode->replaceChild($fragment, $html);
711
            }
712
713
            // Remove body element, preserving child nodes.
714 9
            $body = $newDocument->getDocument()->getElementsByTagName('body')->item(0);
715 9
            $fragment = $newDocument->getDocument()->createDocumentFragment();
716 9
            if ($body instanceof \DOMElement) {
717 4
                while ($body->childNodes->length > 0) {
718
                    /** @noinspection UnusedFunctionResultInspection */
719 4
                    $fragment->appendChild($body->childNodes->item(0));
720
                }
721
                /** @noinspection UnusedFunctionResultInspection */
722 4
                $body->parentNode->replaceChild($fragment, $body);
723
724
                // At this point DOMDocument still added a "<p>"-wrapper around our string,
725
                // so we replace it with "<simpleHtmlDomP>" and delete this at the ending ...
726 4
                $item = $newDocument->getDocument()->getElementsByTagName('p')->item(0);
727 4
                if ($item !== null) {
728
                    /** @noinspection UnusedFunctionResultInspection */
729 4
                    $this->changeElementName($item, 'simpleHtmlDomP');
730
                }
731
            }
732
        }
733
734
        // Remove head element, preserving child nodes.
735 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...
736 9
            $removeExtraHeadTag
737
            &&
738 9
            $newDocument->getIsDOMDocumentCreatedWithoutHeadWrapper()
739
        ) {
740 3
            $html = $this->node->parentNode->getElementsByTagName('head')[0];
741 3
            if ($this->node->parentNode->ownerDocument !== null) {
742 2
                $fragment = $this->node->parentNode->ownerDocument->createDocumentFragment();
743 2
                if ($html !== null) {
744
                    /** @var DOMNode $html */
745
                    while ($html->childNodes->length > 0) {
746
                        /** @noinspection UnusedFunctionResultInspection */
747
                        $fragment->appendChild($html->childNodes->item(0));
748
                    }
749
                    /** @noinspection UnusedFunctionResultInspection */
750
                    $html->parentNode->replaceChild($fragment, $html);
751
                }
752
            }
753
        }
754
755 9
        return $newDocument;
756
    }
757
758
    /**
759
     * Change the name of a tag in a "DOMNode".
760
     *
761
     * @param DOMNode $node
762
     * @param string  $name
763
     *
764
     * @return DOMElement
765
     */
766 4
    protected function changeElementName(\DOMNode $node, string $name): \DOMElement
767
    {
768 4
        $newnode = $node->ownerDocument->createElement($name);
769
770 4
        foreach ($node->childNodes as $child) {
771 4
            $child = $node->ownerDocument->importNode($child, true);
772
            /** @noinspection UnusedFunctionResultInspection */
773 4
            $newnode->appendChild($child);
774
        }
775
776 4
        foreach ($node->attributes as $attrName => $attrNode) {
777
            /** @noinspection UnusedFunctionResultInspection */
778
            $newnode->setAttribute($attrName, $attrNode);
779
        }
780
781
        /** @noinspection UnusedFunctionResultInspection */
782 4
        $newnode->ownerDocument->replaceChild($newnode, $node);
783
784 4
        return $newnode;
785
    }
786
787
    /**
788
     * Set attribute value.
789
     *
790
     * @param string      $name       <p>The name of the html-attribute.</p>
791
     * @param string|null $value      <p>Set to NULL or empty string, to remove the attribute.</p>
792
     * @param bool        $strict     </p>
793
     *                                $value must be NULL, to remove the attribute,
794
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
795
     *                                </p>
796
     *
797
     * @return $this
798
     */
799 10
    public function setAttribute(string $name, $value = null, bool $strict = false): self
800
    {
801
        if (
802 10
            ($strict && $value === null)
803
            ||
804 10
            (!$strict && empty($value))
805
        ) {
806 2
            $this->node->removeAttribute($name);
807
        } else {
808
            /** @noinspection UnusedFunctionResultInspection */
809 10
            $this->node->setAttribute($name, $value);
810
        }
811
812 10
        return $this;
813
    }
814
815
    /**
816
     * @param string|string[]|null $value <p>
817
     *                                    null === get the current input value
818
     *                                    text === set a new input value
819
     *                                    </p>
820
     *
821
     * @return string|string[]|null
822
     */
823 1
    public function val($value = null)
824
    {
825 1
        if ($value === null) {
826
            if (
827 1
                $this->tag === 'input'
828
                &&
829
                (
830 1
                    $this->getAttribute('type') === 'text'
831
                    ||
832 1
                    !$this->hasAttribute('type')
833
                )
834
            ) {
835 1
                return $this->getAttribute('value');
836
            }
837
838
            if (
839 1
                $this->hasAttribute('checked')
840
                &&
841 1
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
842
            ) {
843 1
                return $this->getAttribute('value');
844
            }
845
846 1
            if ($this->node->nodeName === 'select') {
847
                $valuesFromDom = [];
848
                foreach ($this->getElementsByTagName('option') as $option) {
849
                    if ($this->hasAttribute('checked')) {
850
                        $valuesFromDom[] = $option->getAttribute('value');
851
                    }
852
                }
853
854
                if (\count($valuesFromDom) === 0) {
855
                    return null;
856
                }
857
858
                return $valuesFromDom;
859
            }
860
861 1
            if ($this->node->nodeName === 'textarea') {
862 1
                return $this->node->nodeValue;
863
            }
864
        } else {
865 1
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
866 1
                if ($value === $this->getAttribute('value')) {
867
                    /** @noinspection UnusedFunctionResultInspection */
868 1
                    $this->setAttribute('checked', 'checked');
869
                } else {
870 1
                    $this->removeAttribute('checked');
871
                }
872 1
            } elseif ($this->node->nodeName === 'select') {
873
                foreach ($this->node->getElementsByTagName('option') as $option) {
874
                    /** @var DOMElement $option */
875
                    if ($value === $option->getAttribute('value')) {
876
                        /** @noinspection UnusedFunctionResultInspection */
877
                        $option->setAttribute('selected', 'selected');
878
                    } else {
879
                        $option->removeAttribute('selected');
880
                    }
881
                }
882 1
            } elseif ($this->node->nodeName === 'input') {
883
                // Set value for input elements
884
                /** @noinspection UnusedFunctionResultInspection */
885 1
                $this->setAttribute('value', (string) $value);
886 1
            } elseif ($this->node->nodeName === 'textarea') {
887 1
                $this->node->nodeValue = (string) $value;
888
            }
889
        }
890
891 1
        return null;
892
    }
893
894
    /**
895
     * Remove attribute.
896
     *
897
     * @param string $name <p>The name of the html-attribute.</p>
898
     *
899
     * @return mixed
900
     */
901
    public function removeAttribute(string $name)
902
    {
903
        $this->node->removeAttribute($name);
904
905
        return $this;
906
    }
907
908
    /**
909
     * Get dom node's plain text.
910
     *
911
     * @return string
912
     */
913 17
    public function text(): string
914
    {
915 17
        return $this->getHtmlDomParser()->fixHtmlOutput($this->node->textContent);
916
    }
917
}
918