GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — 1.x ( 679511...0760ec )
by Christoph
04:54
created

HtmlPageCrawler::getInnerHtml()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
namespace Wa72\HtmlPageDom;
3
4
use Symfony\Component\DomCrawler\Crawler;
5
6
/**
7
 * Extends \Symfony\Component\DomCrawler\Crawler by adding tree manipulation functions
8
 * for HTML documents inspired by jQuery such as html(), css(), append(), prepend(), before(),
9
 * addClass(), removeClass()
10
 *
11
 * @author Christoph Singer
12
 * @license MIT
13
 *
14
 */
15
class HtmlPageCrawler extends Crawler
16
{
17
    /**
18
     * the (internal) root element name used when importing html fragments
19
     * */
20
    const FRAGMENT_ROOT_TAGNAME = '_root';
21
22
    /**
23
     * Get an HtmlPageCrawler object from a HTML string, DOMNode, DOMNodeList or HtmlPageCrawler
24
     *
25
     * This is the equivalent to jQuery's $() function when used for wrapping DOMNodes or creating DOMElements from HTML code.
26
     *
27
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList|array $content
28
     * @return HtmlPageCrawler
29
     * @api
30
     */
31 19
    public static function create($content)
32
    {
33 19
        if ($content instanceof HtmlPageCrawler) {
34 3
            return $content;
35
        } else {
36 19
            return new HtmlPageCrawler($content);
37
        }
38
    }
39
40
    /**
41
     * Adds the specified class(es) to each element in the set of matched elements.
42
     *
43
     * @param string $name One or more space-separated classes to be added to the class attribute of each matched element.
44
     * @return HtmlPageCrawler $this for chaining
45
     * @api
46
     */
47 1
    public function addClass($name)
48
    {
49 1
        foreach ($this as $node) {
50 1
            if ($node instanceof \DOMElement) {
51
                /** @var \DOMElement $node */
52 1
                $classes = preg_split('/\s+/s', $node->getAttribute('class'));
53 1
                $found = false;
54 1
                $count = count($classes);
55 1
                for ($i = 0; $i < $count; $i++) {
56 1
                    if ($classes[$i] == $name) {
57 1
                        $found = true;
58
                    }
59
                }
60 1
                if (!$found) {
61 1
                    $classes[] = $name;
62 1
                    $node->setAttribute('class', trim(join(' ', $classes)));
63
                }
64
            }
65
        }
66 1
        return $this;
67
    }
68
69
    /**
70
     * Insert content, specified by the parameter, after each element in the set of matched elements.
71
     *
72
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
73
     * @return HtmlPageCrawler $this for chaining
74
     * @api
75
     */
76 3
    public function after($content)
77
    {
78 3
        $content = self::create($content);
79 3
        $newnodes = array();
80 3
        foreach ($this as $i => $node) {
81
            /** @var \DOMNode $node */
82 3
            $refnode = $node->nextSibling;
83 3
            foreach ($content as $newnode) {
84
                /** @var \DOMNode $newnode */
85 3
                $newnode = static::importNewnode($newnode, $node, $i);
86 3
                if ($refnode === null) {
87 3
                    $node->parentNode->appendChild($newnode);
88
                } else {
89 1
                    $node->parentNode->insertBefore($newnode, $refnode);
90
                }
91 3
                $newnodes[] = $newnode;
92
            }
93
        }
94 3
        $content->clear();
95 3
        $content->add($newnodes);
96 3
        return $this;
97
    }
98
99
    /**
100
     * Insert HTML content as child nodes of each element after existing children
101
     *
102
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment or DOMNode to append
103
     * @return HtmlPageCrawler $this for chaining
104
     * @api
105
     */
106 2
    public function append($content)
107
    {
108 2
        $content = self::create($content);
109 2
        $newnodes = array();
110 2
        foreach ($this as $i => $node) {
111
            /** @var \DOMNode $node */
112 2
            foreach ($content as $newnode) {
113
                /** @var \DOMNode $newnode */
114 2
                $newnode = static::importNewnode($newnode, $node, $i);
115
//                if ($newnode->ownerDocument !== $node->ownerDocument) {
116
//                    $newnode = $node->ownerDocument->importNode($newnode, true);
117
//                } else {
118
//                    if ($i > 0) {
119
//                        $newnode = $newnode->cloneNode(true);
120
//                    }
121
//                }
122 2
                $node->appendChild($newnode);
123 2
                $newnodes[] = $newnode;
124
            }
125
        }
126 2
        $content->clear();
127 2
        $content->add($newnodes);
128 2
        return $this;
129
    }
130
131
    /**
132
     * Insert every element in the set of matched elements to the end of the target.
133
     *
134
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
135
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
136
     * @api
137
     */
138 2
    public function appendTo($element)
139
    {
140 2
        $e = self::create($element);
141 2
        $newnodes = array();
142 2
        foreach ($e as $i => $node) {
143
            /** @var \DOMNode $node */
144 2
            foreach ($this as $newnode) {
145
                /** @var \DOMNode $newnode */
146 2
                if ($node !== $newnode) {
147 2
                    $newnode = static::importNewnode($newnode, $node, $i);
148 2
                    $node->appendChild($newnode);
149
                }
150 2
                $newnodes[] = $newnode;
151
            }
152
        }
153 2
        return self::create($newnodes);
154
    }
155
156
    /**
157
     * Returns the attribute value of the first node of the list, or sets an attribute on each element
158
     *
159
     * @see HtmlPageCrawler::getAttribute()
160
     * @see HtmlPageCrawler::setAttribute
161
     *
162
     * @param string $name
163
     * @param null|string $value
164
     * @return null|string|HtmlPageCrawler
165
     * @api
166
     */
167 2
    public function attr($name, $value = null)
168
    {
169 2
        if ($value === null) {
170 2
            return $this->getAttribute($name);
171
        } else {
172 1
            @trigger_error('It will not be possible any more to use method attr($name, $value) as setter function in version 2.0. Use setAttribute($name, $value) instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
173 1
            return $this->setAttribute($name, $value);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->setAttribute($name, $value); (Wa72\HtmlPageDom\HtmlPageCrawler) is incompatible with the return type of the parent method Symfony\Component\DomCrawler\Crawler::attr of type string|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
174
        }
175
    }
176
177
    /**
178
     * Sets an attribute on each element
179
     *
180
     * @param string $name
181
     * @param string $value
182
     * @return HtmlPageCrawler $this for chaining
183
     */
184 3
    public function setAttribute($name, $value)
185
    {
186 3
        foreach ($this as $node) {
187 3
            if ($node instanceof \DOMElement) {
188
                /** @var \DOMElement $node */
189 3
                $node->setAttribute($name, $value);
190
            }
191
        }
192 3
        return $this;
193
    }
194
195
    /**
196
     * Returns the attribute value of the first node of the list.
197
     *
198
     * @param string $name The attribute name
199
     * @return string|null The attribute value or null if the attribute does not exist
200
     * @throws \InvalidArgumentException When current node is empty
201
     *
202
     */
203 2
    public function getAttribute($name)
204
    {
205 2
        if (!count($this)) {
206 1
            throw new \InvalidArgumentException('The current node list is empty.');
207
        }
208 1
        $node = $this->getNode(0);
209 1
        return $node->hasAttribute($name) ? $node->getAttribute($name) : null;
210
    }
211
212
    /**
213
     * Insert content, specified by the parameter, before each element in the set of matched elements.
214
     *
215
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
216
     * @return HtmlPageCrawler $this for chaining
217
     * @api
218
     */
219 2
    public function before($content)
220
    {
221 2
        $content = self::create($content);
222 2
        $newnodes = array();
223 2
        foreach ($this as $i => $node) {
224
            /** @var \DOMNode $node */
225 2
            foreach ($content as $newnode) {
226
                /** @var \DOMNode $newnode */
227 2
                if ($node !== $newnode) {
228 2
                    $newnode = static::importNewnode($newnode, $node, $i);
229 2
                    $node->parentNode->insertBefore($newnode, $node);
230 2
                    $newnodes[] = $newnode;
231
                }
232
            }
233
        }
234 2
        $content->clear();
235 2
        $content->add($newnodes);
236 2
        return $this;
237
    }
238
239
    /**
240
     * Create a deep copy of the set of matched elements.
241
     *
242
     * Equivalent to clone() in jQuery (clone is not a valid PHP function name)
243
     *
244
     * @return HtmlPageCrawler
245
     * @api
246
     */
247 1
    public function makeClone()
248
    {
249 1
        return clone $this;
250
    }
251
252 1
    public function __clone()
253
    {
254 1
        $newnodes = array();
255 1
        foreach ($this as $node) {
256
            /** @var \DOMNode $node */
257 1
            $newnodes[] = $node->cloneNode(true);
258
        }
259 1
        $this->clear();
260 1
        $this->add($newnodes);
261 1
    }
262
263
    /**
264
     * Get one CSS style property of the first element or set it for all elements in the list
265
     *
266
     * Function is here for compatibility with jQuery; it is the same as getStyle() and setStyle()
267
     *
268
     * @see HtmlPageCrawler::getStyle()
269
     * @see HtmlPageCrawler::setStyle()
270
     *
271
     * @param string $key The name of the style property
272
     * @param null|string $value The CSS value to set, or NULL to get the current value
273
     * @return HtmlPageCrawler|string If no param is provided, returns the CSS styles of the first element
274
     * @api
275
     */
276 1
    public function css($key, $value = null)
277
    {
278 1
        if (null === $value) {
279 1
            return $this->getStyle($key);
280
        } else {
281 1
            return $this->setStyle($key, $value);
282
        }
283
    }
284
285
    /**
286
     * get one CSS style property of the first element
287
     *
288
     * @param string $key name of the property
289
     * @return string|null value of the property
290
     */
291 1
    public function getStyle($key)
292
    {
293 1
        $styles = Helpers::cssStringToArray($this->getAttribute('style'));
294 1
        return (isset($styles[$key]) ? $styles[$key] : null);
295
    }
296
297
    /**
298
     * set one CSS style property for all elements in the list
299
     *
300
     * @param string $key name of the property
301
     * @param string $value value of the property
302
     * @return HtmlPageCrawler $this for chaining
303
     */
304 1
    public function setStyle($key, $value)
305
    {
306 1
        foreach ($this as $node) {
307 1
            if ($node instanceof \DOMElement) {
308
                /** @var \DOMElement $node */
309 1
                $styles = Helpers::cssStringToArray($node->getAttribute('style'));
310 1
                if ($value != '') {
311 1
                    $styles[$key] = $value;
312 1
                } elseif (isset($styles[$key])) {
313 1
                    unset($styles[$key]);
314
                }
315 1
                $node->setAttribute('style', Helpers::cssArrayToString($styles));
316
            }
317
        }
318 1
        return $this;
319
    }
320
321
    /**
322
     * Removes all child nodes and text from all nodes in set
323
     *
324
     * Equivalent to jQuery's empty() function which is not a valid function name in PHP
325
     * @return HtmlPageCrawler $this
326
     * @api
327
     */
328 1
    public function makeEmpty()
329
    {
330 1
        foreach ($this as $node) {
331 1
            $node->nodeValue = '';
332
        }
333 1
        return $this;
334
    }
335
336
    /**
337
     * Determine whether any of the matched elements are assigned the given class.
338
     *
339
     * @param string $name
340
     * @return bool
341
     * @api
342
     */
343 2
    public function hasClass($name)
344
    {
345 2
        foreach ($this as $node) {
346 2
            if ($node instanceof \DOMElement && $class = $node->getAttribute('class')) {
347 2
                $classes = preg_split('/\s+/s', $class);
348 2
                if (in_array($name, $classes)) {
349 2
                    return true;
350
                }
351
            }
352
        }
353 2
        return false;
354
    }
355
356
    /**
357
     * Get the HTML contents of the first element in the set of matched elements
358
     * or set the HTML contents of every matched element.
359
     *
360
     * Deprecation warning: It will not be possible any more to use method html($html) as setter function in version 2.0. Use setInnerHtml($html) instead.
361
     *
362
     * @see HtmlPageCrawler::setInnerHtml()
363
     *
364
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList|null $html The HTML content to set, or NULL to get the current content
365
     *
366
     * @return HtmlPageCrawler|string If no param is provided, returns the HTML content of the first element
367
     * @api
368
     */
369 1
    public function html($html = null)
370
    {
371 1
        if (null === $html) {
372 1
            return $this->getInnerHtml();
373
        } else {
374 1
            @trigger_error('It will not be possible any more to use method html($html) as setter function in version 2.0. Use setInnerHtml($html) instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
375 1
            $this->setInnerHtml($html);
376 1
            return $this;
377
        }
378
    }
379
380
    /**
381
     * Get the innerHTML contents of the first element
382
     *
383
     * @return string HTML code fragment
384
     * @see html()
385
     */
386 4
    public function getInnerHtml()
387
    {
388 4
        $node = $this->getNode(0);
389 4
        if ($node instanceof \DOMNode) {
390 3
            $doc = new \DOMDocument('1.0', 'UTF-8');
391 3
            $doc->appendChild($doc->importNode($node, true));
392 3
            $html = trim($doc->saveHTML());
393 3
            $tag = $node->nodeName;
394 3
            return preg_replace('@^<' . $tag . '[^>]*>|</' . $tag . '>$@', '', $html);
395
        } else {
396 1
            return '';
397
        }
398
    }
399
400
    /**
401
     * Set the HTML contents of each element
402
     *
403
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment
404
     * @return HtmlPageCrawler $this for chaining
405
     */
406 3
    public function setInnerHtml($content)
407
    {
408 3
        $content = self::create($content);
409 3
        foreach ($this as $node) {
410 3
            $node->nodeValue = '';
411 3
            foreach ($content as $newnode) {
412
                /** @var \DOMNode $node */
413
                /** @var \DOMNode $newnode */
414 3
                $newnode = static::importNewnode($newnode, $node);
415 3
                $node->appendChild($newnode);
416
            }
417
        }
418 3
        return $this;
419
    }
420
421
    /**
422
     * Insert every element in the set of matched elements after the target.
423
     *
424
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
425
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
426
     * @api
427
     */
428 2
    public function insertAfter($element)
429
    {
430 2
        $e = self::create($element);
431 2
        $newnodes = array();
432 2
        foreach ($e as $i => $node) {
433
            /** @var \DOMNode $node */
434 2
            $refnode = $node->nextSibling;
435 2
            foreach ($this as $newnode) {
436
                /** @var \DOMNode $newnode */
437 2
                $newnode = static::importNewnode($newnode, $node, $i);
438 2
                if ($refnode === null) {
439 2
                    $node->parentNode->appendChild($newnode);
440
                } else {
441 1
                    $node->parentNode->insertBefore($newnode, $refnode);
442
                }
443 2
                $newnodes[] = $newnode;
444
            }
445
        }
446 2
        return self::create($newnodes);
447
    }
448
449
    /**
450
     * Insert every element in the set of matched elements before the target.
451
     *
452
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
453
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
454
     * @api
455
     */
456 2
    public function insertBefore($element)
457
    {
458 2
        $e = self::create($element);
459 2
        $newnodes = array();
460 2
        foreach ($e as $i => $node) {
461
            /** @var \DOMNode $node */
462 2
            foreach ($this as $newnode) {
463
                /** @var \DOMNode $newnode */
464 2
                $newnode = static::importNewnode($newnode, $node, $i);
465 2
                if ($newnode !== $node) {
466 2
                    $node->parentNode->insertBefore($newnode, $node);
467
                }
468 2
                $newnodes[] = $newnode;
469
            }
470
        }
471 2
        return self::create($newnodes);
472
    }
473
474
    /**
475
     * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
476
     *
477
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment
478
     * @return HtmlPageCrawler $this for chaining
479
     * @api
480
     */
481 2
    public function prepend($content)
482
    {
483 2
        $content = self::create($content);
484 2
        $newnodes = array();
485 2
        foreach ($this as $i => $node) {
486 2
            $refnode = $node->firstChild;
487
            /** @var \DOMNode $node */
488 2
            foreach ($content as $newnode) {
489
                /** @var \DOMNode $newnode */
490 2
                $newnode = static::importNewnode($newnode, $node, $i);
491 2
                if ($refnode === null) {
492 1
                    $node->appendChild($newnode);
493 2
                } else if ($refnode !== $newnode) {
494 2
                    $node->insertBefore($newnode, $refnode);
495
                }
496 2
                $newnodes[] = $newnode;
497
            }
498
        }
499 2
        $content->clear();
500 2
        $content->add($newnodes);
501 2
        return $this;
502
    }
503
504
    /**
505
     * Insert every element in the set of matched elements to the beginning of the target.
506
     *
507
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
508
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements prepended to the target elements
509
     * @api
510
     */
511 1
    public function prependTo($element)
512
    {
513 1
        $e = self::create($element);
514 1
        $newnodes = array();
515 1
        foreach ($e as $i => $node) {
516 1
            $refnode = $node->firstChild;
517
            /** @var \DOMNode $node */
518 1
            foreach ($this as $newnode) {
519
                /** @var \DOMNode $newnode */
520 1
                $newnode = static::importNewnode($newnode, $node, $i);
521 1
                if ($newnode !== $node) {
522 1
                    if ($refnode === null) {
523 1
                        $node->appendChild($newnode);
524
                    } else {
525 1
                        $node->insertBefore($newnode, $refnode);
526
                    }
527
                }
528 1
                $newnodes[] = $newnode;
529
            }
530
        }
531 1
        return self::create($newnodes);
532
    }
533
534
    /**
535
     * Remove the set of matched elements from the DOM.
536
     *
537
     * (as opposed to Crawler::clear() which detaches the nodes only from Crawler
538
     * but leaves them in the DOM)
539
     *
540
     * @api
541
     */
542 2
    public function remove()
543
    {
544 2
        foreach ($this as $node) {
545
            /**
546
             * @var \DOMNode $node
547
             */
548 2
            if ($node->parentNode instanceof \DOMElement) {
549 2
                $node->parentNode->removeChild($node);
550
            }
551
        }
552 2
        $this->clear();
553 2
    }
554
555
    /**
556
     * Remove an attribute from each element in the set of matched elements.
557
     *
558
     * Alias for removeAttribute for compatibility with jQuery
559
     *
560
     * @param string $name
561
     * @return HtmlPageCrawler
562
     * @api
563
     */
564 1
    public function removeAttr($name)
565
    {
566 1
        return $this->removeAttribute($name);
567
    }
568
569
    /**
570
     * Remove an attribute from each element in the set of matched elements.
571
     *
572
     * @param string $name
573
     * @return HtmlPageCrawler
574
     */
575 1
    public function removeAttribute($name)
576
    {
577 1
        foreach ($this as $node) {
578 1
            if ($node instanceof \DOMElement) {
579
                /** @var \DOMElement $node */
580 1
                if ($node->hasAttribute($name)) {
581 1
                    $node->removeAttribute($name);
582
                }
583
            }
584
        }
585 1
        return $this;
586
    }
587
588
    /**
589
     * Remove a class from each element in the list
590
     *
591
     * @param string $name
592
     * @return HtmlPageCrawler $this for chaining
593
     * @api
594
     */
595 2
    public function removeClass($name)
596
    {
597 2
        foreach ($this as $node) {
598 2
            if ($node instanceof \DOMElement) {
599
                /** @var \DOMElement $node */
600 2
                $classes = preg_split('/\s+/s', $node->getAttribute('class'));
601 2
                $count = count($classes);
602 2
                for ($i = 0; $i < $count; $i++) {
603 2
                    if ($classes[$i] == $name) {
604 2
                        unset($classes[$i]);
605
                    }
606
                }
607 2
                $node->setAttribute('class', trim(join(' ', $classes)));
608
            }
609
        }
610 2
        return $this;
611
    }
612
613
    /**
614
     * Replace each target element with the set of matched elements.
615
     *
616
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
617
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
618
     * @api
619
     */
620 2
    public function replaceAll($element)
621
    {
622 2
        $e = self::create($element);
623 2
        $newnodes = array();
624 2
        foreach ($e as $i => $node) {
625
            /** @var \DOMNode $node */
626 2
            $parent = $node->parentNode;
627 2
            $refnode  = $node->nextSibling;
628 2
            foreach ($this as $j => $newnode) {
629
                /** @var \DOMNode $newnode */
630 2
                $newnode = static::importNewnode($newnode, $node, $i);
631 2
                if ($j == 0) {
632 2
                    $parent->replaceChild($newnode, $node);
633
                } else {
634 1
                    $parent->insertBefore($newnode, $refnode);
635
                }
636 2
                $newnodes[] = $newnode;
637
            }
638
        }
639 2
        return self::create($newnodes);
640
    }
641
642
    /**
643
     * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.
644
     *
645
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
646
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
647
     * @api
648
     */
649 2
    public function replaceWith($content)
650
    {
651 2
        $content = self::create($content);
652 2
        $newnodes = array();
653 2
        foreach ($this as $i => $node) {
654
            /** @var \DOMNode $node */
655 2
            $parent = $node->parentNode;
656 2
            $refnode  = $node->nextSibling;
657 2
            foreach ($content as $j => $newnode) {
658
                /** @var \DOMNode $newnode */
659 2
                $newnode = static::importNewnode($newnode, $node, $i);
660 2
                if ($j == 0) {
661 2
                    $parent->replaceChild($newnode, $node);
662
                } else {
663 1
                    $parent->insertBefore($newnode, $refnode);
664
                }
665 2
                $newnodes[] = $newnode;
666
            }
667
        }
668 2
        $content->clear();
669 2
        $content->add($newnodes);
670 2
        return $this;
671
    }
672
673
    /**
674
     * Get the combined text contents of each element in the set of matched elements, including their descendants,
675
     * or set the text contents of the matched elements.
676
     *
677
     * ATTENTION: Contrary to the parent Crawler class, which returns the text from the first element only,
678
     * this functions returns the combined text of all elements (as jQuery does). If this is not what you need you
679
     * must call ->first() before calling ->text(), e.g.
680
     *
681
     * in Symfony\DOMCrawler\Crawler: $c->filter('p')->text() returns the text of the first paragraph only
682
     * in HtmlPageCrawler you need to call: $c->filter('p')->first()->text()
683
     *
684
     * DEPRECATION WARNING:
685
     * This function will be removed from here in 2.0, so calling text() then will call the parent implementation, i.e.
686
     * it will return the text from the first node only, and an argument passed is treated as default value for the
687
     * getter function from Symfony 4.3 onwards. It will not be possible to use this function as setter.
688
     *
689
     * Use getCombinedText() for a getter with the old behavior, and use setText() for setting text content.
690
     *
691
     * @see setText()
692
     * @see getCombinedText()
693
     *
694
     * @param null|string $text
695
     * @return string|HtmlPageCrawler
696
     * @api
697
     */
698 2
    public function text($text = null)
699
    {
700 2
        if ($text === null) {
701 2
            @trigger_error('In Version 2.0, Method text() will return the text from only the first element in the set. Consider using getCombinedText() instead.', E_USER_NOTICE);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
702 2
            return $this->getCombinedText();
703
        } else {
704 1
            @trigger_error('It will not be possible any more to use method text($text) as setter function in version 2.0. Use setText($text) instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
705 1
            return $this->setText($text);
706
        }
707
    }
708
709
    /**
710
     * Get the combined text contents of each element in the set of matched elements, including their descendants.
711
     * This is what the jQuery text() function does, contrary to the Crawler::text() method that returns only
712
     * the text of the first node.
713
     *
714
     * @return string
715
     * @api
716
     * @since 1.4
717
     */
718 2
    public function getCombinedText()
719
    {
720 2
        $text = '';
721 2
        foreach ($this as $node) {
722
            /** @var \DOMNode $node */
723 2
            $text .= $node->nodeValue;
724
        }
725 2
        return $text;
726
    }
727
    /**
728
     * Set the text contents of the matched elements.
729
     *
730
     * @param string $text
731
     * @return HtmlPageCrawler
732
     * @api
733
     * @since 1.4
734
     */
735 2
    public function setText($text)
736
    {
737 2
        $text = htmlspecialchars($text);
738 2
        foreach ($this as $node) {
739
            /** @var \DOMNode $node */
740 2
            $node->nodeValue = $text;
741
        }
742 2
        return $this;
743
    }
744
745
746
    /**
747
     * Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.
748
     *
749
     * @param string $classname One or more classnames separated by spaces
750
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
751
     * @api
752
     */
753 1
    public function toggleClass($classname)
754
    {
755 1
        $classes = explode(' ', $classname);
756 1
        foreach ($this as $i => $node) {
757 1
            $c = self::create($node);
758
            /** @var \DOMNode $node */
759 1
            foreach ($classes as $class) {
760 1
                if ($c->hasClass($class)) {
761 1
                    $c->removeClass($class);
762
                } else {
763 1
                    $c->addClass($class);
764
                }
765
            }
766
        }
767 1
        return $this;
768
    }
769
770
    /**
771
     * Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
772
     *
773
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
774
     * @api
775
     */
776 1
    public function unwrap()
777
    {
778 1
        $parents = array();
779 1
        foreach($this as $i => $node) {
780 1
            $parents[] = $node->parentNode;
781
        }
782
783 1
        self::create($parents)->unwrapInner();
784 1
        return $this;
785
    }
786
787
    /**
788
     * Remove the matched elements, but promote the children to take their place.
789
     *
790
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
791
     * @api
792
     */
793 2
    public function unwrapInner()
794
    {
795 2
        foreach($this as $i => $node) {
796 2
            if (!$node->parentNode instanceof \DOMElement) {
797 1
                throw new \InvalidArgumentException('DOMElement does not have a parent DOMElement node.');
798
            }
799
800
            /** @var \DOMNode[] $children */
801 2
            $children = iterator_to_array($node->childNodes);
802 2
            foreach ($children as $child) {
803 1
                $node->parentNode->insertBefore($child, $node);
804
            }
805
806 2
            $node->parentNode->removeChild($node);
807
        }
808 2
    }
809
810
811
    /**
812
     * Wrap an HTML structure around each element in the set of matched elements
813
     *
814
     * The HTML structure must contain only one root node, e.g.:
815
     * Works: <div><div></div></div>
816
     * Does not work: <div></div><div></div>
817
     *
818
     * @param string|HtmlPageCrawler|\DOMNode $wrappingElement
819
     * @return HtmlPageCrawler $this for chaining
820
     * @api
821
     */
822 1
    public function wrap($wrappingElement)
823
    {
824 1
        $content = self::create($wrappingElement);
825 1
        $newnodes = array();
826 1
        foreach ($this as $i => $node) {
827
            /** @var \DOMNode $node */
828 1
            $newnode = $content->getNode(0);
829
            /** @var \DOMNode $newnode */
830
//            $newnode = static::importNewnode($newnode, $node, $i);
831 1
            if ($newnode->ownerDocument !== $node->ownerDocument) {
832 1
                $newnode = $node->ownerDocument->importNode($newnode, true);
833
            } else {
834
                if ($i > 0) {
835
                    $newnode = $newnode->cloneNode(true);
836
                }
837
            }
838 1
            $oldnode = $node->parentNode->replaceChild($newnode, $node);
839 1
            while ($newnode->hasChildNodes()) {
840 1
                $elementFound = false;
841 1
                foreach ($newnode->childNodes as $child) {
842 1
                    if ($child instanceof \DOMElement) {
843 1
                        $newnode = $child;
844 1
                        $elementFound = true;
845 1
                        break;
846
                    }
847
                }
848 1
                if (!$elementFound) {
849 1
                    break;
850
                }
851
            }
852 1
            $newnode->appendChild($oldnode);
853 1
            $newnodes[] = $newnode;
854
        }
855 1
        $content->clear();
856 1
        $content->add($newnodes);
857 1
        return $this;
858
    }
859
860
    /**
861
     * Wrap an HTML structure around all elements in the set of matched elements.
862
     *
863
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
864
     * @throws \LogicException
865
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
866
     * @api
867
     */
868 1
    public function wrapAll($content)
869
    {
870 1
        $content = self::create($content);
871 1
        $parent = $this->getNode(0)->parentNode;
872 1
        foreach ($this as $i => $node) {
873
            /** @var \DOMNode $node */
874 1
            if ($node->parentNode !== $parent) {
875
                throw new \LogicException('Nodes to be wrapped with wrapAll() must all have the same parent');
876
            }
877
        }
878
879 1
        $newnode = $content->getNode(0);
880
        /** @var \DOMNode $newnode */
881 1
        $newnode = static::importNewnode($newnode, $parent);
882
883 1
        $newnode = $parent->insertBefore($newnode,$this->getNode(0));
884 1
        $content->clear();
885 1
        $content->add($newnode);
886
887 1
        while ($newnode->hasChildNodes()) {
888 1
            $elementFound = false;
889 1
            foreach ($newnode->childNodes as $child) {
890 1
                if ($child instanceof \DOMElement) {
891 1
                    $newnode = $child;
892 1
                    $elementFound = true;
893 1
                    break;
894
                }
895
            }
896 1
            if (!$elementFound) {
897
                break;
898
            }
899
        }
900 1
        foreach ($this as $i => $node) {
901
            /** @var \DOMNode $node */
902 1
            $newnode->appendChild($node);
903
        }
904 1
        return $this;
905
    }
906
907
    /**
908
     * Wrap an HTML structure around the content of each element in the set of matched elements.
909
     *
910
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
911
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
912
     * @api
913
     */
914 1
    public function wrapInner($content)
915
    {
916 1
        foreach ($this as $i => $node) {
917
            /** @var \DOMNode $node */
918 1
            self::create($node->childNodes)->wrapAll($content);
919
        }
920 1
        return $this;
921
    }
922
923
    /**
924
     * Get the HTML code fragment of all elements and their contents.
925
     *
926
     * If the first node contains a complete HTML document return only
927
     * the full code of this document.
928
     *
929
     * @return string HTML code (fragment)
930
     * @api
931
     */
932 8
    public function saveHTML()
933
    {
934 8
        if ($this->isHtmlDocument()) {
935 1
            return $this->getDOMDocument()->saveHTML();
936
        } else {
937 8
            $doc = new \DOMDocument('1.0', 'UTF-8');
938 8
            $root = $doc->appendChild($doc->createElement('_root'));
939 8
            foreach ($this as $node) {
940 8
                $root->appendChild($doc->importNode($node, true));
941
            }
942 8
            $html = trim($doc->saveHTML());
943 8
            return preg_replace('@^<'.self::FRAGMENT_ROOT_TAGNAME.'[^>]*>|</'.self::FRAGMENT_ROOT_TAGNAME.'>$@', '', $html);
944
        }
945
    }
946
947 4
    public function __toString()
948
    {
949 4
        return $this->saveHTML();
950
    }
951
952
    /**
953
     * checks whether the first node contains a complete html document
954
     * (as opposed to a document fragment)
955
     *
956
     * @return boolean
957
     */
958 8
    public function isHtmlDocument()
959
    {
960 8
        $node = $this->getNode(0);
961 8
        if ($node instanceof \DOMElement
962 8
            && $node->ownerDocument instanceof \DOMDocument
963 8
            && $node->ownerDocument->documentElement === $node
964 8
            && $node->nodeName == 'html'
965
        ) {
966 1
            return true;
967
        } else {
968 8
            return false;
969
        }
970
    }
971
972
    /**
973
     * get ownerDocument of the first element
974
     *
975
     * @return \DOMDocument|null
976
     */
977 1
    public function getDOMDocument()
978
    {
979 1
        $node = $this->getNode(0);
980 1
        $r = null;
981 1
        if ($node instanceof \DOMElement
982 1
            && $node->ownerDocument instanceof \DOMDocument
983
        ) {
984 1
            $r = $node->ownerDocument;
985
        }
986 1
        return $r;
987
    }
988
989
    /**
990
     * Filters the list of nodes with a CSS selector.
991
     *
992
     * @param string $selector
993
     * @return HtmlPageCrawler
994
     */
995 8
    public function filter($selector)
996
    {
997 8
        return parent::filter($selector);
998
    }
999
1000
    /**
1001
     * Filters the list of nodes with an XPath expression.
1002
     *
1003
     * @param string $xpath An XPath expression
1004
     *
1005
     * @return HtmlPageCrawler A new instance of Crawler with the filtered list of nodes
1006
     *
1007
     * @api
1008
     */
1009 2
    public function filterXPath($xpath)
1010
    {
1011 2
        return parent::filterXPath($xpath);
1012
    }
1013
1014
    /**
1015
     * Adds HTML/XML content to the HtmlPageCrawler object (but not to the DOM of an already attached node).
1016
     *
1017
     * Function overriden from Crawler because HTML fragments are always added as complete documents there
1018
     *
1019
     *
1020
     * @param string      $content A string to parse as HTML/XML
1021
     * @param null|string $type    The content type of the string
1022
     *
1023
     * @return null|void
1024
     */
1025 18
    public function addContent($content, $type = null)
1026
    {
1027 18
        if (empty($type)) {
1028 18
            $type = 'text/html;charset=UTF-8';
1029
        }
1030 18
        if (substr($type, 0, 9) == 'text/html' && !preg_match('/<html\b[^>]*>/i', $content)) {
1031
            // string contains no <html> Tag => no complete document but an HTML fragment!
1032 17
            $this->addHtmlFragment($content);
1033
        } else {
1034 2
            parent::addContent($content, $type);
1035
        }
1036 18
    }
1037
1038 16
    public function addHtmlFragment($content, $charset = 'UTF-8')
1039
    {
1040 16
        $d = new \DOMDocument('1.0', $charset);
1041 16
        $root = $d->appendChild($d->createElement(self::FRAGMENT_ROOT_TAGNAME));
1042 16
        $bodynode = Helpers::getBodyNodeFromHtmlFragment($content, $charset);
1043 16
        foreach ($bodynode->childNodes as $child) {
1044 16
            $inode = $root->appendChild($d->importNode($child, true));
1045 16
            if ($inode) {
1046 16
                $this->addNode($inode);
1047
            }
1048
        }
1049 16
    }
1050
1051
    /**
1052
     * returns the first node
1053
     * deprecated, use getNode(0) instead
1054
     *
1055
     * @return \DOMNode|null
1056
     * @deprecated
1057
     * @see Crawler::getNode
1058
     */
1059 1
    public function getFirstNode()
1060
    {
1061 1
        return $this->getNode(0);
1062
    }
1063
1064
    /**
1065
     * @param int $position
1066
     *
1067
     * overridden from Crawler because it is not public in Symfony 2.3
1068
     * TODO: throw away as soon as we don't need to support SF 2.3 any more
1069
     *
1070
     * @return \DOMElement|null
1071
     */
1072 14
    public function getNode($position)
1073
    {
1074 14
        return parent::getNode($position);
1075
    }
1076
1077
    /**
1078
     * Returns the node name of the first node of the list.
1079
     *
1080
     * in Crawler (parent), this function will be available starting with 2.6.0,
1081
     * therefore this method be removed from here as soon as we don't need to keep compatibility
1082
     * with Symfony < 2.6
1083
     *
1084
     * TODO: throw away as soon as we don't need to support SF 2.3 any more
1085
     *
1086
     * @return string The node name
1087
     *
1088
     * @throws \InvalidArgumentException When current node is empty
1089
     */
1090 1
    public function nodeName()
1091
    {
1092 1
        if (!count($this)) {
1093
            throw new \InvalidArgumentException('The current node list is empty.');
1094
        }
1095 1
        return $this->getNode(0)->nodeName;
1096
    }
1097
1098
    /**
1099
     * Adds a node to the current list of nodes.
1100
     *
1101
     * This method uses the appropriate specialized add*() method based
1102
     * on the type of the argument.
1103
     *
1104
     * Overwritten from parent to allow Crawler to be added
1105
     *
1106
     * @param null|\DOMNodeList|array|\DOMNode|Crawler $node A node
1107
     *
1108
     * @api
1109
     */
1110 30
    public function add($node)
1111
    {
1112 30
        if ($node instanceof Crawler) {
1113 1
            foreach ($node as $childnode) {
1114 1
                $this->addNode($childnode);
1115
            }
1116
        } else {
1117 30
            parent::add($node);
1118
        }
1119 30
    }
1120
1121
    /**
1122
     * @param \DOMNode $newnode
1123
     * @param \DOMNode $referencenode
1124
     * @param int $clone
1125
     * @return \DOMNode
1126
     */
1127 6
    protected static function importNewnode(\DOMNode $newnode, \DOMNode $referencenode, $clone = 0) {
1128 6
        if ($newnode->ownerDocument !== $referencenode->ownerDocument) {
1129 5
            $newnode = $referencenode->ownerDocument->importNode($newnode, true);
1130
        } else {
1131 2
            if ($clone > 0) {
1132
                $newnode = $newnode->cloneNode(true);
1133
            }
1134
        }
1135 6
        return $newnode;
1136
    }
1137
1138
    /**
1139
     * Checks whether the first node in the set is disconnected (has no parent node)
1140
     *
1141
     * @return bool
1142
     * @deprecated
1143
     */
1144 1
    public function isDisconnected()
1145
    {
1146 1
        @trigger_error('Method isDisconnected() is deprecated and will be removed in 2.0.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1147 1
        $parent = $this->getNode(0)->parentNode;
1148 1
        return ($parent == null || $parent->tagName == self::FRAGMENT_ROOT_TAGNAME);
1149
    }
1150
1151 1
    public function __get($name)
1152
    {
1153 1
        switch ($name) {
1154 1
            case 'count':
1155 1
            case 'length':
1156 1
                return count($this);
1157
        }
1158 1
        throw new \Exception('No such property ' . $name);
1159
    }
1160
}
1161