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
Pull Request — master (#19)
by
unknown
02:18
created

HtmlPageCrawler::makeEmpty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 7
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 6
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
0 ignored issues
show
Complexity introduced by
This class has 50 public methods and attributes which exceeds the configured maximum of 45.

The number of this metric differs depending on the chosen design (inheritance vs. composition). For inheritance, the number should generally be a bit lower.

A high number indicates a reusable class. It might also make the class harder to change without breaking other classes though.

Loading history...
Complexity introduced by
This class has 1099 lines of code which exceeds the configured maximum of 1000.

Really long classes often contain too much logic and violate the single responsibility principle.

We suggest to take a look at the “Code” section for options on how to refactor this code.

Loading history...
Complexity introduced by
This class has a complexity of 158 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
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 11
    public static function create($content)
32
    {
33 11
        if ($content instanceof HtmlPageCrawler) {
34 3
            return $content;
35
        } else {
36 11
            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
                        $found = true;
58
                    }
59 1
                }
60 1
                if (!$found) {
61 1
                    $classes[] = $name;
62 1
                    $node->setAttribute('class', trim(join(' ', $classes)));
63 1
                }
64 1
            }
65 1
        }
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 3
                } else {
89
                    $node->parentNode->insertBefore($newnode, $refnode);
90
                }
91 3
                $newnodes[] = $newnode;
92 3
            }
93 3
        }
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 2
            }
125 2
        }
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 1
    public function appendTo($element)
139
    {
140 1
        $e = self::create($element);
141 1
        $newnodes = array();
142 1
        foreach ($e as $i => $node) {
143
            /** @var \DOMNode $node */
144 1
            foreach ($this as $newnode) {
145
                /** @var \DOMNode $newnode */
146 1
                $newnode = static::importNewnode($newnode, $node, $i);
147 1
                $node->appendChild($newnode);
148 1
                $newnodes[] = $newnode;
149 1
            }
150 1
        }
151 1
        return self::create($newnodes);
152
    }
153
154
    /**
155
     * Returns the attribute value of the first node of the list, or sets an attribute on each element
156
     *
157
     * @see HtmlPageCrawler::getAttribute()
158
     * @see HtmlPageCrawler::setAttribute
159
     *
160
     * @param string $name
161
     * @param null|string $value
162
     * @return null|string|HtmlPageCrawler
163
     * @api
164
     */
165 1
    public function attr($name, $value = null)
166
    {
167 1
        if ($value === null) {
168 1
            return $this->getAttribute($name);
169
        } else {
170 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...
171
        }
172
    }
173
174
    /**
175
     * Sets an attribute on each element
176
     *
177
     * @param string $name
178
     * @param string $value
179
     * @return HtmlPageCrawler $this for chaining
180
     */
181 2
    public function setAttribute($name, $value)
182
    {
183 2
        foreach ($this as $node) {
184 2
            if ($node instanceof \DOMElement) {
185
                /** @var \DOMElement $node */
186 2
                $node->setAttribute($name, $value);
187 2
            }
188 2
        }
189 2
        return $this;
190
    }
191
192
    /**
193
     * Returns the attribute value of the first node of the list.
194
     *
195
     * @param string $name The attribute name
196
     * @return string|null The attribute value or null if the attribute does not exist
197
     * @throws \InvalidArgumentException When current node is empty
198
     *
199
     */
200 1
    public function getAttribute($name)
201
    {
202 1
        if (!count($this)) {
203
            throw new \InvalidArgumentException('The current node list is empty.');
204
        }
205 1
        $node = $this->getNode(0);
206 1
        return $node->hasAttribute($name) ? $node->getAttribute($name) : null;
207
    }
208
209
    /**
210
     * Insert content, specified by the parameter, before each element in the set of matched elements.
211
     *
212
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
213
     * @return HtmlPageCrawler $this for chaining
214
     * @api
215
     */
216 2
    public function before($content)
217
    {
218 2
        $content = self::create($content);
219 2
        $newnodes = array();
220 2
        foreach ($this as $i => $node) {
221
            /** @var \DOMNode $node */
222 2
            foreach ($content as $newnode) {
223
                /** @var \DOMNode $newnode */
224 2
                $newnode = static::importNewnode($newnode, $node, $i);
225 2
                $node->parentNode->insertBefore($newnode, $node);
226 2
                $newnodes[] = $newnode;
227 2
            }
228 2
        }
229 2
        $content->clear();
230 2
        $content->add($newnodes);
231 2
        return $this;
232
    }
