Completed
Push — master ( 77e4a4...d10009 )
by Gilles
03:09
created

Dom::clean()   B

Complexity

Conditions 7
Paths 33

Size

Total Lines 50
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 7.004

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 22
c 1
b 0
f 0
nc 33
nop 1
dl 0
loc 50
ccs 22
cts 23
cp 0.9565
crap 7.004
rs 8.6346
1
<?php declare(strict_types=1);
2
namespace PHPHtmlParser;
3
4
use PHPHtmlParser\Dom\AbstractNode;
5
use PHPHtmlParser\Dom\Collection;
6
use PHPHtmlParser\Dom\HtmlNode;
7
use PHPHtmlParser\Dom\TextNode;
8
use PHPHtmlParser\Exceptions\ChildNotFoundException;
9
use PHPHtmlParser\Exceptions\CircularException;
10
use PHPHtmlParser\Exceptions\CurlException;
11
use PHPHtmlParser\Exceptions\NotLoadedException;
12
use PHPHtmlParser\Exceptions\ParentNotFoundException;
13
use PHPHtmlParser\Exceptions\StrictException;
14
use PHPHtmlParser\Exceptions\UnknownChildTypeException;
15
use stringEncode\Encode;
16
17
/**
18
 * Class Dom
19
 *
20
 * @package PHPHtmlParser
21
 */
