PluginProbe
Translate and Go multilingual – Automatic AI translation – wpLingua / 2.10.0
Translate and Go multilingual – Automatic AI translation – wpLingua v2.10.0
2.16.6 2.16.5 2.16.4 2.16.3 2.16.2 2.16.1 2.16.0 2.15.2 2.15.1 2.15.0 2.14.3 2.14.2 2.14.1 2.14.0 2.13.1 2.13.0 2.12.3 2.12.2 2.12.1 trunk 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 All 111 releases
wplingua / inc / lib / simple-html-dom.php

simple-html-dom.php in Translate and Go multilingual – Automatic AI translation – wpLingua 2.10.0, at inc/lib/simple-html-dom.php

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