233
234
    /**
235
     * Create a deep copy of the set of matched elements.
236
     *
237
     * Equivalent to clone() in jQuery (clone is not a valid PHP function name)
238
     *
239
     * @return HtmlPageCrawler
240
     * @api
241
     */
242 1
    public function makeClone()
243
    {
244 1
        return clone $this;
245
    }
246
247 1
    public function __clone()
248
    {
249 1
        $newnodes = array();
250 1
        foreach ($this as $node) {
251
            /** @var \DOMNode $node */
252 1
            $newnodes[] = $node->cloneNode(true);
253 1
        }
254 1
        $this->clear();
255 1
        $this->add($newnodes);
256 1
    }
257
258
    /**
259
     * Get one CSS style property of the first element or set it for all elements in the list
260
     *
261
     * Function is here for compatibility with jQuery; it is the same as getStyle() and setStyle()
262
     *
263
     * @see HtmlPageCrawler::getStyle()
264
     * @see HtmlPageCrawler::setStyle()
265
     *
266
     * @param string $key The name of the style property
267
     * @param null|string $value The CSS value to set, or NULL to get the current value
268
     * @return HtmlPageCrawler|string If no param is provided, returns the CSS styles of the first element
269
     * @api
270
     */
271 1
    public function css($key, $value = null)
272
    {
273 1
        if (null === $value) {
274 1
            return $this->getStyle($key);
275
        } else {
276 1
            return $this->setStyle($key, $value);
277
        }
278
    }
279
280
    /**
281
     * get one CSS style property of the first element
282
     *
283
     * @param string $key name of the property
284
     * @return string|null value of the property
285
     */
286 1
    public function getStyle($key)
287
    {
288 1
        $styles = Helpers::cssStringToArray($this->getAttribute('style'));
289 1
        return (isset($styles[$key]) ? $styles[$key] : null);
290
    }
291
292
    /**
293
     * set one CSS style property for all elements in the list
294
     *
295
     * @param string $key name of the property
296
     * @param string $value value of the property
297
     * @return HtmlPageCrawler $this for chaining
298
     */
299 1
    public function setStyle($key, $value)
300
    {
301 1
        foreach ($this as $node) {
302 1
            if ($node instanceof \DOMElement) {
303
                /** @var \DOMElement $node */
304 1
                $styles = Helpers::cssStringToArray($node->getAttribute('style'));
305 1
                if ($value != '') {
306 1
                    $styles[$key] = $value;
307 1
                } elseif (isset($styles[$key])) {
308 1
                    unset($styles[$key]);
309 1
                }
310 1
                $node->setAttribute('style', Helpers::cssArrayToString($styles));
311 1
            }
312 1
        }
313 1
        return $this;
314
    }
315
316
    /**
317
     * Removes all child nodes and text from all nodes in set
318
     *
319
     * Equivalent to jQuery's empty() function which is not a valid function name in PHP
320
     * @return HtmlPageCrawler $this
321
     * @api
322
     */
323
    public function makeEmpty()
324
    {
325
        foreach ($this as $node) {
326
            $node->nodeValue = '';
327
        }
328
        return $this;
329
    }
330
331
    /**
332
     * Determine whether any of the matched elements are assigned the given class.
333
     *
334
     * @param string $name
335
     * @return bool
336
     * @api
337
     */
338 2
    public function hasClass($name)
339
    {
340 2
        foreach ($this as $node) {
341 2
            if ($node instanceof \DOMElement && $class = $node->getAttribute('class')) {
342 2
                $classes = preg_split('/\s+/s', $class);
343 2
                if (in_array($name, $classes)) {
344 2
                    return true;
345
                }
346 1
            }
347 2
        }
348 2
        return false;
349
    }
