• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

voku / simple_html_dom / 24291591710

11 Apr 2026 09:05PM UTC coverage: 69.094%. Remained the same
24291591710

push

github

web-flow
Merge pull request #118 from devteam-emroc/master

Symfony 8.0 support

1243 of 1799 relevant lines covered (69.09%)

248.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.3
/src/voku/helper/SimpleHtmlDom.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace voku\helper;
6

7
/**
8
 * @noinspection PhpHierarchyChecksInspection
9
 *
10
 * {@inheritdoc}
11
 *
12
 * @implements \IteratorAggregate<int, \DOMNode>
13
 */
14
class SimpleHtmlDom extends AbstractSimpleHtmlDom implements \IteratorAggregate, SimpleHtmlDomInterface
15
{
16
    /**
17
     * @param \DOMElement|\DOMNode $node
18
     */
19
    public function __construct(\DOMNode $node)
20
    {
21
        $this->node = $node;
1,565✔
22
    }
348✔
23

24
    /**
25
     * @param string $name
26
     * @param array  $arguments
27
     *
28
     * @throws \BadMethodCallException
29
     *
30
     * @return SimpleHtmlDomInterface|string|null
31
     */
32
    public function __call($name, $arguments)
33
    {
34
        $name = \strtolower($name);
108✔
35

36
        if (isset(self::$functionAliases[$name])) {
108✔
37
            return \call_user_func_array([$this, self::$functionAliases[$name]], $arguments);
108✔
38
        }
39

40
        throw new \BadMethodCallException('Method does not exist');
×
41
    }
42

43
    /**
44
     * Find list of nodes with a CSS selector.
45
     *
46
     * @param string   $selector
47
     * @param int|null $idx
48
     *
49
     * @return SimpleHtmlDomInterface|SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface<SimpleHtmlDomInterface>
50
     */
51
    public function find(string $selector, $idx = null)
52
    {
53
        return $this->getHtmlDomParser()->find($selector, $idx);
243✔
54
    }
55

56
    public function getTag(): string
57
    {
58
        return $this->tag;
9✔
59
    }
60

61
    /**
62
     * Returns an array of attributes.
63
     *
64
     * @return string[]|null
65
     */
66
    public function getAllAttributes()
67
    {
68
        if (
69
            $this->node
63✔
70
            &&
71
            $this->node->hasAttributes()
63✔
72
        ) {
73
            $attributes = [];
63✔
74
            foreach ($this->node->attributes ?? [] as $attr) {
63✔
75
                $attributes[$attr->name] = HtmlDomParser::putReplacedBackToPreserveHtmlEntities($attr->value);
63✔
76
            }
77

78
            return $attributes;
63✔
79
        }
80

81
        return null;
9✔
82
    }
83

84
    /**
85
     * @return bool
86
     */
87
    public function hasAttributes(): bool
88
    {
89
        return $this->node && $this->node->hasAttributes();
9✔
90
    }
91

92
    /**
93
     * Return attribute value.
94
     *
95
     * @param string $name
96
     *
97
     * @return string
98
     */
99
    public function getAttribute(string $name): string
100
    {
101
        if ($this->node instanceof \DOMElement) {
243✔
102
            return HtmlDomParser::putReplacedBackToPreserveHtmlEntities(
243✔
103
                $this->node->getAttribute($name)
243✔
104
            );
189✔
105
        }
106

107
        return '';
×
108
    }
109

110
    /**
111
     * Determine if an attribute exists on the element.
112
     *
113
     * @param string $name
114
     *
115
     * @return bool
116
     */
117
    public function hasAttribute(string $name): bool
118
    {
119
        if (!$this->node instanceof \DOMElement) {
18✔
120
            return false;
×
121
        }
122

123
        return $this->node->hasAttribute($name);
18✔
124
    }
125

126
    /**
127
     * Get dom node's outer html.
128
     *
129
     * @param bool $multiDecodeNewHtmlEntity
130
     *
131
     * @return string
132
     */
133
    public function html(bool $multiDecodeNewHtmlEntity = false): string
134
    {
135
        return $this->getHtmlDomParser()->html($multiDecodeNewHtmlEntity);
360✔
136
    }
137

138
    /**
139
     * Get dom node's inner html.
140
     *
141
     * @param bool $multiDecodeNewHtmlEntity
142
     * @param bool $putBrokenReplacedBack
143
     *
144
     * @return string
145
     */
146
    public function innerHtml(bool $multiDecodeNewHtmlEntity = false, bool $putBrokenReplacedBack = true): string
147
    {
148
        return $this->getHtmlDomParser()->innerHtml($multiDecodeNewHtmlEntity, $putBrokenReplacedBack);
252✔
149
    }
150

151
    /**
152
     * Remove attribute.
153
     *
154
     * @param string $name <p>The name of the html-attribute.</p>
155
     *
156
     * @return SimpleHtmlDomInterface
157
     */
158
    public function removeAttribute(string $name): SimpleHtmlDomInterface
159
    {
160
        if (\method_exists($this->node, 'removeAttribute')) {
27✔
161
            $this->node->removeAttribute($name);
27✔
162
        }
163

164
        return $this;
27✔
165
    }
166

167
    /**
168
     * Remove all attributes
169
     *
170
     * @return SimpleHtmlDomInterface
171
     */
172
    public function removeAttributes(): SimpleHtmlDomInterface
173
    {
174
        if ($this->hasAttributes()) {
9✔
175
            foreach (array_keys((array)$this->getAllAttributes()) as $attribute) {
9✔
176
                $this->removeAttribute($attribute);
9✔
177
            }
178
        }
179
        return $this;
9✔
180
    }
181

182
    /**
183
     * Replace child node.
184
     *
185
     * @param string $string
186
     * @param bool   $putBrokenReplacedBack
187
     *
188
     * @return SimpleHtmlDomInterface
189
     */
190
    protected function replaceChildWithString(string $string, bool $putBrokenReplacedBack = true): SimpleHtmlDomInterface
191
    {
192
        if (!empty($string)) {
89✔
193
            $newDocument = new HtmlDomParser($string);
80✔
194

195
            $tmpDomString = $this->normalizeStringForComparison($newDocument);
80✔
196
            $tmpStr = $this->normalizeStringForComparison($string);
80✔
197

198
            if ($tmpDomString !== $tmpStr) {
80✔
199
                throw new \RuntimeException(
×
200
                    'Not valid HTML fragment!' . "\n" .
201
                    $tmpDomString . "\n" .
×
202
                    $tmpStr
×
203
                );
204
            }
205
        }
206

207
        /** @var \DOMNode[] $remove_nodes */
208
        $remove_nodes = [];
89✔
209
        if ($this->node->childNodes->length > 0) {
89✔
210
            // INFO: We need to fetch the nodes first, before we can delete them, because of missing references in the dom,
211
            // if we delete the elements on the fly.
212
            foreach ($this->node->childNodes as $node) {
89✔
213
                $remove_nodes[] = $node;
89✔
214
            }
215
        }
216
        foreach ($remove_nodes as $remove_node) {
89✔
217
            $this->node->removeChild($remove_node);
89✔
218
        }
219

220
        if (!empty($newDocument)) {
89✔
221
            $newDocument = $this->cleanHtmlWrapper($newDocument);
80✔
222
            $ownerDocument = $this->node->ownerDocument;
80✔
223
            if (
224
                $ownerDocument
80✔
225
                &&
226
                $newDocument->getDocument()->documentElement
80✔
227
            ) {
228
                $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
80✔
229
                $this->node->appendChild($newNode);
80✔
230
            }
231
        }
232

233
        return $this;
89✔
234
    }
235

236
    /**
237
     * Replace this node.
238
     *
239
     * @param string $string
240
     *
241
     * @return SimpleHtmlDomInterface
242
     */
243
    protected function replaceNodeWithString(string $string): SimpleHtmlDomInterface
244
    {
245
        if (empty($string)) {
81✔
246
            if ($this->node->parentNode) {
45✔
247
                $this->node->parentNode->removeChild($this->node);
45✔
248
            }
249
            $this->node = new \DOMText();
45✔
250

251
            return $this;
45✔
252
        }
253

254
        $newDocument = new HtmlDomParser($string);
54✔
255

256
        $tmpDomOuterTextString = $this->normalizeStringForComparison($newDocument);
54✔
257
        $tmpStr = $this->normalizeStringForComparison($string);
54✔
258

259
        if ($tmpDomOuterTextString !== $tmpStr) {
54✔
260
            throw new \RuntimeException(
×
261
                'Not valid HTML fragment!' . "\n"
262
                . $tmpDomOuterTextString . "\n" .
×
263
                $tmpStr
×
264
            );
265
        }
266

267
        $newDocument = $this->cleanHtmlWrapper($newDocument, true);
54✔
268
        $ownerDocument = $this->node->ownerDocument;
54✔
269
        if (
270
            $ownerDocument === null
54✔
271
            ||
272
            $newDocument->getDocument()->documentElement === null
54✔
273
        ) {
274
            return $this;
×
275
        }
276

277
        $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
54✔
278

279
        $this->node->parentNode->replaceChild($newNode, $this->node);
54✔
280
        $this->node = $newNode;
54✔
281

282
        // Remove head element, preserving child nodes. (again)
283
        if (
284
            $this->node->parentNode instanceof \DOMElement
54✔
285
            &&
286
            $newDocument->getIsDOMDocumentCreatedWithoutHeadWrapper()
54✔
287
        ) {
288
            $html = $this->node->parentNode->getElementsByTagName('head')[0];
27✔
289

290
            if (
291
                $html !== null
27✔
292
                &&
293
                $this->node->parentNode->ownerDocument
27✔
294
            ) {
295
                $fragment = $this->node->parentNode->ownerDocument->createDocumentFragment();
5✔
296
                /** @var \DOMNode $html */
297
                while ($html->childNodes->length > 0) {
5✔
298
                    $tmpNode = $html->childNodes->item(0);
5✔
299
                    if ($tmpNode !== null) {
5✔
300
                        /** @noinspection UnusedFunctionResultInspection */
301
                        $fragment->appendChild($tmpNode);
5✔
302
                    }
303
                }
304
                $html->parentNode->replaceChild($fragment, $html);
5✔
305
            }
306
        }
307

308
        return $this;
54✔
309
    }
310

311
    /**
312
     * Replace this node with text
313
     *
314
     * @param string $string
315
     *
316
     * @return SimpleHtmlDomInterface
317
     */
318
    protected function replaceTextWithString($string): SimpleHtmlDomInterface
319
    {
320
        if (empty($string)) {
9✔
321
            if ($this->node->parentNode) {
9✔
322
                $this->node->parentNode->removeChild($this->node);
9✔
323
            }
324
            $this->node = new \DOMText();
9✔
325

326
            return $this;
9✔
327
        }
328

329
        $ownerDocument = $this->node->ownerDocument;
9✔
330
        if ($ownerDocument) {
9✔
331
            $newElement = $ownerDocument->createTextNode($string);
9✔
332
            $newNode = $ownerDocument->importNode($newElement, true);
9✔
333
            $this->node->parentNode->replaceChild($newNode, $this->node);
9✔
334
            $this->node = $newNode;
9✔
335
        }
336

337
        return $this;
9✔
338
    }
339

340
    /**
341
     * Set attribute value.
342
     *
343
     * @param string      $name                     <p>The name of the html-attribute.</p>
344
     * @param string|null $value                    <p>Set to NULL or empty string, to remove the attribute.</p>
345
     * @param bool        $strictEmptyValueCheck <p>
346
     *                                $value must be NULL, to remove the attribute,
347
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
348
     *                                </p>
349
     *
350
     * @return SimpleHtmlDomInterface
351
     */
352
    public function setAttribute(string $name, $value = null, bool $strictEmptyValueCheck = false): SimpleHtmlDomInterface
353
    {
354
        if (
355
            ($strictEmptyValueCheck && $value === null)
180✔
356
            ||
357
            (!$strictEmptyValueCheck && empty($value))
180✔
358
        ) {
359
            /** @noinspection UnusedFunctionResultInspection */
360
            $this->removeAttribute($name);
18✔
361
        } elseif (\method_exists($this->node, 'setAttribute')) {
180✔
362
            /** @noinspection UnusedFunctionResultInspection */
363
            $this->node->setAttribute($name, HtmlDomParser::replaceToPreserveHtmlEntities((string) $value));
180✔
364
        }
365

366
        return $this;
180✔
367
    }
368

369
    /**
370
     * Get dom node's plain text.
371
     *
372
     * @return string
373
     */
374
    public function text(): string
375
    {
376
        return $this->getHtmlDomParser()->fixHtmlOutput($this->node->textContent);
297✔
377
    }
378

379
    /**
380
     * Change the name of a tag in a "DOMNode".
381
     *
382
     * @param \DOMNode $node
383
     * @param string   $name
384
     *
385
     * @return \DOMElement|false
386
     *                          <p>DOMElement a new instance of class DOMElement or false
387
     *                          if an error occurred.</p>
388
     */
389
    protected function changeElementName(\DOMNode $node, string $name)
390
    {
391
        $ownerDocument = $node->ownerDocument;
94✔
392
        if (!$ownerDocument) {
94✔
393
            return false;
×
394
        }
395

396
        $newNode = $ownerDocument->createElement($name);
94✔
397

398
        foreach ($node->childNodes as $child) {
94✔
399
            $child = $ownerDocument->importNode($child, true);
94✔
400
            $newNode->appendChild($child);
94✔
401
        }
402

403
        foreach ($node->attributes ?? [] as $attrName => $attrNode) {
94✔
404
            /** @noinspection UnusedFunctionResultInspection */
405
            $newNode->setAttribute($attrName, $attrNode);
×
406
        }
407

408
        if ($newNode->ownerDocument) {
94✔
409
            /** @noinspection UnusedFunctionResultInspection */
410
            $newNode->ownerDocument->replaceChild($newNode, $node);
94✔
411
        }
412

413
        return $newNode;
94✔
414
    }
415

416
    /**
417
     * Returns children of node.
418
     *
419
     * @param int $idx
420
     *
421
     * @return SimpleHtmlDomInterface|SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface|null
422
     */
423
    public function childNodes(int $idx = -1)
424
    {
425
        $nodeList = $this->getIterator();
18✔
426

427
        if ($idx === -1) {
18✔
428
            return $nodeList;
18✔
429
        }
430

431
        return $nodeList[$idx] ?? null;
18✔
432
    }
433

434
    /**
435
     * Find nodes with a CSS selector.
436
     *
437
     * @param string $selector
438
     *
439
     * @return SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface<SimpleHtmlDomInterface>
440
     */
441
    public function findMulti(string $selector): SimpleHtmlDomNodeInterface
442
    {
443
        return $this->getHtmlDomParser()->findMulti($selector);
9✔
444
    }
445

446
    /**
447
     * Find nodes with a CSS selector or false, if no element is found.
448
     *
449
     * @param string $selector
450
     *
451
     * @return false|SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface<SimpleHtmlDomInterface>
452
     */
453
    public function findMultiOrFalse(string $selector)
454
    {
455
        return $this->getHtmlDomParser()->findMultiOrFalse($selector);
9✔
456
    }
457

458
    /**
459
     * Find one node with a CSS selector.
460
     *
461
     * @param string $selector
462
     *
463
     * @return SimpleHtmlDomInterface
464
     */
465
    public function findOne(string $selector): SimpleHtmlDomInterface
466
    {
467
        return $this->getHtmlDomParser()->findOne($selector);
27✔
468
    }
469

470
    /**
471
     * Find one node with a CSS selector or false, if no element is found.
472
     *
473
     * @param string $selector
474
     *
475
     * @return false|SimpleHtmlDomInterface
476
     */
477
    public function findOneOrFalse(string $selector)
478
    {
479
        return $this->getHtmlDomParser()->findOneOrFalse($selector);
9✔
480
    }
481

482
    /**
483
     * Returns the first child of node.
484
     *
485
     * @return SimpleHtmlDomInterface|null
486
     */
487
    public function firstChild()
488
    {
489
        /** @var \DOMNode|null $node */
490
        $node = $this->node->firstChild;
36✔
491

492
        if ($node === null) {
36✔
493
            return null;
9✔
494
        }
495

496
        return new static($node);
36✔
497
    }
498

499
    /**
500
     * Return elements by ".class".
501
     *
502
     * @param string $class
503
     *
504
     * @return SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface<SimpleHtmlDomInterface>
505
     */
506
    public function getElementByClass(string $class): SimpleHtmlDomNodeInterface
507
    {
508
        return $this->findMulti(".{$class}");
×
509
    }
510

511
    /**
512
     * Return element by #id.
513
     *
514
     * @param string $id
515
     *
516
     * @return SimpleHtmlDomInterface
517
     */
518
    public function getElementById(string $id): SimpleHtmlDomInterface
519
    {
520
        return $this->findOne("#{$id}");
9✔
521
    }
522

523
    /**
524
     * Return element by tag name.
525
     *
526
     * @param string $name
527
     *
528
     * @return SimpleHtmlDomInterface
529
     */
530
    public function getElementByTagName(string $name): SimpleHtmlDomInterface
531
    {
532
        if ($this->node instanceof \DOMElement) {
9✔
533
            $node = $this->node->getElementsByTagName($name)->item(0);
9✔
534
        } else {
535
            $node = null;
×
536
        }
537

538
        if ($node === null) {
9✔
539
            return new SimpleHtmlDomBlank();
×
540
        }
541

542
        return new static($node);
9✔
543
    }
544

545
    /**
546
     * Returns elements by "#id".
547
     *
548
     * @param string   $id
549
     * @param int|null $idx
550
     *
551
     * @return SimpleHtmlDomInterface|SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface<SimpleHtmlDomInterface>
552
     */
553
    public function getElementsById(string $id, $idx = null)
554
    {
555
        return $this->find("#{$id}", $idx);
×
556
    }
557

558
    /**
559
     * Returns elements by tag name.
560
     *
561
     * @param string   $name
562
     * @param int|null $idx
563
     *
564
     * @return SimpleHtmlDomInterface|SimpleHtmlDomInterface[]|SimpleHtmlDomNodeInterface<SimpleHtmlDomInterface>
565
     */
566
    public function getElementsByTagName(string $name, $idx = null)
567
    {
568
        if ($this->node instanceof \DOMElement) {
9✔
569
            $nodesList = $this->node->getElementsByTagName($name);
9✔
570
        } else {
571
            $nodesList = [];
×
572
        }
573

574
        $elements = new SimpleHtmlDomNode();
9✔
575

576
        foreach ($nodesList as $node) {
9✔
577
            $elements[] = new static($node);
9✔
578
        }
579

580
        // return all elements
581
        if ($idx === null) {
9✔
582
            if (\count($elements) === 0) {
9✔
583
                return new SimpleHtmlDomNodeBlank();
×
584
            }
585

586
            return $elements;
9✔
587
        }
588

589
        // handle negative values
590
        if ($idx < 0) {
×
591
            $idx = \count($elements) + $idx;
×
592
        }
593

594
        // return one element
595
        return $elements[$idx] ?? new SimpleHtmlDomBlank();
×
596
    }
597

598
    /**
599
     * Create a new "HtmlDomParser"-object from the current context.
600
     *
601
     * @return HtmlDomParser
602
     */
603
    public function getHtmlDomParser(): HtmlDomParser
604
    {
605
        return new HtmlDomParser($this);
1,017✔
606
    }
607

608
    /**
609
     * @return \DOMNode
610
     */
611
    public function getNode(): \DOMNode
612
    {
613
        return $this->node;
1,026✔
614
    }
615

616
    /**
617
     * Nodes can get partially destroyed in which they're still an
618
     * actual DOM node (such as \DOMElement) but almost their entire
619
     * body is gone, including the `nodeType` attribute.
620
     *
621
     * @return bool true if node has been destroyed
622
     */
623
    public function isRemoved(): bool
624
    {
625
        return !isset($this->node->nodeType);
×
626
    }
627

628
    /**
629
     * Returns the last child of node.
630
     *
631
     * @return SimpleHtmlDomInterface|null
632
     */
633
    public function lastChild()
634
    {
635
        /** @var \DOMNode|null $node */
636
        $node = $this->node->lastChild;
36✔
637

638
        if ($node === null) {
36✔
639
            return null;
9✔
640
        }
641

642
        return new static($node);
36✔
643
    }
644

645
    /**
646
     * Returns the next sibling of node.
647
     *
648
     * @return SimpleHtmlDomInterface|null
649
     */
650
    public function nextSibling()
651
    {
652
        /** @var \DOMNode|null $node */
653
        $node = $this->node->nextSibling;
9✔
654

655
        if ($node === null) {
9✔
656
            return null;
9✔
657
        }
658

659
        return new static($node);
9✔
660
    }
661

662
    /**
663
     * Returns the next sibling of node.
664
     *
665
     * @return SimpleHtmlDomInterface|null
666
     */
667
    public function nextNonWhitespaceSibling()
668
    {
669
        /** @var \DOMNode|null $node */
670
        $node = $this->node->nextSibling;
9✔
671

672
        while ($node && !\trim($node->textContent)) {
9✔
673
            /** @var \DOMNode|null $node */
674
            $node = $node->nextSibling;
9✔
675
        }
676

677
        if ($node === null) {
9✔
678
            return null;
×
679
        }
680

681
        return new static($node);
9✔
682
    }
683

684
    /**
685
     * Returns the parent of node.
686
     *
687
     * @return SimpleHtmlDomInterface|null
688
     */
689
    public function parentNode(): ?SimpleHtmlDomInterface
690
    {
691
        if ($node = $this->node->parentNode) {
18✔
692
            return new static($node);
18✔
693
        }
694

695
        return null;
×
696
    }
697

698
    /**
699
     * Returns the previous sibling of node.
700
     *
701
     * @return SimpleHtmlDomInterface|null
702
     */
703
    public function previousSibling()
704
    {
705
        /** @var \DOMNode|null $node */
706
        $node = $this->node->previousSibling;
9✔
707

708
        if ($node === null) {
9✔
709
            return null;
9✔
710
        }
711

712
        return new static($node);
9✔
713
    }
714

715
    /**
716
     * Returns the previous sibling of node.
717
     *
718
     * @return SimpleHtmlDomInterface|null
719
     */
720
    public function previousNonWhitespaceSibling()
721
    {
722
        /** @var \DOMNode|null $node */
723
        $node = $this->node->previousSibling;
9✔
724

725
        while ($node && !\trim($node->textContent)) {
9✔
726
            /** @var \DOMNode|null $node */
727
            $node = $node->previousSibling;
9✔
728
        }
729

730
        if ($node === null) {
9✔
731
            return null;
×
732
        }
733

734
        return new static($node);
9✔
735
    }
736

737
    /**
738
     * @param string|string[]|null $value <p>
739
     *                                    null === get the current input value
740
     *                                    text === set a new input value
741
     *                                    </p>
742
     *
743
     * @return string|string[]|null
744
     */
745
    public function val($value = null)
746
    {
747
        if ($value === null) {
9✔
748
            if (
749
                $this->tag === 'input'
9✔
750
                &&
751
                (
752
                    $this->getAttribute('type') === 'hidden'
9✔
753
                    ||
7✔
754
                    $this->getAttribute('type') === 'text'
9✔
755
                    ||
7✔
756
                    !$this->hasAttribute('type')
9✔
757
                )
758
            ) {
759
                return $this->getAttribute('value');
9✔
760
            }
761

762
            if (
763
                $this->hasAttribute('checked')
9✔
764
                &&
765
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
9✔
766
            ) {
767
                return $this->getAttribute('value');
9✔
768
            }
769

770
            if ($this->node->nodeName === 'select') {
9✔
771
                $valuesFromDom = [];
×
772
                $options = $this->getElementsByTagName('option');
×
773
                if ($options instanceof SimpleHtmlDomNode) {
×
774
                    foreach ($options as $option) {
×
775
                        if ($this->hasAttribute('checked')) {
×
776
                            $valuesFromDom[] = (string) $option->getAttribute('value');
×
777
                        }
778
                    }
779
                }
780

781
                if (\count($valuesFromDom) === 0) {
×
782
                    return null;
×
783
                }
784

785
                return $valuesFromDom;
×
786
            }
787

788
            if ($this->node->nodeName === 'textarea') {
9✔
789
                return $this->node->nodeValue;
9✔
790
            }
791
        } else {
792
            /** @noinspection NestedPositiveIfStatementsInspection */
793
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
9✔
794
                if ($value === $this->getAttribute('value')) {
9✔
795
                    /** @noinspection UnusedFunctionResultInspection */
796
                    $this->setAttribute('checked', 'checked');
9✔
797
                } else {
798
                    /** @noinspection UnusedFunctionResultInspection */
799
                    $this->removeAttribute('checked');
6✔
800
                }
801
            } elseif ($this->node instanceof \DOMElement && $this->node->nodeName === 'select') {
9✔
802
                foreach ($this->node->getElementsByTagName('option') as $option) {
×
803
                    /** @var \DOMElement $option */
804
                    if ($value === $option->getAttribute('value')) {
×
805
                        /** @noinspection UnusedFunctionResultInspection */
806
                        $option->setAttribute('selected', 'selected');
×
807
                    } else {
808
                        $option->removeAttribute('selected');
×
809
                    }
810
                }
811
            } elseif ($this->node->nodeName === 'input' && \is_string($value)) {
9✔
812
                // Set value for input elements
813
                /** @noinspection UnusedFunctionResultInspection */
814
                $this->setAttribute('value', $value);
9✔
815
            } elseif ($this->node->nodeName === 'textarea' && \is_string($value)) {
9✔
816
                $this->node->nodeValue = $value;
9✔
817
            }
818
        }
819

820
        return null;
9✔
821
    }
