Passed
Branch scrutinizer (a800ff)
by Thomas
02:24
created

HtmlQuery   F

Complexity

Total Complexity 86

Size/Duplication

Total Lines 873
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 218
c 2
b 1
f 0
dl 0
loc 873
rs 2
wmc 86

48 Methods

Rating   Name   Duplication   Size   Complexity  
A appendTo() 0 5 1
A html() 0 7 2
A insertBefore() 0 5 1
A setCss() 0 4 1
A after() 0 10 1
A text() 0 7 2
A wrapAll() 0 17 3
A setHtml() 0 9 2
A getCss() 0 4 1
A __construct() 0 4 1
A removeData() 0 3 1
A hasClass() 0 5 1
A unwrapSelf() 0 4 1
A before() 0 8 1
A setVal() 0 23 6
A prop() 0 3 1
A removeClass() 0 4 1
A attr() 0 15 4
A unwrap() 0 3 1
A replaceAll() 0 5 1
A setText() 0 4 1
A toggleClass() 0 4 1
A removeCss() 0 4 1
A addClass() 0 4 1
A append() 0 8 1
A outerHtml() 0 4 1
A css() 0 15 5
A getText() 0 4 1
A remove() 0 11 2
A setAttr() 0 4 1
A val() 0 7 2
A hasAttr() 0 5 1
A replaceWith() 0 24 4
A hasData() 0 3 1
A removeProp() 0 3 1
A prependTo() 0 5 1
A removeAllAttrs() 0 4 1
A empty() 0 4 1
A removeAttr() 0 4 1
A data() 0 27 6
A wrap() 0 20 3
A insertAfter() 0 5 1
A wrapInner() 0 26 4
A getVal() 0 24 6
A getHtml() 0 4 1
A getAttr() 0 4 1
A prepend() 0 9 1
A newNode() 0 5 2

How to fix   Complexity   

Complex Class

Complex classes like HtmlQuery often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HtmlQuery, and based on these observations, apply Extract Interface, too.

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

296
        $result = $this->attr(/** @scrutinizer ignore-type */ $name, $value);
Loading history...
297
298
        if (is_string($result)) {
299
            $json = json_decode($result);
300
            if (json_last_error() === JSON_ERROR_NONE) {
301
                return $json;
302
            }
303
        }
304
305
        return $result;
306
    }
307
308
    /**
309
     * Determine whether any of the nodes have the given attribute
310
     * prefix with data-.
311
     *
312
     * @param string $name
313
     *
314
     * @return bool
315
     */
316
    public function hasData(string $name)
317
    {
318
        return $this->hasAttr('data-' . $name);
319
    }
320
321
    /**
322
     * Remove an attribute prefix with data- from every matched nodes.
323
     *
324
     * @param string $name
325
     *
326
     * @return static
327
     */
328
    public function removeData(string $name)
329
    {
330
        return $this->removeAttr('data-' . $name);
331
    }
332
333
    /**
334
     * Remove all child nodes of all matched nodes from the DOM.
335
     *
336
     * @return static
337
     */
338
    public function empty()
339
    {
340
        return $this->each(function (HtmlNode $node) {
341
            $node->empty();
342
        });
343
    }
344
345
    /**
346
     * Remove the matched nodes from the DOM.
347
     * optionally filtered by a selector.
348
     *
349
     * @param string|null $selector
350
     *
351
     * @return static
352
     */
353
    public function remove(?string $selector = null)
354
    {
355
        if (!is_null($selector)) {
356
            $this->filter($selector)->remove();
357
        } else {
358
            $this->each(function (HtmlNode $node) {
359
                $node->remove();
360
            });
361
        }
362
363
        return $this;
364
    }
365
366
    /**
367
     * Get the current value of the first matched node
368
     * or set the value of every matched node.
369
     *
370
     * @param string|null $value
371
     *
372
     * @return string|null|static
373
     */
374
    public function val(?string $value = null)
375
    {
376
        if (is_null($value)) {
377
            return $this->getVal();
378
        }
379
380
        return $this->setVal($value);
381
    }
382
383
    /**
384
     * Get the current value of the first matched node
385
     *
386
     * @return string|null
387
     */
388
    public function getVal()
