PluginProbe
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant / 1.1.4
EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant v1.1.4
2.0.4 2.0.3 2.0.2 2.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2 1.3 2.0.0
easyfonts / lib / simple_html_dom.php

simple_html_dom.php in EasyFonts – Host Google Fonts Locally, Fast & Auto-Optimize, GDPR Compliant 1.1.4, at lib/simple_html_dom.php

2,365 lines 59.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Website: http://sourceforge.net/projects/simplehtmldom/
4 * Additional projects: http://sourceforge.net/projects/debugobject/
5 * Acknowledge: Jose Solorzano (https://sourceforge.net/projects/php-html/)
6 *
7 * Licensed under The MIT License
8 * See the LICENSE file in the project root for more information.
9 *
10 * Authors:
11 * S.C. Chen
12 * John Schlick
13 * Rus Carroll
14 * logmanoriginal
15 *
16 * Contributors:
17 * Yousuke Kumakura
18 * Vadim Voituk
19 * Antcs
20 *
21 * Version Rev. 1.9.1 (291)
22 */
23
24
25 if (!defined('ABSPATH')){ exit(); }
26
27 # edited
28 defined('HGFLMAX_FILE_SIZE') || define('HGFLMAX_FILE_SIZE', 2000000); # Process HTML up to 2 Mb
29 defined('HGFLDEFAULT_TARGET_CHARSET') || define('HGFLDEFAULT_TARGET_CHARSET', 'UTF-8');
30 defined('HGFLDEFAULT_BR_TEXT') || define('HGFLDEFAULT_BR_TEXT', "\r\n");
31 defined('HGFLDEFAULT_SPAN_TEXT') || define('HGFLDEFAULT_SPAN_TEXT', ' ');
32
33 define('HGFLHDOM_TYPE_ELEMENT', 1);
34 define('HGFLHDOM_TYPE_COMMENT', 2);
35 define('HGFLHDOM_TYPE_TEXT', 3);
36 define('HGFLHDOM_TYPE_ENDTAG', 4);
37 define('HGFLHDOM_TYPE_ROOT', 5);
38 define('HGFLHDOM_TYPE_UNKNOWN', 6);
39 define('HGFLHDOM_QUOTE_DOUBLE', 0);
40 define('HGFLHDOM_QUOTE_SINGLE', 1);
41 define('HGFLHDOM_QUOTE_NO', 3);
42 define('HGFLHDOM_INFO_BEGIN', 0);
43 define('HGFLHDOM_INFO_END', 1);
44 define('HGFLHDOM_INFO_QUOTE', 2);
45 define('HGFLHDOM_INFO_SPACE', 3);
46 define('HGFLHDOM_INFO_TEXT', 4);
47 define('HGFLHDOM_INFO_INNER', 5);
48 define('HGFLHDOM_INFO_OUTER', 6);
49 define('HGFLHDOM_INFO_ENDSPACE', 7);
50
51 defined('HGFLDEFAULT_TARGET_CHARSET') || define('HGFLDEFAULT_TARGET_CHARSET', 'UTF-8');
52 defined('HGFLDEFAULT_BR_TEXT') || define('HGFLDEFAULT_BR_TEXT', "\r\n");
53 defined('HGFLDEFAULT_SPAN_TEXT') || define('HGFLDEFAULT_SPAN_TEXT', ' ');
54 defined('HGFLMAX_FILE_SIZE') || define('HGFLMAX_FILE_SIZE', 600000);
55 define('HGFLHDOM_SMARTY_AS_TEXT', 1);
56
57 function hgfl_file_get_html(
58 $url,
59 $use_include_path = false,
60 $context = null,
61 $offset = 0,
62 $maxLen = -1,
63 $lowercase = true,
64 $forceTagsClosed = true,
65 $target_charset = HGFLDEFAULT_TARGET_CHARSET,
66 $stripRN = true,
67 $defaultBRText = HGFLDEFAULT_BR_TEXT,
68 $defaultSpanText = HGFLDEFAULT_SPAN_TEXT)
69 {
70 if($maxLen <= 0) { $maxLen = HGFLMAX_FILE_SIZE; }
71
72 $dom = new hgfl_simple_html_dom(
73 null,
74 $lowercase,
75 $forceTagsClosed,
76 $target_charset,
77 $stripRN,
78 $defaultBRText,
79 $defaultSpanText
80 );
81
82 /**
83 * For sourceforge users: uncomment the next line and comment the
84 * retrieve_url_contents line 2 lines down if it is not already done.
85 */
86 $contents = file_get_contents(
87 $url,
88 $use_include_path,
89 $context,
90 $offset,
91 $maxLen
92 );
93 // $contents = retrieve_url_contents($url);
94
95 if (empty($contents) || strlen($contents) > $maxLen) {
96 $dom->clear();
97 return false;
98 }
99
100 return $dom->load($contents, $lowercase, $stripRN);
101 }
102
103 function hgfl_str_get_html(
104 $str,
105 $lowercase = true,
106 $forceTagsClosed = true,
107 $target_charset = HGFLDEFAULT_TARGET_CHARSET,
108 $stripRN = true,
109 $defaultBRText = HGFLDEFAULT_BR_TEXT,
110 $defaultSpanText = HGFLDEFAULT_SPAN_TEXT)
111 {
112 $dom = new hgfl_simple_html_dom(
113 null,
114 $lowercase,
115 $forceTagsClosed,
116 $target_charset,
117 $stripRN,
118 $defaultBRText,
119 $defaultSpanText
120 );
121
122 if (empty($str) || strlen($str) > HGFLMAX_FILE_SIZE) {
123 $dom->clear();
124 return false;
125 }
126
127 return $dom->load($str, $lowercase, $stripRN);
128 }
129
130 function hgfl_dump_html_tree($node, $show_attr = true, $deep = 0)
131 {
132 $node->dump($node);
133 }
134
135 class hgfl_simple_html_dom_node
136 {
137 public $nodetype = HGFLHDOM_TYPE_TEXT;
138 public $tag = 'text';
139 public $attr = array();
140 public $children = array();
141 public $nodes = array();
142 public $parent = null;
143 public $_ = array();
144 public $tag_start = 0;
145 private $dom = null;
146
147 function __construct($dom)
148 {
149 $this->dom = $dom;
150 $dom->nodes[] = $this;
151 }
152
153 function __destruct()
154 {
155 $this->clear();
156 }
157
158 function __toString()
159 {
160 return $this->outertext();
161 }
162
163 function clear()
164 {
165 $this->dom = null;
166 $this->nodes = null;
167 $this->parent = null;
168 $this->children = null;
169 }
170
171 function dump($show_attr = true, $depth = 0)
172 {
173 echo str_repeat("\t", $depth) . $this->tag;
174
175 if ($show_attr && count($this->attr) > 0) {
176 echo '(';
177 foreach ($this->attr as $k => $v) {
178 echo "[$k]=>\"$v\", ";
179 }
180 echo ')';
181 }
182
183 echo "\n";
184
185 if ($this->nodes) {
186 foreach ($this->nodes as $node) {
187 $node->dump($show_attr, $depth + 1);
188 }
189 }
190 }
191
192 function dump_node($echo = true)
193 {
194 $string = $this->tag;
195
196 if (count($this->attr) > 0) {
197 $string .= '(';
198 foreach ($this->attr as $k => $v) {
199 $string .= "[$k]=>\"$v\", ";
200 }
201 $string .= ')';
202 }
203
204 if (count($this->_) > 0) {
205 $string .= ' $_ (';
206 foreach ($this->_ as $k => $v) {
207 if (is_array($v)) {
208 $string .= "[$k]=>(";
209 foreach ($v as $k2 => $v2) {
210 $string .= "[$k2]=>\"$v2\", ";
211 }
212 $string .= ')';
213 } else {
214 $string .= "[$k]=>\"$v\", ";
215 }
216 }
217 $string .= ')';
218 }
219
220 if (isset($this->text)) {
221 $string .= " text: ({$this->text})";
222 }
223
224 $string .= ' HGFLHDOM_INNER_INFO: ';
225
226 if (isset($node->_[HGFLHDOM_INFO_INNER])) {
227 $string .= "'" . $node->_[HGFLHDOM_INFO_INNER] . "'";
228 } else {
229 $string .= ' NULL ';
230 }
231
232 $string .= ' children: ' . count($this->children);
233 $string .= ' nodes: ' . count($this->nodes);
234 $string .= ' tag_start: ' . $this->tag_start;
235 $string .= "\n";
236
237 if ($echo) {
238 echo $string;
239 return;
240 } else {
241 return $string;
242 }
243 }
244
245 function parent($parent = null)
246 {
247 // I am SURE that this doesn't work properly.
248 // It fails to unset the current node from it's current parents nodes or
249 // children list first.
250 if ($parent !== null) {
251 $this->parent = $parent;
252 $this->parent->nodes[] = $this;
253 $this->parent->children[] = $this;
254 }
255
256 return $this->parent;
257 }
258
259 function has_child()
260 {
261 return !empty($this->children);
262 }
263
264 function children($idx = -1)
265 {
266 if ($idx === -1) {
267 return $this->children;
268 }
269
270 if (isset($this->children[$idx])) {
271 return $this->children[$idx];
272 }
273
274 return null;
275 }
276
277 function first_child()
278 {
279 if (count($this->children) > 0) {
280 return $this->children[0];
281 }
282 return null;
283 }
284
285 function last_child()
286 {
287 if (count($this->children) > 0) {
288 return end($this->children);
289 }
290 return null;
291 }
292
293 function next_sibling()
294 {
295 if ($this->parent === null) {
296 return null;
297 }
298
299 $idx = array_search($this, $this->parent->children, true);
300
301 if ($idx !== false && isset($this->parent->children[$idx + 1])) {
302 return $this->parent->children[$idx + 1];
303 }
304
305 return null;
306 }
307
308 function prev_sibling()
309 {
310 if ($this->parent === null) {
311 return null;
312 }
313
314 $idx = array_search($this, $this->parent->children, true);
315
316 if ($idx !== false && $idx > 0) {
317 return $this->parent->children[$idx - 1];
318 }
319
320 return null;
321 }
322
323 function find_ancestor_tag($tag)
324 {
325 global $debug_object;
326 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
327
328 if ($this->parent === null) {
329 return null;
330 }
331
332 $ancestor = $this->parent;
333
334 while (!is_null($ancestor)) {
335 if (is_object($debug_object)) {
336 $debug_object->debug_log(2, 'Current tag is: ' . $ancestor->tag);
337 }
338
339 if ($ancestor->tag === $tag) {
340 break;
341 }
342
343 $ancestor = $ancestor->parent;
344 }
345
346 return $ancestor;
347 }
348
349 function innertext()
350 {
351 if (isset($this->_[HGFLHDOM_INFO_INNER])) {
352 return $this->_[HGFLHDOM_INFO_INNER];
353 }
354
355 if (isset($this->_[HGFLHDOM_INFO_TEXT])) {
356 return $this->dom->restore_noise($this->_[HGFLHDOM_INFO_TEXT]);
357 }
358
359 $ret = '';
360
361 foreach ($this->nodes as $n) {
362 $ret .= $n->outertext();
363 }
364
365 return $ret;
366 }
367
368 function outertext()
369 {
370 global $debug_object;
371
372 if (is_object($debug_object)) {
373 $text = '';
374
375 if ($this->tag === 'text') {
376 if (!empty($this->text)) {
377 $text = ' with text: ' . $this->text;
378 }
379 }
380
381 $debug_object->debug_log(1, 'Innertext of tag: ' . $this->tag . $text);
382 }
383
384 if ($this->tag === 'root') {
385 return $this->innertext();
386 }
387
388 // todo: What is the use of this callback? Remove?
389 if ($this->dom && $this->dom->callback !== null) {
390 call_user_func_array($this->dom->callback, array($this));
391 }
392
393 if (isset($this->_[HGFLHDOM_INFO_OUTER])) {
394 return $this->_[HGFLHDOM_INFO_OUTER];
395 }
396
397 if (isset($this->_[HGFLHDOM_INFO_TEXT])) {
398 return $this->dom->restore_noise($this->_[HGFLHDOM_INFO_TEXT]);
399 }
400
401 $ret = '';
402
403 if ($this->dom && $this->dom->nodes[$this->_[HGFLHDOM_INFO_BEGIN]]) {
404 $ret = $this->dom->nodes[$this->_[HGFLHDOM_INFO_BEGIN]]->makeup();
405 }
406
407 if (isset($this->_[HGFLHDOM_INFO_INNER])) {
408 // todo: <br> should either never have HGFLHDOM_INFO_INNER or always
409 if ($this->tag !== 'br') {
410 $ret .= $this->_[HGFLHDOM_INFO_INNER];
411 }
412 } elseif ($this->nodes) {
413 foreach ($this->nodes as $n) {
414 $ret .= $this->convert_text($n->outertext());
415 }
416 }
417
418 if (isset($this->_[HGFLHDOM_INFO_END]) && $this->_[HGFLHDOM_INFO_END] != 0) {
419 $ret .= '</' . $this->tag . '>';
420 }
421
422 return $ret;
423 }
424
425 function text()
426 {
427 if (isset($this->_[HGFLHDOM_INFO_INNER])) {
428 return $this->_[HGFLHDOM_INFO_INNER];
429 }
430
431 switch ($this->nodetype) {
432 case HGFLHDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HGFLHDOM_INFO_TEXT]);
433 case HGFLHDOM_TYPE_COMMENT: return '';
434 case HGFLHDOM_TYPE_UNKNOWN: return '';
435 }
436
437 if (strcasecmp($this->tag, 'script') === 0) { return ''; }
438 if (strcasecmp($this->tag, 'style') === 0) { return ''; }
439
440 $ret = '';
441
442 // In rare cases, (always node type 1 or HGFLHDOM_TYPE_ELEMENT - observed
443 // for some span tags, and some p tags) $this->nodes is set to NULL.
444 // NOTE: This indicates that there is a problem where it's set to NULL
445 // without a clear happening.
446 // WHY is this happening?
447 if (!is_null($this->nodes)) {
448 foreach ($this->nodes as $n) {
449 // Start paragraph after a blank line
450 if ($n->tag === 'p') {
451 $ret = trim($ret) . "\n\n";
452 }
453
454 $ret .= $this->convert_text($n->text());
455
456 // If this node is a span... add a space at the end of it so
457 // multiple spans don't run into each other. This is plaintext
458 // after all.
459 if ($n->tag === 'span') {
460 $ret .= $this->dom->HGFLDEFAULT_span_text;
461 }
462 }
463 }
464 return $ret;
465 }
466
467 function xmltext()
468 {
469 $ret = $this->innertext();
470 $ret = str_ireplace('<![CDATA[', '', $ret);
471 $ret = str_replace(']]>', '', $ret);
472 return $ret;
473 }
474
475 function makeup()
476 {
477 // text, comment, unknown
478 if (isset($this->_[HGFLHDOM_INFO_TEXT])) {
479 return $this->dom->restore_noise($this->_[HGFLHDOM_INFO_TEXT]);
480 }
481
482 $ret = '<' . $this->tag;
483 $i = -1;
484
485 foreach ($this->attr as $key => $val) {
486 ++$i;
487
488 // skip removed attribute
489 if ($val === null || $val === false) { continue; }
490
491 $ret .= $this->_[HGFLHDOM_INFO_SPACE][$i][0];
492
493 //no value attr: nowrap, checked selected...
494 if ($val === true) {
495 $ret .= $key;
496 } else {
497 switch ($this->_[HGFLHDOM_INFO_QUOTE][$i])
498 {
499 case HGFLHDOM_QUOTE_DOUBLE: $quote = '"'; break;
500 case HGFLHDOM_QUOTE_SINGLE: $quote = '\''; break;
501 default: $quote = '';
502 }
503
504 $ret .= $key
505 . $this->_[HGFLHDOM_INFO_SPACE][$i][1]
506 . '='
507 . $this->_[HGFLHDOM_INFO_SPACE][$i][2]
508 . $quote
509 . $val
510 . $quote;
511 }
512 }
513
514 $ret = $this->dom->restore_noise($ret);
515 return $ret . $this->_[HGFLHDOM_INFO_ENDSPACE] . '>';
516 }
517
518 function find($selector, $idx = null, $lowercase = false)
519 {
520 $selectors = $this->parse_selector($selector);
521 if (($count = count($selectors)) === 0) { return array(); }
522 $found_keys = array();
523
524 // find each selector
525 for ($c = 0; $c < $count; ++$c) {
526 // The change on the below line was documented on the sourceforge
527 // code tracker id 2788009
528 // used to be: if (($levle=count($selectors[0]))===0) return array();
529 if (($levle = count($selectors[$c])) === 0) { return array(); }
530 if (!isset($this->_[HGFLHDOM_INFO_BEGIN])) { return array(); }
531
532 $head = array($this->_[HGFLHDOM_INFO_BEGIN] => 1);
533 $cmd = ' '; // Combinator
534
535 // handle descendant selectors, no recursive!
536 for ($l = 0; $l < $levle; ++$l) {
537 $ret = array();
538
539 foreach ($head as $k => $v) {
540 $n = ($k === -1) ? $this->dom->root : $this->dom->nodes[$k];
541 //PaperG - Pass this optional parameter on to the seek function.
542 $n->seek($selectors[$c][$l], $ret, $cmd, $lowercase);
543 }
544
545 $head = $ret;
546 $cmd = $selectors[$c][$l][4]; // Next Combinator
547 }
548
549 foreach ($head as $k => $v) {
550 if (!isset($found_keys[$k])) {
551 $found_keys[$k] = 1;
552 }
553 }
554 }
555
556 // sort keys
557 ksort($found_keys);
558
559 $found = array();
560 foreach ($found_keys as $k => $v) {
561 $found[] = $this->dom->nodes[$k];
562 }
563
564 // return nth-element or array
565 if (is_null($idx)) { return $found; }
566 elseif ($idx < 0) { $idx = count($found) + $idx; }
567 return (isset($found[$idx])) ? $found[$idx] : null;
568 }
569
570 protected function seek($selector, &$ret, $parent_cmd, $lowercase = false)
571 {
572 global $debug_object;
573 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
574
575 list($tag, $id, $class, $attributes, $cmb) = $selector;
576 $nodes = array();
577
578 if ($parent_cmd === ' ') { // Descendant Combinator
579 // Find parent closing tag if the current element doesn't have a closing
580 // tag (i.e. void element)
581 $end = (!empty($this->_[HGFLHDOM_INFO_END])) ? $this->_[HGFLHDOM_INFO_END] : 0;
582 if ($end == 0) {
583 $parent = $this->parent;
584 while (!isset($parent->_[HGFLHDOM_INFO_END]) && $parent !== null) {
585 $end -= 1;
586 $parent = $parent->parent;
587 }
588 $end += $parent->_[HGFLHDOM_INFO_END];
589 }
590
591 // Get list of target nodes
592 $nodes_start = $this->_[HGFLHDOM_INFO_BEGIN] + 1;
593 $nodes_count = $end - $nodes_start;
594 $nodes = array_slice($this->dom->nodes, $nodes_start, $nodes_count, true);
595 } elseif ($parent_cmd === '>') { // Child Combinator
596 $nodes = $this->children;
597 } elseif ($parent_cmd === '+'
598 && $this->parent
599 && in_array($this, $this->parent->children)) { // Next-Sibling Combinator
600 $index = array_search($this, $this->parent->children, true) + 1;
601 if ($index < count($this->parent->children))
602 $nodes[] = $this->parent->children[$index];
603 } elseif ($parent_cmd === '~'
604 && $this->parent
605 && in_array($this, $this->parent->children)) { // Subsequent Sibling Combinator
606 $index = array_search($this, $this->parent->children, true);
607 $nodes = array_slice($this->parent->children, $index);
608 }
609
610 // Go throgh each element starting at this element until the end tag
611 // Note: If this element is a void tag, any previous void element is
612 // skipped.
613 foreach($nodes as $node) {
614 $pass = true;
615
616 // Skip root nodes
617 if(!$node->parent) {
618 $pass = false;
619 }
620
621 // Handle 'text' selector
622 if($pass && $tag === 'text' && $node->tag === 'text') {
623 $ret[array_search($node, $this->dom->nodes, true)] = 1;
624 unset($node);
625 continue;
626 }
627
628 // Skip if node isn't a child node (i.e. text nodes)
629 if($pass && !in_array($node, $node->parent->children, true)) {
630 $pass = false;
631 }
632
633 // Skip if tag doesn't match
634 if ($pass && $tag !== '' && $tag !== $node->tag && $tag !== '*') {
635 $pass = false;
636 }
637
638 // Skip if ID doesn't exist
639 if ($pass && $id !== '' && !isset($node->attr['id'])) {
640 $pass = false;
641 }
642
643 // Check if ID matches
644 if ($pass && $id !== '' && isset($node->attr['id'])) {
645 // Note: Only consider the first ID (as browsers do)
646 $node_id = explode(' ', trim($node->attr['id']))[0];
647
648 if($id !== $node_id) { $pass = false; }
649 }
650
651 // Check if all class(es) exist
652 if ($pass && $class !== '' && is_array($class) && !empty($class)) {
653 if (isset($node->attr['class'])) {
654 $node_classes = explode(' ', $node->attr['class']);
655
656 if ($lowercase) {
657 $node_classes = array_map('strtolower', $node_classes);
658 }
659
660 foreach($class as $c) {
661 if(!in_array($c, $node_classes)) {
662 $pass = false;
663 break;
664 }
665 }
666 } else {
667 $pass = false;
668 }
669 }
670
671 // Check attributes
672 if ($pass
673 && $attributes !== ''
674 && is_array($attributes)
675 && !empty($attributes)) {
676 foreach($attributes as $a) {
677 list (
678 $att_name,
679 $att_expr,
680 $att_val,
681 $att_inv,
682 $att_case_sensitivity
683 ) = $a;
684
685 // Handle indexing attributes (i.e. "[2]")
686 /**
687 * Note: This is not supported by the CSS Standard but adds
688 * the ability to select items compatible to XPath (i.e.
689 * the 3rd element within it's parent).
690 *
691 * Note: This doesn't conflict with the CSS Standard which
692 * doesn't work on numeric attributes anyway.
693 */
694 if (is_numeric($att_name)
695 && $att_expr === ''
696 && $att_val === '') {
697 $count = 0;
698
699 // Find index of current element in parent
700 foreach ($node->parent->children as $c) {
701 if ($c->tag === $node->tag) ++$count;
702 if ($c === $node) break;
703 }
704
705 // If this is the correct node, continue with next
706 // attribute
707 if ($count === (int)$att_name) continue;
708 }
709
710 // Check attribute availability
711 if ($att_inv) { // Attribute should NOT be set
712 if (isset($node->attr[$att_name])) {
713 $pass = false;
714 break;
715 }
716 } else { // Attribute should be set
717 // todo: "plaintext" is not a valid CSS selector!
718 if ($att_name !== 'plaintext'
719 && !isset($node->attr[$att_name])) {
720 $pass = false;
721 break;
722 }
723 }
724
725 // Continue with next attribute if expression isn't defined
726 if ($att_expr === '') continue;
727
728 // If they have told us that this is a "plaintext"
729 // search then we want the plaintext of the node - right?
730 // todo "plaintext" is not a valid CSS selector!
731 if ($att_name === 'plaintext') {
732 $nodeKeyValue = $node->text();
733 } else {
734 $nodeKeyValue = $node->attr[$att_name];
735 }
736
737 if (is_object($debug_object)) {
738 $debug_object->debug_log(2,
739 'testing node: '
740 . $node->tag
741 . ' for attribute: '
742 . $att_name
743 . $att_expr
744 . $att_val
745 . ' where nodes value is: '
746 . $nodeKeyValue
747 );
748 }
749
750 // If lowercase is set, do a case insensitive test of
751 // the value of the selector.
752 if ($lowercase) {
753 $check = $this->match(
754 $att_expr,
755 strtolower($att_val),
756 strtolower($nodeKeyValue),
757 $att_case_sensitivity
758 );
759 } else {
760 $check = $this->match(
761 $att_expr,
762 $att_val,
763 $nodeKeyValue,
764 $att_case_sensitivity
765 );
766 }
767
768 if (is_object($debug_object)) {
769 $debug_object->debug_log(2,
770 'after match: '
771 . ($check ? 'true' : 'false')
772 );
773 }
774
775 if (!$check) {
776 $pass = false;
777 break;
778 }
779 }
780 }
781
782 // Found a match. Add to list and clear node
783 if ($pass) $ret[$node->_[HGFLHDOM_INFO_BEGIN]] = 1;
784 unset($node);
785 }
786 // It's passed by reference so this is actually what this function returns.
787 if (is_object($debug_object)) {
788 $debug_object->debug_log(1, 'EXIT - ret: ', $ret);
789 }
790 }
791
792 protected function match($exp, $pattern, $value, $case_sensitivity)
793 {
794 global $debug_object;
795 if (is_object($debug_object)) {$debug_object->debug_log_entry(1);}
796
797 if ($case_sensitivity === 'i') {
798 $pattern = strtolower($pattern);
799 $value = strtolower($value);
800 }
801
802 switch ($exp) {
803 case '=':
804 return ($value === $pattern);
805 case '!=':
806 return ($value !== $pattern);
807 case '^=':
808 return preg_match('/^' . preg_quote($pattern, '/') . '/', $value);
809 case '$=':
810 return preg_match('/' . preg_quote($pattern, '/') . '$/', $value);
811 case '*=':
812 return preg_match('/' . preg_quote($pattern, '/') . '/', $value);
813 case '|=':
814 /**
815 * [att|=val]
816 *
817 * Represents an element with the att attribute, its value
818 * either being exactly "val" or beginning with "val"
819 * immediately followed by "-" (U+002D).
820 */
821 return strpos($value, $pattern) === 0;
822 case '~=':
823 /**
824 * [att~=val]
825 *
826 * Represents an element with the att attribute whose value is a
827 * whitespace-separated list of words, one of which is exactly
828 * "val". If "val" contains whitespace, it will never represent
829 * anything (since the words are separated by spaces). Also if
830 * "val" is the empty string, it will never represent anything.
831 */
832 return in_array($pattern, explode(' ', trim($value)), true);
833 }
834 return false;
835 }
836
837 protected function parse_selector($selector_string)
838 {
839 global $debug_object;
840 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
841
842 /**
843 * Pattern of CSS selectors, modified from mootools (https://mootools.net/)
844 *
845 * Paperg: Add the colon to the attribute, so that it properly finds
846 * <tag attr:ibute="something" > like google does.
847 *
848 * Note: if you try to look at this attribute, you MUST use getAttribute
849 * since $dom->x:y will fail the php syntax check.
850 *
851 * Notice the \[ starting the attribute? and the @? following? This
852 * implies that an attribute can begin with an @ sign that is not
853 * captured. This implies that an html attribute specifier may start
854 * with an @ sign that is NOT captured by the expression. Farther study
855 * is required to determine of this should be documented or removed.
856 *
857 * Matches selectors in this order:
858 *
859 * [0] - full match
860 *
861 * [1] - tag name
862 * ([\w:\*-]*)
863 * Matches the tag name consisting of zero or more words, colons,
864 * asterisks and hyphens.
865 *
866 * [2] - id name
867 * (?:\#([\w-]+))
868 * Optionally matches a id name, consisting of an "#" followed by
869 * the id name (one or more words and hyphens).
870 *
871 * [3] - class names (including dots)
872 * (?:\.([\w\.-]+))?
873 * Optionally matches a list of classs, consisting of an "."
874 * followed by the class name (one or more words and hyphens)
875 * where multiple classes can be chained (i.e. ".foo.bar.baz")
876 *
877 * [4] - attributes
878 * ((?:\[@?(?:!?[\w:-]+)(?:(?:[!*^$|~]?=)[\"']?(?:.*?)[\"']?)?(?:\s*?(?:[iIsS])?)?\])+)?
879 * Optionally matches the attributes list
880 *
881 * [5] - separator
882 * ([\/, >+~]+)
883 * Matches the selector list separator
884 */
885 // phpcs:ignore Generic.Files.LineLength
886 $pattern = "/([\w:\*-]*)(?:\#([\w-]+))?(?:|\.([\w\.-]+))?((?:\[@?(?:!?[\w:-]+)(?:(?:[!*^$|~]?=)[\"']?(?:.*?)[\"']?)?(?:\s*?(?:[iIsS])?)?\])+)?([\/, >+~]+)/is";
887
888 preg_match_all(
889 $pattern,
890 trim($selector_string) . ' ', // Add final ' ' as pseudo separator
891 $matches,
892 PREG_SET_ORDER
893 );
894
895 if (is_object($debug_object)) {
896 $debug_object->debug_log(2, 'Matches Array: ', $matches);
897 }
898
899 $selectors = array();
900 $result = array();
901
902 foreach ($matches as $m) {
903 $m[0] = trim($m[0]);
904
905 // Skip NoOps
906 if ($m[0] === '' || $m[0] === '/' || $m[0] === '//') { continue; }
907
908 // Convert to lowercase
909 if ($this->dom->lowercase) {
910 $m[1] = strtolower($m[1]);
911 }
912
913 // Extract classes
914 if ($m[3] !== '') { $m[3] = explode('.', $m[3]); }
915
916 /* Extract attributes (pattern based on the pattern above!)
917
918 * [0] - full match
919 * [1] - attribute name
920 * [2] - attribute expression
921 * [3] - attribute value
922 * [4] - case sensitivity
923 *
924 * Note: Attributes can be negated with a "!" prefix to their name
925 */
926 if($m[4] !== '') {
927 preg_match_all(
928 "/\[@?(!?[\w:-]+)(?:([!*^$|~]?=)[\"']?(.*?)[\"']?)?(?:\s+?([iIsS])?)?\]/is",
929 trim($m[4]),
930 $attributes,
931 PREG_SET_ORDER
932 );
933
934 // Replace element by array
935 $m[4] = array();
936
937 foreach($attributes as $att) {
938 // Skip empty matches
939 if(trim($att[0]) === '') { continue; }
940
941 $inverted = (isset($att[1][0]) && $att[1][0] === '!');
942 $m[4][] = array(
943 $inverted ? substr($att[1], 1) : $att[1], // Name
944 (isset($att[2])) ? $att[2] : '', // Expression
945 (isset($att[3])) ? $att[3] : '', // Value
946 $inverted, // Inverted Flag
947 (isset($att[4])) ? strtolower($att[4]) : '', // Case-Sensitivity
948 );
949 }
950 }
951
952 // Sanitize Separator
953 if ($m[5] !== '' && trim($m[5]) === '') { // Descendant Separator
954 $m[5] = ' ';
955 } else { // Other Separator
956 $m[5] = trim($m[5]);
957 }
958
959 // Clear Separator if it's a Selector List
960 if ($is_list = ($m[5] === ',')) { $m[5] = ''; }
961
962 // Remove full match before adding to results
963 array_shift($m);
964 $result[] = $m;
965
966 if ($is_list) { // Selector List
967 $selectors[] = $result;
968 $result = array();
969 }
970 }
971
972 if (count($result) > 0) { $selectors[] = $result; }
973 return $selectors;
974 }
975
976 function __get($name)
977 {
978 if (isset($this->attr[$name])) {
979 return $this->convert_text($this->attr[$name]);
980 }
981 switch ($name) {
982 case 'outertext': return $this->outertext();
983 case 'innertext': return $this->innertext();
984 case 'plaintext': return $this->text();
985 case 'xmltext': return $this->xmltext();
986 default: return array_key_exists($name, $this->attr);
987 }
988 }
989
990 function __set($name, $value)
991 {
992 global $debug_object;
993 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
994
995 switch ($name) {
996 case 'outertext': return $this->_[HGFLHDOM_INFO_OUTER] = $value;
997 case 'innertext':
998 if (isset($this->_[HGFLHDOM_INFO_TEXT])) {
999 return $this->_[HGFLHDOM_INFO_TEXT] = $value;
1000 }
1001 return $this->_[HGFLHDOM_INFO_INNER] = $value;
1002 }
1003
1004 if (!isset($this->attr[$name])) {
1005 $this->_[HGFLHDOM_INFO_SPACE][] = array(' ', '', '');
1006 $this->_[HGFLHDOM_INFO_QUOTE][] = HGFLHDOM_QUOTE_DOUBLE;
1007 }
1008
1009 $this->attr[$name] = $value;
1010 }
1011
1012 function __isset($name)
1013 {
1014 switch ($name) {
1015 case 'outertext': return true;
1016 case 'innertext': return true;
1017 case 'plaintext': return true;
1018 }
1019 //no value attr: nowrap, checked selected...
1020 return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]);
1021 }
1022
1023 function __unset($name)
1024 {
1025 if (isset($this->attr[$name])) { unset($this->attr[$name]); }
1026 }
1027
1028 function convert_text($text)
1029 {
1030 global $debug_object;
1031 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
1032
1033 $converted_text = $text;
1034
1035 $sourceCharset = '';
1036 $targetCharset = '';
1037
1038 if ($this->dom) {
1039 $sourceCharset = strtoupper($this->dom->_charset);
1040 $targetCharset = strtoupper($this->dom->_target_charset);
1041 }
1042
1043 if (is_object($debug_object)) {
1044 $debug_object->debug_log(3,
1045 'source charset: '
1046 . $sourceCharset
1047 . ' target charaset: '
1048 . $targetCharset
1049 );
1050 }
1051
1052 if (!empty($sourceCharset)
1053 && !empty($targetCharset)
1054 && (strcasecmp($sourceCharset, $targetCharset) != 0)) {
1055 // Check if the reported encoding could have been incorrect and the text is actually already UTF-8
1056 if ((strcasecmp($targetCharset, 'UTF-8') == 0)
1057 && ($this->is_utf8($text))) {
1058 $converted_text = $text;
1059 } else {
1060 if(function_exists('mb_substitute_character')) { mb_substitute_character("none"); }
1061 $converted_text= mb_convert_encoding($text, $targetCharset, $sourceCharset);
1062 }
1063 }
1064
1065 // Lets make sure that we don't have that silly BOM issue with any of the utf-8 text we output.
1066 if ($targetCharset === 'UTF-8') {
1067 if (substr($converted_text, 0, 3) === "\xef\xbb\xbf") {
1068 $converted_text = substr($converted_text, 3);
1069 }
1070
1071 if (substr($converted_text, -3) === "\xef\xbb\xbf") {
1072 $converted_text = substr($converted_text, 0, -3);
1073 }
1074 }
1075
1076 return $converted_text;
1077 }
1078
1079 static function is_utf8($str)
1080 {
1081 $c = 0; $b = 0;
1082 $bits = 0;
1083 $len = strlen($str);
1084 for($i = 0; $i < $len; $i++) {
1085 $c = ord($str[$i]);
1086 if($c > 128) {
1087 if(($c >= 254)) { return false; }
1088 elseif($c >= 252) { $bits = 6; }
1089 elseif($c >= 248) { $bits = 5; }
1090 elseif($c >= 240) { $bits = 4; }
1091 elseif($c >= 224) { $bits = 3; }
1092 elseif($c >= 192) { $bits = 2; }
1093 else { return false; }
1094 if(($i + $bits) > $len) { return false; }
1095 while($bits > 1) {
1096 $i++;
1097 $b = ord($str[$i]);
1098 if($b < 128 || $b > 191) { return false; }
1099 $bits--;
1100 }
1101 }
1102 }
1103 return true;
1104 }
1105
1106 function get_display_size()
1107 {
1108 global $debug_object;
1109
1110 $width = -1;
1111 $height = -1;
1112
1113 if ($this->tag !== 'img') {
1114 return false;
1115 }
1116
1117 // See if there is aheight or width attribute in the tag itself.
1118 if (isset($this->attr['width'])) {
1119 $width = $this->attr['width'];
1120 }
1121
1122 if (isset($this->attr['height'])) {
1123 $height = $this->attr['height'];
1124 }
1125
1126 // Now look for an inline style.
1127 if (isset($this->attr['style'])) {
1128 // Thanks to user gnarf from stackoverflow for this regular expression.
1129 $attributes = array();
1130
1131 preg_match_all(
1132 '/([\w-]+)\s*:\s*([^;]+)\s*;?/',
1133 $this->attr['style'],
1134 $matches,
1135 PREG_SET_ORDER
1136 );
1137
1138 foreach ($matches as $match) {
1139 $attributes[$match[1]] = $match[2];
1140 }
1141
1142 // If there is a width in the style attributes:
1143 if (isset($attributes['width']) && $width == -1) {
1144 // check that the last two characters are px (pixels)
1145 if (strtolower(substr($attributes['width'], -2)) === 'px') {
1146 $proposed_width = substr($attributes['width'], 0, -2);
1147 // Now make sure that it's an integer and not something stupid.
1148 if (filter_var($proposed_width, FILTER_VALIDATE_INT)) {
1149 $width = $proposed_width;
1150 }
1151 }
1152 }
1153
1154 // If there is a width in the style attributes:
1155 if (isset($attributes['height']) && $height == -1) {
1156 // check that the last two characters are px (pixels)
1157 if (strtolower(substr($attributes['height'], -2)) == 'px') {
1158 $proposed_height = substr($attributes['height'], 0, -2);
1159 // Now make sure that it's an integer and not something stupid.
1160 if (filter_var($proposed_height, FILTER_VALIDATE_INT)) {
1161 $height = $proposed_height;
1162 }
1163 }
1164 }
1165
1166 }
1167
1168 // Future enhancement:
1169 // Look in the tag to see if there is a class or id specified that has
1170 // a height or width attribute to it.
1171
1172 // Far future enhancement
1173 // Look at all the parent tags of this image to see if they specify a
1174 // class or id that has an img selector that specifies a height or width
1175 // Note that in this case, the class or id will have the img subselector
1176 // for it to apply to the image.
1177
1178 // ridiculously far future development
1179 // If the class or id is specified in a SEPARATE css file thats not on
1180 // the page, go get it and do what we were just doing for the ones on
1181 // the page.
1182
1183 $result = array(
1184 'height' => $height,
1185 'width' => $width
1186 );
1187
1188 return $result;
1189 }
1190
1191 function save($filepath = '')
1192 {
1193 $ret = $this->outertext();
1194
1195 if ($filepath !== '') {
1196 file_put_contents($filepath, $ret, LOCK_EX);
1197 }
1198
1199 return $ret;
1200 }
1201
1202 function addClass($class)
1203 {
1204 if (is_string($class)) {
1205 $class = explode(' ', $class);
1206 }
1207
1208 if (is_array($class)) {
1209 foreach($class as $c) {
1210 if (isset($this->class)) {
1211 if ($this->hasClass($c)) {
1212 continue;
1213 } else {
1214 $this->class .= ' ' . $c;
1215 }
1216 } else {
1217 $this->class = $c;
1218 }
1219 }
1220 } else {
1221 if (is_object($debug_object)) {
1222 $debug_object->debug_log(2, 'Invalid type: ', gettype($class));
1223 }
1224 }
1225 }
1226
1227 function hasClass($class)
1228 {
1229 if (is_string($class)) {
1230 if (isset($this->class)) {
1231 return in_array($class, explode(' ', $this->class), true);
1232 }
1233 } else {
1234 if (is_object($debug_object)) {
1235 $debug_object->debug_log(2, 'Invalid type: ', gettype($class));
1236 }
1237 }
1238
1239 return false;
1240 }
1241
1242 function removeClass($class = null)
1243 {
1244 if (!isset($this->class)) {
1245 return;
1246 }
1247
1248 if (is_null($class)) {
1249 $this->removeAttribute('class');
1250 return;
1251 }
1252
1253 if (is_string($class)) {
1254 $class = explode(' ', $class);
1255 }
1256
1257 if (is_array($class)) {
1258 $class = array_diff(explode(' ', $this->class), $class);
1259 if (empty($class)) {
1260 $this->removeAttribute('class');
1261 } else {
1262 $this->class = implode(' ', $class);
1263 }
1264 }
1265 }
1266
1267 function getAllAttributes()
1268 {
1269 return $this->attr;
1270 }
1271
1272 function getAttribute($name)
1273 {
1274 return $this->__get($name);
1275 }
1276
1277 function setAttribute($name, $value)
1278 {
1279 $this->__set($name, $value);
1280 }
1281
1282 function hasAttribute($name)
1283 {
1284 return $this->__isset($name);
1285 }
1286
1287 function removeAttribute($name)
1288 {
1289 $this->__set($name, null);
1290 }
1291
1292 function remove()
1293 {
1294 if ($this->parent) {
1295 $this->parent->removeChild($this);
1296 }
1297 }
1298
1299 function removeChild($node)
1300 {
1301 $nidx = array_search($node, $this->nodes, true);
1302 $cidx = array_search($node, $this->children, true);
1303 $didx = array_search($node, $this->dom->nodes, true);
1304
1305 if ($nidx !== false && $cidx !== false && $didx !== false) {
1306
1307 foreach($node->children as $child) {
1308 $node->removeChild($child);
1309 }
1310
1311 foreach($node->nodes as $entity) {
1312 $enidx = array_search($entity, $node->nodes, true);
1313 $edidx = array_search($entity, $node->dom->nodes, true);
1314
1315 if ($enidx !== false && $edidx !== false) {
1316 unset($node->nodes[$enidx]);
1317 unset($node->dom->nodes[$edidx]);
1318 }
1319 }
1320
1321 unset($this->nodes[$nidx]);
1322 unset($this->children[$cidx]);
1323 unset($this->dom->nodes[$didx]);
1324
1325 $node->clear();
1326
1327 }
1328 }
1329
1330 function getElementById($id)
1331 {
1332 return $this->find("#$id", 0);
1333 }
1334
1335 function getElementsById($id, $idx = null)
1336 {
1337 return $this->find("#$id", $idx);
1338 }
1339
1340 function getElementByTagName($name)
1341 {
1342 return $this->find($name, 0);
1343 }
1344
1345 function getElementsByTagName($name, $idx = null)
1346 {
1347 return $this->find($name, $idx);
1348 }
1349
1350 function parentNode()
1351 {
1352 return $this->parent();
1353 }
1354
1355 function childNodes($idx = -1)
1356 {
1357 return $this->children($idx);
1358 }
1359
1360 function firstChild()
1361 {
1362 return $this->first_child();
1363 }
1364
1365 function lastChild()
1366 {
1367 return $this->last_child();
1368 }
1369
1370 function nextSibling()
1371 {
1372 return $this->next_sibling();
1373 }
1374
1375 function previousSibling()
1376 {
1377 return $this->prev_sibling();
1378 }
1379
1380 function hasChildNodes()
1381 {
1382 return $this->has_child();
1383 }
1384
1385 function nodeName()
1386 {
1387 return $this->tag;
1388 }
1389
1390 function appendChild($node)
1391 {
1392 $node->parent($this);
1393 return $node;
1394 }
1395
1396 }
1397
1398 class hgfl_simple_html_dom
1399 {
1400 public $root = null;
1401 public $nodes = array();
1402 public $callback = null;
1403 public $lowercase = false;
1404 public $original_size;
1405 public $size;
1406
1407 protected $pos;
1408 protected $doc;
1409 protected $char;
1410
1411 protected $cursor;
1412 protected $parent;
1413 protected $noise = array();
1414 protected $token_blank = " \t\r\n";
1415 protected $token_equal = ' =/>';
1416 protected $token_slash = " />\r\n\t";
1417 protected $token_attr = ' >';
1418
1419 public $_charset = '';
1420 public $_target_charset = '';
1421
1422 protected $HGFLDEFAULT_br_text = '';
1423
1424 public $HGFLDEFAULT_span_text = '';
1425
1426 protected $self_closing_tags = array(
1427 'area' => 1,
1428 'base' => 1,
1429 'br' => 1,
1430 'col' => 1,
1431 'embed' => 1,
1432 'hr' => 1,
1433 'img' => 1,
1434 'input' => 1,
1435 'link' => 1,
1436 'meta' => 1,
1437 'param' => 1,
1438 'source' => 1,
1439 'track' => 1,
1440 'wbr' => 1
1441 );
1442 protected $block_tags = array(
1443 'body' => 1,
1444 'div' => 1,
1445 'form' => 1,
1446 'root' => 1,
1447 'span' => 1,
1448 'table' => 1
1449 );
1450 protected $optional_closing_tags = array(
1451 // Not optional, see
1452 // https://www.w3.org/TR/html/textlevel-semantics.html#the-b-element
1453 'b' => array('b' => 1),
1454 'dd' => array('dd' => 1, 'dt' => 1),
1455 // Not optional, see
1456 // https://www.w3.org/TR/html/grouping-content.html#the-dl-element
1457 'dl' => array('dd' => 1, 'dt' => 1),
1458 'dt' => array('dd' => 1, 'dt' => 1),
1459 'li' => array('li' => 1),
1460 'optgroup' => array('optgroup' => 1, 'option' => 1),
1461 'option' => array('optgroup' => 1, 'option' => 1),
1462 'p' => array('p' => 1),
1463 'rp' => array('rp' => 1, 'rt' => 1),
1464 'rt' => array('rp' => 1, 'rt' => 1),
1465 'td' => array('td' => 1, 'th' => 1),
1466 'th' => array('td' => 1, 'th' => 1),
1467 'tr' => array('td' => 1, 'th' => 1, 'tr' => 1),
1468 );
1469
1470 function __construct(
1471 $str = null,
1472 $lowercase = true,
1473 $forceTagsClosed = true,
1474 $target_charset = HGFLDEFAULT_TARGET_CHARSET,
1475 $stripRN = true,
1476 $defaultBRText = HGFLDEFAULT_BR_TEXT,
1477 $defaultSpanText = HGFLDEFAULT_SPAN_TEXT,
1478 $options = 0)
1479 {
1480 if ($str) {
1481 if (preg_match('/^http:\/\//i', $str) || is_file($str)) {
1482 $this->load_file($str);
1483 } else {
1484 $this->load(
1485 $str,
1486 $lowercase,
1487 $stripRN,
1488 $defaultBRText,
1489 $defaultSpanText,
1490 $options
1491 );
1492 }
1493 }
1494 // Forcing tags to be closed implies that we don't trust the html, but
1495 // it can lead to parsing errors if we SHOULD trust the html.
1496 if (!$forceTagsClosed) {
1497 $this->optional_closing_array = array();
1498 }
1499
1500 $this->_target_charset = $target_charset;
1501 }
1502
1503 function __destruct()
1504 {
1505 $this->clear();
1506 }
1507
1508 function load(
1509 $str,
1510 $lowercase = true,
1511 $stripRN = true,
1512 $defaultBRText = HGFLDEFAULT_BR_TEXT,
1513 $defaultSpanText = HGFLDEFAULT_SPAN_TEXT,
1514 $options = 0)
1515 {
1516 global $debug_object;
1517
1518 // prepare
1519 $this->prepare($str, $lowercase, $defaultBRText, $defaultSpanText);
1520
1521 // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037
1522 // Script tags removal now preceeds style tag removal.
1523 // strip out <script> tags
1524 $this->remove_noise("'<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>'is");
1525 $this->remove_noise("'<\s*script\s*>(.*?)<\s*/\s*script\s*>'is");
1526
1527 // strip out the \r \n's if we are told to.
1528 if ($stripRN) {
1529 $this->doc = str_replace("\r", ' ', $this->doc);
1530 $this->doc = str_replace("\n", ' ', $this->doc);
1531
1532 // set the length of content since we have changed it.
1533 $this->size = strlen($this->doc);
1534 }
1535
1536 // strip out cdata
1537 $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is", true);
1538 // strip out comments
1539 $this->remove_noise("'<!--(.*?)-->'is");
1540 // strip out <style> tags
1541 $this->remove_noise("'<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>'is");
1542 $this->remove_noise("'<\s*style\s*>(.*?)<\s*/\s*style\s*>'is");
1543 // strip out preformatted tags
1544 $this->remove_noise("'<\s*(?:code)[^>]*>(.*?)<\s*/\s*(?:code)\s*>'is");
1545 // strip out server side scripts
1546 $this->remove_noise("'(<\?)(.*?)(\?>)'s", true);
1547
1548 if($options & HGFLHDOM_SMARTY_AS_TEXT) { // Strip Smarty scripts
1549 $this->remove_noise("'(\{\w)(.*?)(\})'s", true);
1550 }
1551
1552 // parsing
1553 $this->parse();
1554 // end
1555 $this->root->_[HGFLHDOM_INFO_END] = $this->cursor;
1556 $this->parse_charset();
1557
1558 // make load function chainable
1559 return $this;
1560 }
1561
1562 function load_file()
1563 {
1564 $args = func_get_args();
1565
1566 if(($doc = call_user_func_array('file_get_contents', $args)) !== false) {
1567 $this->load($doc, true);
1568 } else {
1569 return false;
1570 }
1571 }
1572
1573 function set_callback($function_name)
1574 {
1575 $this->callback = $function_name;
1576 }
1577
1578 function remove_callback()
1579 {
1580 $this->callback = null;
1581 }
1582
1583 function save($filepath = '')
1584 {
1585 $ret = $this->root->innertext();
1586 if ($filepath !== '') { file_put_contents($filepath, $ret, LOCK_EX); }
1587 return $ret;
1588 }
1589
1590 function find($selector, $idx = null, $lowercase = false)
1591 {
1592 return $this->root->find($selector, $idx, $lowercase);
1593 }
1594
1595 function clear()
1596 {
1597 if (isset($this->nodes)) {
1598 foreach ($this->nodes as $n) {
1599 $n->clear();
1600 $n = null;
1601 }
1602 }
1603
1604 // This add next line is documented in the sourceforge repository.
1605 // 2977248 as a fix for ongoing memory leaks that occur even with the
1606 // use of clear.
1607 if (isset($this->children)) {
1608 foreach ($this->children as $n) {
1609 $n->clear();
1610 $n = null;
1611 }
1612 }
1613
1614 if (isset($this->parent)) {
1615 $this->parent->clear();
1616 unset($this->parent);
1617 }
1618
1619 if (isset($this->root)) {
1620 $this->root->clear();
1621 unset($this->root);
1622 }
1623
1624 unset($this->doc);
1625 unset($this->noise);
1626 }
1627
1628 function dump($show_attr = true)
1629 {
1630 $this->root->dump($show_attr);
1631 }
1632
1633 protected function prepare(
1634 $str, $lowercase = true,
1635 $defaultBRText = HGFLDEFAULT_BR_TEXT,
1636 $defaultSpanText = HGFLDEFAULT_SPAN_TEXT)
1637 {
1638 $this->clear();
1639
1640 $this->doc = trim($str);
1641 $this->size = strlen($this->doc);
1642 $this->original_size = $this->size; // original size of the html
1643 $this->pos = 0;
1644 $this->cursor = 1;
1645 $this->noise = array();
1646 $this->nodes = array();
1647 $this->lowercase = $lowercase;
1648 $this->HGFLDEFAULT_br_text = $defaultBRText;
1649 $this->HGFLDEFAULT_span_text = $defaultSpanText;
1650 $this->root = new hgfl_simple_html_dom_node($this);
1651 $this->root->tag = 'root';
1652 $this->root->_[HGFLHDOM_INFO_BEGIN] = -1;
1653 $this->root->nodetype = HGFLHDOM_TYPE_ROOT;
1654 $this->parent = $this->root;
1655 if ($this->size > 0) { $this->char = $this->doc[0]; }
1656 }
1657
1658 protected function parse()
1659 {
1660 while (true) {
1661 // Read next tag if there is no text between current position and the
1662 // next opening tag.
1663 if (($s = $this->copy_until_char('<')) === '') {
1664 if($this->read_tag()) {
1665 continue;
1666 } else {
1667 return true;
1668 }
1669 }
1670
1671 // Add a text node for text between tags
1672 $node = new hgfl_simple_html_dom_node($this);
1673 ++$this->cursor;
1674 $node->_[HGFLHDOM_INFO_TEXT] = $s;
1675 $this->link_nodes($node, false);
1676 }
1677 }
1678
1679 protected function parse_charset()
1680 {
1681 global $debug_object;
1682
1683 $charset = null;
1684
1685 if (function_exists('get_last_retrieve_url_contents_content_type')) {
1686 $contentTypeHeader = get_last_retrieve_url_contents_content_type();
1687 $success = preg_match('/charset=(.+)/', $contentTypeHeader, $matches);
1688 if ($success) {
1689 $charset = $matches[1];
1690 if (is_object($debug_object)) {
1691 $debug_object->debug_log(2,
1692 'header content-type found charset of: '
1693 . $charset
1694 );
1695 }
1696 }
1697 }
1698
1699 if (empty($charset)) {
1700 // https://www.w3.org/TR/html/document-metadata.html#statedef-http-equiv-content-type
1701 $el = $this->root->find('meta[http-equiv=Content-Type]', 0, true);
1702
1703 if (!empty($el)) {
1704 $fullvalue = $el->content;
1705 if (is_object($debug_object)) {
1706 $debug_object->debug_log(2,
1707 'meta content-type tag found'
1708 . $fullvalue
1709 );
1710 }
1711
1712 if (!empty($fullvalue)) {
1713 $success = preg_match(
1714 '/charset=(.+)/i',
1715 $fullvalue,
1716 $matches
1717 );
1718
1719 if ($success) {
1720 $charset = $matches[1];
1721 } else {
1722 // If there is a meta tag, and they don't specify the
1723 // character set, research says that it's typically
1724 // ISO-8859-1
1725 if (is_object($debug_object)) {
1726 $debug_object->debug_log(2,
1727 'meta content-type tag couldn\'t be parsed. using iso-8859 default.'
1728 );
1729 }
1730
1731 $charset = 'ISO-8859-1';
1732 }
1733 }
1734 }
1735 }
1736
1737 if (empty($charset)) {
1738 // https://www.w3.org/TR/html/document-metadata.html#character-encoding-declaration
1739 if ($meta = $this->root->find('meta[charset]', 0)) {
1740 $charset = $meta->charset;
1741 if (is_object($debug_object)) {
1742 $debug_object->debug_log(2, 'meta charset: ' . $charset);
1743 }
1744 }
1745 }
1746
1747 if (empty($charset)) {
1748 // Try to guess the charset based on the content
1749 // Requires Multibyte String (mbstring) support (optional)
1750 if (function_exists('mb_detect_encoding')) {
1751 /**
1752 * mb_detect_encoding() is not intended to distinguish between
1753 * charsets, especially single-byte charsets. Its primary
1754 * purpose is to detect which multibyte encoding is in use,
1755 * i.e. UTF-8, UTF-16, shift-JIS, etc.
1756 *
1757 * -- https://bugs.php.net/bug.php?id=38138
1758 *
1759 * Adding both CP1251/ISO-8859-5 and CP1252/ISO-8859-1 will
1760 * always result in CP1251/ISO-8859-5 and vice versa.
1761 *
1762 * Thus, only detect if it's either UTF-8 or CP1252/ISO-8859-1
1763 * to stay compatible.
1764 */
1765 $encoding = mb_detect_encoding(
1766 $this->doc,
1767 array( 'UTF-8', 'CP1252', 'ISO-8859-1' )
1768 );
1769
1770 if ($encoding === 'CP1252' || $encoding === 'ISO-8859-1') {
1771 // Due to a limitation of mb_detect_encoding
1772 // 'CP1251'/'ISO-8859-5' will be detected as
1773 // 'CP1252'/'ISO-8859-1'. This will cause iconv to fail, in
1774 // which case we can simply assume it is the other charset.
1775 if (!@iconv('CP1252', 'UTF-8', $this->doc)) {
1776 $encoding = 'CP1251';
1777 }
1778 }
1779
1780 if ($encoding !== false) {
1781 $charset = $encoding;
1782 if (is_object($debug_object)) {
1783 $debug_object->debug_log(2, 'mb_detect: ' . $charset);
1784 }
1785 }
1786 }
1787 }
1788
1789 if (empty($charset)) {
1790 // Assume it's UTF-8 as it is the most likely charset to be used
1791 $charset = 'UTF-8';
1792 if (is_object($debug_object)) {
1793 $debug_object->debug_log(2, 'No match found, assume ' . $charset);
1794 }
1795 }
1796
1797 // Since CP1252 is a superset, if we get one of it's subsets, we want
1798 // it instead.
1799 if ((strtolower($charset) == 'iso-8859-1')
1800 || (strtolower($charset) == 'latin1')
1801 || (strtolower($charset) == 'latin-1')) {
1802 $charset = 'CP1252';
1803 if (is_object($debug_object)) {
1804 $debug_object->debug_log(2,
1805 'replacing ' . $charset . ' with CP1252 as its a superset'
1806 );
1807 }
1808 }
1809
1810 if (is_object($debug_object)) {
1811 $debug_object->debug_log(1, 'EXIT - ' . $charset);
1812 }
1813
1814 return $this->_charset = $charset;
1815 }
1816
1817 protected function read_tag()
1818 {
1819 // Set end position if no further tags found
1820 if ($this->char !== '<') {
1821 $this->root->_[HGFLHDOM_INFO_END] = $this->cursor;
1822 return false;
1823 }
1824
1825 $begin_tag_pos = $this->pos;
1826 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1827
1828 // end tag
1829 if ($this->char === '/') {
1830 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1831
1832 // Skip whitespace in end tags (i.e. in "</ html>")
1833 $this->skip($this->token_blank);
1834 $tag = $this->copy_until_char('>');
1835
1836 // Skip attributes in end tags
1837 if (($pos = strpos($tag, ' ')) !== false) {
1838 $tag = substr($tag, 0, $pos);
1839 }
1840
1841 $parent_lower = strtolower($this->parent->tag);
1842 $tag_lower = strtolower($tag);
1843
1844 // The end tag is supposed to close the parent tag. Handle situations
1845 // when it doesn't
1846 if ($parent_lower !== $tag_lower) {
1847 // Parent tag does not have to be closed necessarily (optional closing tag)
1848 // Current tag is a block tag, so it may close an ancestor
1849 if (isset($this->optional_closing_tags[$parent_lower])
1850 && isset($this->block_tags[$tag_lower])) {
1851
1852 $this->parent->_[HGFLHDOM_INFO_END] = 0;
1853 $org_parent = $this->parent;
1854
1855 // Traverse ancestors to find a matching opening tag
1856 // Stop at root node
1857 while (($this->parent->parent)
1858 && strtolower($this->parent->tag) !== $tag_lower
1859 ){
1860 $this->parent = $this->parent->parent;
1861 }
1862
1863 // If we don't have a match add current tag as text node
1864 if (strtolower($this->parent->tag) !== $tag_lower) {
1865 $this->parent = $org_parent; // restore origonal parent
1866
1867 if ($this->parent->parent) {
1868 $this->parent = $this->parent->parent;
1869 }
1870
1871 $this->parent->_[HGFLHDOM_INFO_END] = $this->cursor;
1872 return $this->as_text_node($tag);
1873 }
1874 } elseif (($this->parent->parent)
1875 && isset($this->block_tags[$tag_lower])
1876 ) {
1877 // Grandparent exists and current tag is a block tag, so our
1878 // parent doesn't have an end tag
1879 $this->parent->_[HGFLHDOM_INFO_END] = 0; // No end tag
1880 $org_parent = $this->parent;
1881
1882 // Traverse ancestors to find a matching opening tag
1883 // Stop at root node
1884 while (($this->parent->parent)
1885 && strtolower($this->parent->tag) !== $tag_lower
1886 ) {
1887 $this->parent = $this->parent->parent;
1888 }
1889
1890 // If we don't have a match add current tag as text node
1891 if (strtolower($this->parent->tag) !== $tag_lower) {
1892 $this->parent = $org_parent; // restore origonal parent
1893 $this->parent->_[HGFLHDOM_INFO_END] = $this->cursor;
1894 return $this->as_text_node($tag);
1895 }
1896 } elseif (($this->parent->parent)
1897 && strtolower($this->parent->parent->tag) === $tag_lower
1898 ) { // Grandparent exists and current tag closes it
1899 $this->parent->_[HGFLHDOM_INFO_END] = 0;
1900 $this->parent = $this->parent->parent;
1901 } else { // Random tag, add as text node
1902 return $this->as_text_node($tag);
1903 }
1904 }
1905
1906 // Set end position of parent tag to current cursor position
1907 $this->parent->_[HGFLHDOM_INFO_END] = $this->cursor;
1908
1909 if ($this->parent->parent) {
1910 $this->parent = $this->parent->parent;
1911 }
1912
1913 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1914 return true;
1915 }
1916
1917 // start tag
1918 $node = new hgfl_simple_html_dom_node($this);
1919 $node->_[HGFLHDOM_INFO_BEGIN] = $this->cursor;
1920 ++$this->cursor;
1921 $tag = $this->copy_until($this->token_slash); // Get tag name
1922 $node->tag_start = $begin_tag_pos;
1923
1924 // doctype, cdata & comments...
1925 // <!DOCTYPE html>
1926 // <![CDATA[ ... ]]>
1927 // <!-- Comment -->
1928 if (isset($tag[0]) && $tag[0] === '!') {
1929 $node->_[HGFLHDOM_INFO_TEXT] = '<' . $tag . $this->copy_until_char('>');
1930
1931 if (isset($tag[2]) && $tag[1] === '-' && $tag[2] === '-') { // Comment ("<!--")
1932 $node->nodetype = HGFLHDOM_TYPE_COMMENT;
1933 $node->tag = 'comment';
1934 } else { // Could be doctype or CDATA but we don't care
1935 $node->nodetype = HGFLHDOM_TYPE_UNKNOWN;
1936 $node->tag = 'unknown';
1937 }
1938
1939 if ($this->char === '>') { $node->_[HGFLHDOM_INFO_TEXT] .= '>'; }
1940
1941 $this->link_nodes($node, true);
1942 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1943 return true;
1944 }
1945
1946 // The start tag cannot contain another start tag, if so add as text
1947 // i.e. "<<html>"
1948 if ($pos = strpos($tag, '<') !== false) {
1949 $tag = '<' . substr($tag, 0, -1);
1950 $node->_[HGFLHDOM_INFO_TEXT] = $tag;
1951 $this->link_nodes($node, false);
1952 $this->char = $this->doc[--$this->pos]; // prev
1953 return true;
1954 }
1955
1956 // Handle invalid tag names (i.e. "<html#doc>")
1957 if (!preg_match('/^\w[\w:-]*$/', $tag)) {
1958 $node->_[HGFLHDOM_INFO_TEXT] = '<' . $tag . $this->copy_until('<>');
1959
1960 // Next char is the beginning of a new tag, don't touch it.
1961 if ($this->char === '<') {
1962 $this->link_nodes($node, false);
1963 return true;
1964 }
1965
1966 // Next char closes current tag, add and be done with it.
1967 if ($this->char === '>') { $node->_[HGFLHDOM_INFO_TEXT] .= '>'; }
1968 $this->link_nodes($node, false);
1969 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
1970 return true;
1971 }
1972
1973 // begin tag, add new node
1974 $node->nodetype = HGFLHDOM_TYPE_ELEMENT;
1975 $tag_lower = strtolower($tag);
1976 $node->tag = ($this->lowercase) ? $tag_lower : $tag;
1977
1978 // handle optional closing tags
1979 if (isset($this->optional_closing_tags[$tag_lower])) {
1980 // Traverse ancestors to close all optional closing tags
1981 while (isset($this->optional_closing_tags[$tag_lower][strtolower($this->parent->tag)])) {
1982 $this->parent->_[HGFLHDOM_INFO_END] = 0;
1983 $this->parent = $this->parent->parent;
1984 }
1985 $node->parent = $this->parent;
1986 }
1987
1988 $guard = 0; // prevent infinity loop
1989
1990 // [0] Space between tag and first attribute
1991 $space = array($this->copy_skip($this->token_blank), '', '');
1992
1993 // attributes
1994 do {
1995 // Everything until the first equal sign should be the attribute name
1996 $name = $this->copy_until($this->token_equal);
1997
1998 if ($name === '' && $this->char !== null && $space[0] === '') {
1999 break;
2000 }
2001
2002 if ($guard === $this->pos) { // Escape infinite loop
2003 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2004 continue;
2005 }
2006
2007 $guard = $this->pos;
2008
2009 // handle endless '<'
2010 // Out of bounds before the tag ended
2011 if ($this->pos >= $this->size - 1 && $this->char !== '>') {
2012 $node->nodetype = HGFLHDOM_TYPE_TEXT;
2013 $node->_[HGFLHDOM_INFO_END] = 0;
2014 $node->_[HGFLHDOM_INFO_TEXT] = '<' . $tag . $space[0] . $name;
2015 $node->tag = 'text';
2016 $this->link_nodes($node, false);
2017 return true;
2018 }
2019
2020 // handle mismatch '<'
2021 // Attributes cannot start after opening tag
2022 if ($this->doc[$this->pos - 1] == '<') {
2023 $node->nodetype = HGFLHDOM_TYPE_TEXT;
2024 $node->tag = 'text';
2025 $node->attr = array();
2026 $node->_[HGFLHDOM_INFO_END] = 0;
2027 $node->_[HGFLHDOM_INFO_TEXT] = substr(
2028 $this->doc,
2029 $begin_tag_pos,
2030 $this->pos - $begin_tag_pos - 1
2031 );
2032 $this->pos -= 2;
2033 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2034 $this->link_nodes($node, false);
2035 return true;
2036 }
2037
2038 if ($name !== '/' && $name !== '') { // this is a attribute name
2039 // [1] Whitespace after attribute name
2040 $space[1] = $this->copy_skip($this->token_blank);
2041
2042 $name = $this->restore_noise($name); // might be a noisy name
2043
2044 if ($this->lowercase) { $name = strtolower($name); }
2045
2046 if ($this->char === '=') { // attribute with value
2047 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2048 $this->parse_attr($node, $name, $space); // get attribute value
2049 } else {
2050 //no value attr: nowrap, checked selected...
2051 $node->_[HGFLHDOM_INFO_QUOTE][] = HGFLHDOM_QUOTE_NO;
2052 $node->attr[$name] = true;
2053 if ($this->char != '>') { $this->char = $this->doc[--$this->pos]; } // prev
2054 }
2055
2056 $node->_[HGFLHDOM_INFO_SPACE][] = $space;
2057
2058 // prepare for next attribute
2059 $space = array(
2060 $this->copy_skip($this->token_blank),
2061 '',
2062 ''
2063 );
2064 } else { // no more attributes
2065 break;
2066 }
2067 } while ($this->char !== '>' && $this->char !== '/'); // go until the tag ended
2068
2069 $this->link_nodes($node, true);
2070 $node->_[HGFLHDOM_INFO_ENDSPACE] = $space[0];
2071
2072 // handle empty tags (i.e. "<div/>")
2073 if ($this->copy_until_char('>') === '/') {
2074 $node->_[HGFLHDOM_INFO_ENDSPACE] .= '/';
2075 $node->_[HGFLHDOM_INFO_END] = 0;
2076 } else {
2077 // reset parent
2078 if (!isset($this->self_closing_tags[strtolower($node->tag)])) {
2079 $this->parent = $node;
2080 }
2081 }
2082
2083 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2084
2085 // If it's a BR tag, we need to set it's text to the default text.
2086 // This way when we see it in plaintext, we can generate formatting that the user wants.
2087 // since a br tag never has sub nodes, this works well.
2088 if ($node->tag === 'br') {
2089 $node->_[HGFLHDOM_INFO_INNER] = $this->HGFLDEFAULT_br_text;
2090 }
2091
2092 return true;
2093 }
2094
2095 protected function parse_attr($node, $name, &$space)
2096 {
2097 $is_duplicate = isset($node->attr[$name]);
2098
2099 if (!$is_duplicate) // Copy whitespace between "=" and value
2100 $space[2] = $this->copy_skip($this->token_blank);
2101
2102 switch ($this->char) {
2103 case '"':
2104 $quote_type = HGFLHDOM_QUOTE_DOUBLE;
2105 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2106 $value = $this->copy_until_char('"');
2107 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2108 break;
2109 case '\'':
2110 $quote_type = HGFLHDOM_QUOTE_SINGLE;
2111 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2112 $value = $this->copy_until_char('\'');
2113 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2114 break;
2115 default:
2116 $quote_type = HGFLHDOM_QUOTE_NO;
2117 $value = $this->copy_until($this->token_attr);
2118 }
2119
2120 $value = $this->restore_noise($value);
2121
2122 // PaperG: Attributes should not have \r or \n in them, that counts as
2123 // html whitespace.
2124 if ($name !== 'd') {
2125 $value = str_replace("\r", '', $value);
2126 $value = str_replace("\n", '', $value);
2127 }
2128
2129 // PaperG: If this is a "class" selector, lets get rid of the preceeding
2130 // and trailing space since some phgflle leave it in the multi class case.
2131 if ($name === 'class') {
2132 $value = trim($value);
2133 }
2134
2135 if (!$is_duplicate) {
2136 $node->_[HGFLHDOM_INFO_QUOTE][] = $quote_type;
2137 $node->attr[$name] = $value;
2138 }
2139 }
2140
2141 protected function link_nodes(&$node, $is_child)
2142 {
2143 $node->parent = $this->parent;
2144 $this->parent->nodes[] = $node;
2145 if ($is_child) {
2146 $this->parent->children[] = $node;
2147 }
2148 }
2149
2150 protected function as_text_node($tag)
2151 {
2152 $node = new hgfl_simple_html_dom_node($this);
2153 ++$this->cursor;
2154 $node->_[HGFLHDOM_INFO_TEXT] = '</' . $tag . '>';
2155 $this->link_nodes($node, false);
2156 $this->char = (++$this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2157 return true;
2158 }
2159
2160 protected function skip($chars)
2161 {
2162 $this->pos += strspn($this->doc, $chars, $this->pos);
2163 $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2164 }
2165
2166 protected function copy_skip($chars)
2167 {
2168 $pos = $this->pos;
2169 $len = strspn($this->doc, $chars, $pos);
2170 $this->pos += $len;
2171 $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2172 if ($len === 0) { return ''; }
2173 return substr($this->doc, $pos, $len);
2174 }
2175
2176 protected function copy_until($chars)
2177 {
2178 $pos = $this->pos;
2179 $len = strcspn($this->doc, $chars, $pos);
2180 $this->pos += $len;
2181 $this->char = ($this->pos < $this->size) ? $this->doc[$this->pos] : null; // next
2182 return substr($this->doc, $pos, $len);
2183 }
2184
2185 protected function copy_until_char($char)
2186 {
2187 if ($this->char === null) { return ''; }
2188
2189 if (($pos = strpos($this->doc, $char, $this->pos)) === false) {
2190 $ret = substr($this->doc, $this->pos, $this->size - $this->pos);
2191 $this->char = null;
2192 $this->pos = $this->size;
2193 return $ret;
2194 }
2195
2196 if ($pos === $this->pos) { return ''; }
2197
2198 $pos_old = $this->pos;
2199 $this->char = $this->doc[$pos];
2200 $this->pos = $pos;
2201 return substr($this->doc, $pos_old, $pos - $pos_old);
2202 }
2203
2204 protected function remove_noise($pattern, $remove_tag = false)
2205 {
2206 global $debug_object;
2207 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
2208
2209 $count = preg_match_all(
2210 $pattern,
2211 $this->doc,
2212 $matches,
2213 PREG_SET_ORDER | PREG_OFFSET_CAPTURE
2214 );
2215
2216 for ($i = $count - 1; $i > -1; --$i) {
2217 $key = '___noise___' . sprintf('% 5d', count($this->noise) + 1000);
2218
2219 if (is_object($debug_object)) {
2220 $debug_object->debug_log(2, 'key is: ' . $key);
2221 }
2222
2223 $idx = ($remove_tag) ? 0 : 1; // 0 = entire match, 1 = submatch
2224 $this->noise[$key] = $matches[$i][$idx][0];
2225 $this->doc = substr_replace($this->doc, $key, $matches[$i][$idx][1], strlen($matches[$i][$idx][0]));
2226 }
2227
2228 // reset the length of content
2229 $this->size = strlen($this->doc);
2230
2231 if ($this->size > 0) {
2232 $this->char = $this->doc[0];
2233 }
2234 }
2235
2236 function restore_noise($text)
2237 {
2238 global $debug_object;
2239 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
2240
2241 while (($pos = strpos($text, '___noise___')) !== false) {
2242 // Sometimes there is a broken piece of markup, and we don't GET the
2243 // pos+11 etc... token which indicates a problem outside of us...
2244
2245 // todo: "___noise___1000" (or any number with four or more digits)
2246 // in the DOM causes an infinite loop which could be utilized by
2247 // malicious software
2248 if (strlen($text) > $pos + 15) {
2249 $key = '___noise___'
2250 . $text[$pos + 11]
2251 . $text[$pos + 12]
2252 . $text[$pos + 13]
2253 . $text[$pos + 14]
2254 . $text[$pos + 15];
2255
2256 if (is_object($debug_object)) {
2257 $debug_object->debug_log(2, 'located key of: ' . $key);
2258 }
2259
2260 if (isset($this->noise[$key])) {
2261 $text = substr($text, 0, $pos)
2262 . $this->noise[$key]
2263 . substr($text, $pos + 16);
2264 } else {
2265 // do this to prevent an infinite loop.
2266 $text = substr($text, 0, $pos)
2267 . 'UNDEFINED NOISE FOR KEY: '
2268 . $key
2269 . substr($text, $pos + 16);
2270 }
2271 } else {
2272 // There is no valid key being given back to us... We must get
2273 // rid of the ___noise___ or we will have a problem.
2274 $text = substr($text, 0, $pos)
2275 . 'NO NUMERIC NOISE KEY'
2276 . substr($text, $pos + 11);
2277 }
2278 }
2279 return $text;
2280 }
2281
2282 function search_noise($text)
2283 {
2284 global $debug_object;
2285 if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
2286
2287 foreach($this->noise as $noiseElement) {
2288 if (strpos($noiseElement, $text) !== false) {
2289 return $noiseElement;
2290 }
2291 }
2292 }
2293
2294 function __toString()
2295 {
2296 return $this->root->innertext();
2297 }
2298
2299 function __get($name)
2300 {
2301 switch ($name) {
2302 case 'outertext':
2303 return $this->root->innertext();
2304 case 'innertext':
2305 return $this->root->innertext();
2306 case 'plaintext':
2307 return $this->root->text();
2308 case 'charset':
2309 return $this->_charset;
2310 case 'target_charset':
2311 return $this->_target_charset;
2312 }
2313 }
2314
2315 function childNodes($idx = -1)
2316 {
2317 return $this->root->childNodes($idx);
2318 }
2319
2320 function firstChild()
2321 {
2322 return $this->root->first_child();
2323 }
2324
2325 function lastChild()
2326 {
2327 return $this->root->last_child();
2328 }
2329
2330 function createElement($name, $value = null)
2331 {
2332 return @hgfl_str_get_html("<$name>$value</$name>")->firstChild();
2333 }
2334
2335 function createTextNode($value)
2336 {
2337 return @end(hgfl_str_get_html($value)->nodes);
2338 }
2339
2340 function getElementById($id)
2341 {
2342 return $this->find("#$id", 0);
2343 }
2344
2345 function getElementsById($id, $idx = null)
2346 {
2347 return $this->find("#$id", $idx);
2348 }
2349
2350 function getElementByTagName($name)
2351 {
2352 return $this->find($name, 0);
2353 }
2354
2355 function getElementsByTagName($name, $idx = -1)
2356 {
2357 return $this->find($name, $idx);
2358 }
2359
2360 function loadFile()
2361 {
2362 $args = func_get_args();
2363 $this->load_file($args);
2364 }
2365 }