822

823
    /**
824
     * @param HtmlDomParser $newDocument
825
     * @param bool          $removeExtraHeadTag
826
     *
827
     * @return HtmlDomParser
828
     */
829
    protected function cleanHtmlWrapper(
830
        HtmlDomParser $newDocument,
831
        $removeExtraHeadTag = false
832
    ): HtmlDomParser {
833
        if (
834
            $newDocument->getIsDOMDocumentCreatedWithoutHtml()
134✔
835
            ||
836
            $newDocument->getIsDOMDocumentCreatedWithoutHtmlWrapper()
134✔
837
        ) {
838
            // Remove doc-type node.
839
            if ($newDocument->getDocument()->doctype !== null) {
134✔
840
                $newDocument->getDocument()->doctype->parentNode->removeChild($newDocument->getDocument()->doctype);
×
841
            }
842

843
            // Replace html element, preserving child nodes -> but keep the html wrapper, otherwise we got other problems ...
844
            // so we replace it with "<simpleHtmlDomHtml>" and delete this at the ending.
845
            $item = $newDocument->getDocument()->getElementsByTagName('html')->item(0);
134✔
846
            if ($item !== null) {
134✔
847
                /** @noinspection UnusedFunctionResultInspection */
848
                $this->changeElementName($item, 'simpleHtmlDomHtml');
94✔
849
            }
850

851
            if ($newDocument->getIsDOMDocumentCreatedWithoutPTagWrapper()) {
134✔
852
                // Remove <p>-element, preserving child nodes.
853
                $pElement = $newDocument->getDocument()->getElementsByTagName('p')->item(0);
125✔
854
                if ($pElement instanceof \DOMElement) {
125✔
855
                    $fragment = $newDocument->getDocument()->createDocumentFragment();
80✔
856

857
                    while ($pElement->childNodes->length > 0) {
80✔
858
                        $tmpNode = $pElement->childNodes->item(0);
80✔
859
                        if ($tmpNode !== null) {
80✔
860
                            /** @noinspection UnusedFunctionResultInspection */
861
                            $fragment->appendChild($tmpNode);
80✔
862
                        }
863
                    }
864

865
                    if ($pElement->parentNode !== null) {
80✔
866
                        $pElement->parentNode->replaceChild($fragment, $pElement);
80✔
867
                    }
868
                }
869
            }
870

871
            // Remove <body>-element, preserving child nodes.
872
            $body = $newDocument->getDocument()->getElementsByTagName('body')->item(0);
134✔
873
            if ($body instanceof \DOMElement) {
134✔
874
                $fragment = $newDocument->getDocument()->createDocumentFragment();
80✔
875

876
                while ($body->childNodes->length > 0) {
80✔
877
                    $tmpNode = $body->childNodes->item(0);
80✔
878
                    if ($tmpNode !== null) {
80✔
879
                        /** @noinspection UnusedFunctionResultInspection */
880
                        $fragment->appendChild($tmpNode);
80✔
881
                    }
882
                }
883

884
                if ($body->parentNode !== null) {
80✔
885
                    $body->parentNode->replaceChild($fragment, $body);
80✔
886
                }
887
            }
888
        }
889

890
        // Remove head element, preserving child nodes.
891
        if (
892
            $removeExtraHeadTag
134✔
893
            &&
894
            $this->node->parentNode instanceof \DOMElement
134✔
895
            &&
896
            $newDocument->getIsDOMDocumentCreatedWithoutHeadWrapper()
134✔
897
        ) {
898
            $html = $this->node->parentNode->getElementsByTagName('head')[0] ?? null;
27✔
899

900
            if (
901
                $html !== null
27✔
902
                &&
903
                $this->node->parentNode->ownerDocument
27✔
904
            ) {
905
                $fragment = $this->node->parentNode->ownerDocument->createDocumentFragment();
×
906

907
                /** @var \DOMNode $html */
908
                while ($html->childNodes->length > 0) {
×
909
                    $tmpNode = $html->childNodes->item(0);
×
910
                    if ($tmpNode !== null) {
×
911
                        /** @noinspection UnusedFunctionResultInspection */
912
                        $fragment->appendChild($tmpNode);
×
913
                    }
914
                }
915

916
                $html->parentNode->replaceChild($fragment, $html);
×
917
            }
918
        }
919

920
        return $newDocument;
134✔
921
    }