350
351
    /**
352
     * Get the HTML contents of the first element in the set of matched elements
353
     * or set the HTML contents of every matched element.
354
     *
355
     * Function is here for compatibility with jQuery: When called with a parameter, it is
356
     * equivalent to setInnerHtml(), without parameter it is the same as getInnerHtml()
357
     *
358
     * @see HtmlPageCrawler::setInnerHtml()
359
     * @see HtmlPageCrawler::getInnerHtml()
360
     *
361
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList|null $html The HTML content to set, or NULL to get the current content
362
     *
363
     * @return HtmlPageCrawler|string If no param is provided, returns the HTML content of the first element
364
     * @api
365
     */
366
    public function html($html = null)
367
    {
368
        if (null === $html) {
369
            return $this->getInnerHtml();
370
        } else {
371
            $this->setInnerHtml($html);
372
            return $this;
373
        }
374
    }
375
376
    /**
377
     * Get the innerHTML contents of the first element
378
     *
379
     * @return string HTML code fragment
380
     */
381 2
    public function getInnerHtml()
382
    {
383 2
        $node = $this->getNode(0);
384 2
        if ($node instanceof \DOMNode) {
385 2
            $doc = new \DOMDocument('1.0', 'UTF-8');
386 2
            $doc->loadHTML('<meta http-equiv="Content-Type" content="text/html;charset=utf-8">');
387 2
            $doc->appendChild($doc->importNode($node, true));
388 2
            $html = trim($doc->saveHTML());
389 2
            $tag = $node->nodeName;
390 2
            return preg_replace('@^.*<' . $tag . '[^>]*>|</' . $tag . '>$@s', '', $html);
391
        } else {
392
            return '';
393
        }
394
    }
395
396
    /**
397
     * Set the HTML contents of each element
398
     *
399
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment
400
     * @return HtmlPageCrawler $this for chaining
401
     */
402 2
    public function setInnerHtml($content)
403
    {
404 2
        $content = self::create($content);
405 2
        foreach ($this as $node) {
406 2
            $node->nodeValue = '';
407 2
            foreach ($content as $newnode) {
408
                /** @var \DOMNode $node */
409
                /** @var \DOMNode $newnode */
410 2
                $newnode = static::importNewnode($newnode, $node);
411 2
                $node->appendChild($newnode);
412 2
            }
413 2
        }
414 2
        return $this;
415
    }
416
417
    /**
418
     * Insert every element in the set of matched elements after the target.
419
     *
420
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
421
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
422
     * @api
423
     */
424 1
    public function insertAfter($element)
425
    {
426 1
        $e = self::create($element);
427 1
        $newnodes = array();
428 1
        foreach ($e as $i => $node) {
429
            /** @var \DOMNode $node */
430 1
            $refnode = $node->nextSibling;
431 1
            foreach ($this as $newnode) {
432
                /** @var \DOMNode $newnode */
433 1
                $newnode = static::importNewnode($newnode, $node, $i);
434 1
                if ($refnode === null) {
435 1
                    $node->parentNode->appendChild($newnode);
436 1
                } else {
437 1
                    $node->parentNode->insertBefore($newnode, $refnode);
438
                }
439 1
                $newnodes[] = $newnode;
440 1
            }
441 1
        }
442 1
        return self::create($newnodes);
443
    }
444
445
    /**
446
     * Insert every element in the set of matched elements before the target.
447
     *
448
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
449
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
450
     * @api
451
     */
452 1
    public function insertBefore($element)
453
    {
454 1
        $e = self::create($element);
455 1
        $newnodes = array();
456 1
        foreach ($e as $i => $node) {
457
            /** @var \DOMNode $node */
458 1
            foreach ($this as $newnode) {
459
                /** @var \DOMNode $newnode */
460 1
                $newnode = static::importNewnode($newnode, $node, $i);
461 1
                $node->parentNode->insertBefore($newnode, $node);
462 1
                $newnodes[] = $newnode;
463 1
            }
464 1
        }
465 1
        return self::create($newnodes);
466
    }
467
468
    /**
469
     * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.
470
     *
471
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content HTML code fragment
472
     * @return HtmlPageCrawler $this for chaining
473
     * @api
474
     */
475 2
    public function prepend($content)