389
    {
390
        return $this->mapFirst(function (DOMElement $node) {
391
            switch ($node->tagName) {
392
                case 'input':
393
                    return $node->getAttribute('value');
394
                case 'textarea':
395
                    return $node->nodeValue;
396
                case 'select':
397
                    $ht = $this->resolve($node);
398
399
                    $selected = $ht->children('option:selected');
400
                    if ($selected->count()) {
401
                        return $selected->getAttr('value');
402
                    }
403
404
                    $fistChild = $ht->xpathFind('child::*[1]');
405
                    if ($fistChild->count()) {
406
                        return $fistChild->getAttr('value');
407
                    }
408
                    break;
409
            }
410
411
            return null;
412
        });
413
    }
414
415
    /**
416
     * Set the value of every matched node.
417
     *
418
     * @param string $value
419
     *
420
     * @return static
421
     */
422
    public function setVal(string $value)
423
    {
424
        return $this->each(function (DOMElement $node) use ($value) {
425
            switch ($node->tagName) {
426
                case 'input':
427
                    $node->setAttribute('value', $value);
428
                    break;
429
                case 'textarea':
430
                    $node->nodeValue = $value;
431
                    break;
432
                case 'select':
433
                    $ht = $this->resolve($node);
434
435
                    $selected = $ht->children('option:selected');
436
                    if ($selected->count()) {
437
                        $selected->removeAttr('selected');
438
                    }
439
440
                    $options = $ht->children("option[value='{$value}']");
441
                    if ($options->count()) {
442
                        $options->first()->setAttr('selected', 'selected');
443
                    }
444
                    break;
445
            }
446
        });
447
    }
448
449
    /**
450
     * Adds the specified class(es) to each node in the matched nodes.
451
     *
452
     * @param string $className
453
     *
454
     * @return static
455
     */
456
    public function addClass(string $className)
457
    {
458
        return $this->each(function (HtmlElement $node) use ($className) {
459
            $node->addClass($className);
460
        });
461
    }
462
463
    /**
464
     * Determine whether any of the matched nodes are assigned the given class.
465
     *
466
     * @param string $className
467
     *
468
     * @return bool
469
     */
470
    public function hasClass(string $className)
471
    {
472
        return $this->mapAnyTrue(
473
            function (HtmlElement $node) use ($className) {
474
                return $node->hasClass($className);
475
            }
476
        );
477
    }
478
479
    /**
480
     * Remove a single class, multiple classes, or all classes
481
     * from each matched node.
482
     *
483
     * @param string|null $className
484
     *
485
     * @return static
486
     */
487
    public function removeClass(?string $className = null)
488
    {
489
        return $this->each(function (HtmlElement $node) use ($className) {
490
            $node->removeClass($className);
491
        });
492
    }
493
494
    /**
495
     * Add or remove class(es) from each matched node, depending on
496
     * either the class's presence or the value of the state argument.
497
     *
498
     * @param string $className
499
     * @param bool|null   $state
500
     *
501
     * @return static
502
     */
503
    public function toggleClass(string $className, ?bool $state = null)
504
    {
505
        return $this->each(function (HtmlElement $node) use ($className, $state) {
506
            $node->toggleClass($className, $state);
507
        });
508
    }
509
510
    /**
511
     * Get the value of a computed style property for the first matched node
512
     * or set one or more CSS properties for every matched node.
513
     *
514
     * @param string|array $name
515
     * @param string|null  $value
516
     *
517
     * @return static|string|null
518
     */
519
    public function css($name, $value = null)
520
    {
521
        if (is_null($value) && !is_array($name)) {
522
            return $this->getCss($name);
523
        }
524
525
        if (is_array($name)) {
526
            foreach ($name as $key => $val) {
527
                $this->setCss($key, $val);
528
            }
529
        } else {
530
            $this->setCss($name, $value);
531
        }
532
533
        return $this;
534
    }
535
536
537
    /**
538
     * Get the value of a computed style property for the first matched node
539
     *
540
     * @param string $name
541
     *
542
     * @return string|null
543
     */
544
    public function getCss(string $name)
545
    {
546
        return $this->mapFirst(function (HtmlElement $node) use ($name) {
547
            return $node->getCss($name);
548
        });
549
    }