922

923
    /**
924
     * Retrieve an external iterator.
925
     *
926
     * @see  http://php.net/manual/en/iteratoraggregate.getiterator.php
927
     *
928
     * @return SimpleHtmlDomNode
929
     *                           <p>
930
     *                              An instance of an object implementing <b>Iterator</b> or
931
     *                              <b>Traversable</b>
932
     *                           </p>
933
     */
934
    public function getIterator(): SimpleHtmlDomNodeInterface
935
    {
936
        $elements = new SimpleHtmlDomNode();
27✔
937
        if ($this->node->hasChildNodes()) {
27✔
938
            foreach ($this->node->childNodes as $node) {
27✔
939
                $elements[] = new static($node);
27✔
940
            }
941
        }
942

943
        return $elements;
27✔
944
    }
945

946
    /**
947
     * Get dom node's inner html.
948
     *
949
     * @param bool $multiDecodeNewHtmlEntity
950
     *
951
     * @return string
952
     */
953
    public function innerXml(bool $multiDecodeNewHtmlEntity = false): string
954
    {
955
        return $this->getHtmlDomParser()->innerXml($multiDecodeNewHtmlEntity);
×
956
    }
957

958
    /**
959
     * Normalize the given input for comparison.
960
     *
961
     * @param HtmlDomParser|string $input
962
     *
963
     * @return string
964
     */