476
    {
477 2
        $content = self::create($content);
478 2
        $newnodes = array();
479 2
        foreach ($this as $i => $node) {
480 2
            $refnode = $node->firstChild;
481
            /** @var \DOMNode $node */
482 2
            foreach ($content as $newnode) {
483
                /** @var \DOMNode $newnode */
484 2
                $newnode = static::importNewnode($newnode, $node, $i);
485 2
                if ($refnode === null) {
486
                    $node->appendChild($newnode);
487
                } else {
488 2
                    $node->insertBefore($newnode, $refnode);
489
                }
490 2
                $newnodes[] = $newnode;
491 2
            }
492 2
        }
493 2
        $content->clear();
494 2
        $content->add($newnodes);
495 2
        return $this;
496
    }
497
498
    /**
499
     * Insert every element in the set of matched elements to the beginning of the target.
500
     *
501
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
502
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements prepended to the target elements
503
     * @api
504
     */
505
    public function prependTo($element)
506
    {
507
        $e = self::create($element);
508
        $newnodes = array();
509
        foreach ($e as $i => $node) {
510
            $refnode = $node->firstChild;
511
            /** @var \DOMNode $node */
512
            foreach ($this as $newnode) {
513
                /** @var \DOMNode $newnode */
514
                $newnode = static::importNewnode($newnode, $node, $i);
515
                if ($refnode === null) {
516
                    $node->appendChild($newnode);
517
                } else {
518
                    $node->insertBefore($newnode, $refnode);
519
                }
520
                $newnodes[] = $newnode;
521
            }
522
        }
523
        return self::create($newnodes);
524
    }
525
526
    /**
527
     * Remove the set of matched elements from the DOM.
528
     *
529
     * (as opposed to Crawler::clear() which detaches the nodes only from Crawler
530
     * but leaves them in the DOM)
531
     *
532
     * @api
533
     */
534 2
    public function remove()
535
    {
536 2
        foreach ($this as $node) {
537
            /**
538
             * @var \DOMNode $node
539
             */
540 2
            if ($node->parentNode instanceof \DOMElement) {
541 2
                $node->parentNode->removeChild($node);
542 2
            }
543 2
        }
544 2
        $this->clear();
545 2
    }
546
547
    /**
548
     * Remove an attribute from each element in the set of matched elements.
549
     *
550
     * Alias for removeAttribute for compatibility with jQuery
551
     *
552
     * @param string $name
553
     * @return HtmlPageCrawler
554
     * @api
555
     */
556 1
    public function removeAttr($name)
557
    {
558 1
        return $this->removeAttribute($name);
559
    }
560
561
    /**
562
     * Remove an attribute from each element in the set of matched elements.
563
     *
564
     * @param string $name
565
     * @return HtmlPageCrawler
566
     */
567 1
    public function removeAttribute($name)
568
    {
569 1
        foreach ($this as $node) {
570 1
            if ($node instanceof \DOMElement) {
571
                /** @var \DOMElement $node */
572 1
                if ($node->hasAttribute($name)) {
573 1
                    $node->removeAttribute($name);
574 1
                }
575 1
            }
576 1
        }
577 1
        return $this;
578
    }
579
580
    /**
581
     * Remove a class from each element in the list
582
     *
583
     * @param string $name
584
     * @return HtmlPageCrawler $this for chaining
585
     * @api
586
     */
587 2
    public function removeClass($name)
588
    {
589 2
        foreach ($this as $node) {
590 2
            if ($node instanceof \DOMElement) {
591
                /** @var \DOMElement $node */
592 2
                $classes = preg_split('/\s+/s', $node->getAttribute('class'));
593 2
                $count = count($classes);
594 2
                for ($i = 0; $i < $count; $i++) {
595 2
                    if ($classes[$i] == $name) {
596 2
                        unset($classes[$i]);
597 2
                    }
598 2
                }
599 2
                $node->setAttribute('class', trim(join(' ', $classes)));
600 2
            }
601 2
        }
602 2
        return $this;
603
    }
604
605
    /**
606
     * Replace each target element with the set of matched elements.
607
     *
608
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $element
609
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler A new Crawler object containing all elements appended to the target elements
610
     * @api
611
     */
612 2
    public function replaceAll($element)