22
class Dom
23
{
24
25
    /**
26
     * The charset we would like the output to be in.
27
     *
28
     * @var string
29
     */
30
    protected $defaultCharset = 'UTF-8';
31
32
    /**
33
     * Contains the root node of this dom tree.
34
     *
35
     * @var HtmlNode
36
     */
37
    public $root;
38
39
    /**
40
     * The raw version of the document string.
41
     *
42
     * @var string
43
     */
44
    protected $raw;
45
46
    /**
47
     * The document string.
48
     *
49
     * @var Content
50
     */
51
    protected $content = null;
52
53
    /**
54
     * The original file size of the document.
55
     *
56
     * @var int
57
     */
58
    protected $rawSize;
59
60
    /**
61
     * The size of the document after it is cleaned.
62
     *
63
     * @var int
64
     */
65
    protected $size;
66
67
    /**
68
     * A global options array to be used by all load calls.
69
     *
70
     * @var array
71
     */
72
    protected $globalOptions = [];
73
74
    /**
75
     * A persistent option object to be used for all options in the
76
     * parsing of the file.
77
     *
78
     * @var Options
79
     */
80
    protected $options;
81
82
    /**
83
     * A list of tags which will always be self closing
84
     *
85
     * @var array
86
     */
87
    protected $selfClosing = [
88
        'area',
89
        'base',
90
        'basefont',
91
        'br',
92
        'col',
93
        'embed',
94
        'hr',
95
        'img',
96
        'input',
97
        'keygen',
98
        'link',
99
        'meta',
100
        'param',
101
        'source',
102
        'spacer',
103
        'track',
104
        'wbr'
105
    ];
106
107
    /**
108
     * A list of tags where there should be no /> at the end (html5 style)
109
     *
110
     * @var array
111
     */
112
    protected $noSlash = [];
113
114
    /**
115
     * Returns the inner html of the root node.
116
     *
117
     * @return string
118
     * @throws ChildNotFoundException
119
     * @throws UnknownChildTypeException
120
     */
121 24
    public function __toString(): string
122
    {
123 24
        return $this->root->innerHtml();
124
    }
125
126
    /**
127
     * A simple wrapper around the root node.
128
     *
129
     * @param string $name
130
     * @return mixed
131
     */
132 15
    public function __get($name)
133
    {
134 15
        return $this->root->$name;
135
    }
136
137
    /**
138
     * Attempts to load the dom from any resource, string, file, or URL.
139
     * @param string $str
140
     * @param array  $options
141
     * @return Dom
142
     * @throws ChildNotFoundException
143
     * @throws CircularException
144
     * @throws CurlException
145
     * @throws StrictException
146
     */
147 168
    public function load(string $str, array $options = []): Dom
148
    {
149 168
        AbstractNode::resetCount();
150
        // check if it's a file
151 168
        if (strpos($str, "\n") === false && is_file($str)) {
152 6
            return $this->loadFromFile($str, $options);
153
        }
154
        // check if it's a url
155 162
        if (preg_match("/^https?:\/\//i", $str)) {
156
            return $this->loadFromUrl($str, $options);
157
        }
158
159 162
        return $this->loadStr($str, $options);
160
    }
161
162
    /**
163
     * Loads the dom from a document file/url
164
     * @param string $file
165
     * @param array  $options
166
     * @return Dom
167
     * @throws ChildNotFoundException
168
     * @throws CircularException
169
     * @throws StrictException
170
     */
171 48
    public function loadFromFile(string $file, array $options = []): Dom
172
    {
173 48
        return $this->loadStr(file_get_contents($file), $options);
174
    }
175
176
    /**
177
     * Use a curl interface implementation to attempt to load
178
     * the content from a url.
179
     * @param string                            $url
180
     * @param array                             $options
181
     * @param CurlInterface|null $curl
182
     * @return Dom
183
     * @throws ChildNotFoundException
184
     * @throws CircularException
185
     * @throws CurlException
186
     * @throws StrictException
187
     */
188 6
    public function loadFromUrl(string $url, array $options = [], CurlInterface $curl = null): Dom
189
    {
190 6
        if (is_null($curl)) {
191
            // use the default curl interface
192
            $curl = new Curl;
193
        }
194 6
        $content = $curl->get($url);
195
196 6
        return $this->loadStr($content, $options);
197
    }
198
199
    /**
200
     * Parsers the html of the given string. Used for load(), loadFromFile(),
201
     * and loadFromUrl().
202
     * @param string $str
203
     * @param array  $option
204
     * @return Dom
205
     * @throws ChildNotFoundException
206
     * @throws CircularException
207
     * @throws StrictException
208
     */
209 228
    public function loadStr(string $str, array $option = []): Dom
210
    {
211 228
        $this->options = new Options;
212 228
        $this->options->setOptions($this->globalOptions)
213 228
                      ->setOptions($option);
214
215 228
        $this->rawSize = strlen($str);
216 228
        $this->raw     = $str;
217
218 228
        $html = $this->clean($str);
219
220 228
        $this->size    = strlen($str);
221 228
        $this->content = new Content($html);
222
223 228
        $this->parse();
224 222
        $this->detectCharset();
225
226 222
        return $this;
227
    }
228
229
    /**
230
     * Sets a global options array to be used by all load calls.
231
     *
232
     * @param array $options
233
     * @return Dom
234
     * @chainable
235
     */
236 48
    public function setOptions(array $options): Dom
237
    {
238 48
        $this->globalOptions = $options;
239
240 48
        return $this;
241
    }
242
243
    /**
244
     * Find elements by css selector on the root node.
245
     * @param string   $selector
246
     * @param int|null $nth
247
     * @return mixed|Collection|null
248
     * @throws ChildNotFoundException
249
     * @throws NotLoadedException
250
     */
251 168
    public function find(string $selector, int $nth = null)
252
    {
253 168
        $this->isLoaded();
254
255 165
        return $this->root->find($selector, $nth, $this->options->get('depthFirstSearch'));
0 ignored issues
show
Bug introduced by
It seems like $this->options->get('depthFirstSearch') can also be of type null; however, parameter $depthFirst of PHPHtmlParser\Dom\AbstractNode::find() does only seem to accept boolean, 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

255
        return $this->root->find($selector, $nth, /** @scrutinizer ignore-type */ $this->options->get('depthFirstSearch'));
Loading history...
256
    }
257
258
    /**
259
     * Find element by Id on the root node
260
     * @param int $id
261
     * @return bool|AbstractNode
262
     * @throws ChildNotFoundException
263
     * @throws NotLoadedException
264
     * @throws ParentNotFoundException
265
     */
266 9
    public function findById(int $id)
267
    {
268 9
        $this->isLoaded();
269
270 9
        return $this->root->findById($id);
271
    }
272
273
    /**
274
     * Adds the tag (or tags in an array) to the list of tags that will always
275
     * be self closing.
276
     *
277
     * @param string|array $tag
278
     * @return Dom
279
     * @chainable
280
     */
281 6
    public function addSelfClosingTag($tag): Dom
282
    {
283 6
        if ( ! is_array($tag)) {
284 3
            $tag = [$tag];
285
        }
286 6
        foreach ($tag as $value) {
287 6
            $this->selfClosing[] = $value;
288
        }
289
290 6
        return $this;
291
    }
292
293
    /**
294
     * Removes the tag (or tags in an array) from the list of tags that will
295
     * always be self closing.
296
     *
297
     * @param string|array $tag
298
     * @return Dom
299
     * @chainable
300
     */
301 3
    public function removeSelfClosingTag($tag): Dom
302
    {
303 3
        if ( ! is_array($tag)) {
304 3
            $tag = [$tag];
305
        }
306 3
        $this->selfClosing = array_diff($this->selfClosing, $tag);
307
308 3
        return $this;
309
    }
310
311
    /**
312
     * Sets the list of self closing tags to empty.
313
     *
314
     * @return Dom
315
     * @chainable
316
     */
317 3
    public function clearSelfClosingTags(): Dom
318
    {
319 3
        $this->selfClosing = [];
320
321 3
        return $this;
322
    }
323
324
325
    /**
326
     * Adds a tag to the list of self closing tags that should not have a trailing slash
327
     *
328
     * @param $tag
329
     * @return Dom
330
     * @chainable
331
     */
332 3
    public function addNoSlashTag($tag): Dom
333
    {
334 3
        if ( ! is_array($tag)) {
335 3
            $tag = [$tag];
336
        }
337 3
        foreach ($tag as $value) {
338 3
            $this->noSlash[] = $value;
339
        }
340
341 3
        return $this;
342
    }
343
344
    /**
345
     * Removes a tag from the list of no-slash tags.
346
     *
347
     * @param $tag
348
     * @return Dom
349
     * @chainable
350
     */
351
    public function removeNoSlashTag($tag): Dom
352
    {
353
        if ( ! is_array($tag)) {
354
            $tag = [$tag];
355
        }
356
        $this->noSlash = array_diff($this->noSlash, $tag);
357
358
        return $this;
359
    }
360
361
    /**
362
     * Empties the list of no-slash tags.
363
     *
364
     * @return Dom
365
     * @chainable
366
     */
367
    public function clearNoSlashTags(): Dom
368
    {
369
        $this->noSlash = [];
370
371
        return $this;
372
    }
373
374
    /**
375
     * Simple wrapper function that returns the first child.
376
     * @return AbstractNode
377
     * @throws ChildNotFoundException
378
     * @throws NotLoadedException
379
     */
380 3
    public function firstChild(): AbstractNode
381
    {
382 3
        $this->isLoaded();
383
384 3
        return $this->root->firstChild();
385
    }
386
387
    /**
388
     * Simple wrapper function that returns the last child.
389
     * @return AbstractNode
390
     * @throws ChildNotFoundException
391
     * @throws NotLoadedException
392
     */
393 3
    public function lastChild(): AbstractNode
394
    {
395 3
        $this->isLoaded();
396
397 3
        return $this->root->lastChild();
398
    }
399
400
    /**
401
     * Simple wrapper function that returns count of child elements
402
     *
403
     * @return int
404
     * @throws NotLoadedException
405
     */
406 3
    public function countChildren(): int
407
    {
408 3
        $this->isLoaded();
409
410 3
        return $this->root->countChildren();
411
    }
412
413
    /**
414
     * Get array of children
415
     *
416
     * @return array
417
     * @throws NotLoadedException
418
     */
419 3
    public function getChildren(): array
420
    {
421 3
        $this->isLoaded();
422
423 3
        return $this->root->getChildren();
424
    }
425
426
    /**
427
     * Check if node have children nodes
428
     *
429
     * @return bool
430
     * @throws NotLoadedException
431
     */
432 3
    public function hasChildren(): bool
433
    {
434 3
        $this->isLoaded();
435
436 3
        return $this->root->hasChildren();
437
    }
438
439
    /**
440
     * Simple wrapper function that returns an element by the
441
     * id.
442
     * @param $id
443
     * @return mixed|Collection|null
444
     * @throws ChildNotFoundException
445
     * @throws NotLoadedException
446
     */
447 12
    public function getElementById($id)
448
    {
449 12
        $this->isLoaded();
450
451 12
        return $this->find('#'.$id, 0);
452
    }
453
454
    /**
455
     * Simple wrapper function that returns all elements by
456
     * tag name.
457
     * @param string $name
458
     * @return mixed|Collection|null
459
     * @throws ChildNotFoundException
460
     * @throws NotLoadedException
461
     */
462 15
    public function getElementsByTag(string $name)
463
    {
464 15
        $this->isLoaded();
465
466 15
        return $this->find($name);
467
    }
468
469
    /**
470
     * Simple wrapper function that returns all elements by
471
     * class name.
472
     * @param string $class
473
     * @return mixed|Collection|null
474
     * @throws ChildNotFoundException
475
     * @throws NotLoadedException
476
     */
477 3
    public function getElementsByClass(string $class)
478
    {
479 3
        $this->isLoaded();
480
481 3
        return $this->find('.'.$class);
482
    }
483
484
    /**
485
     * Checks if the load methods have been called.
486
     *
487
     * @throws NotLoadedException
488
     */
489 192
    protected function isLoaded(): void
490
    {
491 192
        if (is_null($this->content)) {
492 3
            throw new NotLoadedException('Content is not loaded!');
493
        }
494 189
    }
495
496
    /**
497
     * Cleans the html of any none-html information.
498
     *
499
     * @param string $str
500
     * @return string
501
     */
502 228
    protected function clean(string $str): string
503
    {
504 228
        if ($this->options->get('cleanupInput') != true) {
505
            // skip entire cleanup step
506 6
            return $str;
507
        }
508
509
        // remove white space before closing tags
510 222
        $str = mb_eregi_replace("'\s+>", "'>", $str);
511 222
        $str = mb_eregi_replace('"\s+>', '">', $str);
512
513
        // clean out the \n\r
514 222
        $replace = ' ';
515 222
        if ($this->options->get('preserveLineBreaks')) {
516 3
            $replace = '&#10;';
517
        }
518 222
        $str = str_replace(["\r\n", "\r", "\n"], $replace, $str);
519
520
        // strip the doctype
521 222
        $str = mb_eregi_replace("<!doctype(.*?)>", '', $str);
522
523
        // strip out comments
524 222
        $str = mb_eregi_replace("<!--(.*?)-->", '', $str);
525
526
        // strip out cdata
527 222
        $str = mb_eregi_replace("<!\[CDATA\[(.*?)\]\]>", '', $str);
528
529
        // strip out <script> tags
530 222
        if ($this->options->get('removeScripts')) {
531 219
            $str = mb_eregi_replace("<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>", '', $str);
532 219
            $str = mb_eregi_replace("<\s*script\s*>(.*?)<\s*/\s*script\s*>", '', $str);
533
        }
534
535
        // strip out <style> tags
536 222
        if ($this->options->get('removeStyles')) {
537 219
            $str = mb_eregi_replace("<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>", '', $str);
538 219
            $str = mb_eregi_replace("<\s*style\s*>(.*?)<\s*/\s*style\s*>", '', $str);
539
        }
540
541
        // strip out server side scripts
542 222
        if ($this->options->get('serverSideScripts')) {
543
            $str = mb_eregi_replace("(<\?)(.*?)(\?>)", '', $str);
544
        }
545
546
        // strip smarty scripts
547 222
        if ($this->options->get('removeSmartyScripts')) {
548 219
            $str = mb_eregi_replace("(\{\w)(.*?)(\})", '', $str);
549
        }
550
551 222
        return $str;
552
    }
553
554
    /**
555
     * Attempts to parse the html in content.
556
     *
557
     * @return void
558
     * @throws ChildNotFoundException
559
     * @throws CircularException
560
     * @throws StrictException
561
     */
562 228
    protected function parse(): void
563
    {
564
        // add the root node
565 228
        $this->root = new HtmlNode('root');
566 228
        $this->root->setHtmlSpecialCharsDecode($this->options->htmlSpecialCharsDecode);
567 228
        $activeNode = $this->root;
568 228
        while ( ! is_null($activeNode)) {
569 228
            $str = $this->content->copyUntil('<');
570 228
            if ($str == '') {
571 228
                $info = $this->parseTag();
572 228
                if ( ! $info['status']) {
573
                    // we are done here
574 222
                    $activeNode = null;
575 222
                    continue;
576
                }
577
578
                // check if it was a closing tag
579 222
                if ($info['closing']) {
580 219
                    $foundOpeningTag  = true;
581 219
                    $originalNode     = $activeNode;
582 219
                    while ($activeNode->getTag()->name() != $info['tag']) {
583 78
                        $activeNode = $activeNode->getParent();
584 78
                        if (is_null($activeNode)) {
585
                            // we could not find opening tag
586 36
                            $activeNode = $originalNode;
587 36
                            $foundOpeningTag = false;
588 36
                            break;
589
                        }
590
                    }
591 219
                    if ($foundOpeningTag) {
592 219
                        $activeNode = $activeNode->getParent();
593
                    }
594 219
                    continue;
595
                }
596
597 222
                if ( ! isset($info['node'])) {
598 12
                    continue;
599
                }
600
601
                /** @var AbstractNode $node */
602 222
                $node = $info['node'];
603 222
                $activeNode->addChild($node);
604
605
                // check if node is self closing
606 222
                if ( ! $node->getTag()->isSelfClosing()) {
607 222
                    $activeNode = $node;
608
                }
609 216
            } else if ($this->options->whitespaceTextNode ||
610 216
                trim($str) != ''
611
            ) {
612
                // we found text we care about
613 216
                $textNode = new TextNode($str, $this->options->removeDoubleSpace);
614 216
                $textNode->setHtmlSpecialCharsDecode($this->options->htmlSpecialCharsDecode);
615 216
                $activeNode->addChild($textNode);
616
            }
617
        }
618 222
    }
619
620
    /**
621
     * Attempt to parse a tag out of the content.
622
     *
623
     * @return array
624
     * @throws StrictException
625
     */
626 228
    protected function parseTag(): array
627
    {
628
        $return = [
629 228
            'status'  => false,
630
            'closing' => false,
631
            'node'    => null,
632
        ];
633 228
        if ($this->content->char() != '<') {
634
            // we are not at the beginning of a tag
635 219
            return $return;
636
        }
637
638
        // check if this is a closing tag
639 222
        if ($this->content->fastForward(1)->char() == '/') {
640
            // end tag
641 219
            $tag = $this->content->fastForward(1)
642 219
                                 ->copyByToken('slash', true);
643
            // move to end of tag
644 219
            $this->content->copyUntil('>');
645 219
            $this->content->fastForward(1);
646
647
            // check if this closing tag counts
648 219
            $tag = strtolower($tag);
649 219
            if (in_array($tag, $this->selfClosing)) {
650 12
                $return['status'] = true;
651
652 12
                return $return;
653
            } else {
654 219
                $return['status']  = true;
655 219
                $return['closing'] = true;
656 219
                $return['tag']     = strtolower($tag);
657
            }
658
659 219
            return $return;
660
        }
661
662 222
        $tag  = strtolower($this->content->copyByToken('slash', true));
663 222
        if (trim($tag) == '')
664
        {
665
            // no tag found, invalid < found
666 3
            return $return;
667
        }
668 222
        $node = new HtmlNode($tag);
669 222
        $node->setHtmlSpecialCharsDecode($this->options->htmlSpecialCharsDecode);
670
671
        // attributes
672 222
        while ($this->content->char() != '>' &&
673 222
            $this->content->char() != '/') {
674 213
            $space = $this->content->skipByToken('blank', true);
675 213
            if (empty($space)) {
676 6
                $this->content->fastForward(1);
677 6
                continue;
678
            }
679
680 213
            $name = $this->content->copyByToken('equal', true);
681 213
            if ($name == '/') {
682
                break;
683
            }
684
685 213
            if (empty($name)) {
686 117
				$this->content->skipByToken('blank');
687 117
				continue;
688
            }
689
690 213
            $this->content->skipByToken('blank');
691 213
            if ($this->content->char() == '=') {
692 213
                $attr = [];
693 213
                $this->content->fastForward(1)
694 213
                              ->skipByToken('blank');
695 213
                switch ($this->content->char()) {
696 213
                    case '"':
697 198
                        $attr['doubleQuote'] = true;
698 198
                        $this->content->fastForward(1);
699 198
                        $string = $this->content->copyUntil('"', true, true);
700
                        do {
701 198
                            $moreString = $this->content->copyUntilUnless('"', '=>');
702 198
                            $string .= $moreString;
703 198
                        } while ( ! empty($moreString));
704 198
                        $attr['value'] = $string;
705 198
                        $this->content->fastForward(1);
706 198
                        $node->getTag()->$name = $attr;
707 198
                        break;
708 21
                    case "'":
709 18
                        $attr['doubleQuote'] = false;
710 18
                        $this->content->fastForward(1);
711 18
                        $string = $this->content->copyUntil("'", true, true);
712
                        do {
713 18
                            $moreString = $this->content->copyUntilUnless("'", '=>');
714 18
                            $string .= $moreString;
715 18
                        } while ( ! empty($moreString));
716 18
                        $attr['value'] = $string;
717 18
                        $this->content->fastForward(1);
718 18
                        $node->getTag()->$name = $attr;
719 18
                        break;
720
                    default:
721 3
                        $attr['doubleQuote']   = true;
722 3
                        $attr['value']         = $this->content->copyByToken('attr', true);
723 3
                        $node->getTag()->$name = $attr;
724 213
                        break;
725
                }
726
            } else {
727
                // no value attribute
728 66
                if ($this->options->strict) {
729
                    // can't have this in strict html
730 3
                    $character = $this->content->getPosition();
731 3
                    throw new StrictException("Tag '$tag' has an attribute '$name' with out a value! (character #$character)");
732
                }
733 63
                $node->getTag()->$name = [
734
                    'value'       => null,
735
                    'doubleQuote' => true,
736
                ];
737 63
                if ($this->content->char() != '>') {
738 12
                    $this->content->rewind(1);
739
                }
740
            }
741
        }
742
743 222
        $this->content->skipByToken('blank');
744 222
        if ($this->content->char() == '/') {
745
            // self closing tag
746 114
            $node->getTag()->selfClosing();
747 114
            $this->content->fastForward(1);
748 222
        } elseif (in_array($tag, $this->selfClosing)) {
749
750
            // Should be a self closing tag, check if we are strict
751 81
            if ($this->options->strict) {
752 3
                $character = $this->content->getPosition();
753 3
                throw new StrictException("Tag '$tag' is not self closing! (character #$character)");
754
            }
755
756
            // We force self closing on this tag.
757 78
            $node->getTag()->selfClosing();
758
759
            // Should this tag use a trailing slash?
760 78
            if(in_array($tag, $this->noSlash))
761
            {
762 3
                $node->getTag()->noTrailingSlash();
763
            }
764
765
        }
766
767 222
        $this->content->fastForward(1);
768
769 222
        $return['status'] = true;
770 222
        $return['node']   = $node;
771
772 222
        return $return;
773
    }
774
775
    /**
776
     * Attempts to detect the charset that the html was sent in.
777
     *
778
     * @return bool
779
     * @throws ChildNotFoundException
780
     */
781 222
    protected function detectCharset(): bool
782
    {
783
        // set the default
784 222
        $encode = new Encode;
785 222
        $encode->from($this->defaultCharset);
786 222
        $encode->to($this->defaultCharset);
787
788 222
        if ( ! is_null($this->options->enforceEncoding)) {
789
            //  they want to enforce the given encoding
790
            $encode->from($this->options->enforceEncoding);
791
            $encode->to($this->options->enforceEncoding);
792
793
            return false;
794
        }
795
796 222
        $meta = $this->root->find('meta[http-equiv=Content-Type]', 0);
797 222
        if (is_null($meta)) {
798
            // could not find meta tag
799 192
            $this->root->propagateEncoding($encode);
800
801 192
            return false;
802
        }
803 30
        $content = $meta->content;
0 ignored issues
show
Bug Best Practice introduced by
The property content does not exist on PHPHtmlParser\Dom\Collection. Since you implemented __get, consider adding a @property annotation.
Loading history...
804 30
        if (empty($content)) {
805
            // could not find content
806
            $this->root->propagateEncoding($encode);
807
808
            return false;
809
        }
810 30
        $matches = [];
811 30
        if (preg_match('/charset=(.+)/', $content, $matches)) {
812 30
            $encode->from(trim($matches[1]));
813 30
            $this->root->propagateEncoding($encode);
814
815 30
            return true;
816
        }
817
818
        // no charset found
819
        $this->root->propagateEncoding($encode);
820
821
        return false;
822
    }
823
}
824