app/Plugin/TaxManagement42/Services/Simple_html_dom.php line 1124

Open in your IDE?
  1. <?php
  2. namespace Plugin\TaxManagement42\Services;
  3. /**
  4.  * Website: http://sourceforge.net/projects/simplehtmldom/
  5.  * Additional projects that may be used: http://sourceforge.net/projects/debugobject/
  6.  * Acknowledge: Jose Solorzano (https://sourceforge.net/projects/php-html/)
  7.  * Contributions by:
  8.  *     Yousuke Kumakura (Attribute filters)
  9.  *     Vadim Voituk (Negative indexes supports of "find" method)
  10.  *     Antcs (Constructor with automatically load contents either text or file/url)
  11.  *
  12.  * all affected sections have comments starting with "PaperG"
  13.  *
  14.  * Paperg - Added case insensitive testing of the value of the selector.
  15.  * Paperg - Added tag_start for the starting index of tags - NOTE: This works but not accurately.
  16.  *  This tag_start gets counted AFTER \r\n have been crushed out, and after the remove_noice calls so it will not reflect the REAL position of the tag in the source,
  17.  *  it will almost always be smaller by some amount.
  18.  *  We use this to determine how far into the file the tag in question is.  This "percentage will never be accurate as the $dom->size is the "real" number of bytes the dom was created from.
  19.  *  but for most purposes, it's a really good estimation.
  20.  * Paperg - Added the forceTagsClosed to the dom constructor.  Forcing tags closed is great for malformed html, but it CAN lead to parsing errors.
  21.  * Allow the user to tell us how much they trust the html.
  22.  * Paperg add the text and plaintext to the selectors for the find syntax.  plaintext implies text in the innertext of a node.  text implies that the tag is a text node.
  23.  * This allows for us to find tags based on the text they contain.
  24.  * Create find_ancestor_tag to see if a tag is - at any level - inside of another specific tag.
  25.  * Paperg: added parse_charset so that we know about the character set of the source document.
  26.  *  NOTE:  If the user's system has a routine called get_last_retrieve_url_contents_content_type availalbe, we will assume it's returning the content-type header from the
  27.  *  last transfer or curl_exec, and we will parse that and use it in preference to any other method of charset detection.
  28.  *
  29.  * Found infinite loop in the case of broken html in restore_noise.  Rewrote to protect from that.
  30.  * PaperG (John Schlick) Added get_display_size for "IMG" tags.
  31.  *
  32.  * Licensed under The MIT License
  33.  * Redistributions of files must retain the above copyright notice.
  34.  *
  35.  * @author S.C. Chen <me578022@gmail.com>
  36.  * @author John Schlick
  37.  * @author Rus Carroll
  38.  * @version 1.5 ($Rev: 210 $)
  39.  * @package PlaceLocalInclude
  40.  * @subpackage simple_html_dom
  41.  */
  42. /**
  43.  * All of the Defines for the classes below.
  44.  * @author S.C. Chen <me578022@gmail.com>
  45.  */
  46.     
  47. define('HDOM_TYPE_ELEMENT'1);
  48. define('HDOM_TYPE_COMMENT'2);
  49. define('HDOM_TYPE_TEXT',    3);
  50. define('HDOM_TYPE_ENDTAG',  4);
  51. define('HDOM_TYPE_ROOT',    5);
  52. define('HDOM_TYPE_UNKNOWN'6);
  53. define('HDOM_QUOTE_DOUBLE'0);
  54. define('HDOM_QUOTE_SINGLE'1);
  55. define('HDOM_QUOTE_NO',     3);
  56. define('HDOM_INFO_BEGIN',   0);
  57. define('HDOM_INFO_END',     1);
  58. define('HDOM_INFO_QUOTE',   2);
  59. define('HDOM_INFO_SPACE',   3);
  60. define('HDOM_INFO_TEXT',    4);
  61. define('HDOM_INFO_INNER',   5);
  62. define('HDOM_INFO_OUTER',   6);
  63. define('HDOM_INFO_ENDSPACE',7);
  64. define('DEFAULT_TARGET_CHARSET''UTF-8');
  65. define('DEFAULT_BR_TEXT'"\r\n");
  66. define('DEFAULT_SPAN_TEXT'" ");
  67. define('MAX_FILE_SIZE'6000000);
  68. // helper functions
  69. // -----------------------------------------------------------------------------
  70. // get html dom from file
  71. // $maxlen is defined in the code as PHP_STREAM_COPY_ALL which is defined as -1.
  72. function file_get_html($url$use_include_path false$context=null$offset = -1$maxLen=-1$lowercase true$forceTagsClosed=true$target_charset DEFAULT_TARGET_CHARSET$stripRN=true$defaultBRText=DEFAULT_BR_TEXT$defaultSpanText=DEFAULT_SPAN_TEXT)
  73. {
  74.     // We DO force the tags to be terminated.
  75.     $dom = new simple_html_dom(null$lowercase$forceTagsClosed$target_charset$stripRN$defaultBRText$defaultSpanText);
  76.     // For sourceforge users: uncomment the next line and comment the retreive_url_contents line 2 lines down if it is not already done.
  77.     $contents file_get_contents($url$use_include_path$context$offset);
  78.     // Paperg - use our own mechanism for getting the contents as we want to control the timeout.
  79.     //$contents = retrieve_url_contents($url);
  80.     if (empty($contents) || strlen($contents) > MAX_FILE_SIZE)
  81.     {
  82.         return false;
  83.     }
  84.     // The second parameter can force the selectors to all be lowercase.
  85.     $dom->load($contents$lowercase$stripRN);
  86.     return $dom;
  87. }
  88. // get html dom from string
  89. function str_get_html($str$lowercase=true$forceTagsClosed=true$target_charset DEFAULT_TARGET_CHARSET$stripRN=true$defaultBRText=DEFAULT_BR_TEXT$defaultSpanText=DEFAULT_SPAN_TEXT)
  90. {
  91.     $dom = new simple_html_dom(null$lowercase$forceTagsClosed$target_charset$stripRN$defaultBRText$defaultSpanText);
  92.     if (empty($str) || strlen($str) > MAX_FILE_SIZE)
  93.     {
  94.         $dom->clear();
  95.         return false;
  96.     }
  97.     $dom->load($str$lowercase$stripRN);
  98.     return $dom;
  99. }
  100. // dump html dom tree
  101. function dump_html_tree($node$show_attr=true$deep=0)
  102. {
  103.     $node->dump($node);
  104. }
  105. /**
  106.  * simple html dom node
  107.  * PaperG - added ability for "find" routine to lowercase the value of the selector.
  108.  * PaperG - added $tag_start to track the start position of the tag in the total byte index
  109.  *
  110.  * @package PlaceLocalInclude
  111.  */
  112. class simple_html_dom_node
  113. {
  114.     public $nodetype HDOM_TYPE_TEXT;
  115.     public $tag 'text';
  116.     public $attr = array();
  117.     public $children = array();
  118.     public $nodes = array();
  119.     public $parent null;
  120.     // The "info" array - see HDOM_INFO_... for what each element contains.
  121.     public $_ = array();
  122.     public $tag_start 0;
  123.     private $dom null;
  124.     function __construct($dom)
  125.     {
  126.         $this->dom $dom;
  127.         $dom->nodes[] = $this;
  128.     }
  129.     function __destruct()
  130.     {
  131.         $this->clear();
  132.     }
  133.     function __toString()
  134.     {
  135.         return $this->outertext();
  136.     }
  137.     // clean up memory due to php5 circular references memory leak...
  138.     function clear()
  139.     {
  140.         $this->dom null;
  141.         $this->nodes null;
  142.         $this->parent null;
  143.         $this->children null;
  144.     }
  145.     // dump node's tree
  146.     function dump($show_attr=true$deep=0)
  147.     {
  148.         $lead str_repeat('    '$deep);
  149.         echo $lead.$this->tag;
  150.         if ($show_attr && count($this->attr)>0)
  151.         {
  152.             echo '(';
  153.             foreach ($this->attr as $k=>$v)
  154.                 echo "[$k]=>\"".$this->$k.'", ';
  155.             echo ')';
  156.         }
  157.         echo "\n";
  158.         if ($this->nodes)
  159.         {
  160.             foreach ($this->nodes as $c)
  161.             {
  162.                 $c->dump($show_attr$deep+1);
  163.             }
  164.         }
  165.     }
  166.     // Debugging function to dump a single dom node with a bunch of information about it.
  167.     function dump_node($echo=true)
  168.     {
  169.         $string $this->tag;
  170.         if (count($this->attr)>0)
  171.         {
  172.             $string .= '(';
  173.             foreach ($this->attr as $k=>$v)
  174.             {
  175.                 $string .= "[$k]=>\"".$this->$k.'", ';
  176.             }
  177.             $string .= ')';
  178.         }
  179.         if (count($this->_)>0)
  180.         {
  181.             $string .= ' $_ (';
  182.             foreach ($this->as $k=>$v)
  183.             {
  184.                 if (is_array($v))
  185.                 {
  186.                     $string .= "[$k]=>(";
  187.                     foreach ($v as $k2=>$v2)
  188.                     {
  189.                         $string .= "[$k2]=>\"".$v2.'", ';
  190.                     }
  191.                     $string .= ")";
  192.                 } else {
  193.                     $string .= "[$k]=>\"".$v.'", ';
  194.                 }
  195.             }
  196.             $string .= ")";
  197.         }
  198.         if (isset($this->text))
  199.         {
  200.             $string .= " text: (" $this->text ")";
  201.         }
  202.         $string .= " HDOM_INNER_INFO: '";
  203.         if (isset($node->_[HDOM_INFO_INNER]))
  204.         {
  205.             $string .= $node->_[HDOM_INFO_INNER] . "'";
  206.         }
  207.         else
  208.         {
  209.             $string .= ' NULL ';
  210.         }
  211.         $string .= " children: " count($this->children);
  212.         $string .= " nodes: " count($this->nodes);
  213.         $string .= " tag_start: " $this->tag_start;
  214.         $string .= "\n";
  215.         if ($echo)
  216.         {
  217.             echo $string;
  218.             return;
  219.         }
  220.         else
  221.         {
  222.             return $string;
  223.         }
  224.     }
  225.     // returns the parent of node
  226.     // If a node is passed in, it will reset the parent of the current node to that one.
  227.     function parent($parent=null)
  228.     {
  229.         // I am SURE that this doesn't work properly.
  230.         // It fails to unset the current node from it's current parents nodes or children list first.
  231.         if ($parent !== null)
  232.         {
  233.             $this->parent $parent;
  234.             $this->parent->nodes[] = $this;
  235.             $this->parent->children[] = $this;
  236.         }
  237.         return $this->parent;
  238.     }
  239.     // verify that node has children
  240.     function has_child()
  241.     {
  242.         return !empty($this->children);
  243.     }
  244.     // returns children of node
  245.     function children($idx=-1)
  246.     {
  247.         if ($idx===-1)
  248.         {
  249.             return $this->children;
  250.         }
  251.         if (isset($this->children[$idx]))
  252.         {
  253.             return $this->children[$idx];
  254.         }
  255.         return null;
  256.     }
  257.     // returns the first child of node
  258.     function first_child()
  259.     {
  260.         if (count($this->children)>0)
  261.         {
  262.             return $this->children[0];
  263.         }
  264.         return null;
  265.     }
  266.     // returns the last child of node
  267.     function last_child()
  268.     {
  269.         if (($count=count($this->children))>0)
  270.         {
  271.             return $this->children[$count-1];
  272.         }
  273.         return null;
  274.     }
  275.     // returns the next sibling of node
  276.     function next_sibling()
  277.     {
  278.         if ($this->parent===null)
  279.         {
  280.             return null;
  281.         }
  282.         $idx 0;
  283.         $count count($this->parent->children);
  284.         while ($idx<$count && $this!==$this->parent->children[$idx])
  285.         {
  286.             ++$idx;
  287.         }
  288.         if (++$idx>=$count)
  289.         {
  290.             return null;
  291.         }
  292.         return $this->parent->children[$idx];
  293.     }
  294.     // returns the previous sibling of node
  295.     function prev_sibling()
  296.     {
  297.         if ($this->parent===null) return null;
  298.         $idx 0;
  299.         $count count($this->parent->children);
  300.         while ($idx<$count && $this!==$this->parent->children[$idx])
  301.             ++$idx;
  302.         if (--$idx<0) return null;
  303.         return $this->parent->children[$idx];
  304.     }
  305.     // function to locate a specific ancestor tag in the path to the root.
  306.     function find_ancestor_tag($tag)
  307.     {
  308.         global $debug_object;
  309.         if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
  310.         // Start by including ourselves in the comparison.
  311.         $returnDom $this;
  312.         while (!is_null($returnDom))
  313.         {
  314.             if (is_object($debug_object)) { $debug_object->debug_log(2"Current tag is: " $returnDom->tag); }
  315.             if ($returnDom->tag == $tag)
  316.             {
  317.                 break;
  318.             }
  319.             $returnDom $returnDom->parent;
  320.         }
  321.         return $returnDom;
  322.     }
  323.     // get dom node's inner html
  324.     function innertext()
  325.     {
  326.         if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER];
  327.         if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  328.         $ret '';
  329.         foreach ($this->nodes as $n)
  330.             $ret .= $n->outertext();
  331.         return $ret;
  332.     }
  333.     // get dom node's outer text (with tag)
  334.     function outertext()
  335.     {
  336.         global $debug_object;
  337.         if (is_object($debug_object))
  338.         {
  339.             $text '';
  340.             if ($this->tag == 'text')
  341.             {
  342.                 if (!empty($this->text))
  343.                 {
  344.                     $text " with text: " $this->text;
  345.                 }
  346.             }
  347.             $debug_object->debug_log(1'Innertext of tag: ' $this->tag $text);
  348.         }
  349.         if ($this->tag==='root') return $this->innertext();
  350.         // trigger callback
  351.         if ($this->dom && $this->dom->callback!==null)
  352.         {
  353.             call_user_func_array($this->dom->callback, array($this));
  354.         }
  355.         if (isset($this->_[HDOM_INFO_OUTER])) return $this->_[HDOM_INFO_OUTER];
  356.         if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  357.         // render begin tag
  358.         if ($this->dom && $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]])
  359.         {
  360.             $ret $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup();
  361.         } else {
  362.             $ret "";
  363.         }
  364.         // render inner text
  365.         if (isset($this->_[HDOM_INFO_INNER]))
  366.         {
  367.             // If it's a br tag...  don't return the HDOM_INNER_INFO that we may or may not have added.
  368.             if ($this->tag != "br")
  369.             {
  370.                 $ret .= $this->_[HDOM_INFO_INNER];
  371.             }
  372.         } else {
  373.             if ($this->nodes)
  374.             {
  375.                 foreach ($this->nodes as $n)
  376.                 {
  377.                     $ret .= $this->convert_text($n->outertext());
  378.                 }
  379.             }
  380.         }
  381.         // render end tag
  382.         if (isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END]!=0)
  383.             $ret .= '</'.$this->tag.'>';
  384.         return $ret;
  385.     }
  386.     // get dom node's plain text
  387.     function text()
  388.     {
  389.         if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER];
  390.         switch ($this->nodetype)
  391.         {
  392.             case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  393.             case HDOM_TYPE_COMMENT: return '';
  394.             case HDOM_TYPE_UNKNOWN: return '';
  395.         }
  396.         if (strcasecmp($this->tag'script')===0) return '';
  397.         if (strcasecmp($this->tag'style')===0) return '';
  398.         $ret '';
  399.         // In rare cases, (always node type 1 or HDOM_TYPE_ELEMENT - observed for some span tags, and some p tags) $this->nodes is set to NULL.
  400.         // NOTE: This indicates that there is a problem where it's set to NULL without a clear happening.
  401.         // WHY is this happening?
  402.         if (!is_null($this->nodes))
  403.         {
  404.             foreach ($this->nodes as $n)
  405.             {
  406.                 $ret .= $this->convert_text($n->text());
  407.             }
  408.             // If this node is a span... add a space at the end of it so multiple spans don't run into each other.  This is plaintext after all.
  409.             if ($this->tag == "span")
  410.             {
  411.                 $ret .= $this->dom->default_span_text;
  412.             }
  413.         }
  414.         return $ret;
  415.     }
  416.     function xmltext()
  417.     {
  418.         $ret $this->innertext();
  419.         $ret str_ireplace('<![CDATA['''$ret);
  420.         $ret str_replace(']]>'''$ret);
  421.         return $ret;
  422.     }
  423.     // build node's text with tag
  424.     function makeup()
  425.     {
  426.         // text, comment, unknown
  427.         if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  428.         $ret '<'.$this->tag;
  429.         $i = -1;
  430.         foreach ($this->attr as $key=>$val)
  431.         {
  432.             ++$i;
  433.             // skip removed attribute
  434.             if ($val===null || $val===false)
  435.                 continue;
  436.             $ret .= $this->_[HDOM_INFO_SPACE][$i][0];
  437.             //no value attr: nowrap, checked selected...
  438.             if ($val===true)
  439.                 $ret .= $key;
  440.             else {
  441.                 switch ($this->_[HDOM_INFO_QUOTE][$i])
  442.                 {
  443.                     case HDOM_QUOTE_DOUBLE$quote '"'; break;
  444.                     case HDOM_QUOTE_SINGLE$quote '\''; break;
  445.                     default: $quote '';
  446.                 }
  447.                 $ret .= $key.$this->_[HDOM_INFO_SPACE][$i][1].'='.$this->_[HDOM_INFO_SPACE][$i][2].$quote.$val.$quote;
  448.             }
  449.         }
  450.         $ret $this->dom->restore_noise($ret);
  451.         return $ret $this->_[HDOM_INFO_ENDSPACE] . '>';
  452.     }
  453.     // find elements by css selector
  454.     //PaperG - added ability for find to lowercase the value of the selector.
  455.     function find($selector$idx=null$lowercase=false)
  456.     {
  457.         $selectors $this->parse_selector($selector);
  458.         if (($count=count($selectors))===0) return array();
  459.         $found_keys = array();
  460.         // find each selector
  461.         for ($c=0$c<$count; ++$c)
  462.         {
  463.             // The change on the below line was documented on the sourceforge code tracker id 2788009
  464.             // used to be: if (($levle=count($selectors[0]))===0) return array();
  465.             if (($levle=count($selectors[$c]))===0) return array();
  466.             if (!isset($this->_[HDOM_INFO_BEGIN])) return array();
  467.             $head = array($this->_[HDOM_INFO_BEGIN]=>1);
  468.             // handle descendant selectors, no recursive!
  469.             for ($l=0$l<$levle; ++$l)
  470.             {
  471.                 $ret = array();
  472.                 foreach ($head as $k=>$v)
  473.                 {
  474.                     $n = ($k===-1) ? $this->dom->root $this->dom->nodes[$k];
  475.                     //PaperG - Pass this optional parameter on to the seek function.
  476.                     $n->seek($selectors[$c][$l], $ret$lowercase);
  477.                 }
  478.                 $head $ret;
  479.             }
  480.             foreach ($head as $k=>$v)
  481.             {
  482.                 if (!isset($found_keys[$k]))
  483.                 {
  484.                     $found_keys[$k] = 1;
  485.                 }
  486.             }
  487.         }
  488.         // sort keys
  489.         ksort($found_keys);
  490.         $found = array();
  491.         foreach ($found_keys as $k=>$v)
  492.             $found[] = $this->dom->nodes[$k];
  493.         // return nth-element or array
  494.         if (is_null($idx)) return $found;
  495.         else if ($idx<0$idx count($found) + $idx;
  496.         return (isset($found[$idx])) ? $found[$idx] : null;
  497.     }
  498.     // seek for given conditions
  499.     // PaperG - added parameter to allow for case insensitive testing of the value of a selector.
  500.     protected function seek($selector, &$ret$lowercase=false)
  501.     {
  502.         global $debug_object;
  503.         if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
  504.         list($tag$key$val$exp$no_key) = $selector;
  505.         // xpath index
  506.         if ($tag && $key && is_numeric($key))
  507.         {
  508.             $count 0;
  509.             foreach ($this->children as $c)
  510.             {
  511.                 if ($tag==='*' || $tag===$c->tag) {
  512.                     if (++$count==$key) {
  513.                         $ret[$c->_[HDOM_INFO_BEGIN]] = 1;
  514.                         return;
  515.                     }
  516.                 }
  517.             }
  518.             return;
  519.         }
  520.         $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0;
  521.         if ($end==0) {
  522.             $parent $this->parent;
  523.             while (!isset($parent->_[HDOM_INFO_END]) && $parent!==null) {
  524.                 $end -= 1;
  525.                 $parent $parent->parent;
  526.             }
  527.             $end += $parent->_[HDOM_INFO_END];
  528.         }
  529.         for ($i=$this->_[HDOM_INFO_BEGIN]+1$i<$end; ++$i) {
  530.             $node $this->dom->nodes[$i];
  531.             $pass true;
  532.             if ($tag==='*' && !$key) {
  533.                 if (in_array($node$this->childrentrue))
  534.                     $ret[$i] = 1;
  535.                 continue;
  536.             }
  537.             // compare tag
  538.             if ($tag && $tag!=$node->tag && $tag!=='*') {$pass=false;}
  539.             // compare key
  540.             if ($pass && $key) {
  541.                 if ($no_key) {
  542.                     if (isset($node->attr[$key])) $pass=false;
  543.                 } else {
  544.                     if (($key != "plaintext") && !isset($node->attr[$key])) $pass=false;
  545.                 }
  546.             }
  547.             // compare value
  548.             if ($pass && $key && $val  && $val!=='*') {
  549.                 // If they have told us that this is a "plaintext" search then we want the plaintext of the node - right?
  550.                 if ($key == "plaintext") {
  551.                     // $node->plaintext actually returns $node->text();
  552.                     $nodeKeyValue $node->text();
  553.                 } else {
  554.                     // this is a normal search, we want the value of that attribute of the tag.
  555.                     $nodeKeyValue $node->attr[$key];
  556.                 }
  557.                 if (is_object($debug_object)) {$debug_object->debug_log(2"testing node: " $node->tag " for attribute: " $key $exp $val " where nodes value is: " $nodeKeyValue);}
  558.                 //PaperG - If lowercase is set, do a case insensitive test of the value of the selector.
  559.                 if ($lowercase) {
  560.                     $check $this->match($expstrtolower($val), strtolower($nodeKeyValue));
  561.                 } else {
  562.                     $check $this->match($exp$val$nodeKeyValue);
  563.                 }
  564.                 if (is_object($debug_object)) {$debug_object->debug_log(2"after match: " . ($check "true" "false"));}
  565.                 // handle multiple class
  566.                 if (!$check && strcasecmp($key'class')===0) {
  567.                     foreach (explode(' ',$node->attr[$key]) as $k) {
  568.                         // Without this, there were cases where leading, trailing, or double spaces lead to our comparing blanks - bad form.
  569.                         if (!empty($k)) {
  570.                             if ($lowercase) {
  571.                                 $check $this->match($expstrtolower($val), strtolower($k));
  572.                             } else {
  573.                                 $check $this->match($exp$val$k);
  574.                             }
  575.                             if ($check) break;
  576.                         }
  577.                     }
  578.                 }
  579.                 if (!$check$pass false;
  580.             }
  581.             if ($pass$ret[$i] = 1;
  582.             unset($node);
  583.         }
  584.         // It's passed by reference so this is actually what this function returns.
  585.         if (is_object($debug_object)) {$debug_object->debug_log(1"EXIT - ret: "$ret);}
  586.     }
  587.     protected function match($exp$pattern$value) {
  588.         global $debug_object;
  589.         if (is_object($debug_object)) {$debug_object->debug_log_entry(1);}
  590.         switch ($exp) {
  591.             case '=':
  592.                 return ($value===$pattern);
  593.             case '!=':
  594.                 return ($value!==$pattern);
  595.             case '^=':
  596.                 return preg_match("/^".preg_quote($pattern,'/')."/"$value);
  597.             case '$=':
  598.                 return preg_match("/".preg_quote($pattern,'/')."$/"$value);
  599.             case '*=':
  600.                 if ($pattern[0]=='/') {
  601.                     return preg_match($pattern$value);
  602.                 }
  603.                 return preg_match("/".$pattern."/i"$value);
  604.         }
  605.         return false;
  606.     }
  607.     protected function parse_selector($selector_string) {
  608.         global $debug_object;
  609.         if (is_object($debug_object)) {$debug_object->debug_log_entry(1);}
  610.         // pattern of CSS selectors, modified from mootools
  611.         // Paperg: Add the colon to the attrbute, so that it properly finds <tag attr:ibute="something" > like google does.
  612.         // Note: if you try to look at this attribute, yo MUST use getAttribute since $dom->x:y will fail the php syntax check.
  613. // Notice the \[ starting the attbute?  and the @? following?  This implies that an attribute can begin with an @ sign that is not captured.
  614. // This implies that an html attribute specifier may start with an @ sign that is NOT captured by the expression.
  615. // farther study is required to determine of this should be documented or removed.
  616. //        $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
  617.         $pattern "/([\w\-:\*]*)(?:\#([\w\-]+)|\.([\w\-]+))?(?:\[@?(!?[\w\-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
  618.         preg_match_all($patterntrim($selector_string).' '$matchesPREG_SET_ORDER);
  619.         if (is_object($debug_object)) {$debug_object->debug_log(2"Matches Array: "$matches);}
  620.         $selectors = array();
  621.         $result = array();
  622.         //print_r($matches);
  623.         foreach ($matches as $m) {
  624.             $m[0] = trim($m[0]);
  625.             if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue;
  626.             // for browser generated xpath
  627.             if ($m[1]==='tbody') continue;
  628.             list($tag$key$val$exp$no_key) = array($m[1], nullnull'='false);
  629.             if (!empty($m[2])) {$key='id'$val=$m[2];}
  630.             if (!empty($m[3])) {$key='class'$val=$m[3];}
  631.             if (!empty($m[4])) {$key=$m[4];}
  632.             if (!empty($m[5])) {$exp=$m[5];}
  633.             if (!empty($m[6])) {$val=$m[6];}
  634.             // convert to lowercase
  635.             if ($this->dom->lowercase) {$tag=strtolower($tag); $key=strtolower($key);}
  636.             //elements that do NOT have the specified attribute
  637.             if (isset($key[0]) && $key[0]==='!') {$key=substr($key1); $no_key=true;}
  638.             $result[] = array($tag$key$val$exp$no_key);
  639.             if (trim($m[7])===',') {
  640.                 $selectors[] = $result;
  641.                 $result = array();
  642.             }
  643.         }
  644.         if (count($result)>0)
  645.             $selectors[] = $result;
  646.         return $selectors;
  647.     }
  648.     function __get($name)
  649.     {
  650.         if (isset($this->attr[$name]))
  651.         {
  652.             return $this->convert_text($this->attr[$name]);
  653.         }
  654.         switch ($name)
  655.         {
  656.             case 'outertext': return $this->outertext();
  657.             case 'innertext': return $this->innertext();
  658.             case 'plaintext': return $this->text();
  659.             case 'xmltext': return $this->xmltext();
  660.             default: return array_key_exists($name$this->attr);
  661.         }
  662.     }
  663.     function __set($name$value)
  664.     {
  665.         global $debug_object;
  666.         if (is_object($debug_object)) {$debug_object->debug_log_entry(1);}
  667.         switch ($name)
  668.         {
  669.             case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value;
  670.             case 'innertext':
  671.                 if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value;
  672.                 return $this->_[HDOM_INFO_INNER] = $value;
  673.         }
  674.         if (!isset($this->attr[$name]))
  675.         {
  676.             $this->_[HDOM_INFO_SPACE][] = array(' ''''');
  677.             $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE;
  678.         }
  679.         $this->attr[$name] = $value;
  680.     }
  681.     function __isset($name)
  682.     {
  683.         switch ($name)
  684.         {
  685.             case 'outertext': return true;
  686.             case 'innertext': return true;
  687.             case 'plaintext': return true;
  688.         }
  689.         //no value attr: nowrap, checked selected...
  690.         return (array_key_exists($name$this->attr)) ? true : isset($this->attr[$name]);
  691.     }
  692.     function __unset($name) {
  693.         if (isset($this->attr[$name]))
  694.             unset($this->attr[$name]);
  695.     }
  696.     // PaperG - Function to convert the text from one character set to another if the two sets are not the same.
  697.     function convert_text($text)
  698.     {
  699.         global $debug_object;
  700.         if (is_object($debug_object)) {$debug_object->debug_log_entry(1);}
  701.         $converted_text $text;
  702.         $sourceCharset "";
  703.         $targetCharset "";
  704.         if ($this->dom)
  705.         {
  706.             $sourceCharset strtoupper($this->dom->_charset);
  707.             $targetCharset strtoupper($this->dom->_target_charset);
  708.         }
  709.         if (is_object($debug_object)) {$debug_object->debug_log(3"source charset: " $sourceCharset " target charaset: " $targetCharset);}
  710.         if (!empty($sourceCharset) && !empty($targetCharset) && (strcasecmp($sourceCharset$targetCharset) != 0))
  711.         {
  712.             // Check if the reported encoding could have been incorrect and the text is actually already UTF-8
  713.             if ((strcasecmp($targetCharset'UTF-8') == 0) && ($this->is_utf8($text)))
  714.             {
  715.                 $converted_text $text;
  716.             }
  717.             else
  718.             {
  719.                 $converted_text iconv($sourceCharset$targetCharset$text);
  720.             }
  721.         }
  722.         // Lets make sure that we don't have that silly BOM issue with any of the utf-8 text we output.
  723.         if ($targetCharset == 'UTF-8')
  724.         {
  725.             if (substr($converted_text03) == "\xef\xbb\xbf")
  726.             {
  727.                 $converted_text substr($converted_text3);
  728.             }
  729.             if (substr($converted_text, -3) == "\xef\xbb\xbf")
  730.             {
  731.                 $converted_text substr($converted_text0, -3);
  732.             }
  733.         }
  734.         return $converted_text;
  735.     }
  736.     /**
  737.     * Returns true if $string is valid UTF-8 and false otherwise.
  738.     *
  739.     * @param mixed $str String to be tested
  740.     * @return boolean
  741.     */
  742.     static function is_utf8($str)
  743.     {
  744.         $c=0$b=0;
  745.         $bits=0;
  746.         $len=strlen($str);
  747.         for($i=0$i<$len$i++)
  748.         {
  749.             $c=ord($str[$i]);
  750.             if($c 128)
  751.             {
  752.                 if(($c >= 254)) return false;
  753.                 elseif($c >= 252$bits=6;
  754.                 elseif($c >= 248$bits=5;
  755.                 elseif($c >= 240$bits=4;
  756.                 elseif($c >= 224$bits=3;
  757.                 elseif($c >= 192$bits=2;
  758.                 else return false;
  759.                 if(($i+$bits) > $len) return false;
  760.                 while($bits 1)
  761.                 {
  762.                     $i++;
  763.                     $b=ord($str[$i]);
  764.                     if($b 128 || $b 191) return false;
  765.                     $bits--;
  766.                 }
  767.             }
  768.         }
  769.         return true;
  770.     }
  771.     /*
  772.     function is_utf8($string)
  773.     {
  774.         //this is buggy
  775.         return (utf8_encode(utf8_decode($string)) == $string);
  776.     }
  777.     */
  778.     /**
  779.      * Function to try a few tricks to determine the displayed size of an img on the page.
  780.      * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types.
  781.      *
  782.      * @author John Schlick
  783.      * @version April 19 2012
  784.      * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out.
  785.      */
  786.     function get_display_size()
  787.     {
  788.         global $debug_object;
  789.         $width = -1;
  790.         $height = -1;
  791.         if ($this->tag !== 'img')
  792.         {
  793.             return false;
  794.         }
  795.         // See if there is aheight or width attribute in the tag itself.
  796.         if (isset($this->attr['width']))
  797.         {
  798.             $width $this->attr['width'];
  799.         }
  800.         if (isset($this->attr['height']))
  801.         {
  802.             $height $this->attr['height'];
  803.         }
  804.         // Now look for an inline style.
  805.         if (isset($this->attr['style']))
  806.         {
  807.             // Thanks to user gnarf from stackoverflow for this regular expression.
  808.             $attributes = array();
  809.             preg_match_all("/([\w\-]+)\s*:\s*([^;]+)\s*;?/"$this->attr['style'], $matchesPREG_SET_ORDER);
  810.             foreach ($matches as $match) {
  811.               $attributes[$match[1]] = $match[2];
  812.             }
  813.             // If there is a width in the style attributes:
  814.             if (isset($attributes['width']) && $width == -1)
  815.             {
  816.                 // check that the last two characters are px (pixels)
  817.                 if (strtolower(substr($attributes['width'], -2)) == 'px')
  818.                 {
  819.                     $proposed_width substr($attributes['width'], 0, -2);
  820.                     // Now make sure that it's an integer and not something stupid.
  821.                     if (filter_var($proposed_widthFILTER_VALIDATE_INT))
  822.                     {
  823.                         $width $proposed_width;
  824.                     }
  825.                 }
  826.             }
  827.             // If there is a width in the style attributes:
  828.             if (isset($attributes['height']) && $height == -1)
  829.             {
  830.                 // check that the last two characters are px (pixels)
  831.                 if (strtolower(substr($attributes['height'], -2)) == 'px')
  832.                 {
  833.                     $proposed_height substr($attributes['height'], 0, -2);
  834.                     // Now make sure that it's an integer and not something stupid.
  835.                     if (filter_var($proposed_heightFILTER_VALIDATE_INT))
  836.                     {
  837.                         $height $proposed_height;
  838.                     }
  839.                 }
  840.             }
  841.         }
  842.         // Future enhancement:
  843.         // Look in the tag to see if there is a class or id specified that has a height or width attribute to it.
  844.         // Far future enhancement
  845.         // Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width
  846.         // Note that in this case, the class or id will have the img subselector for it to apply to the image.
  847.         // ridiculously far future development
  848.         // If the class or id is specified in a SEPARATE css file thats not on the page, go get it and do what we were just doing for the ones on the page.
  849.         $result = array('height' => $height,
  850.                         'width' => $width);
  851.         return $result;
  852.     }
  853.     // camel naming conventions
  854.     function getAllAttributes() {return $this->attr;}
  855.     function getAttribute($name) {return $this->__get($name);}
  856.     function setAttribute($name$value) {$this->__set($name$value);}
  857.     function hasAttribute($name) {return $this->__isset($name);}
  858.     function removeAttribute($name) {$this->__set($namenull);}
  859.     function getElementById($id) {return $this->find("#$id"0);}
  860.     function getElementsById($id$idx=null) {return $this->find("#$id"$idx);}
  861.     function getElementByTagName($name) {return $this->find($name0);}
  862.     function getElementsByTagName($name$idx=null) {return $this->find($name$idx);}
  863.     function parentNode() {return $this->parent();}
  864.     function childNodes($idx=-1) {return $this->children($idx);}
  865.     function firstChild() {return $this->first_child();}
  866.     function lastChild() {return $this->last_child();}
  867.     function nextSibling() {return $this->next_sibling();}
  868.     function previousSibling() {return $this->prev_sibling();}
  869.     function hasChildNodes() {return $this->has_child();}
  870.     function nodeName() {return $this->tag;}
  871.     function appendChild($node) {$node->parent($this); return $node;}
  872. }
  873. /**
  874.  * simple html dom parser
  875.  * Paperg - in the find routine: allow us to specify that we want case insensitive testing of the value of the selector.
  876.  * Paperg - change $size from protected to public so we can easily access it
  877.  * Paperg - added ForceTagsClosed in the constructor which tells us whether we trust the html or not.  Default is to NOT trust it.
  878.  *
  879.  * @package PlaceLocalInclude
  880.  */
  881. class Simple_html_dom
  882. {
  883.     public $root null;
  884.     public $nodes = array();
  885.     public $callback null;
  886.     public $lowercase false;
  887.     // Used to keep track of how large the text was when we started.
  888.     public $original_size;
  889.     public $size;
  890.     protected $pos;
  891.     protected $doc;
  892.     protected $char;
  893.     protected $cursor;
  894.     protected $parent;
  895.     protected $noise = array();
  896.     protected $token_blank " \t\r\n";
  897.     protected $token_equal ' =/>';
  898.     protected $token_slash " />\r\n\t";
  899.     protected $token_attr ' >';
  900.     // Note that this is referenced by a child node, and so it needs to be public for that node to see this information.
  901.     public $_charset '';
  902.     public $_target_charset '';
  903.     protected $default_br_text "";
  904.     public $default_span_text "";
  905.     // use isset instead of in_array, performance boost about 30%...
  906.     protected $self_closing_tags = array('img'=>1'br'=>1'input'=>1'meta'=>1'link'=>1'hr'=>1'base'=>1'embed'=>1'spacer'=>1);
  907.     protected $block_tags = array('root'=>1'body'=>1'form'=>1'div'=>1'span'=>1'table'=>1);
  908.     // Known sourceforge issue #2977341
  909.     // B tags that are not closed cause us to return everything to the end of the document.
  910.     protected $optional_closing_tags = array(
  911.         'tr'=>array('tr'=>1'td'=>1'th'=>1),
  912.         'th'=>array('th'=>1),
  913.         'td'=>array('td'=>1),
  914.         'li'=>array('li'=>1),
  915.         'dt'=>array('dt'=>1'dd'=>1),
  916.         'dd'=>array('dd'=>1'dt'=>1),
  917.         'dl'=>array('dd'=>1'dt'=>1),
  918.         'p'=>array('p'=>1),
  919.         'nobr'=>array('nobr'=>1),
  920.         'b'=>array('b'=>1),
  921.         'option'=>array('option'=>1),
  922.     );
  923.     function __construct($str=null$lowercase=true$forceTagsClosed=true$target_charset=DEFAULT_TARGET_CHARSET$stripRN=true$defaultBRText=DEFAULT_BR_TEXT$defaultSpanText=DEFAULT_SPAN_TEXT)
  924.     {
  925.         if ($str)
  926.         {
  927.             if (preg_match("/^http:\/\//i",$str) || is_file($str))
  928.             {
  929.                 $this->load_file($str);
  930.             }
  931.             else
  932.             {
  933.                 $this->load($str$lowercase$stripRN$defaultBRText$defaultSpanText);
  934.             }
  935.         }
  936.         // Forcing tags to be closed implies that we don't trust the html, but it can lead to parsing errors if we SHOULD trust the html.
  937.         if (!$forceTagsClosed) {
  938.             $this->optional_closing_array=array();
  939.         }
  940.         $this->_target_charset $target_charset;
  941.     }
  942.     function __destruct()
  943.     {
  944.         $this->clear();
  945.     }
  946.     // load html from string
  947.     function load($str$lowercase=true$stripRN=true$defaultBRText=DEFAULT_BR_TEXT$defaultSpanText=DEFAULT_SPAN_TEXT)
  948.     {
  949.         global $debug_object;
  950.         // prepare
  951.         $this->prepare($str$lowercase$stripRN$defaultBRText$defaultSpanText);
  952.         // strip out cdata
  953.         $this->remove_noise("'<!\[CDATA\[(.*?)\]\]>'is"true);
  954.         // strip out comments
  955.         $this->remove_noise("'<!--(.*?)-->'is");
  956.         // Per sourceforge http://sourceforge.net/tracker/?func=detail&aid=2949097&group_id=218559&atid=1044037
  957.         // Script tags removal now preceeds style tag removal.
  958.         // strip out <script> tags
  959.         $this->remove_noise("'<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>'is");
  960.         $this->remove_noise("'<\s*script\s*>(.*?)<\s*/\s*script\s*>'is");
  961.         // strip out <style> tags
  962.         $this->remove_noise("'<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>'is");
  963.         $this->remove_noise("'<\s*style\s*>(.*?)<\s*/\s*style\s*>'is");
  964.         // strip out preformatted tags
  965.         $this->remove_noise("'<\s*(?:code)[^>]*>(.*?)<\s*/\s*(?:code)\s*>'is");
  966.         // strip out server side scripts
  967.         $this->remove_noise("'(<\?)(.*?)(\?>)'s"true);
  968.         // strip smarty scripts
  969.         $this->remove_noise("'(\{\w)(.*?)(\})'s"true);
  970.         // parsing
  971.         while ($this->parse());
  972.         // end
  973.         $this->root->_[HDOM_INFO_END] = $this->cursor;
  974.         $this->parse_charset();
  975.         // make load function chainable
  976.         return $this;
  977.     }
  978.     // load html from file
  979.     function load_file()
  980.     {
  981.         $args func_get_args();
  982.         $this->load(call_user_func_array('file_get_contents'$args), true);
  983.         // Throw an error if we can't properly load the dom.
  984.         if (($error=error_get_last())!==null) {
  985.             $this->clear();
  986.             return false;
  987.         }
  988.     }
  989.     // set callback function
  990.     function set_callback($function_name)
  991.     {
  992.         $this->callback $function_name;
  993.     }
  994.     // remove callback function
  995.     function remove_callback()
  996.     {
  997.         $this->callback null;
  998.     }
  999.     // save dom as string
  1000.     function save($filepath='')
  1001.     {
  1002.         $ret $this->root->innertext();
  1003.         if ($filepath!==''file_put_contents($filepath$retLOCK_EX);
  1004.         return $ret;
  1005.     }
  1006.     // find dom node by css selector
  1007.     // Paperg - allow us to specify that we want case insensitive testing of the value of the selector.
  1008.     function find($selector$idx=null$lowercase=false)
  1009.     {
  1010.         return $this->root->find($selector$idx$lowercase);
  1011.     }
  1012.     // clean up memory due to php5 circular references memory leak...
  1013.     function clear()
  1014.     {
  1015.         foreach ($this->nodes as $n) {$n->clear(); $n null;}
  1016.         // This add next line is documented in the sourceforge repository. 2977248 as a fix for ongoing memory leaks that occur even with the use of clear.
  1017.         if (isset($this->children)) foreach ($this->children as $n) {$n->clear(); $n null;}
  1018.         if (isset($this->parent)) {$this->parent->clear(); unset($this->parent);}
  1019.         if (isset($this->root)) {$this->root->clear(); unset($this->root);}
  1020.         unset($this->doc);
  1021.         unset($this->noise);
  1022.     }
  1023.     function dump($show_attr=true)
  1024.     {
  1025.         $this->root->dump($show_attr);
  1026.     }
  1027.     // prepare HTML data and init everything
  1028.     protected function prepare($str$lowercase=true$stripRN=true$defaultBRText=DEFAULT_BR_TEXT$defaultSpanText=DEFAULT_SPAN_TEXT)
  1029.     {
  1030.         $this->clear();
  1031.         // set the length of content before we do anything to it.
  1032.         $this->size strlen($str);
  1033.         // Save the original size of the html that we got in.  It might be useful to someone.
  1034.         $this->original_size $this->size;
  1035.         //before we save the string as the doc...  strip out the \r \n's if we are told to.
  1036.         if ($stripRN) {
  1037.             $str str_replace("\r"" "$str);
  1038.             $str str_replace("\n"" "$str);
  1039.             // set the length of content since we have changed it.
  1040.             $this->size strlen($str);
  1041.         }
  1042.         $this->doc $str;
  1043.         $this->pos 0;
  1044.         $this->cursor 1;
  1045.         $this->noise = array();
  1046.         $this->nodes = array();
  1047.         $this->lowercase $lowercase;
  1048.         $this->default_br_text $defaultBRText;
  1049.         $this->default_span_text $defaultSpanText;
  1050.         $this->root = new simple_html_dom_node($this);
  1051.         $this->root->tag 'root';
  1052.         $this->root->_[HDOM_INFO_BEGIN] = -1;
  1053.         $this->root->nodetype HDOM_TYPE_ROOT;
  1054.         $this->parent $this->root;
  1055.         if ($this->size>0$this->char $this->doc[0];
  1056.     }
  1057.     // parse html content
  1058.     protected function parse()
  1059.     {
  1060.         if (($s $this->copy_until_char('<'))==='')
  1061.         {
  1062.             return $this->read_tag();
  1063.         }
  1064.         // text
  1065.         $node = new simple_html_dom_node($this);
  1066.         ++$this->cursor;
  1067.         $node->_[HDOM_INFO_TEXT] = $s;
  1068.         $this->link_nodes($nodefalse);
  1069.         return true;
  1070.     }
  1071.     // PAPERG - dkchou - added this to try to identify the character set of the page we have just parsed so we know better how to spit it out later.
  1072.     // NOTE:  IF you provide a routine called get_last_retrieve_url_contents_content_type which returns the CURLINFO_CONTENT_TYPE from the last curl_exec
  1073.     // (or the content_type header from the last transfer), we will parse THAT, and if a charset is specified, we will use it over any other mechanism.
  1074.     protected function parse_charset()
  1075.     {
  1076.         global $debug_object;
  1077.         $charset null;
  1078.         if (function_exists('get_last_retrieve_url_contents_content_type'))
  1079.         {
  1080.             $contentTypeHeader get_last_retrieve_url_contents_content_type();
  1081.             $success preg_match('/charset=(.+)/'$contentTypeHeader$matches);
  1082.             if ($success)
  1083.             {
  1084.                 $charset $matches[1];
  1085.                 if (is_object($debug_object)) {$debug_object->debug_log(2'header content-type found charset of: ' $charset);}
  1086.             }
  1087.         }
  1088.         if (empty($charset))
  1089.         {
  1090.             $el $this->root->find('meta[http-equiv=Content-Type]',0true);
  1091.             if (!empty($el))
  1092.             {
  1093.                 $fullvalue $el->content;
  1094.                 if (is_object($debug_object)) {$debug_object->debug_log(2'meta content-type tag found' $fullvalue);}
  1095.                 if (!empty($fullvalue))
  1096.                 {
  1097.                     $success preg_match('/charset=(.+)/i'$fullvalue$matches);
  1098.                     if ($success)
  1099.                     {
  1100.                         $charset $matches[1];
  1101.                     }
  1102.                     else
  1103.                     {
  1104.                         // If there is a meta tag, and they don't specify the character set, research says that it's typically ISO-8859-1
  1105.                         if (is_object($debug_object)) {$debug_object->debug_log(2'meta content-type tag couldn\'t be parsed. using iso-8859 default.');}
  1106.                         $charset 'ISO-8859-1';
  1107.                     }
  1108.                 }
  1109.             }
  1110.         }
  1111.         // If we couldn't find a charset above, then lets try to detect one based on the text we got...
  1112.         if (empty($charset))
  1113.         {
  1114.             // Use this in case mb_detect_charset isn't installed/loaded on this machine.
  1115.             $charset false;
  1116.             if (function_exists('mb_detect_encoding'))
  1117.             {
  1118.                 // Have php try to detect the encoding from the text given to us.
  1119.                 $charset mb_detect_encoding($this->root->plaintext "ascii"$encoding_list = array( "UTF-8""CP1252" ) );
  1120.                 if (is_object($debug_object)) {$debug_object->debug_log(2'mb_detect found: ' $charset);}
  1121.             }
  1122.             // and if this doesn't work...  then we need to just wrongheadedly assume it's UTF-8 so that we can move on - cause this will usually give us most of what we need...
  1123.             if ($charset === false)
  1124.             {
  1125.                 if (is_object($debug_object)) {$debug_object->debug_log(2'since mb_detect failed - using default of utf-8');}
  1126.                 $charset 'UTF-8';
  1127.             }
  1128.         }
  1129.         // Since CP1252 is a superset, if we get one of it's subsets, we want it instead.
  1130.         if ((strtolower($charset) == strtolower('ISO-8859-1')) || (strtolower($charset) == strtolower('Latin1')) || (strtolower($charset) == strtolower('Latin-1')))
  1131.         {
  1132.             if (is_object($debug_object)) {$debug_object->debug_log(2'replacing ' $charset ' with CP1252 as its a superset');}
  1133.             $charset 'CP1252';
  1134.         }
  1135.         if (is_object($debug_object)) {$debug_object->debug_log(1'EXIT - ' $charset);}
  1136.         return $this->_charset $charset;
  1137.     }
  1138.     // read tag info
  1139.     protected function read_tag()
  1140.     {
  1141.         if ($this->char!=='<')
  1142.         {
  1143.             $this->root->_[HDOM_INFO_END] = $this->cursor;
  1144.             return false;
  1145.         }
  1146.         $begin_tag_pos $this->pos;
  1147.         $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1148.         // end tag
  1149.         if ($this->char==='/')
  1150.         {
  1151.             $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1152.             // This represents the change in the simple_html_dom trunk from revision 180 to 181.
  1153.             // $this->skip($this->token_blank_t);
  1154.             $this->skip($this->token_blank);
  1155.             $tag $this->copy_until_char('>');
  1156.             // skip attributes in end tag
  1157.             if (($pos strpos($tag' '))!==false)
  1158.                 $tag substr($tag0$pos);
  1159.             $parent_lower strtolower($this->parent->tag);
  1160.             $tag_lower strtolower($tag);
  1161.             if ($parent_lower!==$tag_lower)
  1162.             {
  1163.                 if (isset($this->optional_closing_tags[$parent_lower]) && isset($this->block_tags[$tag_lower]))
  1164.                 {
  1165.                     $this->parent->_[HDOM_INFO_END] = 0;
  1166.                     $org_parent $this->parent;
  1167.                     while (($this->parent->parent) && strtolower($this->parent->tag)!==$tag_lower)
  1168.                         $this->parent $this->parent->parent;
  1169.                     if (strtolower($this->parent->tag)!==$tag_lower) {
  1170.                         $this->parent $org_parent// restore origonal parent
  1171.                         if ($this->parent->parent$this->parent $this->parent->parent;
  1172.                         $this->parent->_[HDOM_INFO_END] = $this->cursor;
  1173.                         return $this->as_text_node($tag);
  1174.                     }
  1175.                 }
  1176.                 else if (($this->parent->parent) && isset($this->block_tags[$tag_lower]))
  1177.                 {
  1178.                     $this->parent->_[HDOM_INFO_END] = 0;
  1179.                     $org_parent $this->parent;
  1180.                     while (($this->parent->parent) && strtolower($this->parent->tag)!==$tag_lower)
  1181.                         $this->parent $this->parent->parent;
  1182.                     if (strtolower($this->parent->tag)!==$tag_lower)
  1183.                     {
  1184.                         $this->parent $org_parent// restore origonal parent
  1185.                         $this->parent->_[HDOM_INFO_END] = $this->cursor;
  1186.                         return $this->as_text_node($tag);
  1187.                     }
  1188.                 }
  1189.                 else if (($this->parent->parent) && strtolower($this->parent->parent->tag)===$tag_lower)
  1190.                 {
  1191.                     $this->parent->_[HDOM_INFO_END] = 0;
  1192.                     $this->parent $this->parent->parent;
  1193.                 }
  1194.                 else
  1195.                     return $this->as_text_node($tag);
  1196.             }
  1197.             $this->parent->_[HDOM_INFO_END] = $this->cursor;
  1198.             if ($this->parent->parent$this->parent $this->parent->parent;
  1199.             $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1200.             return true;
  1201.         }
  1202.         $node = new simple_html_dom_node($this);
  1203.         $node->_[HDOM_INFO_BEGIN] = $this->cursor;
  1204.         ++$this->cursor;
  1205.         $tag $this->copy_until($this->token_slash);
  1206.         $node->tag_start $begin_tag_pos;
  1207.         // doctype, cdata & comments...
  1208.         if (isset($tag[0]) && $tag[0]==='!') {
  1209.             $node->_[HDOM_INFO_TEXT] = '<' $tag $this->copy_until_char('>');
  1210.             if (isset($tag[2]) && $tag[1]==='-' && $tag[2]==='-') {
  1211.                 $node->nodetype HDOM_TYPE_COMMENT;
  1212.                 $node->tag 'comment';
  1213.             } else {
  1214.                 $node->nodetype HDOM_TYPE_UNKNOWN;
  1215.                 $node->tag 'unknown';
  1216.             }
  1217.             if ($this->char==='>'$node->_[HDOM_INFO_TEXT].='>';
  1218.             $this->link_nodes($nodetrue);
  1219.             $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1220.             return true;
  1221.         }
  1222.         // text
  1223.         if ($pos=strpos($tag'<')!==false) {
  1224.             $tag '<' substr($tag0, -1);
  1225.             $node->_[HDOM_INFO_TEXT] = $tag;
  1226.             $this->link_nodes($nodefalse);
  1227.             $this->char $this->doc[--$this->pos]; // prev
  1228.             return true;
  1229.         }
  1230.         if (!preg_match("/^[\w\-:]+$/"$tag)) {
  1231.             $node->_[HDOM_INFO_TEXT] = '<' $tag $this->copy_until('<>');
  1232.             if ($this->char==='<') {
  1233.                 $this->link_nodes($nodefalse);
  1234.                 return true;
  1235.             }
  1236.             if ($this->char==='>'$node->_[HDOM_INFO_TEXT].='>';
  1237.             $this->link_nodes($nodefalse);
  1238.             $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1239.             return true;
  1240.         }
  1241.         // begin tag
  1242.         $node->nodetype HDOM_TYPE_ELEMENT;
  1243.         $tag_lower strtolower($tag);
  1244.         $node->tag = ($this->lowercase) ? $tag_lower $tag;
  1245.         // handle optional closing tags
  1246.         if (isset($this->optional_closing_tags[$tag_lower]) )
  1247.         {
  1248.             while (isset($this->optional_closing_tags[$tag_lower][strtolower($this->parent->tag)]))
  1249.             {
  1250.                 $this->parent->_[HDOM_INFO_END] = 0;
  1251.                 $this->parent $this->parent->parent;
  1252.             }
  1253.             $node->parent $this->parent;
  1254.         }
  1255.         $guard 0// prevent infinity loop
  1256.         $space = array($this->copy_skip($this->token_blank), '''');
  1257.         // attributes
  1258.         do
  1259.         {
  1260.             if ($this->char!==null && $space[0]==='')
  1261.             {
  1262.                 break;
  1263.             }
  1264.             $name $this->copy_until($this->token_equal);
  1265.             if ($guard===$this->pos)
  1266.             {
  1267.                 $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1268.                 continue;
  1269.             }
  1270.             $guard $this->pos;
  1271.             // handle endless '<'
  1272.             if ($this->pos>=$this->size-&& $this->char!=='>') {
  1273.                 $node->nodetype HDOM_TYPE_TEXT;
  1274.                 $node->_[HDOM_INFO_END] = 0;
  1275.                 $node->_[HDOM_INFO_TEXT] = '<'.$tag $space[0] . $name;
  1276.                 $node->tag 'text';
  1277.                 $this->link_nodes($nodefalse);
  1278.                 return true;
  1279.             }
  1280.             // handle mismatch '<'
  1281.             if ($this->doc[$this->pos-1]=='<') {
  1282.                 $node->nodetype HDOM_TYPE_TEXT;
  1283.                 $node->tag 'text';
  1284.                 $node->attr = array();
  1285.                 $node->_[HDOM_INFO_END] = 0;
  1286.                 $node->_[HDOM_INFO_TEXT] = substr($this->doc$begin_tag_pos$this->pos-$begin_tag_pos-1);
  1287.                 $this->pos -= 2;
  1288.                 $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1289.                 $this->link_nodes($nodefalse);
  1290.                 return true;
  1291.             }
  1292.             if ($name!=='/' && $name!=='') {
  1293.                 $space[1] = $this->copy_skip($this->token_blank);
  1294.                 $name $this->restore_noise($name);
  1295.                 if ($this->lowercase$name strtolower($name);
  1296.                 if ($this->char==='=') {
  1297.                     $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1298.                     $this->parse_attr($node$name$space);
  1299.                 }
  1300.                 else {
  1301.                     //no value attr: nowrap, checked selected...
  1302.                     $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO;
  1303.                     $node->attr[$name] = true;
  1304.                     if ($this->char!='>'$this->char $this->doc[--$this->pos]; // prev
  1305.                 }
  1306.                 $node->_[HDOM_INFO_SPACE][] = $space;
  1307.                 $space = array($this->copy_skip($this->token_blank), '''');
  1308.             }
  1309.             else
  1310.                 break;
  1311.         } while ($this->char!=='>' && $this->char!=='/');
  1312.         $this->link_nodes($nodetrue);
  1313.         $node->_[HDOM_INFO_ENDSPACE] = $space[0];
  1314.         // check self closing
  1315.         if ($this->copy_until_char_escape('>')==='/')
  1316.         {
  1317.             $node->_[HDOM_INFO_ENDSPACE] .= '/';
  1318.             $node->_[HDOM_INFO_END] = 0;
  1319.         }
  1320.         else
  1321.         {
  1322.             // reset parent
  1323.             if (!isset($this->self_closing_tags[strtolower($node->tag)])) $this->parent $node;
  1324.         }
  1325.         $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1326.         // If it's a BR tag, we need to set it's text to the default text.
  1327.         // This way when we see it in plaintext, we can generate formatting that the user wants.
  1328.         // since a br tag never has sub nodes, this works well.
  1329.         if ($node->tag == "br")
  1330.         {
  1331.             $node->_[HDOM_INFO_INNER] = $this->default_br_text;
  1332.         }
  1333.         return true;
  1334.     }
  1335.     // parse attributes
  1336.     protected function parse_attr($node$name, &$space)
  1337.     {
  1338.         // Per sourceforge: http://sourceforge.net/tracker/?func=detail&aid=3061408&group_id=218559&atid=1044037
  1339.         // If the attribute is already defined inside a tag, only pay atetntion to the first one as opposed to the last one.
  1340.         if (isset($node->attr[$name]))
  1341.         {
  1342.             return;
  1343.         }
  1344.         $space[2] = $this->copy_skip($this->token_blank);
  1345.         switch ($this->char) {
  1346.             case '"':
  1347.                 $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE;
  1348.                 $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1349.                 $node->attr[$name] = $this->restore_noise($this->copy_until_char_escape('"'));
  1350.                 $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1351.                 break;
  1352.             case '\'':
  1353.                 $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_SINGLE;
  1354.                 $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1355.                 $node->attr[$name] = $this->restore_noise($this->copy_until_char_escape('\''));
  1356.                 $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1357.                 break;
  1358.             default:
  1359.                 $node->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_NO;
  1360.                 $node->attr[$name] = $this->restore_noise($this->copy_until($this->token_attr));
  1361.         }
  1362.         // PaperG: Attributes should not have \r or \n in them, that counts as html whitespace.
  1363.         $node->attr[$name] = str_replace("\r"""$node->attr[$name]);
  1364.         $node->attr[$name] = str_replace("\n"""$node->attr[$name]);
  1365.         // PaperG: If this is a "class" selector, lets get rid of the preceeding and trailing space since some people leave it in the multi class case.
  1366.         if ($name == "class") {
  1367.             $node->attr[$name] = trim($node->attr[$name]);
  1368.         }
  1369.     }
  1370.     // link node's parent
  1371.     protected function link_nodes(&$node$is_child)
  1372.     {
  1373.         $node->parent $this->parent;
  1374.         $this->parent->nodes[] = $node;
  1375.         if ($is_child)
  1376.         {
  1377.             $this->parent->children[] = $node;
  1378.         }
  1379.     }
  1380.     // as a text node
  1381.     protected function as_text_node($tag)
  1382.     {
  1383.         $node = new simple_html_dom_node($this);
  1384.         ++$this->cursor;
  1385.         $node->_[HDOM_INFO_TEXT] = '</' $tag '>';
  1386.         $this->link_nodes($nodefalse);
  1387.         $this->char = (++$this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1388.         return true;
  1389.     }
  1390.     protected function skip($chars)
  1391.     {
  1392.         $this->pos += strspn($this->doc$chars$this->pos);
  1393.         $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1394.     }
  1395.     protected function copy_skip($chars)
  1396.     {
  1397.         $pos $this->pos;
  1398.         $len strspn($this->doc$chars$pos);
  1399.         $this->pos += $len;
  1400.         $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1401.         if ($len===0) return '';
  1402.         return substr($this->doc$pos$len);
  1403.     }
  1404.     protected function copy_until($chars)
  1405.     {
  1406.         $pos $this->pos;
  1407.         $len strcspn($this->doc$chars$pos);
  1408.         $this->pos += $len;
  1409.         $this->char = ($this->pos<$this->size) ? $this->doc[$this->pos] : null// next
  1410.         return substr($this->doc$pos$len);
  1411.     }
  1412.     protected function copy_until_char($char)
  1413.     {
  1414.         if ($this->char===null) return '';
  1415.         if (($pos strpos($this->doc$char$this->pos))===false) {
  1416.             $ret substr($this->doc$this->pos$this->size-$this->pos);
  1417.             $this->char null;
  1418.             $this->pos $this->size;
  1419.             return $ret;
  1420.         }
  1421.         if ($pos===$this->pos) return '';
  1422.         $pos_old $this->pos;
  1423.         $this->char $this->doc[$pos];
  1424.         $this->pos $pos;
  1425.         return substr($this->doc$pos_old$pos-$pos_old);
  1426.     }
  1427.     protected function copy_until_char_escape($char)
  1428.     {
  1429.         if ($this->char===null) return '';
  1430.         $start $this->pos;
  1431.         while (1)
  1432.         {
  1433.             if (($pos strpos($this->doc$char$start))===false)
  1434.             {
  1435.                 $ret substr($this->doc$this->pos$this->size-$this->pos);
  1436.                 $this->char null;
  1437.                 $this->pos $this->size;
  1438.                 return $ret;
  1439.             }
  1440.             if ($pos===$this->pos) return '';
  1441.             if ($this->doc[$pos-1]==='\\') {
  1442.                 $start $pos+1;
  1443.                 continue;
  1444.             }
  1445.             $pos_old $this->pos;
  1446.             $this->char $this->doc[$pos];
  1447.             $this->pos $pos;
  1448.             return substr($this->doc$pos_old$pos-$pos_old);
  1449.         }
  1450.     }
  1451.     // remove noise from html content
  1452.     // save the noise in the $this->noise array.
  1453.     protected function remove_noise($pattern$remove_tag=false)
  1454.     {
  1455.         global $debug_object;
  1456.         if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
  1457.         $count preg_match_all($pattern$this->doc$matchesPREG_SET_ORDER|PREG_OFFSET_CAPTURE);
  1458.         for ($i=$count-1$i>-1; --$i)
  1459.         {
  1460.             $key '___noise___'.sprintf('% 5d'count($this->noise)+1000);
  1461.             if (is_object($debug_object)) { $debug_object->debug_log(2'key is: ' $key); }
  1462.             $idx = ($remove_tag) ? 1;
  1463.             $this->noise[$key] = $matches[$i][$idx][0];
  1464.             $this->doc substr_replace($this->doc$key$matches[$i][$idx][1], strlen($matches[$i][$idx][0]));
  1465.         }
  1466.         // reset the length of content
  1467.         $this->size strlen($this->doc);
  1468.         if ($this->size>0)
  1469.         {
  1470.             $this->char $this->doc[0];
  1471.         }
  1472.     }
  1473.     // restore noise to html content
  1474.     function restore_noise($text)
  1475.     {
  1476.         global $debug_object;
  1477.         if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
  1478.         while (($pos=strpos($text'___noise___'))!==false)
  1479.         {
  1480.             // Sometimes there is a broken piece of markup, and we don't GET the pos+11 etc... token which indicates a problem outside of us...
  1481.             if (strlen($text) > $pos+15)
  1482.             {
  1483.                 $key '___noise___'.$text[$pos+11].$text[$pos+12].$text[$pos+13].$text[$pos+14].$text[$pos+15];
  1484.                 if (is_object($debug_object)) { $debug_object->debug_log(2'located key of: ' $key); }
  1485.                 if (isset($this->noise[$key]))
  1486.                 {
  1487.                     $text substr($text0$pos).$this->noise[$key].substr($text$pos+16);
  1488.                 }
  1489.                 else
  1490.                 {
  1491.                     // do this to prevent an infinite loop.
  1492.                     $text substr($text0$pos).'UNDEFINED NOISE FOR KEY: '.$key substr($text$pos+16);
  1493.                 }
  1494.             }
  1495.             else
  1496.             {
  1497.                 // There is no valid key being given back to us... We must get rid of the ___noise___ or we will have a problem.
  1498.                 $text substr($text0$pos).'NO NUMERIC NOISE KEY' substr($text$pos+11);
  1499.             }
  1500.         }
  1501.         return $text;
  1502.     }
  1503.     // Sometimes we NEED one of the noise elements.
  1504.     function search_noise($text)
  1505.     {
  1506.         global $debug_object;
  1507.         if (is_object($debug_object)) { $debug_object->debug_log_entry(1); }
  1508.         foreach($this->noise as $noiseElement)
  1509.         {
  1510.             if (strpos($noiseElement$text)!==false)
  1511.             {
  1512.                 return $noiseElement;
  1513.             }
  1514.         }
  1515.     }
  1516.     function __toString()
  1517.     {
  1518.         return $this->root->innertext();
  1519.     }
  1520.     function __get($name)
  1521.     {
  1522.         switch ($name)
  1523.         {
  1524.             case 'outertext':
  1525.                 return $this->root->innertext();
  1526.             case 'innertext':
  1527.                 return $this->root->innertext();
  1528.             case 'plaintext':
  1529.                 return $this->root->text();
  1530.             case 'charset':
  1531.                 return $this->_charset;
  1532.             case 'target_charset':
  1533.                 return $this->_target_charset;
  1534.         }
  1535.     }
  1536.     // camel naming conventions
  1537.     function childNodes($idx=-1) {return $this->root->childNodes($idx);}
  1538.     function firstChild() {return $this->root->first_child();}
  1539.     function lastChild() {return $this->root->last_child();}
  1540.     function createElement($name$value=null) {return @str_get_html("<$name>$value</$name>")->first_child();}
  1541.     function createTextNode($value) {return @end(str_get_html($value)->nodes);}
  1542.     function getElementById($id) {return $this->find("#$id"0);}
  1543.     function getElementsById($id$idx=null) {return $this->find("#$id"$idx);}
  1544.     function getElementByTagName($name) {return $this->find($name0);}
  1545.     function getElementsByTagName($name$idx=-1) {return $this->find($name$idx);}
  1546.     function loadFile() {$args func_get_args();$this->load_file($args);}
  1547. }
  1548. ?>