613
    {
614 2
        $e = self::create($element);
615 2
        $newnodes = array();
616 2
        foreach ($e as $i => $node) {
617
            /** @var \DOMNode $node */
618 2
            $parent = $node->parentNode;
619 2
            $refnode  = $node->nextSibling;
620 2
            foreach ($this as $j => $newnode) {
621
                /** @var \DOMNode $newnode */
622 2
                $newnode = static::importNewnode($newnode, $node, $i);
623 2
                if ($j == 0) {
624 2
                    $parent->replaceChild($newnode, $node);
625 2
                } else {
626 1
                    $parent->insertBefore($newnode, $refnode);
627
                }
628 2
                $newnodes[] = $newnode;
629 2
            }
630 2
        }
631 2
        return self::create($newnodes);
632
    }
633
634
    /**
635
     * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed.
636
     *
637
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
638
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
639
     * @api
640
     */
641 2
    public function replaceWith($content)
642
    {
643 2
        $content = self::create($content);
644 2
        $newnodes = array();
645 2
        foreach ($this as $i => $node) {
646
            /** @var \DOMNode $node */
647 2
            $parent = $node->parentNode;
648 2
            $refnode  = $node->nextSibling;
649 2
            foreach ($content as $j => $newnode) {
650
                /** @var \DOMNode $newnode */
651 2
                $newnode = static::importNewnode($newnode, $node, $i);
652 2
                if ($j == 0) {
653 2
                    $parent->replaceChild($newnode, $node);
654 2
                } else {
655 1
                    $parent->insertBefore($newnode, $refnode);
656
                }
657 2
                $newnodes[] = $newnode;
658 2
            }
659 2
        }
660 2
        $content->clear();
661 2
        $content->add($newnodes);
662 2
        return $this;
663
    }
664
665
    /**
666
     * Get the combined text contents of each element in the set of matched elements, including their descendants,
667
     * or set the text contents of the matched elements.
668
     *
669
     * ATTENTION: Contrary to the parent Crawler class, which returns the text from the first element only,
670
     * this functions returns the combined text of all elements (as jQuery does). If this is not what you need you
671
     * must call ->first() before calling ->text(), e.g.
672
     *
673
     * in Symfony\DOMCrawler\Crawler: $c->filter('p')->text() returns the text of the first paragraph only
674
     * in HtmlPageCrawler you need to call: $c->filter('p')->first()->text()
675
     *
676
     * @param null|string $text
677
     * @return string|HtmlPageCrawler
678
     * @api
679
     */
680 1
    public function text($text = null)
681
    {
682 1
        if ($text === null) {
683 1
            $text = '';
684 1
            foreach ($this as $node) {
685
                /** @var \DOMNode $node */
686 1
                $text .= $node->nodeValue;
687 1
            }
688 1
            return $text;
689
        } else {
690 1
            foreach ($this as $node) {
691
                /** @var \DOMNode $node */
692 1
                $node->nodeValue = $text;
693 1
            }
694 1
            return $this;
695
        }
696
    }
697
698
699
    /**
700
     * Add or remove one or more classes from each element in the set of matched elements, depending the class’s presence.
701
     *
702
     * @param string $classname One or more classnames separated by spaces
703
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
704
     * @api
705
     */
706 1
    public function toggleClass($classname)
707
    {
708 1
        $classes = explode(' ', $classname);
709 1
        foreach ($this as $i => $node) {
710 1
            $c = self::create($node);
711
            /** @var \DOMNode $node */
712 1
            foreach ($classes as $class) {
713 1
                if ($c->hasClass($class)) {
714 1
                    $c->removeClass($class);
715 1
                } else {
716 1
                    $c->addClass($class);
717
                }
718 1
            }
719 1
        }
720 1
        return $this;
721
    }
722
723
    /**
724
     * Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
725
     *
726
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
727
     * @api
728
     */
729 1
    public function unwrap()
730
    {
731 1
        $parents = array();
732 1
        foreach($this as $i => $node) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FOREACH keyword; 0 found
Loading history...
733 1
            $parents[] = $node->parentNode;
734 1
        }
735
736 1
        self::create($parents)->unwrapInner();
737 1
        return $this;
738
    }
739
740
    /**
741
     * Remove the matched elements, but promote the children to take their place.
742
     *
743
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
744
     * @api
745
     */
746 1
    public function unwrapInner()
747
    {
748 1
        foreach($this as $i => $node) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FOREACH keyword; 0 found
Loading history...
749 1
            if (!$node->parentNode instanceof \DOMElement) {
750
                throw new \InvalidArgumentException('DOMElement does not have a parent DOMElement node.');
751
            }
752
753
            /** @var \DOMNode[] $children */
754 1
            $children = iterator_to_array($node->childNodes);
755 1
            foreach ($children as $child) {
756 1
                $node->parentNode->insertBefore($child, $node);
757 1
            }
758
759 1
            $node->parentNode->removeChild($node);
760 1
        }
761 1
    }
762
763
764
    /**
765
     * Wrap an HTML structure around each element in the set of matched elements
766
     *
767
     * The HTML structure must contain only one root node, e.g.:
768
     * Works: <div><div></div></div>
769
     * Does not work: <div></div><div></div>
770
     *
771
     * @param string|HtmlPageCrawler|\DOMNode $wrappingElement
772
     * @return HtmlPageCrawler $this for chaining
773
     * @api
774
     */
775 1
    public function wrap($wrappingElement)
776
    {
777 1
        $content = self::create($wrappingElement);
778 1
        $newnodes = array();
779 1
        foreach ($this as $i => $node) {
780
            /** @var \DOMNode $node */
781 1
            $newnode = $content->getNode(0);
782
            /** @var \DOMNode $newnode */
783
//            $newnode = static::importNewnode($newnode, $node, $i);
784 1
            if ($newnode->ownerDocument !== $node->ownerDocument) {
785 1
                $newnode = $node->ownerDocument->importNode($newnode, true);
786 1
            } else {
787
                if ($i > 0) {
788
                    $newnode = $newnode->cloneNode(true);
789
                }
790
            }
791 1
            $oldnode = $node->parentNode->replaceChild($newnode, $node);
792 1
            while ($newnode->hasChildNodes()) {
793 1
                $elementFound = false;
794 1
                foreach ($newnode->childNodes as $child) {
795 1
                    if ($child instanceof \DOMElement) {
796 1
                        $newnode = $child;
797 1
                        $elementFound = true;
798 1
                        break;
799
                    }
800 1
                }
801 1
                if (!$elementFound) {
802 1
                    break;
803
                }
804 1
            }
805 1
            $newnode->appendChild($oldnode);
806 1
            $newnodes[] = $newnode;
807 1
        }
808 1
        $content->clear();
809 1
        $content->add($newnodes);
810 1
        return $this;
811
    }
812
813
    /**
814
     * Wrap an HTML structure around all elements in the set of matched elements.
815
     *
816
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
817
     * @throws \LogicException
818
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
819
     * @api
820
     */
821 1
    public function wrapAll($content)
822
    {
823 1
        $content = self::create($content);
824 1
        $parent = $this->getNode(0)->parentNode;
825 1
        foreach ($this as $i => $node) {
826
            /** @var \DOMNode $node */
827 1
            if ($node->parentNode !== $parent) {
828
                throw new \LogicException('Nodes to be wrapped with wrapAll() must all have the same parent');
829
            }
830 1
        }
831
832 1
        $newnode = $content->getNode(0);
833
        /** @var \DOMNode $newnode */
834 1
        $newnode = static::importNewnode($newnode, $parent);
835
836 1
        $newnode = $parent->insertBefore($newnode,$this->getNode(0));
837 1
        $content->clear();
838 1
        $content->add($newnode);
839
840 1
        while ($newnode->hasChildNodes()) {
841 1
            $elementFound = false;
842 1
            foreach ($newnode->childNodes as $child) {
843 1
                if ($child instanceof \DOMElement) {
844 1
                    $newnode = $child;
845 1
                    $elementFound = true;
846 1
                    break;
847
                }
848 1
            }
849 1
            if (!$elementFound) {
850
                break;
851
            }
852 1
        }
853 1
        foreach ($this as $i => $node) {
854
            /** @var \DOMNode $node */
855 1
            $newnode->appendChild($node);
856 1
        }
857 1
        return $this;
858
    }
859
860
    /**
861
     * Wrap an HTML structure around the content of each element in the set of matched elements.
862
     *
863
     * @param string|HtmlPageCrawler|\DOMNode|\DOMNodeList $content
864
     * @return \Wa72\HtmlPageDom\HtmlPageCrawler $this for chaining
865
     * @api
866
     */
867 1
    public function wrapInner($content)
868
    {
869 1
        foreach ($this as $i => $node) {
870
            /** @var \DOMNode $node */
871 1
            self::create($node->childNodes)->wrapAll($content);
872 1
        }
873 1
        return $this;
874
    }
875
876
    /**
877
     * Get the HTML code fragment of all elements and their contents.
878
     *
879
     * If the first node contains a complete HTML document return only
880
     * the full code of this document.
881
     *
882
     * @return string HTML code (fragment)
883
     * @api
884
     */
885 3
    public function saveHTML()
886
    {
887
        /*  don't see any reason we should handle the complete HTML document seperately. */
888
        // if ($this->isHtmlDocument()) {
889
            // return $this->getDOMDocument()->saveHTML();
890
        // } else {
891 3
            $doc = new \DOMDocument('1.0', 'UTF-8');
892 3
            $doc->loadHTML('<meta http-equiv="Content-Type" content="text/html;charset=utf-8">');
893 3
            $root = $doc->appendChild($doc->createElement(self::FRAGMENT_ROOT_TAGNAME));
894 3
            foreach ($this as $node) {
895 3
                $root->appendChild($doc->importNode($node, true));
896 3
            }
897 3
            $html = trim($doc->saveHTML());
898 3
            return preg_replace('@^.*<'.self::FRAGMENT_ROOT_TAGNAME.'[^>]*>|</'.self::FRAGMENT_ROOT_TAGNAME.'>$@s', '', $html);
899
        // }
900
    }
901
902
    public function __toString()
903
    {
904
        return $this->saveHTML();
905
    }
906
907
    /**
908
     * checks whether the first node contains a complete html document
909
     * (as opposed to a document fragment)
910
     *
911
     * @return boolean
912
     */
913 1
    public function isHtmlDocument()
914
    {
915 1
        $node = $this->getNode(0);
916
        if ($node instanceof \DOMElement
917 1
            && $node->ownerDocument instanceof \DOMDocument
918 1
            && $node->ownerDocument->documentElement === $node
919 1
            && $node->nodeName == 'html'
920 1
        ) {
921 1
            return true;
922
        } else {
923 1
            return false;
924
        }
925
    }
926
927
    /**
928
     * get ownerDocument of the first element
929
     *
930
     * @return \DOMDocument|null
931
     */
932
    public function getDOMDocument()
933
    {
934
        $node = $this->getNode(0);
935
        $r = null;
936
        if ($node instanceof \DOMElement
937
            && $node->ownerDocument instanceof \DOMDocument
938
        ) {
939
            $r = $node->ownerDocument;
940
        }
941
        return $r;
942
    }
943
944
    /**
945
     * Filters the list of nodes with a CSS selector.
946
     *
947
     * @param string $selector
948
     * @return HtmlPageCrawler
949
     */
950 6
    public function filter($selector)
951
    {
952 6
        return parent::filter($selector);
953
    }
954
955
    /**
956
     * Filters the list of nodes with an XPath expression.
957
     *
958
     * @param string $xpath An XPath expression
959
     *
960
     * @return HtmlPageCrawler A new instance of Crawler with the filtered list of nodes
961
     *
962
     * @api
963
     */
964 1
    public function filterXPath($xpath)
965
    {
966 1
        return parent::filterXPath($xpath);
967
    }
968
969
    /**
970
     * Adds HTML/XML content to the HtmlPageCrawler object (but not to the DOM of an already attached node).
971
     *
972
     * Function overriden from Crawler because HTML fragments are always added as complete documents there
973
     *
974
     *
975
     * @param string      $content A string to parse as HTML/XML
976
     * @param null|string $type    The content type of the string
977
     *
978
     * @return null|void
979
     */
980 12
    public function addContent($content, $type = null)
981
    {
982 12
        if (empty($type)) {
983 12
            $type = 'text/html;charset=UTF-8';
984 12
        }
985 12
        if (substr($type, 0, 9) == 'text/html' && !preg_match('/<html\b[^>]*>/i', $content)) {
986
            // string contains no <html> Tag => no complete document but an HTML fragment!
987 10
            $this->addHtmlFragment($content);
988 10
        } else {
989 2
            parent::addContent($content, $type);
990
        }
991 12
    }
992
993 10
    public function addHtmlFragment($content, $charset = 'UTF-8')
994
    {
995 10
        $d = new \DOMDocument('1.0', $charset);
996 10
        $root = $d->appendChild($d->createElement(self::FRAGMENT_ROOT_TAGNAME));
997 10
        $bodynode = Helpers::getBodyNodeFromHtmlFragment($content, $charset);
998 10
        foreach ($bodynode->childNodes as $child) {
999 10
            $inode = $root->appendChild($d->importNode($child, true));
1000 10
            if ($inode) {
1001 10
                $this->addNode($inode);
1002 10
            }
1003 10
        }
1004 10
    }
1005
1006
    /**
1007
     * returns the first node
1008
     * deprecated, use getNode(0) instead
1009
     *
1010
     * @return \DOMNode|null
1011
     * @deprecated
1012
     * @see Crawler::getNode
1013
     */
1014
    public function getFirstNode()
1015
    {
1016
        return $this->getNode(0);
1017
    }
1018
1019
    /**
1020
     * @param int $position
1021
     *
1022
     * overridden from Crawler because it is not public in Symfony 2.3
1023
     * TODO: throw away as soon as we don't need to support SF 2.3 any more
1024
     *
1025
     * @return \DOMElement|null
1026
     */
1027 3
    public function getNode($position)
1028
    {
1029 3
        return parent::getNode($position);
1030
    }
1031
1032
    /**
1033
     * Returns the node name of the first node of the list.
1034
     *
1035
     * in Crawler (parent), this function will be available starting with 2.6.0,
1036
     * therefore this method be removed from here as soon as we don't need to keep compatibility
1037
     * with Symfony < 2.6
1038
     *
1039
     * TODO: throw away as soon as we don't need to support SF 2.3 any more
1040
     *
1041
     * @return string The node name
1042
     *
1043
     * @throws \InvalidArgumentException When current node is empty
1044
     */
1045 1
    public function nodeName()
1046
    {
1047 1
        if (!count($this)) {
1048
            throw new \InvalidArgumentException('The current node list is empty.');
1049
        }
1050 1
        return $this->getNode(0)->nodeName;
1051
    }
1052
1053
    /**
1054
     * Adds a node to the current list of nodes.
1055
     *
1056
     * This method uses the appropriate specialized add*() method based
1057
     * on the type of the argument.
1058
     *
1059
     * Overwritten from parent to allow Crawler to be added
1060
     *
1061
     * @param null|\DOMNodeList|array|\DOMNode|Crawler $node A node
1062
     *
1063
     * @api
1064
     */
1065 14
    public function add($node)
1066
    {
1067 14
        if ($node instanceof Crawler) {
1068
            foreach ($node as $childnode) {
1069
                $this->addNode($childnode);
1070
            }
1071
        } else {
1072 14
            parent::add($node);
1073
        }
1074 14
    }
1075
1076
    /**
1077
     * @param \DOMNode $newnode
1078
     * @param \DOMNode $referencenode
1079
     * @param int $clone
1080
     * @return \DOMNode
1081
     */
1082 5
    protected static function importNewnode(\DOMNode $newnode, \DOMNode $referencenode, $clone = 0) {
1083 5
        if ($newnode->ownerDocument !== $referencenode->ownerDocument) {
1084 4
            $newnode = $referencenode->ownerDocument->importNode($newnode, true);
1085 4
        } else {
1086 2
            if ($clone > 0) {
1087
                $newnode = $newnode->cloneNode(true);
1088
            }
1089
        }
1090 5
        return $newnode;
1091
    }
1092
1093
    /**
1094
     * Checks whether the first node in the set is disconnected (has no parent node)
1095
     *
1096
     * @return bool
1097
     */
1098 1
    public function isDisconnected()
1099
    {
1100 1
        $parent = $this->getNode(0)->parentNode;
1101 1
        return ($parent == null || $parent->tagName == self::FRAGMENT_ROOT_TAGNAME);
1102
    }
1103
1104 1
    public function __get($name)
1105
    {
1106
        switch ($name) {
1107 1
            case 'count':
1108 1
            case 'length':
1109 1
                return count($this);
1110
        }
1111 1
        throw new \Exception('No such property ' . $name);
1112
    }
1113
}
1114