12 abstract class Node implements \Iterator, \Countable {
14 const VERSION =
'2.0.2';
16 public static $last_http_result;
19 public static $selected_doc = NULL;
21 protected $_prop = array();
31 static $_ar_ = array() ;
32 static $_mi_ = PHP_INT_MAX ;
34 static $_fl_ = false ;
38 protected function __construct($doc, $ids, $is_ctx=
false) {
40 if(is_int($ids)) $ids = array($ids => $doc->ids[$ids]);
41 $this->ids = $is_ctx ? $this->
_ctx_ids($ids) : $ids;
44 self::$selected_doc = $this;
48 public function __destruct() {
49 if(self::$selected_doc === $this) self::$selected_doc = self::$_nl_;
50 $this->ids = self::$_nl_;
51 unset($this->
doc, $this->ids);
62 public function attr($attr=NULL, $to_str=
false) {
68 return isset($k) ? $this->
doc()->get_attr_byId($k, $attr, $to_str) : NULL;
73 public function is_empty() {
return $this->isEmpty() ;}
75 public function isEmpty() {
76 return empty($this->ids);
79 public function isDoc() {
80 return !isset($this->
doc) || $this === $this->doc;
88 public function doc() {
89 return isset($this->
doc) ? $this->
doc : $this;
101 public function find($sel, $attr=NULL) {
102 return $this->
doc()->find($sel, $attr, $this);
105 public function exclude($sel, $attr=NULL) {
106 $e = $this->
find($sel, $attr, $this);
108 if(empty($this->exc)) {
109 $this->exc = $e->ids;
113 $this->exc = $e->ids + $this->exc;
120 public function __toString() {
122 if($this->isDoc())
return $this->html;
127 if ( !empty($this->exc) ) {
128 $ids = array_diff_key($ids, $this->exc);
130 foreach($ids as $p => $q) {
133 if($p < $q) $ret .= substr($doc->html, $p, $q-$p);
142 public function html($id=NULL) {
143 if($this->isDoc())
return $this->html;
145 $id = $this->_my_ids($id);
146 if($id ===
false)
return self::$_fl_;
149 if ( !empty($this->exc) ) {
150 $id = array_diff_key($id, $this->exc);
154 foreach($id as $p => $q) {
157 if($p<$q) $ret .= substr($doc->html, $p, $q-$p);
166 $dm = $this->isDoc() && !isset($id);
167 if($dm)
return $this->html;
169 $id = $this->_my_ids($id);
170 if($id ===
false)
return self::$_fl_;
173 $map = isset($this->tag_map) ? $this->tag_map : (isset($doc->tag_map) ? $doc->tag_map : NULL);
174 foreach($id as $p => $q) {
175 $a = $doc->get_attr_byId($p, NULL,
true);
177 if($map && isset($map[$_n=strtolower($n)])) $n = $map[$_n];
178 $h = $p++ == $q ? false : ($p<$q ? substr($doc->html, $p, $q-$p) :
'');
179 $ret .=
'<'.$n.($a?
' '.$a:
'') . ($h ===
false ?
' />' :
'>' . $h .
'</'.$n.
'>');
187 public function text($id=NULL) {
188 return html_entity_decode(strip_tags($this->
html($id)), ENT_QUOTES);
194 public function nodeName($caseFolding = NULL, $id=NULL) {
195 if(!isset($caseFolding)) $caseFolding = hQuery_HTML_Parser::$case_folding;
196 $dm = $this->isDoc() && !isset($id);
197 if($dm) $ret = array_unique($this->tags);
199 $id = $this->_my_ids($id,
true);
200 if($id ===
false)
return self::$_fl_;
201 $ret = self::array_select($this->
doc()->tags, $id);
204 foreach($ret as $i => $n) $ret[$i] = strtolower($n);
205 if($dm) $ret = array_unique($ret);
207 return count($ret) <= 1 ? reset($ret) : $ret;
237 public function pos($restore=
true) {
238 $k = key($this->ids);
241 $k = key($this->ids);
242 if($k !== NULL && $restore) {
259 if(!isset($ids)) $ids = $this->ids;
260 elseif(is_int($ids)) $ids = isset($this->ids[$ids]) ? array($ids => $this->ids[$ids]) : self::$_fl_;
262 foreach($ids as $b => $e) {
263 if($b <= $m || $b+1 >= $e and empty($exc[$b])) unset($ids[$b]);
277 $ce = reset($this->ids);
278 $cb = key($this->ids);
280 foreach($doc->ids as $b => $e) {
281 if($b < $cb || !$eq && $b == $cb)
continue;
286 $ce = next($this->ids);
288 $cb = key($this->ids);
299 protected function _doc_ids($el, $force_array=
true) {
300 if($el instanceof
self) $el = $el->ids;
302 if(is_int($el)) $el = array($el=>$this->
doc()->ids[$el]);
303 if(!is_array($el))
throw new \Exception(__CLASS__ .
'->' . __FUNCTION__ .
': not Array!');
308 protected function _my_ids($id=NULL, $keys=
false) {
309 if(!isset($id)) $id = $this->ids;
310 elseif(is_int($id)) {
311 if(!isset($this->ids[$id]))
return self::$_fl_;
312 if($keys)
return $id;
313 $id = array($id => $this->ids[$id]);
315 elseif(!$id)
return self::$_fl_;
317 return $keys ? array_keys($id) : $id;
320 protected function _parent($ids=NULL, $n=0) {
322 $ids = $this->_my_ids($ids);
323 if(!$ids)
return $ret;
328 $dids = &$this->
doc()->ids;
329 foreach($dids as $b => $e) {
332 if($ib < $le && $lb < $ib) {
338 if($ie ===
false) { $ib = -1;
break 2; }
348 if($ib <= $b && $ib < $le && $lb < $ib) {
354 function _children($ids=NULL, $n=NULL) {
356 $ids = $this->_my_ids($ids);
357 if(!$ids)
return $ret;
359 $dids = &$this->
doc()->ids;
361 if(current($dids) ===
false) $ie = reset($dids);
363 foreach($ids as $b => $e) {
364 if($b+4 >= $e)
continue;
368 while($b < $ib)
if($ie = prev($dids)) $ib = key($dids);
else { reset($dids);
break; }
371 if($ie ===
false && $ib < $b)
break;
376 if($ie = next($dids)) $ib = key($dids);
377 else { end($dids);
break; }
383 if(!isset($n)) $ret[$ib] = $ie;
384 elseif($n == $i) { $ret[$ib] = $ie;
break; }
386 $lie = $ie < $e ? $ie : $e;
388 if($ie = next($dids)) $ib = key($dids);
389 else { end($dids);
continue 3; }
397 function _next($ids=NULL, $n=0) {
399 $ids = $this->_my_ids($ids);
400 if(!$ids)
return $ret;
402 $dids = &$this->
doc()->ids;
403 $kb = $le = self::$_mi_;
408 if($e !==
false)
do { $b = key($dids); }
while( ($ib <= $b || $e < $ib) && ($e = prev($dids)) ) ;
409 if(empty($e)) $e = reset($dids);
416 if($kb < $ke) $st[$kb] = $ke;
420 $ib = ($ie = next($ids)) ? key($ids) : self::$_mi_;
424 while($le < $ib && $pt) {
426 unset($pt[$lb = key($pt)]);
430 if($b < $ib && $ib < $e) {
439 if($ke < $kb)
return $ret;
443 foreach($st as $kb => $ke) {
444 if($e !==
false)
do { $b = key($dids); }
while( $kb < $b && ($e = prev($dids)) ) ;
445 if(empty($e)) $e = reset($dids);
452 if($n == $i) { $ret[$b] = $e;
break; }
else ++$i;
453 $lie = $e < $ke ? $e : $ke;
454 while($b <= $lie && ($e = next($dids))) $b = key($dids);
455 if(!$e) { $e = end($dids);
break; }
459 }
while($e = next($dids));
465 function _prev($ids=NULL, $n=0) {
467 $ids = $this->_my_ids($ids);
468 if(!$ids)
return $ret;
470 $dids = &$this->
doc()->ids;
471 $kb = $le = self::$_mi_;
476 if($e !==
false)
do {
478 }
while( ($ib <= $b || $e < $ib) && ($e = prev($dids)) ) ;
479 if(empty($e)) $e = reset($dids);
485 if($kb < $ke) $st[$kb] = $ke;
489 $ib = ($ie = next($ids)) ? key($ids) : self::$_mi_;
493 while($le < $ib && $pt) {
495 unset($pt[$lb = key($pt)]);
499 if($b < $ib && $ib < $e) {
508 if($ke < $kb)
return $ret;
510 if($e !==
false)
do { $b = key($dids); }
while( $kb < $b && ($e = prev($dids)) ) ;
511 if(empty($e)) $e = reset($dids);
527 $lie = $e < $ke ? $e : $ke;
528 while($b <= $lie && ($e = next($dids))) $b = key($dids);
529 if(!$e) { $e = end($dids);
break; }
534 $i = $n < 0 ? 0 : $c;
536 if(0 <= $i && $i < $c) {
537 $pt = array_slice($pt, $i, 1,
true);
538 $ret[key($pt)] = reset($pt);
543 if(empty($st))
break;
546 if($kb = reset($st)) unset($st[$ke = key($st)]);
547 else $kb = self::$_mi_;
550 while($kb < $b && ($e = prev($dids))) $b = key($dids);
551 if(!$e) $e = reset($dids);
553 }
while($e = next($dids));
558 function _all($ids=NULL) {
560 $ids = $this->_my_ids($ids);
561 if(!$ids)
return $ret;
563 return $this->
doc()->_find(
'*', NULL, NULL, $ids);
567 function _has($el, $eq=
false) {
569 $e = end($this->ids);
570 if($el >= $e)
return self::$_fl_;
571 foreach($this->ids as $b => $e) {
572 if($el < $b)
return self::$_fl_;
573 if($el == $b)
return $eq;
574 if($el < $e)
return self::$_tr_;
578 if($el instanceof
self) {
if($el === $this)
return self::$_fl_; $el = $el->ids; }
else 580 foreach($el as $b => $e)
if(!$this->
_has($b))
return self::$_fl_;
592 if($el instanceof
self) $o = $el;
599 foreach($this->ids as $b => $e) {
601 while($ib < $b || !$eq && $ib == $b) {
603 if($ie ===
false) { $ib = -1;
break 2; }
610 if($ie ===
false) { $ib = -1;
break 2; }
623 public function __get($name) {
624 if($this->_prop && array_key_exists($name, $this->_prop))
return $this->_prop[$name];
625 return $this->
attr($name);
627 public function __set($name, $value) {
628 if(isset($value))
return $this->_prop[$name] = $value;
629 $this->__unset($name);
631 public function __isset($name) {
632 return isset($this->_prop[$name]);
634 public function __unset($name) {
635 unset($this->_prop[$name]);
639 public function count() {
return isset($this->ids) ? count($this->ids) : 0; }
642 public function current() {
643 $k = key($this->ids);
644 if($k === NULL)
return false;
645 return array($k => $this->ids[$k]);
647 public function valid() {
return current($this->ids) !==
false; }
648 public function key() {
return key($this->ids); }
649 public function next() {
return next($this->ids) !==
false ? $this->current() : false; }
650 public function prev() {
return prev($this->ids) !==
false ? $this->current() : false; }
651 public function rewind() { reset($this->ids);
return $this->current(); }
664 if(is_int($p))
return $p;
666 if((
string)$i === $p)
return $i;
677 $p = explode(
'(', $p, 2);
678 $p[1] = isset($p[1]) ? trim(rtrim($p[1],
')')) : NULL;
681 case 'first-child':
return 0;
683 case 'last-child' :
return -1;
684 case 'eq' :
return (
int)$p[1];
686 if(isset($map[$p[0]])) {
688 if(isset($p[1])) $p[1] = (int)$p[1];
694 return array($p[0]=>$p[1]);
727 $def = array(
'n'=>$n,
'i'=>$n,
'c'=>$a,
'p'=>$a);
728 $sel = rtrim(trim(preg_replace(
'/\\s*(>|,)\\s*/',
'$1', $sel),
" \t\n\r,>"), $sc);
729 $sel = explode(
',', $sel);
730 foreach($sel as &$a) {
731 $a = preg_split(
'|\\s+|', $a);
733 $b = explode(
'>', $b);
737 $j = strcspn($c, $sc, 0, $l);
738 if($j) $d[
'n'] = substr($c, 0, $j);
742 $j = strcspn($c, $sc, $i, $l);
744 $e = substr($c, $i, $j);
746 case '.': $d[
'c'][] = $e;
break;
747 case '#': $d[
'i'] = $e;
break;
748 case ':': $d[
'p'][] = self::html_normal_pseudoClass($e);
break;
753 if(empty($d[
'c'])) $d[
'c'] = $n;
754 if(empty($d[
'p'])) $d[
'p'] = $n;
763 protected static function html_findTagClose($str, $p) {
765 while ( $i = $p < $l ? strpos($str,
'>', $p) : $l ) {
767 $p += strcspn($str,
'"\'', $p, $i);
770 if ( $p >= $i )
return $i;
776 $e += strcspn($str,
'=', $e, $p);
781 $p += strcspn($str,
'>' . $q, $p, $l);
783 if ( $str[$p] ==
'>' )
return $p;
787 $p += strcspn($str, $q, $p, $l);
795 static function html_parseAttrStr($str, $case_folding =
true, $extended =
false) {
796 static $_attrName_firstLet = NULL;
797 if(!$_attrName_firstLet) $_attrName_firstLet = self::str_range(
'a-zA-Z_');
800 for($i = strspn($str,
" \t\n\r"), $len = strlen($str); $i < $len;) {
801 $i += strcspn($str, $_attrName_firstLet, $i);
804 $i += strcspn($str,
" \t\n\r=\"\'", $i);
805 $attrName = rtrim(substr($str, $b, $i-$b));
806 if($case_folding) $attrName = strtolower($attrName);
807 $i += strspn($str,
" \t\n\r", $i);
809 if($i<$len && $str[$i]==
'=') {
811 $i += strspn($str,
" \t\n\r", $i);
813 $q = substr($str, $i, 1);
814 if($q==
'"' || $q==
"'") {
816 $e = strpos($str, $q, $i);
818 $attrValue = substr($str, $b, $e-$b);
827 $i += strcspn($str,
" \t\n\r\"\'", $i);
828 $attrValue = substr($str, $b, $i-$b);
832 if($extended && $attrValue)
switch($case_folding ? $attrName : strtolower($attrName)) {
834 $attrValue = preg_split(
"|\\s+|", trim($attrValue));
835 if(count($attrValue) == 1) $attrValue = reset($attrValue);
836 else sort($attrValue);
840 $attrValue = self::parseCSStr($attrValue, $case_folding);
844 $ret[$attrName] = $attrValue;
849 static function html_attr2str($attr, $quote=
'"') {
850 $sq = htmlspecialchars($quote);
851 if($sq == $quote) $sq =
false;
853 if(isset($attr[
'class']) && is_array($attr[
'class'])) {
854 sort($attr[
'class']);
855 $attr[
'class'] = implode(
' ', $attr[
'class']);
857 if(isset($attr[
'style']) && is_array($attr[
'style'])) {
858 $attr[
'style'] = self::CSSArr2Str($attr[
'style']);
861 foreach($attr as $n => $v) {
862 $ret[] = $n .
'=' . $quote . ($sq ? str_replace($quote, $sq, $v) : $v) . $quote;
864 return implode(
' ', $ret);
867 static function parseCSStr($str, $case_folding =
true) {
869 $a = explode(
';', $str);
871 $v = explode(
':', $v, 2);
872 $n = trim(reset($v));
873 if($case_folding) $n = strtolower($n);
874 $ret[$n] = count($v) == 2 ? trim(end($v)) : NULL;
880 static function CSSArr2Str($css) {
884 foreach($css as $n => $v) $ret[] = $n.
':'.$v;
885 return implode(
';', $ret);
890 static function str_range($comp, $pos=0, $len=NULL) {
893 if(!isset($len) || $len > $b) $len = $b;
896 switch($c = $comp[$pos++]) {
898 $b = substr($comp, $pos, 1);
903 $c_ = ord($c=substr($comp, $pos, 1));
905 while($b++ < $c_) $ret[chr($b)] = $pos;
906 while($b-- > $c_) $ret[chr($b)] = $pos;
914 return implode(
'', array_keys($ret));
917 static function array_select($arr, $keys, $force_null=
false) {
919 is_array($keys) or is_object($keys) or $keys = array($keys);
920 foreach($keys as $k) {
921 if(isset($arr[$k])) $ret[$k] = $arr[$k];
922 elseif($force_null) $ret[$k] = NULL;
927 static function convert_encoding($a, $to, $from=NULL) {
929 isset($meth) or $meth = function_exists(
'mb_convert_encoding');
930 isset($from) or $from = $meth ? mb_internal_encoding() : iconv_get_encoding(
'internal_encoding');
934 foreach($a as $n => $v) {
935 $ret[is_string($n)?self::convert_encoding($n,$to,$from):$n] = is_string($v) || is_array($v) || $v instanceof stdClass
936 ? self::convert_encoding($v, $to, $from)
941 elseif($a instanceof stdClass) {
942 $ret = (object)array();
943 foreach($a as $n => $v) {
944 $ret->{is_string($n)?self::convert_encoding($n,$to,$from):$n} = is_string($v) || is_array($v) || $v instanceof stdClass
945 ? self::convert_encoding($v, $to, $from)
950 return is_string($a) ? $meth ? mb_convert_encoding($a, $to, $from) : iconv($from, $to, $a) : $a;
_filter_contains($el, $eq=false)
nodeName($caseFolding=NULL, $id=NULL)
_has($el, $eq=false)
$el < $this, with $eq == true -> $el <= $this
_doc_ids($el, $force_array=true)
static html_normal_pseudoClass($p)
static html_selector2struc($sel)
attr($attr=NULL, $to_str=false)