965
    private function normalizeStringForComparison($input): string
966
    {
967
        if ($input instanceof HtmlDomParser) {
134✔
968
            $string = $input->html(false, true);
134✔
969

970
            if ($input->getIsDOMDocumentCreatedWithoutHeadWrapper()) {
134✔
971
                /** @noinspection HtmlRequiredTitleElement */
972
                $string = \str_replace(['<head>', '</head>'], '', $string);
134✔
973
            }
974
        } else {
975
            // Also restore any broken-HTML placeholders that may already be
976
            // present in the raw string (e.g. when innerhtmlKeep concatenates
977
            // the current innerHTML — which still contains placeholders — with
978
            // new content before passing the combined string back as the new
979
            // innerHTML).  This keeps both sides of the comparison at the same
980
            // level of substitution.
981
            $string = HtmlDomParser::putReplacedBackToPreserveHtmlEntities((string) $input, true);
134✔
982
        }
983

984
        return
104✔
985
            \urlencode(
134✔
986
                \urldecode(
134✔
987
                    \trim(
134✔
988
                        \str_replace(
134✔
989
                            [
104✔
990
                                ' ',
134✔
991
                                "\n",
104✔
992
                                "\r",
104✔
993
                                '/>',
104✔
994
                            ],
104✔
995
                            [
104✔
996
                                '',
134✔
997
                                '',
104✔
998
                                '',
104✔
999
                                '>',
104✔
1000
                            ],
104✔
1001
                            \strtolower($string)
134✔
1002
                        )
104✔
1003
                    )
104✔
1004
                )
104✔
1005
            );
104✔
1006
    }
1007

1008
    /**
1009
     * Delete
1010
     *
1011
     * @return void
1012
     */
1013
    public function delete()
1014
    {
1015
        $this->outertext = '';
9✔
1016
    }
2✔
1017
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc