Advertisement
Guest User

Simple_html_dom_node

a guest
Feb 17th, 2014
115
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 13.79 KB | None | 0 0
  1. <?php
  2.  
  3. // simple html dom node
  4. // -----------------------------------------------------------------------------
  5. class Simple_html_dom_node {
  6.     public $nodetype = HDOM_TYPE_TEXT;
  7.     public $tag = 'text';
  8.     public $attr = array();
  9.     public $children = array();
  10.     public $nodes = array();
  11.     public $parent = null;
  12.     public $_ = array();
  13.     private $dom = null;
  14.  
  15.     function __construct($dom=false) {
  16.         if($dom)
  17.         {
  18.             $this->dom = $dom;
  19.             $dom->nodes[] = $this;
  20.         }
  21.     }
  22.  
  23.     function __destruct() {
  24.         $this->clear();
  25.     }
  26.  
  27.     function __toString() {
  28.         return $this->outertext();
  29.     }
  30.  
  31.     // clean up memory due to php5 circular references memory leak...
  32.     function clear() {
  33.         $this->dom = null;
  34.         $this->nodes = null;
  35.         $this->parent = null;
  36.         $this->children = null;
  37.     }
  38.    
  39.     // dump node's tree
  40.     function dump($show_attr=true) {
  41.         dump_html_tree($this, $show_attr);
  42.     }
  43.  
  44.     // returns the parent of node
  45.     function parent() {
  46.         return $this->parent;
  47.     }
  48.  
  49.     // returns children of node
  50.     function children($idx=-1) {
  51.         if ($idx===-1) return $this->children;
  52.         if (isset($this->children[$idx])) return $this->children[$idx];
  53.         return null;
  54.     }
  55.  
  56.     // returns the first child of node
  57.     function first_child() {
  58.         if (count($this->children)>0) return $this->children[0];
  59.         return null;
  60.     }
  61.  
  62.     // returns the last child of node
  63.     function last_child() {
  64.         if (($count=count($this->children))>0) return $this->children[$count-1];
  65.         return null;
  66.     }
  67.  
  68.     // returns the next sibling of node    
  69.     function next_sibling() {
  70.         if ($this->parent===null) return null;
  71.         $idx = 0;
  72.         $count = count($this->parent->children);
  73.         while ($idx<$count && $this!==$this->parent->children[$idx])
  74.             ++$idx;
  75.         if (++$idx>=$count) return null;
  76.         return $this->parent->children[$idx];
  77.     }
  78.  
  79.     // returns the previous sibling of node
  80.     function prev_sibling() {
  81.         if ($this->parent===null) return null;
  82.         $idx = 0;
  83.         $count = count($this->parent->children);
  84.         while ($idx<$count && $this!==$this->parent->children[$idx])
  85.             ++$idx;
  86.         if (--$idx<0) return null;
  87.         return $this->parent->children[$idx];
  88.     }
  89.  
  90.     // get dom node's inner html
  91.     function innertext() {
  92.         if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER];
  93.         if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  94.  
  95.         $ret = '';
  96.         foreach($this->nodes as $n)
  97.             $ret .= $n->outertext();
  98.         return $ret;
  99.     }
  100.  
  101.     // get dom node's outer text (with tag)
  102.     function outertext() {
  103.         if ($this->tag==='root') return $this->innertext();
  104.  
  105.         // trigger callback
  106.         if ($this->dom->callback!==null)
  107.             call_user_func_array($this->dom->callback, array($this));
  108.  
  109.         if (isset($this->_[HDOM_INFO_OUTER])) return $this->_[HDOM_INFO_OUTER];
  110.         if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  111.  
  112.         // render begin tag
  113.         $ret = $this->dom->nodes[$this->_[HDOM_INFO_BEGIN]]->makeup();
  114.  
  115.         // render inner text
  116.         if (isset($this->_[HDOM_INFO_INNER]))
  117.             $ret .= $this->_[HDOM_INFO_INNER];
  118.         else {
  119.             foreach($this->nodes as $n)
  120.                 $ret .= $n->outertext();
  121.         }
  122.  
  123.         // render end tag
  124.         if(isset($this->_[HDOM_INFO_END]) && $this->_[HDOM_INFO_END]!=0)
  125.             $ret .= '</'.$this->tag.'>';
  126.         return $ret;
  127.     }
  128.  
  129.     // get dom node's plain text
  130.     function text() {
  131.         if (isset($this->_[HDOM_INFO_INNER])) return $this->_[HDOM_INFO_INNER];
  132.         switch ($this->nodetype) {
  133.             case HDOM_TYPE_TEXT: return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  134.             case HDOM_TYPE_COMMENT: return '';
  135.             case HDOM_TYPE_UNKNOWN: return '';
  136.         }
  137.         if (strcasecmp($this->tag, 'script')===0) return '';
  138.         if (strcasecmp($this->tag, 'style')===0) return '';
  139.  
  140.         $ret = '';
  141.         foreach($this->nodes as $n)
  142.             $ret .= $n->text();
  143.         return $ret;
  144.     }
  145.    
  146.     function xmltext() {
  147.         $ret = $this->innertext();
  148.         $ret = str_ireplace('<![CDATA[', '', $ret);
  149.         $ret = str_replace(']]>', '', $ret);
  150.         return $ret;
  151.     }
  152.  
  153.     // build node's text with tag
  154.     function makeup() {
  155.         // text, comment, unknown
  156.         if (isset($this->_[HDOM_INFO_TEXT])) return $this->dom->restore_noise($this->_[HDOM_INFO_TEXT]);
  157.  
  158.         $ret = '<'.$this->tag;
  159.         $i = -1;
  160.  
  161.         foreach($this->attr as $key=>$val) {
  162.             ++$i;
  163.  
  164.             // skip removed attribute
  165.             if ($val===null || $val===false)
  166.                 continue;
  167.  
  168.             $ret .= $this->_[HDOM_INFO_SPACE][$i][0];
  169.             //no value attr: nowrap, checked selected...
  170.             if ($val===true)
  171.                 $ret .= $key;
  172.             else {
  173.                 switch($this->_[HDOM_INFO_QUOTE][$i]) {
  174.                     case HDOM_QUOTE_DOUBLE: $quote = '"'; break;
  175.                     case HDOM_QUOTE_SINGLE: $quote = '\''; break;
  176.                     default: $quote = '';
  177.                 }
  178.                 $ret .= $key.$this->_[HDOM_INFO_SPACE][$i][1].'='.$this->_[HDOM_INFO_SPACE][$i][2].$quote.$val.$quote;
  179.             }
  180.         }
  181.         $ret = $this->dom->restore_noise($ret);
  182.         return $ret . $this->_[HDOM_INFO_ENDSPACE] . '>';
  183.     }
  184.  
  185.     // find elements by css selector
  186.     function find($selector, $idx=null) {
  187.         $selectors = $this->parse_selector($selector);
  188.         if (($count=count($selectors))===0) return array();
  189.         $found_keys = array();
  190.  
  191.         // find each selector
  192.         for ($c=0; $c<$count; ++$c) {
  193.             if (($levle=count($selectors[0]))===0) return array();
  194.             if (!isset($this->_[HDOM_INFO_BEGIN])) return array();
  195.  
  196.             $head = array($this->_[HDOM_INFO_BEGIN]=>1);
  197.  
  198.             // handle descendant selectors, no recursive!
  199.             for ($l=0; $l<$levle; ++$l) {
  200.                 $ret = array();
  201.                 foreach($head as $k=>$v) {
  202.                     $n = ($k===-1) ? $this->dom->root : $this->dom->nodes[$k];
  203.                     $n->seek($selectors[$c][$l], $ret);
  204.                 }
  205.                 $head = $ret;
  206.             }
  207.  
  208.             foreach($head as $k=>$v) {
  209.                 if (!isset($found_keys[$k]))
  210.                     $found_keys[$k] = 1;
  211.             }
  212.         }
  213.  
  214.         // sort keys
  215.         ksort($found_keys);
  216.  
  217.         $found = array();
  218.         foreach($found_keys as $k=>$v)
  219.             $found[] = $this->dom->nodes[$k];
  220.  
  221.         // return nth-element or array
  222.         if (is_null($idx)) return $found;
  223.         else if ($idx<0) $idx = count($found) + $idx;
  224.         return (isset($found[$idx])) ? $found[$idx] : null;
  225.     }
  226.  
  227.     // seek for given conditions
  228.     protected function seek($selector, &$ret) {
  229.         list($tag, $key, $val, $exp, $no_key) = $selector;
  230.  
  231.         // xpath index
  232.         if ($tag && $key && is_numeric($key)) {
  233.             $count = 0;
  234.             foreach ($this->children as $c) {
  235.                 if ($tag==='*' || $tag===$c->tag) {
  236.                     if (++$count==$key) {
  237.                         $ret[$c->_[HDOM_INFO_BEGIN]] = 1;
  238.                         return;
  239.                     }
  240.                 }
  241.             }
  242.             return;
  243.         }
  244.  
  245.         $end = (!empty($this->_[HDOM_INFO_END])) ? $this->_[HDOM_INFO_END] : 0;
  246.         if ($end==0) {
  247.             $parent = $this->parent;
  248.             while (!isset($parent->_[HDOM_INFO_END]) && $parent!==null) {
  249.                 $end -= 1;
  250.                 $parent = $parent->parent;
  251.             }
  252.             $end += $parent->_[HDOM_INFO_END];
  253.         }
  254.  
  255.         for($i=$this->_[HDOM_INFO_BEGIN]+1; $i<$end; ++$i) {
  256.             $node = $this->dom->nodes[$i];
  257.             $pass = true;
  258.  
  259.             if ($tag==='*' && !$key) {
  260.                 if (in_array($node, $this->children, true))
  261.                     $ret[$i] = 1;
  262.                 continue;
  263.             }
  264.  
  265.             // compare tag
  266.             if ($tag && $tag!=$node->tag && $tag!=='*') {$pass=false;}
  267.             // compare key
  268.             if ($pass && $key) {
  269.                 if ($no_key) {
  270.                     if (isset($node->attr[$key])) $pass=false;
  271.                 }
  272.                 else if (!isset($node->attr[$key])) $pass=false;
  273.             }
  274.             // compare value
  275.             if ($pass && $key && $val  && $val!=='*') {
  276.                 $check = $this->match($exp, $val, $node->attr[$key]);
  277.                 // handle multiple class
  278.                 if (!$check && strcasecmp($key, 'class')===0) {
  279.                     foreach(explode(' ',$node->attr[$key]) as $k) {
  280.                         $check = $this->match($exp, $val, $k);
  281.                         if ($check) break;
  282.                     }
  283.                 }
  284.                 if (!$check) $pass = false;
  285.             }
  286.             if ($pass) $ret[$i] = 1;
  287.             unset($node);
  288.         }
  289.     }
  290.  
  291.     protected function match($exp, $pattern, $value) {
  292.         switch ($exp) {
  293.             case '=':
  294.                 return ($value===$pattern);
  295.             case '!=':
  296.                 return ($value!==$pattern);
  297.             case '^=':
  298.                 return preg_match("/^".preg_quote($pattern,'/')."/", $value);
  299.             case '$=':
  300.                 return preg_match("/".preg_quote($pattern,'/')."$/", $value);
  301.             case '*=':
  302.                 if ($pattern[0]=='/')
  303.                     return preg_match($pattern, $value);
  304.                 return preg_match("/".$pattern."/i", $value);
  305.         }
  306.         return false;
  307.     }
  308.  
  309.     protected function parse_selector($selector_string) {
  310.         // pattern of CSS selectors, modified from mootools
  311.         $pattern = "/([\w-:\*]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
  312.         preg_match_all($pattern, trim($selector_string).' ', $matches, PREG_SET_ORDER);
  313.         $selectors = array();
  314.         $result = array();
  315.         //print_r($matches);
  316.  
  317.         foreach ($matches as $m) {
  318.             $m[0] = trim($m[0]);
  319.             if ($m[0]==='' || $m[0]==='/' || $m[0]==='//') continue;
  320.             // for borwser grnreated xpath
  321.             if ($m[1]==='tbody') continue;
  322.  
  323.             list($tag, $key, $val, $exp, $no_key) = array($m[1], null, null, '=', false);
  324.             if(!empty($m[2])) {$key='id'; $val=$m[2];}
  325.             if(!empty($m[3])) {$key='class'; $val=$m[3];}
  326.             if(!empty($m[4])) {$key=$m[4];}
  327.             if(!empty($m[5])) {$exp=$m[5];}
  328.             if(!empty($m[6])) {$val=$m[6];}
  329.  
  330.             // convert to lowercase
  331.             if ($this->dom->lowercase) {$tag=strtolower($tag); $key=strtolower($key);}
  332.             //elements that do NOT have the specified attribute
  333.             if (isset($key[0]) && $key[0]==='!') {$key=substr($key, 1); $no_key=true;}
  334.  
  335.             $result[] = array($tag, $key, $val, $exp, $no_key);
  336.             if (trim($m[7])===',') {
  337.                 $selectors[] = $result;
  338.                 $result = array();
  339.             }
  340.         }
  341.         if (count($result)>0)
  342.             $selectors[] = $result;
  343.         return $selectors;
  344.     }
  345.  
  346.     function __get($name) {
  347.         if (isset($this->attr[$name])) return $this->attr[$name];
  348.         switch($name) {
  349.             case 'outertext': return $this->outertext();
  350.             case 'innertext': return $this->innertext();
  351.             case 'plaintext': return $this->text();
  352.             case 'xmltext': return $this->xmltext();
  353.             default: return array_key_exists($name, $this->attr);
  354.         }
  355.     }
  356.  
  357.     function __set($name, $value) {
  358.         switch($name) {
  359.             case 'outertext': return $this->_[HDOM_INFO_OUTER] = $value;
  360.             case 'innertext':
  361.                 if (isset($this->_[HDOM_INFO_TEXT])) return $this->_[HDOM_INFO_TEXT] = $value;
  362.                 return $this->_[HDOM_INFO_INNER] = $value;
  363.         }
  364.         if (!isset($this->attr[$name])) {
  365.             $this->_[HDOM_INFO_SPACE][] = array(' ', '', '');
  366.             $this->_[HDOM_INFO_QUOTE][] = HDOM_QUOTE_DOUBLE;
  367.         }
  368.         $this->attr[$name] = $value;
  369.     }
  370.  
  371.     function __isset($name) {
  372.         switch($name) {
  373.             case 'outertext': return true;
  374.             case 'innertext': return true;
  375.             case 'plaintext': return true;
  376.         }
  377.         //no value attr: nowrap, checked selected...
  378.         return (array_key_exists($name, $this->attr)) ? true : isset($this->attr[$name]);
  379.     }
  380.  
  381.     function __unset($name) {
  382.         if (isset($this->attr[$name]))
  383.             unset($this->attr[$name]);
  384.     }
  385.  
  386.     // camel naming conventions
  387.     function getAllAttributes() {return $this->attr;}
  388.     function getAttribute($name) {return $this->__get($name);}
  389.     function setAttribute($name, $value) {$this->__set($name, $value);}
  390.     function hasAttribute($name) {return $this->__isset($name);}
  391.     function removeAttribute($name) {$this->__set($name, null);}
  392.     function getElementById($id) {return $this->find("#$id", 0);}
  393.     function getElementsById($id, $idx=null) {return $this->find("#$id", $idx);}
  394.     function getElementByTagName($name) {return $this->find($name, 0);}
  395.     function getElementsByTagName($name, $idx=null) {return $this->find($name, $idx);}
  396.     function parentNode() {return $this->parent();}
  397.     function childNodes($idx=-1) {return $this->children($idx);}
  398.     function firstChild() {return $this->first_child();}
  399.     function lastChild() {return $this->last_child();}
  400.     function nextSibling() {return $this->next_sibling();}
  401.     function previousSibling() {return $this->prev_sibling();}
  402. }
  403.  
  404. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement