Completed
Push — master ( 60197c...2a5e10 )
by Lars
02:03
created

HtmlDomParser::__call()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 10
Ratio 100 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 10
loc 10
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
crap 2
1
<?php
2
3
namespace voku\helper;
4
5
use BadMethodCallException;
6
use DOMDocument;
7
use DOMXPath;
8
use InvalidArgumentException;
9
use RuntimeException;
10
11
/**
12
 * Class HtmlDomParser
13
 *
14
 * @package voku\helper
15
 *
16
 * @property-read string outerText Get dom node's outer html (alias for "outerHtml")
17
 * @property-read string outerHtml Get dom node's outer html
18
 * @property-read string innerText Get dom node's inner html (alias for "innerHtml")
19
 * @property-read string innerHtml Get dom node's inner html
20
 * @property-read string plaintext Get dom node's plain text
21
 *
22
 * @method string outerText() Get dom node's outer html (alias for "outerHtml()")
23
 * @method string outerHtml() Get dom node's outer html
24
 * @method string innerText() Get dom node's inner html (alias for "innerHtml()")
25
 * @method HtmlDomParser load() load($html) Load HTML from string
26
 * @method HtmlDomParser load_file() load_file($html) Load HTML from file
27
 *
28
 * @method static HtmlDomParser file_get_html() file_get_html($html, $libXMLExtraOptions = null) Load HTML from file
29
 * @method static HtmlDomParser str_get_html() str_get_html($html, $libXMLExtraOptions = null) Load HTML from string
30
 */
31
class HtmlDomParser
32
{
33
  /**
34
   * @var array
35
   */
36
  protected static $functionAliases = array(
37
      'outertext' => 'html',
38
      'outerhtml' => 'html',
39
      'innertext' => 'innerHtml',
40
      'innerhtml' => 'innerHtml',
41
      'load'      => 'loadHtml',
42
      'load_file' => 'loadHtmlFile',
43
  );
44
45
  /**
46
   * @var string[][]
47
   */
48
  protected static $domLinkReplaceHelper = array(
49
      'orig' => array('[', ']', '{', '}',),
50
      'tmp'  => array(
51
          '!!!!SIMPLE_HTML_DOM__VOKU__SQUARE_BRACKET_LEFT!!!!',
52
          '!!!!SIMPLE_HTML_DOM__VOKU__SQUARE_BRACKET_RIGHT!!!!',
53
          '!!!!SIMPLE_HTML_DOM__VOKU__BRACKET_LEFT!!!!',
54
          '!!!!SIMPLE_HTML_DOM__VOKU__BRACKET_RIGHT!!!!',
55
      ),
56
  );
57
58
  /**
59
   * @var array
60
   */
61
  protected static $domReplaceHelper = array(
62
      'orig' => array('&', '|', '+', '%'),
63
      'tmp'  => array(
64
          '!!!!SIMPLE_HTML_DOM__VOKU__AMP!!!!',
65
          '!!!!SIMPLE_HTML_DOM__VOKU__PIPE!!!!',
66
          '!!!!SIMPLE_HTML_DOM__VOKU__PLUS!!!!',
67
          '!!!!SIMPLE_HTML_DOM__VOKU__PERCENT!!!!',
68
      ),
69
  );
70
71
  /**
72
   * @var Callable
73
   */
74
  protected static $callback;
75
76
  /**
77
   * @var DOMDocument
78
   */
79
  protected $document;
80
81
  /**
82
   * @var string
83
   */
84
  protected $encoding = 'UTF-8';
85
86
  /**
87
   * @var bool
88
   */
89
  protected $isDOMDocumentCreatedWithoutHtml = false;
90
91
  /**
92
   * @var bool
93
   */
94
  protected $isDOMDocumentCreatedWithoutHtmlWrapper = false;
95
96
  /**
97
   * Constructor
98
   *
99
   * @param string|SimpleHtmlDom|\DOMNode $element HTML code or SimpleHtmlDom, \DOMNode
100
   */
101 119
  public function __construct($element = null)
102
  {
103 119
    $this->document = new \DOMDocument('1.0', $this->getEncoding());
104
105
    // DOMDocument settings
106 119
    $this->document->preserveWhiteSpace = true;
107 119
    $this->document->formatOutput = true;
108
109 119
    if ($element instanceof SimpleHtmlDom) {
110 51
      $element = $element->getNode();
111
    }
112
113 119
    if ($element instanceof \DOMNode) {
114 51
      $domNode = $this->document->importNode($element, true);
115
116 51
      if ($domNode instanceof \DOMNode) {
117 51
        $this->document->appendChild($domNode);
118
      }
119
120 51
      return;
121
    }
122
123 119
    if ($element !== null) {
124 70
      $this->loadHtml($element);
125
    }
126 118
  }
127
128
  /**
129
   * @param $name
130
   * @param $arguments
131
   *
132
   * @return bool|mixed
133
   */
134 34 View Code Duplication
  public function __call($name, $arguments)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
135
  {
136 34
    $name = strtolower($name);
137
138 34
    if (isset(self::$functionAliases[$name])) {
139 33
      return call_user_func_array(array($this, self::$functionAliases[$name]), $arguments);
140
    }
141
142 1
    throw new BadMethodCallException('Method does not exist: ' . $name);
143
  }
144
145
  /**
146
   * @param $name
147
   * @param $arguments
148
   *
149
   * @return HtmlDomParser
150
   */
151 14
  public static function __callStatic($name, $arguments)
152
  {
153 14
    $arguments0 = null;
154 14
    if (isset($arguments[0])) {
155 13
      $arguments0 = $arguments[0];
156
    }
157
158 14
    $arguments1 = null;
159 14
    if (isset($arguments[1])) {
160 1
      $arguments1 = $arguments[1];
161
    }
162
163 14
    if ($name === 'str_get_html') {
164 9
      $parser = new self();
165
166 9
      return $parser->loadHtml($arguments0, $arguments1);
167
    }
168
169 5
    if ($name === 'file_get_html') {
170 4
      $parser = new self();
171
172 4
      return $parser->loadHtmlFile($arguments0, $arguments1);
173
    }
174
175 1
    throw new BadMethodCallException('Method does not exist');
176
  }
177
178
  /** @noinspection MagicMethodsValidityInspection */
179
  /**
180
   * @param $name
181
   *
182
   * @return string
183
   */
184 14
  public function __get($name)
185
  {
186 14
    $name = strtolower($name);
187
188
    switch ($name) {
189 14
      case 'outerhtml':
190 14
      case 'outertext':
191 7
        return $this->html();
192 7
      case 'innerhtml':
193 3
      case 'innertext':
194 5
        return $this->innerHtml();
195 2
      case 'text':
196 2
      case 'plaintext':
197 1
        return $this->text();
198
    }
199
200 1
    return null;
201
  }
202
203
  /**
204
   * @param string $selector
205
   * @param int    $idx
206
   *
207
   * @return SimpleHtmlDom|SimpleHtmlDomNode|null
208
   */
209 3
  public function __invoke($selector, $idx = null)
210
  {
211 3
    return $this->find($selector, $idx);
212
  }
213
214
  /**
215
   * @return string
216
   */
217 14
  public function __toString()
218
  {
219 14
    return $this->html();
220
  }
221
222
  /**
223
   * does nothing (only for api-compatibility-reasons)
224
   *
225
   * @return bool
226
   */
227 1
  public function clear()
228
  {
229 1
    return true;
230
  }
231
232
  /**
233
   * @param string $html
234
   *
235
   * @return string
236
   */
237 72
  public static function replaceToPreserveHtmlEntities($html)
238
  {
239
    // init
240 72
    $linksNew = array();
241 72
    $linksOld = array();
242
243 72
    if (strpos($html, 'http') !== false) {
244 49
      preg_match_all("/(\bhttps?:\/\/[^\s()<>]+(?:\([\w\d]+\)|[^[:punct:]\s]|\/|\}|\]))/i", $html, $linksOld);
245
246 49
      if (!empty($linksOld[1])) {
247 49
        $linksOld = $linksOld[1];
248 49
        foreach ((array)$linksOld as $linkKey => $linkOld) {
249 49
          $linksNew[$linkKey] = str_replace(
250 49
              self::$domLinkReplaceHelper['orig'],
251 49
              self::$domLinkReplaceHelper['tmp'],
252
              $linkOld
253
          );
254
        }
255
      }
256
    }
257
258 72
    $linksNewCount = count($linksNew);
259 72
    if ($linksNewCount > 0 && count($linksOld) === $linksNewCount) {
260 49
      $search = array_merge($linksOld, self::$domReplaceHelper['orig']);
261 49
      $replace = array_merge($linksNew, self::$domReplaceHelper['tmp']);
262
    } else {
263 24
      $search = self::$domReplaceHelper['orig'];
264 24
      $replace = self::$domReplaceHelper['tmp'];
265
    }
266
267 72
    return str_replace($search, $replace, $html);
268
  }
269
270
  /**
271
   * @param string $html
272
   *
273
   * @return string
274
   */
275 56
  public static function putReplacedBackToPreserveHtmlEntities($html)
276
  {
277 56
    static $DOM_REPLACE__HELPER_CACHE = null;
278 56
    if ($DOM_REPLACE__HELPER_CACHE === null) {
279 1
      $DOM_REPLACE__HELPER_CACHE['tmp'] = array_merge(
280 1
          self::$domLinkReplaceHelper['tmp'],
281 1
          self::$domReplaceHelper['tmp']
282
      );
283 1
      $DOM_REPLACE__HELPER_CACHE['orig'] = array_merge(
284 1
          self::$domLinkReplaceHelper['orig'],
285 1
          self::$domReplaceHelper['orig']
286
      );
287
    }
288
289 56
    return str_replace($DOM_REPLACE__HELPER_CACHE['tmp'], $DOM_REPLACE__HELPER_CACHE['orig'], $html);
290
  }
291
292
  /**
293
   * create DOMDocument from HTML
294
   *
295
   * @param string   $html
296
   * @param int|null $libXMLExtraOptions
297
   *
298
   * @return \DOMDocument
299
   */
300 107
  private function createDOMDocument($html, $libXMLExtraOptions = null)
301
  {
302 107
    if (strpos($html, '<') === false) {
303 6
      $this->isDOMDocumentCreatedWithoutHtml = true;
304
    }
305
306 107
    if (strpos($html, '<html') === false) {
307 59
      $this->isDOMDocumentCreatedWithoutHtmlWrapper = true;
308
    }
309
310
    // set error level
311 107
    $internalErrors = libxml_use_internal_errors(true);
312 107
    $disableEntityLoader = libxml_disable_entity_loader(true);
313 107
    libxml_clear_errors();
314
315 107
    $optionsSimpleXml = LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET;
316 107
    $optionsXml = LIBXML_DTDATTR | LIBXML_NONET;
317
318 107
    if (defined('LIBXML_BIGLINES')) {
319
      $optionsSimpleXml |= LIBXML_BIGLINES;
320
      $optionsXml |= LIBXML_BIGLINES;
321
    }
322
323 107
    if (defined('LIBXML_COMPACT')) {
324 107
      $optionsSimpleXml |= LIBXML_COMPACT;
325 107
      $optionsXml |= LIBXML_COMPACT;
326
    }
327
328 107
    if (defined('LIBXML_HTML_NOIMPLIED')) {
329 107
      $optionsSimpleXml |= LIBXML_HTML_NOIMPLIED;
330
    }
331
332 107
    if (defined('LIBXML_HTML_NODEFDTD')) {
333 107
      $optionsSimpleXml |= LIBXML_HTML_NODEFDTD;
334
    }
335
336 107
    if ($libXMLExtraOptions !== null) {
337 1
      $optionsSimpleXml |= $libXMLExtraOptions;
338 1
      $optionsXml |= $libXMLExtraOptions;
339
    }
340
341 107
    $sxe = simplexml_load_string($html, 'SimpleXMLElement', $optionsSimpleXml);
342 107
    if ($sxe !== false && count(libxml_get_errors()) === 0) {
343 37
      $this->document = dom_import_simplexml($sxe)->ownerDocument;
344
    } else {
345
346
      // UTF-8 hack: http://php.net/manual/en/domdocument.loadhtml.php#95251
347 72
      $html = trim($html);
348 72
      $xmlHackUsed = false;
349 72
      if (stripos('<?xml', $html) !== 0) {
350 72
        $xmlHackUsed = true;
351 72
        $html = '<?xml encoding="' . $this->getEncoding() . '" ?>' . $html;
352
      }
353
354 72
      $html = self::replaceToPreserveHtmlEntities($html);
355
356 72
      if (Bootup::is_php('5.4')) {
357 72
        $this->document->loadHTML($html, $optionsXml);
358
      } else {
359
        $this->document->loadHTML($html);
360
      }
361
362
      // remove the "xml-encoding" hack
363 72
      if ($xmlHackUsed === true) {
364 72
        foreach ($this->document->childNodes as $child) {
365 72
          if ($child->nodeType === XML_PI_NODE) {
366 72
            $this->document->removeChild($child);
367
          }
368
        }
369
      }
370
371 72
      libxml_clear_errors();
372
    }
373
374
    // set encoding
375 107
    $this->document->encoding = $this->getEncoding();
376
377
    // restore lib-xml settings
378 107
    libxml_use_internal_errors($internalErrors);
379 107
    libxml_disable_entity_loader($disableEntityLoader);
380
381 107
    return $this->document;
382
  }
383
384
  /**
385
   * Return SimpleHtmlDom by id.
386
   *
387
   * @param string $id
388
   *
389
   * @return SimpleHtmlDom|SimpleHtmlDomNodeBlank
390
   */
391 2
  public function getElementById($id)
392
  {
393 2
    return $this->find("#$id", 0);
394
  }
395
396
  /**
397
   * Return SimpleHtmlDom by tag name.
398
   *
399
   * @param string $name
400
   *
401
   * @return SimpleHtmlDom|SimpleHtmlDomNodeBlank
402
   */
403 1
  public function getElementByTagName($name)
404
  {
405 1
    $node = $this->document->getElementsByTagName($name)->item(0);
406
407 1
    if ($node !== null) {
408 1
      return new SimpleHtmlDom($node);
409
    } else {
410
      return new SimpleHtmlDomNodeBlank();
411
    }
412
  }
413
414
  /**
415
   * Returns Elements by id
416
   *
417
   * @param string   $id
418
   * @param null|int $idx
419
   *
420
   * @return SimpleHtmlDomNode|SimpleHtmlDomNode[]|SimpleHtmlDomNodeBlank
421
   */
422
  public function getElementsById($id, $idx = null)
423
  {
424
    return $this->find("#$id", $idx);
425
  }
426
427
  /**
428
   * Returns Elements by tag name
429
   *
430
   * @param string   $name
431
   * @param null|int $idx
432
   *
433
   * @return SimpleHtmlDomNode|SimpleHtmlDomNode[]|SimpleHtmlDomNodeBlank
434
   */
435 3 View Code Duplication
  public function getElementsByTagName($name, $idx = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

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

Loading history...
436
  {
437 3
    $nodesList = $this->document->getElementsByTagName($name);
438
439 3
    $elements = new SimpleHtmlDomNode();
440
441 3
    foreach ($nodesList as $node) {
442 3
      $elements[] = new SimpleHtmlDom($node);
443
    }
444
445 3
    if (null === $idx) {
446 2
      return $elements;
447
    } else {
448 1
      if ($idx < 0) {
449
        $idx = count($elements) + $idx;
450
      }
451
    }
452
453 1
    if (isset($elements[$idx])) {
454 1
      return $elements[$idx];
455
    } else {
456
      return new SimpleHtmlDomNodeBlank();
457
    }
458
  }
459
460
  /**
461
   * Find list of nodes with a CSS selector.
462
   *
463
   * @param string $selector
464
   * @param int    $idx
465
   *
466
   * @return SimpleHtmlDom|SimpleHtmlDom[]|SimpleHtmlDomNodeBlank
467
   */
468 77
  public function find($selector, $idx = null)
469
  {
470 77
    $xPathQuery = SelectorConverter::toXPath($selector);
471
472 77
    $xPath = new DOMXPath($this->document);
473 77
    $nodesList = $xPath->query($xPathQuery);
474 77
    $elements = new SimpleHtmlDomNode();
475
476 77
    foreach ($nodesList as $node) {
477 73
      $elements[] = new SimpleHtmlDom($node);
478
    }
479
480 77
    if (null === $idx) {
481 50
      return $elements;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $elements; (voku\helper\SimpleHtmlDomNode) is incompatible with the return type documented by voku\helper\HtmlDomParser::find of type voku\helper\SimpleHtmlDo...\SimpleHtmlDomNodeBlank.

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...
482
    } else {
483 38
      if ($idx < 0) {
484 10
        $idx = count($elements) + $idx;
485
      }
486
    }
487
488 38
    if (isset($elements[$idx])) {
489 35
      return $elements[$idx];
490
    } else {
491 5
      return new SimpleHtmlDomNodeBlank();
492
    }
493
  }
494
495
  /**
496
   * @param string $content
497
   *
498
   * @return string
499
   */
500 47
  protected function fixHtmlOutput($content)
501
  {
502
    // INFO: DOMDocument will encapsulate plaintext into a paragraph tag (<p>),
503
    //          so we try to remove it here again ...
504
505 47
    if ($this->isDOMDocumentCreatedWithoutHtmlWrapper === true) {
506 20
      $content = str_replace(
507
          array(
508 20
              "\n",
509
              "\r\n",
510
              "\r",
511
              '<simpleHtmlDomP>',
512
              '</simpleHtmlDomP>',
513
              '<body>',
514
              '</body>',
515
              '<html>',
516
              '</html>',
517
          ),
518 20
          '',
519
          $content
520
      );
521
    }
522
523 47
    if ($this->isDOMDocumentCreatedWithoutHtml === true) {
524 5
      $content = str_replace(
525
          array(
526 5
              '<p>',
527
              '</p>',
528
              '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
529
          ),
530 5
          '',
531
          $content
532
      );
533
    }
534
535 47
    $content = UTF8::html_entity_decode($content);
536 47
    $content = trim($content);
537 47
    $content = UTF8::rawurldecode($content);
538
539 47
    $content = self::putReplacedBackToPreserveHtmlEntities($content);
540
541 47
    return $content;
542
  }
543
544
  /**
545
   * @return DOMDocument
546
   */
547 35
  public function getDocument()
548
  {
549 35
    return $this->document;
550
  }
551
552
  /**
553
   * Get the encoding to use
554
   *
555
   * @return string
556
   */
557 119
  private function getEncoding()
558
  {
559 119
    return $this->encoding;
560
  }
561
562
  /**
563
   * @return bool
564
   */
565 6
  public function getIsDOMDocumentCreatedWithoutHtml()
566
  {
567 6
    return $this->isDOMDocumentCreatedWithoutHtml;
568
  }
569
570
  /**
571
   * @return bool
572
   */
573 33
  public function getIsDOMDocumentCreatedWithoutHtmlWrapper()
574
  {
575 33
    return $this->isDOMDocumentCreatedWithoutHtmlWrapper;
576
  }
577
578
  /**
579
   * Get dom node's outer html
580
   *
581
   * @return string
582
   */
583 33
  public function html()
584
  {
585 33
    if ($this::$callback !== null) {
586
      call_user_func($this::$callback, array($this));
587
    }
588
589 33
    if ($this->getIsDOMDocumentCreatedWithoutHtmlWrapper()) {
590 14
      $content = $this->document->saveHTML($this->document->documentElement);
591
    } else {
592 22
      $content = $this->document->saveHTML();
593
    }
594
595 33
    return $this->fixHtmlOutput($content);
596
  }
597
598
  /**
599
   * Get the HTML as XML.
600
   *
601
   * @return string
602
   */
603 1
  public function xml()
604
  {
605 1
    $xml = $this->document->saveXML(null, LIBXML_NOEMPTYTAG);
606
607
    // remove the XML-header
608 1
    $xml = ltrim(preg_replace('/<\?xml.*\?>/', '', $xml));
609
610 1
    return $this->fixHtmlOutput($xml);
611
  }
612
613
  /**
614
   * Get dom node's inner html
615
   *
616
   * @return string
617
   */
618 15
  public function innerHtml()
619
  {
620 15
    $text = '';
621
622 15
    foreach ($this->document->documentElement->childNodes as $node) {
623 15
      $text .= $this->fixHtmlOutput($this->document->saveHTML($node));
624
    }
625
626 15
    return $text;
627
  }
628
629
  /**
630
   * Load HTML from string
631
   *
632
   * @param string   $html
633
   * @param int|null $libXMLExtraOptions
634
   *
635
   * @return HtmlDomParser
636
   *
637
   * @throws InvalidArgumentException if argument is not string
638
   */
639 110
  public function loadHtml($html, $libXMLExtraOptions = null)
640
  {
641 110
    if (!is_string($html)) {
642 3
      throw new InvalidArgumentException(__METHOD__ . ' expects parameter 1 to be string.');
643
    }
644
645 107
    $this->document = $this->createDOMDocument($html, $libXMLExtraOptions);
646
647 107
    return $this;
648
  }
649
650
  /**
651
   * Load HTML from file
652
   *
653
   * @param string   $filePath
654
   * @param int|null $libXMLExtraOptions
655
   *
656
   * @return HtmlDomParser
657
   */
658 12
  public function loadHtmlFile($filePath, $libXMLExtraOptions = null)
659
  {
660 12
    if (!is_string($filePath)) {
661 2
      throw new InvalidArgumentException(__METHOD__ . ' expects parameter 1 to be string.');
662
    }
663
664 10
    if (!preg_match("/^https?:\/\//i", $filePath) && !file_exists($filePath)) {
665 1
      throw new RuntimeException("File $filePath not found");
666
    }
667
668
    try {
669 9
      $html = UTF8::file_get_contents($filePath);
670
671 1
    } catch (\Exception $e) {
672 1
      throw new RuntimeException("Could not load file $filePath");
673
    }
674
675 8
    if ($html === false) {
676
      throw new RuntimeException("Could not load file $filePath");
677
    }
678
679 8
    $this->loadHtml($html, $libXMLExtraOptions);
680
681 8
    return $this;
682
  }
683
684
  /**
685
   * Save dom as string
686
   *
687
   * @param string $filepath
688
   *
689
   * @return string
690
   */
691 1
  public function save($filepath = '')
692
  {
693 1
    $string = $this->innerHtml();
694 1
    if ($filepath !== '') {
695
      file_put_contents($filepath, $string, LOCK_EX);
696
    }
697
698 1
    return $string;
699
  }
700
701
  /**
702
   * @param $functionName
703
   */
704
  public function set_callback($functionName)
705
  {
706
    $this::$callback = $functionName;
707
  }
708
709
  /**
710
   * Get dom node's plain text
711
   *
712
   * @return string
713
   */
714 2
  public function text()
715
  {
716 2
    return $this->fixHtmlOutput($this->document->textContent);
717
  }
718
}
719