Completed
Push — master ( 8222ac...7c71fa )
by Lars
01:37
created

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