550
551
    /**
552
     * Set or Remove one CSS property for every matched node.
553
     *
554
     * @param string      $name
555
     * @param string|null $value
556
     *
557
     * @return static
558
     */
559
    public function setCss(string $name, ?string $value)
560
    {
561
        return $this->each(function (HtmlElement $node) use ($name, $value) {
562
            $node->setCss($name, $value);
563
        });
564
    }
565
566
    /**
567
     * Remove one CSS property for every matched node.
568
     *
569
     * @param string $name
570
     *
571
     * @return static
572
     */
573
    public function removeCss(string $name)
574
    {
575
        return $this->each(function (HtmlElement $node) use ($name) {
576
            $node->removeCss($name);
577
        });
578
    }
579
580
    /**
581
     * Insert content or node(s) before each matched node.
582
     *
583
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
584
     *
585
     * @return static
586
     */
587
    public function before($content)
588
    {
589
        $content = $this->contentResolve($content);
590
591
        return $this->each(function (HtmlNode $node, $index) use ($content) {
592
            $content->each(function (DOMNode $newNode) use ($node, $index) {
593
                $newNode = $this->newNode($newNode, $index);
594
                $node->before($newNode);
595
            });
596
        });
597
    }
598
599
    /**
600
     * Insert every matched node before the target.
601
     *
602
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
603
     *
604
     * @return static
605
     */
606
    public function insertBefore($selector)
607
    {
608
        $target = $this->targetResolve($selector);
609
610
        return $target->before($this);
611
    }
612
613
    /**
614
     * Insert content or node(s) after each matched node.
615
     *
616
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
617
     *
618
     * @return static
619
     */
620
    public function after($content)
621
    {
622
        $content = $this->contentResolve($content);
623
624
        return $this->each(function (HtmlNode $node, $index) use ($content) {
625
            $content->each(function (DOMNode $newNode) use ($node, $index) {
626
                $newNode = $this->newNode($newNode, $index);
627
628
                $node->after($newNode);
629
            }, true);
630
        });
631
    }
632
633
    /**
634
     * Insert every matched node after the target.
635
     *
636
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
637
     *
638
     * @return static
639
     */
640
    public function insertAfter($selector)
641
    {
642
        $target = $this->targetResolve($selector);
643
644
        return $target->after($this);
645
    }
646
647
    /**
648
     * Insert content or node(s) to the end of every matched node.
649
     *
650
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
651
     *
652
     * @return static
653
     */
654
    public function append($content)
655
    {
656
        $content = $this->contentResolve($content);
657
658
        return $this->each(function (HtmlNode $node, $index) use ($content) {
659
            $content->each(function (DOMNode $newNode) use ($node, $index) {
660
                $newNode = $this->newNode($newNode, $index);
661
                $node->append($newNode);
662
            });
663
        });
664
    }
665
666
    /**
667
     * Insert every matched node to the end of the target.
668
     *
669
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
670
     *
671
     * @return static
672
     */
673
    public function appendTo($selector)
674
    {
675
        $target = $this->targetResolve($selector);
676
677
        return $target->append($this);
678
    }
679
680
    /**
681
     * Insert content or node(s) to the beginning of each matched node.
682
     *
683
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
684
     *
685
     * @return static
686
     */
687
    public function prepend($content)
688
    {
689
        $content = $this->contentResolve($content);
690
691
        return $this->each(function (HtmlNode $node, $index) use ($content) {
692
            $content->each(function (DOMNode $newNode) use ($node, $index) {
693
                $newNode = $this->newNode($newNode, $index);
694
                $node->prepend($newNode);
695
            }, true);
696
        });
697
    }
698
699
    /**
700
     * Insert every matched node to the beginning of the target.
701
     *
702
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
703
     *
704
     * @return static
705
     */
706
    public function prependTo($selector)
707
    {
708
        $target = $this->targetResolve($selector);
709
710
        return $target->prepend($this);
711
    }
712
713
    /**
714
     * Replace each matched node with the provided new content or node(s)
715
     *
716
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
717
     *
718
     * @return static
719
     */
720
    public function replaceWith($content)
721
    {
722
        $content = $this->contentResolve($content);
723
        return $this->each(function (DOMNode $node, $index) use ($content) {
724
            if (!$node->parentNode) {
725
                return;
726
            }
727
728
            $len = $content->count();
729
            $content->each(
730
                function (DOMNode $newNode) use ($node, $index, $len) {
731
                    $newNode = $this->newNode($newNode, $index);
732
733
                    if ($len === 1) {
734
                        $node->parentNode->replaceChild($newNode, $node);
735
                    } else {
736
                        $this->resolve($newNode)->insertAfter($node);
737
                    }
738
                },
739
                true
740
            );
741
742
            if ($len !== 1) {
743
                $node->parentNode->removeChild($node);
744
            }
745
        });
746
    }
747
748
    /**
749
     * Replace each target node with the matched node(s)
750
     *
751
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
752
     *
753
     * @return static
754
     */
755
    public function replaceAll($selector)
756
    {
757
        $target = $this->targetResolve($selector);
758
759
        return $target->replaceWith($this);
760
    }
761
762
    /**
763
     * Wrap an HTML structure around each matched node.
764
     *
765
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
766
     *
767
     * @return static
768
     */
769
    public function wrap($content)
770
    {
771
        $content = $this->contentResolve($content);
772
        $newNode = $content[0];
773
774
        if (empty($newNode)) {
775
            return $this;
776
        }
777
778
        return $this->each(function (DOMNode $node, $index) use ($newNode) {
779
            $newNode = $this->newNode($newNode, $index);
780
781
            $nodes = $this->xpathQuery('descendant::*[last()]', $newNode);
782
            if (!$nodes) {
783
                throw new Exception('Invalid wrap html format.');
784
            }
785
786
            $deepestNode = end($nodes);
787
            $node->parentNode->replaceChild($newNode, $node);
788
            $deepestNode->appendChild($node);
789
        });
790
    }
791
792
    /**
793
     * Wrap an HTML structure around the content of each matched node.
794
     *
795
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
796
     *
797
     * @return static
798
     */
799
    public function wrapInner($content)
800
    {
801
        $content = $this->contentResolve($content);
802
        $newNode = $content[0];
803
804
        if (empty($newNode)) {
805
            return $this;
806
        }
807
808
        return $this->each(function (DOMNode $node, $index) use ($newNode) {
809
            $newNode = $this->newNode($newNode, $index);
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
818
            // Can't loop $this->node->childNodes directly to append child,
819
            // Because childNodes will change once appending child.
820
            foreach (iterator_to_array($node->childNodes) as $childNode) {
821
                $deepestNode->appendChild($childNode);
822
            }
823
824
            $node->appendChild($newNode);
825
        });
826
    }
827
828
    /**
829
     * Wrap an HTML structure around all matched nodes.
830
     *
831
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
832
     *
833
     * @return static
834
     */
835
    public function wrapAll($content)
836
    {
837
        $content = $this->contentResolve($content);
838
        if (!$content->count()) {
839
            return $this;
840
        }
841
842
        $newNode = $content[0];
843
        $this->each(function (DOMNode $node, $index) use ($newNode) {
844
            if ($index === 0) {
845
                $this->resolve($node)->wrap($newNode);
846
            } else {
847
                $this->nodes[0]->parentNode->appendChild($node);
848
            }
849
        });
850
851
        return $this;
852
    }
853
854
    /**
855
     * Remove the parents of the matched nodes from the DOM.
856
     * A optional selector to check the parent node against.
857
     *
858
     * @param string|null $selector
859
     *
860
     * @return static
861
     */
862
    public function unwrap(?string $selector = null)
863
    {
864
        return $this->parent($selector)->unwrapSelf();
865
    }
866
867
    /**
868
     * Remove the HTML tag of the matched nodes from the DOM.
869
     * Leaving the child nodes in their place.
870
     *
871
     * @return static
872
     */
873
    public function unwrapSelf()
874
    {
875
        return $this->each(function (HtmlNode $node) {
876
            $node->unwrapSelf();
877
        });
878
    }
879
880
    protected function newNode(DOMNode $newNode, int $index)
881
    {
882
        return $index !== $this->count() - 1
883
            ? $newNode->cloneNode(true)
884
            : $newNode;
885
    }
886
}
887