Passed
Branch scrutinizer (dd8772)
by Thomas
03:03
created

HtmlQuery::removeCss()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use DOMDocument, DOMElement, DOMNode, DOMNodeList;
6
use Traversable;
7
8
/**
9
 * Class HtmlQuery
10
 *
11
 * @package Sulao\HtmlQuery
12
 */
13
class HtmlQuery extends Selection
14
{
15
    const VERSION = '1.0.0';
16
17
    /**
18
     * @var DOMDocument
19
     */
20
    protected $doc;
21
22
    /**
23
     * @var DOMNode[]
24
     */
25
    protected $nodes;
26
27
    /**
28
     * HtmlQuery constructor.
29
     *
30
     * @param DOMDocument                   $doc
31
     * @param DOMNode|DOMNode[]|DOMNodeList $nodes
32
     *
33
     * @throws Exception
34
     */
35
    public function __construct(DOMDocument $doc, $nodes)
36
    {
37
        $this->doc = $doc;
38
        $this->nodes = $this->validateNodes($nodes);
39
    }
40
41
    /**
42
     * Get the outer HTML contents of the first matched node.
43
     *
44
     * @return string|null
45
     */
46
    public function outerHtml()
47
    {
48
        return $this->mapFirst(function (HtmlNode $node) {
49
            return $node->outerHtml();
50
        });
51
    }
52
53
    /**
54
     * Get the inner HTML contents of the first matched node or
55
     * set the inner HTML contents of every matched node.
56
     *
57
     * @param string|null $html
58
     *
59
     * @return string|null|static
60
     */
61
    public function html(?string $html = null)
62
    {
63
        if (!is_null($html)) {
64
            return $this->setHtml($html);
65
        }
66
67
        return $this->getHtml();
68
    }
69
70
    /**
71
     * Get the inner HTML contents of the first matched node.
72
     *
73
     * @return string|null
74
     */
75
    public function getHtml()
76
    {
77
        return $this->mapFirst(function (HtmlNode $node) {
78
            return $node->getHtml();
79
        });
80
    }
81
82
    /**
83
     * Set the inner HTML contents of every matched node.
84
     *
85
     * @param string $html
86
     *
87
     * @return static
88
     */
89
    public function setHtml(string $html)
90
    {
91
        $this->empty();
92
93
        if ($html !== '') {
94
            $this->append($html);
95
        }
96
97
        return $this;
98
    }
99
100
    /**
101
     * Get the combined text contents of the first matched node, including
102
     * it's descendants, or set the text contents of every matched node.
103
     *
104
     * @param string|null $text
105
     *
106
     * @return string|null|static
107
     */
108
    public function text(?string $text = null)
109
    {
110
        if (!is_null($text)) {
111
            return $this->setText($text);
112
        }
113
114
        return $this->getText();
115
    }
116
117
    /**
118
     * Get the combined text contents of the first matched node,
119
     * including it's descendants.
120
     *
121
     * @return string|null
122
     */
123
    public function getText()
124
    {
125
        return $this->mapFirst(function (HtmlNode $node) {
126
            return $node->getText();
127
        });
128
    }
129
130
    /**
131
     * set the text contents of every matched node.
132
     *
133
     * @param string $text
134
     *
135
     * @return static
136
     */
137
    public function setText(string $text)
138
    {
139
        return $this->each(function (HtmlNode $node) use ($text) {
140
            $node->setText($text);
141
        });
142
    }
143
144
    /**
145
     * Get the value of an attribute for the first matched node
146
     * or set one or more attributes for every matched node.
147
     *
148
     * @param string|array $name
149
     * @param string|null  $value
150
     *
151
     * @return static|mixed|null
152
     */
153
    public function attr($name, $value = null)
154
    {
155
        if (is_array($name)) {
156
            foreach ($name as $key => $val) {
157
                $this->setAttr($key, $val);
158
            }
159
160
            return $this;
161
        }
162
163
        if (!is_null($value)) {
164
            return $this->setAttr($name, $value);
165
        }
166
167
        return $this->getAttr($name);
168
    }
169
170
    /**
171
     * Get the value of an attribute for the first matched node
172
     *
173
     * @param string $name
174
     *
175
     * @return string|null
176
     */
177
    public function getAttr(string $name)
178
    {
179
        return $this->mapFirst(function (HtmlNode $node) use ($name) {
180
            return $node->getAttr($name);
181
        });
182
    }
183
184
    /**
185
     * Set one or more attributes for every matched node.
186
     *
187
     * @param string $name
188
     * @param string $value
189
     *
190
     * @return static
191
     */
192
    public function setAttr(string $name, string $value)
193
    {
194
        return $this->each(function (HtmlNode $node) use ($name, $value) {
195
            $node->setAttr($name, $value);
196
        });
197
    }
198
199
    /**
200
     * Remove an attribute from every matched nodes.
201
     *
202
     * @param string $attributeName
203
     *
204
     * @return static
205
     */
206
    public function removeAttr(string $attributeName)
207
    {
208
        return $this->each(function (HtmlNode $node) use ($attributeName) {
209
            $node->removeAttr($attributeName);
210
        });
211
    }
212
213
    /**
214
     * Remove all attributes from every matched nodes except the specified ones.
215
     *
216
     * @param string|array $except The attribute name(s) that won't be removed
217
     *
218
     * @return static
219
     */
220
    public function removeAllAttrs($except = [])
221
    {
222
        return $this->each(function (HtmlNode $node) use ($except) {
223
            $node->removeAllAttrs($except);
224
        });
225
    }
226
227
    /**
228
     * Determine whether any of the nodes have the given attribute.
229
     *
230
     * @param string $attributeName
231
     *
232
     * @return bool
233
     */
234
    public function hasAttr(string $attributeName)
235
    {
236
        return $this->mapAnyTrue(
237
            function (HtmlNode $node) use ($attributeName) {
238
                return $node->hasAttr($attributeName);
239
            }
240
        );
241
    }
242
243
    /**
244
     * Alias of attr
245
     *
246
     * @param string|array $name
247
     * @param string|null  $value
248
     *
249
     * @return static|mixed|null
250
     */
251
    public function prop($name, $value = null)
252
    {
253
        return $this->attr($name, $value);
254
    }
255
256
    /**
257
     * Alias of removeAttr
258
     *
259
     * @param string $attributeName
260
     *
261
     * @return static
262
     */
263
    public function removeProp(string $attributeName)
264
    {
265
        return $this->removeAttr($attributeName);
266
    }
267
268
    /**
269
     * Get the value of an attribute with prefix data- for the first matched
270
     * node, if the value is valid json string, returns the value encoded in
271
     * json in appropriate PHP type
272
     *
273
     * or set one or more attributes with prefix data- for every matched node.
274
     *
275
     * @param string|array $name
276
     * @param string|null  $value
277
     *
278
     * @return static|mixed|null
279
     */
280
    public function data($name, $value = null)
281
    {
282
        if (is_array($name)) {
283
            $keys = array_keys($name);
284
            $keys = array_map(function ($value) {
285
                return 'data-' . $value;
286
            }, $keys);
287
288
            $name = array_combine($keys, $name);
289
        } else {
290
            $name = 'data-' . $name;
291
        }
292
293
        if (!is_null($value) && !is_string($value)) {
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
294
            $value = (string) json_encode($value);
295
        }
296
297
        $result = $this->attr($name, $value);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type false; however, parameter $name of Sulao\HtmlQuery\HtmlQuery::attr() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

297
        $result = $this->attr(/** @scrutinizer ignore-type */ $name, $value);
Loading history...
298
299
        if (is_string($result)) {
300
            $json = json_decode($result);
301
            if (json_last_error() === JSON_ERROR_NONE) {
302
                return $json;
303
            }
304
        }
305
306
        return $result;
307
    }
308
309
    /**
310
     * Determine whether any of the nodes have the given attribute
311
     * prefix with data-.
312
     *
313
     * @param string $name
314
     *
315
     * @return bool
316
     */
317
    public function hasData(string $name)
318
    {
319
        return $this->hasAttr('data-' . $name);
320
    }
321
322
    /**
323
     * Remove an attribute prefix with data- from every matched nodes.
324
     *
325
     * @param string $name
326
     *
327
     * @return static
328
     */
329
    public function removeData(string $name)
330
    {
331
        return $this->removeAttr('data-' . $name);
332
    }
333
334
    /**
335
     * Remove all child nodes of all matched nodes from the DOM.
336
     *
337
     * @return static
338
     */
339
    public function empty()
340
    {
341
        return $this->each(function (HtmlNode $node) {
342
            $node->empty();
343
        });
344
    }
345
346
    /**
347
     * Remove the matched nodes from the DOM.
348
     * optionally filtered by a selector.
349
     *
350
     * @param string|null $selector
351
     *
352
     * @return static
353
     */
354
    public function remove(?string $selector = null)
355
    {
356
        if (!is_null($selector)) {
357
            $this->filter($selector)->remove();
358
        } else {
359
            $this->each(function (HtmlNode $node) {
360
                $node->remove();
361
            });
362
        }
363
364
        return $this;
365
    }
366
367
    /**
368
     * Get the current value of the first matched node
369
     * or set the value of every matched node.
370
     *
371
     * @param string|null $value
372
     *
373
     * @return string|null|static
374
     */
375
    public function val(?string $value = null)
376
    {
377
        if (is_null($value)) {
378
            return $this->getVal();
379
        }
380
381
        return $this->setVal($value);
382
    }
383
384
    /**
385
     * Get the current value of the first matched node
386
     *
387
     * @return string|null
388
     */
389
    public function getVal()
390
    {
391
        return $this->mapFirst(function (DOMNode $node) {
392
            if (!($node instanceof DOMElement)) {
393
                return null;
394
            }
395
396
            switch ($node->tagName) {
397
                case 'input':
398
                    return $node->getAttribute('value');
399
                case 'textarea':
400
                    return $node->nodeValue;
401
                case 'select':
402
                    $ht = $this->resolve($node);
403
404
                    $selected = $ht->children('option:selected');
405
                    if ($selected->count()) {
406
                        return $selected->getAttr('value');
407
                    }
408
409
                    $fistChild = $ht->xpathFind('child::*[1]');
410
                    if ($fistChild->count()) {
411
                        return $fistChild->getAttr('value');
412
                    }
413
                    break;
414
            }
415
416
            return null;
417
        });
418
    }
419
420
    /**
421
     * Set the value of every matched node.
422
     *
423
     * @param string $value
424
     *
425
     * @return static
426
     */
427
    public function setVal(string $value)
428
    {
429
        return $this->each(function (DOMNode $node) use ($value) {
430
            if (!($node instanceof DOMElement)) {
431
                return;
432
            }
433
434
            switch ($node->tagName) {
435
                case 'input':
436
                    $node->setAttribute('value', $value);
437
                    break;
438
                case 'textarea':
439
                    $node->nodeValue = $value;
440
                    break;
441
                case 'select':
442
                    $ht = $this->resolve($node);
443
444
                    $selected = $ht->children('option:selected');
445
                    if ($selected->count()) {
446
                        $selected->removeAttr('selected');
447
                    }
448
449
                    $options = $ht->children("option[value='{$value}']");
450
                    if ($options->count()) {
451
                        $options->first()->setAttr('selected', 'selected');
452
                    }
453
                    break;
454
            }
455
        });
456
    }
457
458
    /**
459
     * Adds the specified class(es) to each node in the matched nodes.
460
     *
461
     * @param string $className
462
     *
463
     * @return static
464
     */
465
    public function addClass(string $className)
466
    {
467
        return $this->each(function (HtmlNode $node) use ($className) {
468
            $node->addClass($className);
469
        });
470
    }
471
472
    /**
473
     * Determine whether any of the matched nodes are assigned the given class.
474
     *
475
     * @param string $className
476
     *
477
     * @return bool
478
     */
479
    public function hasClass(string $className)
480
    {
481
        return $this->mapAnyTrue(
482
            function (HtmlNode $node) use ($className) {
483
                return $node->hasClass($className);
484
            }
485
        );
486
    }
487
488
    /**
489
     * Remove a single class, multiple classes, or all classes
490
     * from each matched node.
491
     *
492
     * @param string|null $className
493
     *
494
     * @return static
495
     */
496
    public function removeClass(?string $className = null)
497
    {
498
        return $this->each(function (HtmlNode $node) use ($className) {
499
            $node->removeClass($className);
500
        });
501
    }
502
503
    /**
504
     * Add or remove class(es) from each matched node, depending on
505
     * either the class's presence or the value of the state argument.
506
     *
507
     * @param string $className
508
     * @param bool|null   $state
509
     *
510
     * @return static
511
     */
512
    public function toggleClass(string $className, ?bool $state = null)
513
    {
514
        return $this->each(function (HtmlNode $node) use ($className, $state) {
515
            $node->toggleClass($className, $state);
516
        });
517
    }
518
519
    /**
520
     * Get the value of a computed style property for the first matched node
521
     * or set one or more CSS properties for every matched node.
522
     *
523
     * @param string|array $name
524
     * @param string|null  $value
525
     *
526
     * @return static|string|null
527
     */
528
    public function css($name, $value = null)
529
    {
530
        if (is_null($value) && !is_array($name)) {
531
            return $this->getCss($name);
532
        }
533
534
        if (is_array($name)) {
535
            foreach ($name as $key => $val) {
536
                $this->setCss($key, $val);
537
            }
538
        } else {
539
            $this->setCss($name, $value);
540
        }
541
542
        return $this;
543
    }
544
545
546
    /**
547
     * Get the value of a computed style property for the first matched node
548
     *
549
     * @param string $name
550
     *
551
     * @return string|null
552
     */
553
    public function getCss(string $name)
554
    {
555
        return $this->mapFirst(function (HtmlNode $node) use ($name) {
556
            return $node->getCss($name);
557
        });
558
    }
559
560
    /**
561
     * Set or Remove one CSS property for every matched node.
562
     *
563
     * @param string      $name
564
     * @param string|null $value
565
     *
566
     * @return static
567
     */
568
    public function setCss(string $name, ?string $value)
569
    {
570
        return $this->each(function (HtmlNode $node) use ($name, $value) {
571
            $node->setCss($name, $value);
572
        });
573
    }
574
575
    /**
576
     * Remove one CSS property for every matched node.
577
     *
578
     * @param string $name
579
     *
580
     * @return static
581
     */
582
    public function removeCss(string $name)
583
    {
584
        return $this->each(function (HtmlNode $node) use ($name) {
585
            return $node->removeCss($name);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $node->removeCss($name) targeting Sulao\HtmlQuery\HtmlNode::removeCss() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
586
        });
587
    }
588
589
    /**
590
     * Insert content or node(s) before each matched node.
591
     *
592
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
593
     *
594
     * @return static
595
     */
596
    public function before($content)
597
    {
598
        $content = $this->contentResolve($content);
599
600
        return $this->each(function (HtmlNode $node, $index) use ($content) {
601
            $content->each(function (DOMNode $newNode) use ($node, $index) {
602
                $newNode = $index !== $this->count() - 1
603
                    ? $newNode->cloneNode(true)
604
                    : $newNode;
605
606
                $node->before($newNode);
607
            });
608
        });
609
    }
610
611
    /**
612
     * Insert every matched node before the target.
613
     *
614
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
615
     *
616
     * @return static
617
     */
618
    public function insertBefore($selector)
619
    {
620
        $target = $this->targetResolve($selector);
621
622
        return $target->before($this);
623
    }
624
625
    /**
626
     * Insert content or node(s) after each matched node.
627
     *
628
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
629
     *
630
     * @return static
631
     */
632
    public function after($content)
633
    {
634
        $content = $this->contentResolve($content);
635
636
        return $this->each(function (HtmlQuery $node, $index) use ($content) {
637
            $content->each(function (DOMNode $newNode) use ($node, $index) {
638
                $newNode = $index !== $this->count() - 1
639
                    ? $newNode->cloneNode(true)
640
                    : $newNode;
641
642
                if ($node->next()->count()) {
643
                    $node->next()->before($newNode);
644
                } else {
645
                    $node->parent()->append($newNode);
646
                }
647
            }, true);
648
        });
649
    }
650
651
    /**
652
     * Insert every matched node after the target.
653
     *
654
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
655
     *
656
     * @return static
657
     */
658
    public function insertAfter($selector)
659
    {
660
        $target = $this->targetResolve($selector);
661
662
        return $target->after($this);
663
    }
664
665
    /**
666
     * Insert content or node(s) to the end of every matched node.
667
     *
668
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
669
     *
670
     * @return static
671
     */
672
    public function append($content)
673
    {
674
        $content = $this->contentResolve($content);
675
676
        return $this->each(function (HtmlNode $node, $index) use ($content) {
677
            $content->each(function (DOMNode $newNode) use ($node, $index) {
678
                $newNode = $index !== $this->count() - 1
679
                    ? $newNode->cloneNode(true)
680
                    : $newNode;
681
682
                $node->append($newNode);
683
            });
684
        });
685
    }
686
687
    /**
688
     * Insert every matched node to the end of the target.
689
     *
690
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
691
     *
692
     * @return static
693
     */
694
    public function appendTo($selector)
695
    {
696
        $target = $this->targetResolve($selector);
697
698
        return $target->append($this);
699
    }
700
701
    /**
702
     * Insert content or node(s) to the beginning of each matched node.
703
     *
704
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
705
     *
706
     * @return static
707
     */
708
    public function prepend($content)
709
    {
710
        $content = $this->contentResolve($content);
711
712
        return $this->each(function (HtmlNode $node, $index) use ($content) {
713
            $content->each(function (DOMNode $newNode) use ($node, $index) {
714
                $newNode = $index !== $this->count() - 1
715
                    ? $newNode->cloneNode(true)
716
                    : $newNode;
717
718
                $node->prepend($newNode);
719
            }, true);
720
        });
721
    }
722
723
    /**
724
     * Insert every matched node to the beginning of the target.
725
     *
726
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
727
     *
728
     * @return static
729
     */
730
    public function prependTo($selector)
731
    {
732
        $target = $this->targetResolve($selector);
733
734
        return $target->prepend($this);
735
    }
736
737
    /**
738
     * Replace each matched node with the provided new content or node(s)
739
     *
740
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
741
     *
742
     * @return static
743
     */
744
    public function replaceWith($content)
745
    {
746
        $content = $this->contentResolve($content);
747
        return $this->each(function (DOMNode $node, $index) use ($content) {
748
            if (!$node->parentNode) {
749
                return;
750
            }
751
752
            $len = $content->count();
753
            $content->each(
754
                function (DOMNode $newNode) use ($node, $index, $len) {
755
                    $newNode = $index !== $this->count() - 1
756
                        ? $newNode->cloneNode(true)
757
                        : $newNode;
758
759
                    if ($len === 1) {
760
                        $node->parentNode->replaceChild($newNode, $node);
761
                    } else {
762
                        $this->resolve($newNode)->insertAfter($node);
763
                    }
764
                },
765
                true
766
            );
767
768
            if ($len !== 1) {
769
                $node->parentNode->removeChild($node);
770
            }
771
        });
772
    }
773
774
    /**
775
     * Replace each target node with the matched node(s)
776
     *
777
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
778
     *
779
     * @return static
780
     */
781
    public function replaceAll($selector)
782
    {
783
        $target = $this->targetResolve($selector);
784
785
        return $target->replaceWith($this);
786
    }
787
788
    /**
789
     * Wrap an HTML structure around each matched node.
790
     *
791
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
792
     *
793
     * @return static
794
     */
795
    public function wrap($content)
796
    {
797
        $content = $this->contentResolve($content);
798
        $newNode = $content[0];
799
800
        if (empty($newNode)) {
801
            return $this;
802
        }
803
804
        $newNode = $content[0];
805
806
        return $this->each(function (DOMNode $node, $index) use ($newNode) {
807
            $newNode = $index !== $this->count() - 1
808
                ? $newNode->cloneNode(true)
0 ignored issues
show
Bug introduced by
The method cloneNode() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

808
                ? $newNode->/** @scrutinizer ignore-call */ cloneNode(true)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
809
                : $newNode;
810
811
            $nodes = $this->xpathQuery('descendant::*[last()]', $newNode);
812
            if (!$nodes) {
813
                throw new Exception('Invalid wrap html format.');
814
            }
815
816
            $deepestNode = end($nodes);
817
            $node->parentNode->replaceChild($newNode, $node);
0 ignored issues
show
Bug introduced by
It seems like $newNode can also be of type null; however, parameter $newnode of DOMNode::replaceChild() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

817
            $node->parentNode->replaceChild(/** @scrutinizer ignore-type */ $newNode, $node);
Loading history...
818
            $deepestNode->appendChild($node);
819
        });
820
    }
821
822
    /**
823
     * Wrap an HTML structure around the content of each matched node.
824
     *
825
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
826
     *
827
     * @return static
828
     */
829
    public function wrapInner($content)
830
    {
831
        $content = $this->contentResolve($content);
832
        $newNode = $content[0];
833
834
        if (empty($newNode)) {
835
            return $this;
836
        }
837
838
        return $this->each(function (DOMNode $node, $index) use ($newNode) {
839
            $newNode = $index !== $this->count() - 1
840
                ? $newNode->cloneNode(true)
841
                : $newNode;
842
843
            $nodes = $this->xpathQuery('descendant::*[last()]', $newNode);
844
            if (!$nodes) {
845
                throw new Exception('Invalid wrap html format.');
846
            }
847
848
            $deepestNode = end($nodes);
849
850
            // Can't loop $this->node->childNodes directly to append child,
851
            // Because childNodes will change once appending child.
852
            foreach (iterator_to_array($node->childNodes) as $childNode) {
853
                $deepestNode->appendChild($childNode);
854
            }
855
856
            $node->appendChild($newNode);
857
        });
858
    }
859
860
    /**
861
     * Wrap an HTML structure around all matched nodes.
862
     *
863
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
864
     *
865
     * @return static
866
     */
867
    public function wrapAll($content)
868
    {
869
        $content = $this->contentResolve($content);
870
        if (!$content->count()) {
871
            return $this;
872
        }
873
874
        $newNode = $content[0];
875
        $this->each(function (DOMNode $node, $index) use ($newNode) {
876
            if ($index === 0) {
877
                $this->resolve($node)->wrap($newNode);
878
            } else {
879
                $this->nodes[0]->parentNode->appendChild($node);
880
            }
881
        });
882
883
        return $this;
884
    }
885
886
    /**
887
     * Remove the parents of the matched nodes from the DOM.
888
     * A optional selector to check the parent node against.
889
     *
890
     * @param string|null $selector
891
     *
892
     * @return static
893
     */
894
    public function unwrap(?string $selector = null)
895
    {
896
        return $this->parent($selector)->unwrapSelf();
897
    }
898
899
    /**
900
     * Remove the HTML tag of the matched nodes from the DOM.
901
     * Leaving the child nodes in their place.
902
     *
903
     * @return static
904
     */
905
    public function unwrapSelf()
906
    {
907
        return $this->each(function (DOMNode $node) {
908
            if (!$node->parentNode) {
909
                return;
910
            }
911
912
            foreach (iterator_to_array($node->childNodes) as $childNode) {
913
                $node->parentNode->insertBefore($childNode, $node);
914
            }
915
916
            $node->parentNode->removeChild($node);
917
        });
918
    }
919
920
    /**
921
     * Validate the nodes
922
     *
923
     * @param DOMNode|DOMNode[]|DOMNodeList|static $nodes
924
     *
925
     * @return DOMNode[]
926
     * @throws Exception
927
     */
928
    protected function validateNodes($nodes)
929
    {
930
        if (empty($nodes)) {
931
            $nodes = [];
932
        } elseif ($nodes instanceof Traversable) {
933
            $nodes = iterator_to_array($nodes);
934
        } elseif ($nodes instanceof DOMNode || !is_array($nodes)) {
935
            $nodes = [$nodes];
936
        }
937
938
        $nodes = Helper::strictArrayUnique($nodes);
939
        foreach ($nodes as $node) {
940
            if (!($node instanceof DOMNode)) {
941
                throw new Exception(
942
                    'Expect an instance of DOMNode, '
943
                        . gettype($node) . ' given.'
944
                );
945
            }
946
947
            if ((!$node->ownerDocument && $node !== $this->doc)
948
                || ($node->ownerDocument && $node->ownerDocument !== $this->doc)
949
            ) {
950
                throw new Exception(
951
                    'The DOMNode does not belong to the DOMDocument.'
952
                );
953
            }
954
        }
955
956
        return $nodes;
957
    }
958
}
959