Completed
Branch scrutinizer (79ce98)
by Thomas
02:53
created

HtmlQuery::unwrap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Sulao\HtmlQuery;
4
5
use DOMDocument;
6
use DOMNode, DOMNodeList;
7
8
/**
9
 * Class HtmlQuery
10
 *
11
 * @package Sulao\HtmlQuery
12
 */
13
class HtmlQuery extends Attribute
14
{
15
    const VERSION = '1.0.0';
16
    /**
17
     * HtmlQuery constructor.
18
     *
19
     * @param DOMDocument                   $doc
20
     * @param DOMNode|DOMNode[]|DOMNodeList $nodes
21
     *
22
     * @throws Exception
23
     */
24
    public function __construct(DOMDocument $doc, $nodes)
25
    {
26
        $this->doc = $doc;
27
        $this->nodes = $this->validateNodes($nodes);
28
    }
29
30
    /**
31
     * Get the outer HTML contents of the first matched node.
32
     *
33
     * @return string|null
34
     */
35
    public function outerHtml()
36
    {
37
        return $this->mapFirst(function (DOMNode $node) {
38
            return $this->doc->saveHTML($node);
39
        });
40
    }
41
42
    /**
43
     * Get the inner HTML contents of the first matched node or
44
     * set the inner HTML contents of every matched node.
45
     *
46
     * @param string|null $html
47
     *
48
     * @return string|null|static
49
     */
50
    public function html(?string $html = null)
51
    {
52
        if (!is_null($html)) {
53
            return $this->setHtml($html);
54
        }
55
56
        return $this->getHtml();
57
    }
58
59
    /**
60
     * Get the inner HTML contents of the first matched node.
61
     *
62
     * @return string|null
63
     */
64
    public function getHtml()
65
    {
66
        return $this->mapFirst(function (DOMNode $node) {
67
            $content = '';
68
            foreach (iterator_to_array($node->childNodes) as $childNode) {
69
                $content .= $this->doc->saveHTML($childNode);
70
            }
71
72
            return $content;
73
        });
74
    }
75
76
    /**
77
     * Set the inner HTML contents of every matched node.
78
     *
79
     * @param string $html
80
     *
81
     * @return static
82
     */
83
    public function setHtml(string $html)
84
    {
85
        $this->empty();
86
87
        $this->each(function (DOMNode $node) {
88
            $node->nodeValue = '';
89
        });
90
91
        if ($html !== '') {
92
            $this->append($html);
93
        }
94
95
        return $this;
96
    }
97
98
    /**
99
     * Get the combined text contents of the first matched node, including
100
     * it's descendants, or set the text contents of every matched node.
101
     *
102
     * @param string|null $text
103
     *
104
     * @return string|null|static
105
     */
106
    public function text(?string $text = null)
107
    {
108
        if (!is_null($text)) {
109
            return $this->setText($text);
110
        }
111
112
        return $this->getText();
113
    }
114
115
    /**
116
     * Get the combined text contents of the first matched node,
117
     * including it's descendants.
118
     *
119
     * @return string|null
120
     */
121
    public function getText()
122
    {
123
        return $this->mapFirst(function (DOMNode $node) {
124
            return $node->textContent;
125
        });
126
    }
127
128
    /**
129
     * set the text contents of every matched node.
130
     *
131
     * @param string $text
132
     *
133
     * @return static
134
     */
135
    public function setText(string $text)
136
    {
137
        return $this->each(function (DOMNode $node) use ($text) {
138
            return $node->nodeValue = $text;
139
        });
140
    }
141
142
    /**
143
     * Remove all child nodes of all matched nodes from the DOM.
144
     *
145
     * @return static
146
     */
147
    public function empty()
148
    {
149
        return $this->each(function (DOMNode $node) {
150
            $node->nodeValue = '';
151
        });
152
    }
153
154
    /**
155
     * Remove the matched nodes from the DOM.
156
     * optionally filtered by a selector.
157
     *
158
     * @param string|null $selector
159
     *
160
     * @return static
161
     */
162
    public function remove(?string $selector = null)
163
    {
164
        if (!is_null($selector)) {
165
            $this->filter($selector)->remove();
166
        } else {
167
            $this->each(function (DOMNode $node) {
168
                if ($node->parentNode) {
169
                    $node->parentNode->removeChild($node);
170
                }
171
            });
172
        }
173
174
        return $this;
175
    }
176
177
    /**
178
     * Insert content or node(s) before each matched node.
179
     *
180
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
181
     *
182
     * @return static
183
     */
184
    public function before($content)
185
    {
186
        $content = $this->contentResolve($content);
187
188
        return $this->each(function (DOMNode $node, $index) use ($content) {
189
            $content->each(function (DOMNode $newNode) use ($node, $index) {
190
                if ($node->parentNode) {
191
                    $newNode = $index !== $this->count() - 1
192
                        ? $newNode->cloneNode(true)
193
                        : $newNode;
194
195
                    $node->parentNode->insertBefore($newNode, $node);
196
                }
197
            });
198
        });
199
    }
200
201
    /**
202
     * Insert every matched node before the target.
203
     *
204
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $selector
205
     *
206
     * @return static
207
     */
208
    public function insertBefore($selector)
209
    {
210
        $target = $this->targetResolve($selector);
211
212
        return $target->before($this);
213
    }
214
215
    /**
216
     * Insert content or node(s) after each matched node.
217
     *
218
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
219
     *
220
     * @return static
221
     */
222
    public function after($content)
223
    {
224
        $content = $this->contentResolve($content);
225
226
        return $this->each(function (HtmlQuery $node, $index) use ($content) {
227
            $content->each(function (DOMNode $newNode) use ($node, $index) {
228
                $newNode = $index !== $this->count() - 1
229
                    ? $newNode->cloneNode(true)
230
                    : $newNode;
231
232
                if ($node->next()->count()) {
233
                    $node->next()->before($newNode);
234
                } else {
235
                    $node->parent()->append($newNode);
236
                }
237
            }, true);
238
        });
239
    }
240
241
    /**
242
     * Insert every matched node after the target.
243
     *
244
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
245
     *
246
     * @return static
247
     */
248
    public function insertAfter($selector)
249
    {
250
        $target = $this->targetResolve($selector);
251
252
        return $target->after($this);
253
    }
254
255
    /**
256
     * Insert content or node(s) to the end of every matched node.
257
     *
258
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
259
     *
260
     * @return static
261
     */
262
    public function append($content)
263
    {
264
        $content = $this->contentResolve($content);
265
266
        return $this->each(function (DOMNode $node, $index) use ($content) {
267
            $content->each(function (DOMNode $newNode) use ($node, $index) {
268
                $newNode = $index !== $this->count() - 1
269
                    ? $newNode->cloneNode(true)
270
                    : $newNode;
271
272
                $node->appendChild($newNode);
273
            });
274
        });
275
    }
276
277
    /**
278
     * Insert every matched node to the end of the target.
279
     *
280
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
281
     *
282
     * @return static
283
     */
284
    public function appendTo($selector)
285
    {
286
        $target = $this->targetResolve($selector);
287
288
        return $target->append($this);
289
    }
290
291
    /**
292
     * Insert content or node(s) to the beginning of each matched node.
293
     *
294
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
295
     *
296
     * @return static
297
     */
298
    public function prepend($content)
299
    {
300
        $content = $this->contentResolve($content);
301
302
        return $this->each(function (DOMNode $node, $index) use ($content) {
303
            $content->each(function (DOMNode $newNode) use ($node, $index) {
304
                $newNode = $index !== $this->count() - 1
305
                    ? $newNode->cloneNode(true)
306
                    : $newNode;
307
308
                if ($node->firstChild) {
309
                    $node->insertBefore($newNode, $node->firstChild);
310
                } else {
311
                    $node->appendChild($newNode);
312
                }
313
            }, true);
314
        });
315
    }
316
317
    /**
318
     * Insert every matched node to the beginning of the target.
319
     *
320
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
321
     *
322
     * @return static
323
     */
324
    public function prependTo($selector)
325
    {
326
        $target = $this->targetResolve($selector);
327
328
        return $target->prepend($this);
329
    }
330
331
    /**
332
     * Replace each matched node with the provided new content or node(s)
333
     *
334
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
335
     *
336
     * @return static
337
     */
338
    public function replaceWith($content)
339
    {
340
        $content = $this->contentResolve($content);
341
        return $this->each(function (DOMNode $node, $index) use ($content) {
342
            if (!$node->parentNode) {
343
                return;
344
            }
345
346
            $len = $content->count();
347
            $content->each(
348
                function (DOMNode $newNode) use ($node, $index, $len) {
349
                    $newNode = $index !== $this->count() - 1
350
                        ? $newNode->cloneNode(true)
351
                        : $newNode;
352
353
                    if ($len === 1) {
354
                        $node->parentNode->replaceChild($newNode, $node);
355
                    } else {
356
                        $this->resolve($newNode)->insertAfter($node);
357
                    }
358
                },
359
                true
360
            );
361
362
            if ($len !== 1) {
363
                $node->parentNode->removeChild($node);
364
            }
365
        });
366
    }
367
368
    /**
369
     * Replace each target node with the matched node(s)
370
     *
371
     * @param string|DOMNode|DOMNode[]DOMNodeList|static $selector
372
     *
373
     * @return static
374
     */
375
    public function replaceAll($selector)
376
    {
377
        $target = $this->targetResolve($selector);
378
379
        return $target->replaceWith($this);
380
    }
381
382
    /**
383
     * Wrap an HTML structure around each matched node.
384
     *
385
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
386
     *
387
     * @return static
388
     */
389
    public function wrap($content)
390
    {
391
        $content = $this->contentResolve($content);
392
        $newNode = $content[0];
393
394
        if (empty($newNode)) {
395
            return $this;
396
        }
397
398
        $newNode = $content[0];
399
400
        return $this->each(function (DOMNode $node, $index) use ($newNode) {
401
            $newNode = $index !== $this->count() - 1
402
                ? $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

402
                ? $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...
403
                : $newNode;
404
405
            $nodes = $this->xpathQuery('descendant::*[last()]', $newNode);
406
            if (!$nodes) {
407
                throw new Exception('Invalid wrap html format.');
408
            }
409
410
            $deepestNode = end($nodes);
411
            $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

411
            $node->parentNode->replaceChild(/** @scrutinizer ignore-type */ $newNode, $node);
Loading history...
412
            $deepestNode->appendChild($node);
413
        });
414
    }
415
416
    /**
417
     * Wrap an HTML structure around the content of each matched node.
418
     *
419
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
420
     *
421
     * @return static
422
     */
423
    public function wrapInner($content)
424
    {
425
        $content = $this->contentResolve($content);
426
        $newNode = $content[0];
427
428
        if (empty($newNode)) {
429
            return $this;
430
        }
431
432
        return $this->each(function (DOMNode $node, $index) use ($newNode) {
433
            $newNode = $index !== $this->count() - 1
434
                ? $newNode->cloneNode(true)
435
                : $newNode;
436
437
            $nodes = $this->xpathQuery('descendant::*[last()]', $newNode);
438
            if (!$nodes) {
439
                throw new Exception('Invalid wrap html format.');
440
            }
441
442
            $deepestNode = end($nodes);
443
444
            // Can't loop $this->node->childNodes directly to append child,
445
            // Because childNodes will change once appending child.
446
            foreach (iterator_to_array($node->childNodes) as $childNode) {
447
                $deepestNode->appendChild($childNode);
448
            }
449
450
            $node->appendChild($newNode);
451
        });
452
    }
453
454
    /**
455
     * Wrap an HTML structure around all matched nodes.
456
     *
457
     * @param string|DOMNode|DOMNode[]|DOMNodeList|static $content
458
     *
459
     * @return static
460
     */
461
    public function wrapAll($content)
462
    {
463
        $content = $this->contentResolve($content);
464
        if (!$content->count()) {
465
            return $this;
466
        }
467
468
        $newNode = $content[0];
469
        $this->each(function (DOMNode $node, $index) use ($newNode) {
470
            if ($index === 0) {
471
                $this->resolve($node)->wrap($newNode);
472
            } else {
473
                $this->nodes[0]->parentNode->appendChild($node);
474
            }
475
        });
476
477
        return $this;
478
    }
479
480
    /**
481
     * Remove the parents of the matched nodes from the DOM.
482
     * A optional selector to check the parent node against.
483
     *
484
     * @param string|null $selector
485
     *
486
     * @return static
487
     */
488
    public function unwrap(?string $selector = null)
489
    {
490
        return $this->parent($selector)->unwrapSelf();
491
    }
492
493
    /**
494
     * Remove the HTML tag of the matched nodes from the DOM.
495
     * Leaving the child nodes in their place.
496
     *
497
     * @return static
498
     */
499
    public function unwrapSelf()
500
    {
501
        return $this->each(function (DOMNode $node) {
502
            if (!$node->parentNode) {
503
                return;
504
            }
505
506
            foreach (iterator_to_array($node->childNodes) as $childNode) {
507
                $node->parentNode->insertBefore($childNode, $node);
508
            }
509
510
            $node->parentNode->removeChild($node);
511
        });
512
    }
513
}
514