hQuery.php
HTML_Parser.php
1 <?php
2 namespace duzun\hQuery;
3 // ------------------------------------------------------------------------
4 class_exists('duzun\\hQuery\\Node', false) or require_once __DIR__ . DIRECTORY_SEPARATOR . 'Node.php';
5 
6 // ------------------------------------------------------------------------
12 class HTML_Parser extends Node {
13  public static $del_spaces = false;
14  public static $case_folding = true;
15  public static $autoclose_tags = false; // 1 - auto-close non-empty tags, 2 - auto-close all tags
16 
17  public static $_emptyTags = array('base','meta','link','hr','br','basefont','param','img','area','input','isindex','col');
18  public static $_specialTags = array('--'=>'--', '[CDATA['=>']]');
19  public static $_unparsedTags = array('style', 'script');
20  public static $_index_attribs = array('href', 'src');
21  public static $_url_attribs = array('href'=>'href', 'src'=>'src');
22 
23  protected static $_tagID_first_letter = 'a-zA-Z_';
24  protected static $_tagID_letters = 'a-zA-Z_0-9:\-';
25  protected static $_icharset = 'UTF-8'; // Internal charset
26 
27  protected $html = ''; // html string
28 
29  // Indexed data
30  protected $tags ; // id => nodeName
31  protected $attrs ; // id => attrId
32  protected $attribs ; // attrId => attrStr | attrArr
33  protected $idx_attr ; // attrName => [id=>attrVal]
34  protected $tag_idx ; // nodeNames => [ids] , [ids] == [id => end]
35  protected $attr_idx ; // attrId => id | [ids]
36  protected $class_idx ; // class => aid | [aids=>[ids]]
37 
38  protected $o = NULL;
39 
40  protected $indexed = false; // completely indexed
41 
42  // ------------------------------------------------------------------------
43  // The magic of properties
44  public function __get($name) {
45  if($this->_prop && array_key_exists($name, $this->_prop)) return $this->_prop[$name];
46  switch($name) {
47  case 'size':
48  return $this->strlen();
49 
50  case 'baseURI':
51  return $this->baseURI();
52 
53  case 'base_url':
54  return @$this->_prop['baseURL'];
55 
56  case 'href':
57  return $this->location();
58 
59  case 'charset':
60  if($this->html) {
61  $this->_prop['charset'] =
62  $c = self::detect_charset($this->html);
63  return $c;
64  }
65  break;
66  }
67  }
68 
69  public function __set($name, $value) {
70  switch($name) {
71  case 'hostURL': return false;
72  case 'baseURI':
73  case 'base_url':
74  case 'baseURL':
75  return $this->baseURI($value);
76 
77  case 'location':
78  case 'href':
79  return $this->location($value);
80  }
81 
82  if(isset($value)) return $this->_prop[$name] = $value;
83  $this->__unset($name);
84  }
85  // ------------------------------------------------------------------------
86 
87  public function location($href=NULL) {
88  if(func_num_args() < 1) {
89  return @$this->_prop['location']['href'];
90  }
91  else {
92  if(!isset($this->_prop['baseURI'])) {
93  $this->baseURI($href);
94  }
95  $this->_prop['location']['href'] = $href;
96  }
97  return ;
98  }
99 
101  public function baseURI($href=NULL) {
102  if(func_num_args() < 1) {
103  $href = @$this->_prop['baseURI'];
104  }
105  else {
106  if($href) {
107  $t = self::get_url_base($href, true);
108  if(!$t) return false;
109  list($bh, $bu) = $t;
110  }
111  else {
112  $bh = $bu = NULL;
113  }
114  $this->_prop['hostURL'] = $bh;
115  $this->_prop['baseURL'] = $bu;
116  $this->_prop['baseURI'] = $href;
117  }
118  return $href;
119 
120  }
121 
122  public function __construct($html, $idx=true) {
123  if(!is_string($html)) $html = (string)$html;
124  $c = self::detect_charset($html) or $c = NULL;
125  if($c) {
126  $ic = self::$_icharset;
127  if($c != $ic) $html = self::convert_encoding($html, $ic, $c);
128  }
129  $this->_prop['charset'] = $c;
130  if(self::$del_spaces) {
131  $html = preg_replace('#(>)?\\s+(<)?#', '$1 $2', $html); // reduce the size
132  }
133  $this->tags = self::$_ar_;
134  $l = strlen($html);
135  parent::__construct($this, self::$_ar_);
136  $this->html = $html;
137  unset($html);
138 
139  $this->_prop['baseURI'] =
140  $this->_prop['baseURL'] =
141  $this->_prop['hostURL'] = NULL;
142 
143  if($this->html && $idx) $this->_index_all();
144  }
145 
146  public function __toString() { return $this->html; }
147 
148  // ------------------------------------------------------------------------
149  public static function get_url_base($url, $array=false) {
150  if($ub = self::get_url_path($url)) {
151  $up = $ub;
152  $q = strpos($up, '/', strpos($up, '//')+2);
153  $ub = substr($up, 0, $q+1);
154  }
155  return $array && $ub ? array($ub, $up) : $ub;
156  }
157 
158  public static function get_url_path($url) {
159  $p = strpos($url, '//');
160  if($p === false || $p && !preg_match('|^[a-z]+\:$|', substr($url, 0, $p))) return false;
161  $q = strrpos($url, '/');
162  if($p+1 < $q) {
163  $url = substr($url, 0, $q+1);
164  }
165  else {
166  $url .= '/';
167  }
168  return $url;
169  }
170 
171  public function url2abs($url) {
172  if( isset($this->_prop['baseURL']) ) {
173  return self::abs_url($url, $this->_prop['baseURL']);
174  }
175 
176  // if(isset($this->_prop['baseURL']) && !preg_match('|^([a-z]{1,20}:)?\/\/|', $url)) {
177  // if($url[0] == '/') { // abs path
178  // $bu = $this->_prop['hostURL'];
179  // $url = substr($url, 1);
180  // }
181  // else {
182  // $bu = $this->_prop['baseURL'];
183  // }
184  // $url = $bu . $url;
185  // }
186  return $url;
187  }
188 
189  // ------------------------------------------------------------------------
197  public static function is_url_path($path) {
198  return preg_match('/^[a-zA-Z]+\:\/\//', $path);
199  }
200 
208  public static function is_abs_path($path) {
209  $ds = array('\\'=>1,'/'=>2);
210  if( isset($ds[substr($path, 0, 1)]) ||
211  substr($path, 1, 1) == ':' && isset($ds[substr($path, 2, 1)])
212  ) {
213  return true;
214  }
215  if(($l=strpos($path, '://')) && $l < 32) return $l;
216  return false;
217  }
218 
228  public static function abs_url($url, $base) {
229  if (!self::is_url_path($url)) {
230  $t = is_array($base) ? $base : parse_url($base);
231  if (strncmp($url, '//', 2) == 0) {
232  if ( !empty($t['scheme']) ) {
233  $url = $t['scheme'] . ':' . $url;
234  }
235  }
236  else {
237  $base = (empty($t['scheme']) ? '//' : $t['scheme'] . '://') .
238  $t['host'] . (empty($t['port']) ? '' : ':' . $t['port']);
239  if (!empty($t['path'])) {
240  $s = dirname($t['path'] . 'f');
241  if (DIRECTORY_SEPARATOR != '/') {
242  $s = strtr($s, DIRECTORY_SEPARATOR, '/');
243  }
244  if ($s && $s !== '.' && $s !== '/' && substr($url, 0, 1) !== '/') {
245  $base .= '/' . ltrim($s, '/');
246  }
247  }
248  $url = rtrim($base, '/') . '/' . ltrim($url, '/');
249  }
250  }
251  else {
252  $p = strpos($url, ':');
253  if (substr($url, $p + 3, 1) === '/' && in_array(substr($url, 0, $p), array('http', 'https'))) {
254  $url = substr($url, 0, $p + 3) . ltrim(substr($url, $p + 3), '/');
255  }
256  }
257  return $url;
258  }
259 
260  // ------------------------------------------------------------------------
261  /* <meta charset="utf-8" /> */
262  /* <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-2" /> */
263  public static function detect_charset($str) {
264  $l = 1024;
265  $str = substr($str, 0, $l);
266  $str_ = strtolower($str);
267  $p = 0;
268  while($p < $l) {
269  $p = strpos($str_, '<meta', $p);
270  if($p === false) break;
271  $p+=5;
272  $q = strpos($str_, '>', $p);
273  if($q < $p) $q = strlen($str_);
274  $a = substr($str, $p, $q-$p);
275  $p = $q+2;
276  $a = self::html_parseAttrStr($a, true);
277  if(!empty($a['charset'])) {
278  return strtoupper($a['charset']);
279  }
280  if(isset($a['http-equiv']) && strtolower($a['http-equiv']) === 'content-type') {
281  if(empty($a['content'])) return false;
282  $a = explode('charset=', $a['content']);
283  return empty($a) || empty($a[1]) ? false : strtoupper(trim($a[1]));
284  }
285  }
286  return false;
287  }
288 
289  public function strlen() {
290  return isset($this->html) ? strlen($this->html) : 0;
291  }
292 
293  public function substr($start, $length=NULL) {
294  return isset($this->html)
295  ? substr($this->html, $start, isset($length) ? $length : strlen($this->html))
296  : self::$_fl_;
297  ;
298  }
299 
301  public function _info() {
302  $inf = array();
303  $ar = array();
304  foreach($this->attribs as $i => $a) $ar[$i] = self::html_attr2str($a);
305  $inf['attribs'] = $ar ;
306  $inf['attrs'] = $this->attrs ;
307  $inf['idx_attr'] = $this->idx_attr ;
308  $inf['tag_idx'] = $this->tag_idx ;
309  $inf['attr_idx'] = $this->attr_idx ;
310  $inf['class_idx'] = $this->class_idx ;
311 
312  $lev = array();
313  $nm = array();
314  $st = array();
315  $pb = -1; $pe = PHP_INT_MAX;
316  $l = 0;
317  foreach($this->ids as $b => $e) {
318  if($pb < $b && $b < $pe) {
319  $st[] = array($pb, $pe);
320  list($pb, $pe) = array($b, $e);
321  }
322  else while($pe < $b && $st) {
323  list($pb, $pe) = array_pop($st);
324  }
325  $nm[$b] = $this->tags[$b];
326  $lev[$b] = count($st);
327  }
328  foreach($nm as $b => &$n) {
329  $n = str_repeat(' -', $lev[$b]) . ' < ' . $n . ' ' . $this->get_attr_byId($b, NULL, true) . ' >';
330  }
331  $nm = implode("\n", $nm);
332  $inf['struc'] = $nm;
333  unset($lev, $st, $nm);
334  return $inf;
335  }
336 
338  protected function _index_comments_html($o) {
339  if(!isset($o->l)) $o->l = strlen($o->h);
340  $o->tg = self::$_ar_;
341  $i = $o->i;
342  while($i < $o->l) {
343  $i = strpos($o->h, '<!--', $i);
344  if($i === false) break;
345  $p = $i;
346  $i += 4;
347  $i = strpos($o->h, '-->', $i);
348  if($i === false) $i = $o->l;
349  else $i += 3;
350  $o->tg[$p] = $i;
351  }
352  return $o;
353  }
354 
356  private function _index_tags() {
357  $s = $nix = $ix = self::$_ar_;
358  $ids = $this->ids;
359  foreach($this->tags as $id => $n) {
360  // if(!isset($ix[$n])) $ix[$n] = array();
361  $ix[$n][$id] = $ids[$id];
362  }
363  foreach($ix as $n => $v) {
364  foreach($v as $id => $e) $this->tags[$id] = $n;
365  if(isset($nix[$n])) continue;
366  $_n = strtolower($n);
367  if(isset($nix[$_n])) {
368  foreach($v as $id => $e) $nix[$_n][$id] = $e;
369  $s[] = $_n;
370  }
371  else {
372  $nix[$_n] = $v;
373  }
374  }
375  foreach($s as $_n) asort($nix[$_n]);
376  return $this->tag_idx = $nix;
377  }
378 
380  private function _index_attribs($attrs) {
381  $this->attr_idx = $this->attrs = $aix = $six = $iix = $iax = self::$_ar_;
382  $i = 0;
383  $ian = self::$_index_attribs;
384  foreach($ian as $atn) if(!isset($iax[$atn])) $iax[$atn] = self::$_ar_;
385  foreach($attrs as $str => $v) {
386  $a = self::html_parseAttrStr($str, true, false);
387  unset($attrs[$str]); // free mem
388  foreach($ian as $atn) {
389  if(isset($a[$atn])) { // href attribute has a separate index
390  if(is_array($v)) {
391  foreach($v as $e) $iax[$atn][$e] = $a[$atn];
392  }
393  else {
394  $iax[$atn][$v] = $a[$atn];
395  }
396  unset($a[$atn]);
397  }
398  }
399  if(empty($a)) continue;
400  $str = self::html_attr2str($a);
401  $aid = $i;
402  if(isset($six[$str])) {
403  $aid = $six[$str];
404  if(!is_array($iix[$aid])) $iix[$aid] = array($iix[$aid]);
405  if(is_array($v)) foreach($v as $v_) $iix[$aid][] = $v_;
406  else $iix[$aid][] = $v;
407  }
408  else {
409  $six[$str] = $aid;
410  $aix[$aid] = $a;
411  $iix[$aid] = $v;
412  ++$i;
413  }
414  }
415  unset($six, $attrs, $i); // free mem
416  foreach($aix as $aid => $a) {
417  $v = $iix[$aid];
418  if(is_array($v)) {
419  if(count($v) == 1) {
420  $v = reset($v);
421  }
422  elseif($v) {
423  $u = array();
424  foreach($v as $e) {
425  $u[$e] = $this->ids[$e];
426  $this->attrs[$e] = $aid;
427  }
428  $v = $u; unset($u);
429  }
430  }
431  if(!is_array($v)) $this->attrs[$v] = $aid;
432  $this->attr_idx[$aid] = $v; // ids for this attr
433  }
434  foreach($iax as $atn => $v) if(!$v) unset($iax[$atn]);
435  $this->idx_attr = $iax;
436  $this->attribs = $aix;
437 
438  return $this->attr_idx;
439  }
440 
442  private function _index_classes() {
443  $ix = array();
444  $aix = $this->attr_idx;
445  foreach($this->attribs as $aid => &$a) if(!empty($a['class'])) {
446  $cl = $a['class'];
447  if(!is_array($cl)) {
448  $cl = preg_split('|\\s+|',trim($cl));
449  }
450  foreach($cl as $cl) {
451  if(isset($ix[$cl])) {
452  if(!is_array($ix[$cl])) $ix[$cl] = array($ix[$cl]=>$this->attr_idx[$ix[$cl]]);
453  $ix[$cl][$aid] = $this->attr_idx[$aid];
454  }
455  else {
456  $ix[$cl] = $aid;
457  }
458  }
459  }
460  return $this->class_idx = $ix;
461  }
462 
463  protected function _index_all() {
464  if($this->indexed) return $this->tag_idx;
465  $this->indexed = true;
466 
467  // Parser state object
468  $this->o = $o = new \stdClass;
469  $o->h = $this->html;
470  $o->l = strlen($o->h);
471  $o->i = 0;
472  $o->tg = self::$_ar_;
473  $this->_index_comments_html($o);
474 
475  $firstLetterChars = self::str_range(self::$_tagID_first_letter); // first letter chars
476  $tagLettersChars = self::str_range(self::$_tagID_letters); // tag name chars
477  $specialTags = array('!'=>1, '?'=>2); // special tags
478  $unparsedTags = array_flip(self::$_unparsedTags);
479 
480  $utn = NULL; // current unparsed tag name
481  $i = $o->i;
482  $stack = $a = self::$_ar_;
483 
484  while($i < $o->l) {
485  $i = strpos($o->h, '<', $i);
486  if($i === false) break;
487  ++$i;
488  $b = $i;
489  $c = $o->h[$i];
490 
491  // if close tags
492  if($isCloseTag = $c === '/') {
493  ++$i;
494  $c = $o->h[$i];
495  }
496 
497  // usual tags
498  if( false !== strpos($firstLetterChars, $c) ) {
499  ++$i; // posibly second letter of tagName
500  $j = strspn($o->h, $tagLettersChars, $i);
501  $n = substr($o->h, $i-1, $j+1);
502  $i += $j;
503  if($utn) {
504  $n = strtolower($n);
505  if($utn !== $n || !$isCloseTag) {
506  continue;
507  }
508  $utn = NULL;
509  }
510  $i = self::html_findTagClose($o->h, $i);
511  if($i === false) break;
512  $e = $i++;
513  // open tag
514  if(!$isCloseTag) {
515  $this->ids[$e] = $e; // the end of the tag contents (>)
516  $this->tags[$e] = $n;
517  $b += $j+1;
518  $b += strspn($o->h, " \n\r\t", $b);
519  if( $b < $e ) {
520  $at = trim(substr($o->h, $b, $e-$b));
521  if($at) {
522  if(!isset($a[$at])) $a[$at] = $e;
523  elseif(!is_array($a[$at])) $a[$at] = array($a[$at], $e);
524  else $a[$at][] = $e;
525  };
526  }
527  if($o->h[$e-1] != '/') {
528  $n = strtolower($n);
529  if(isset($unparsedTags[$n])) {
530  $utn = $n;
531  }
532  $stack[$n][$b] = $e; // put in stack
533  }
534  }
535  // close tag
536  else {
537  $n = strtolower($n);
538  $s = &$stack[$n];
539  if(empty($s)) ; // error - tag not opened, but closed - ???
540  else {
541  $q = end($s);
542  $p = key($s);
543  unset($s[$p], $s);
544  $this->ids[$q] = $b-1; // the end of the tag contents (<)
545  }
546  }
547  }
548  elseif(!$isCloseTag) {
549  // special tags
550  if(isset($specialTags[$c])) {
551  $b--;
552  if(isset($o->tg[$b])) {
553  $i = $o->tg[$b];
554  continue;
555  }
556  // ???
557  }
558  else continue; // not a tag
559  $i = strpos($o->h, '>', $i);
560  if($i === false) break;
561  $e = $i++;
562  };
563  }
564 
565  foreach($stack as $n => $st) if(empty($st)) unset($stack[$n]);
566  // if(self::$autoclose_tags) {
567  // foreach($stack as $n => $st) { // ???
568  // }
569  // } else {
570  // foreach($stack as $n => $st) { // ???
571  // }
572  // }
573 
574  $this->_index_tags();
575  $this->_index_attribs($a); unset($a);
576  $this->_index_classes();
577 
578  // debug($stack); // unclosed tags
579 
580  $this->o = self::$_nl_; // Free mem
581 
582  // Read <base href="..." /> tag
583  if(!empty($this->tag_idx['base'])) {
584  foreach($this->tag_idx['base'] as $b => $e) {
585  if($a = $this->get_attr_byId($b, 'href', false)) {
586  $this->baseURI($a);
587  break;
588  }
589  }
590  }
591 
592  return $this->tag_idx;
593  }
594 
595  // ------------------------------------------------------------------------
596  protected function _get_ctx($ctx) {
597  if ( !($ctx instanceof parent) ) {
598  if(is_array($ctx) || is_int($ctx)) {
599  $ctx = new Context($this, $ctx, true);
600  }
601  else {
602  $ctx = self::$_fl_;
603  }
604  }
605  return $ctx && count($ctx) ? $ctx : self::$_fl_; // false for error - something is not ok
606  }
607  // ------------------------------------------------------------------------
608  protected function _find($name, $class=NULL, $attr=NULL, $ctx=NULL, $rec=true) {
609  // if(!in_array($name, array('meta', 'head'))) debug(compact('name', 'class', 'attr','ctx', 'rec'));
610 
611  $aids = NULL;
612  if($class) {
613  if($attr) $aids = $this->get_aids_byClassAttr($class, $attr, true);
614  else $aids = $this->get_aids_byClass($class, true);
615  if(!$aids) return self::$_nl_;
616  }
617  elseif($attr) {
618  $aids = $this->get_aids_byAttr($attr, true);
619  if(!$aids) return self::$_nl_;
620  }
621 
622  if(is_string($name) && $name !== '' && $name != '*') {
623  $name = strtolower(trim($name));
624  if(empty($this->tag_idx[$name])) return self::$_nl_; // no such tag-name
625  }
626  else $name = NULL;
627 
628  if(isset($ctx)) {
629  $ctx = $this->_get_ctx($ctx);
630  if(!$ctx) throw new \Exception(__CLASS__.'->'.__FUNCTION__.': Invalid context!');
631  }
632 
633  if(isset($aids)) {
634  $ni = $this->get_ids_byAid($aids, true, true);
635  if($ni && $ctx) $ni = $ctx->_filter_contains($ni);
636  if(!$ni) return self::$_nl_;
637  if($name) $ni = array_intersect_key($ni, $this->tag_idx[$name]);
638  }
639  else {
640  if($name) {
641  $ni = $this->tag_idx[$name];
642  if($ni && $ctx) $ni = $ctx->_filter_contains($ni);
643  }
644  else {
645  if($ctx) $ni = $ctx->_sub_ids(false);
646  else $ni = $this->ids; // all tags
647  }
648  }
649 
650  return $ni ? $ni : self::$_nl_;
651  }
652 
660  public function hasClass($id, $cl) {
661  if(!is_array($cl)) $cl = preg_split('|\\s+|',trim($cl));
662 
663  if ( is_string($id) && !is_numeric($id) ) {
664  $id = $this->find($id);
665  }
666  if ( $id instanceof Node ) {
667  $exc = $id->exc;
668  $id = $id->ids;
669  if ( !empty($exc) ) {
670  $id = array_diff_key($id, $exc);
671  }
672  }
673  if(is_array($id)) {
674  $ret = self::$_ar_;
675  foreach($id as $id => $e) {
676  $c = $this->hasClass($id, $cl);
677  if($c) $ret[$id] = $e;
678  elseif($c === false) return $c;
679  }
680  return $ret;
681  }
682  if(!isset($this->attrs[$id])) return 0; // $id has no attributes at all (but indexed)
683  foreach($cl as $cl) {
684  if(!isset($this->class_idx[$cl])) return self::$_fl_; // class doesn't exist
685  $cl = $this->class_idx[$cl];
686  $aid = $this->attrs[$id];
687  if( is_array($cl) ? !isset($cl[$aid]) : $cl != $aid ) return 0;
688  }
689  return self::$_tr_;
690  }
691 
692  protected function _filter($ids, $name=NULL, $class=NULL, $attr=NULL, $ctx=NULL) {
693  $aids = NULL;
694  if($class) {
695  if($attr) $aids = $this->get_aids_byClassAttr($class, $attr, true);
696  else $aids = $this->get_aids_byClass($class, true);
697  if(!$aids) return self::$_nl_;
698  }
699  elseif($attr) {
700  $aids = $this->get_aids_byAttr($attr, true);
701  if(!$aids) return self::$_nl_;
702  }
703  unset($class, $attr);
704  if($aids) {
705  foreach($ids as $b => $e) if(!isset($this->attrs[$b], $aids[$this->attrs[$b]])) unset($ids[$b]);
706  if(!$ids) return self::$_nl_;
707  }
708  unset($aids);
709 
710  if(is_string($name) && $name !== '' && $name != '*') {
711  $name = strtolower(trim($name));
712  if(empty($this->tag_idx[$name])) return self::$_nl_; // no such tag-name
713  foreach($ids as $b => $e) if(!isset($this->tag_idx[$name][$b])) unset($ids[$b]);
714  if(!$ids) return self::$_nl_;
715  }
716  unset($name);
717 
718  if(isset($ctx)) {
719  $ctx = $this->_get_ctx($ctx);
720  if(!$ctx) throw new \Exception(__CLASS__.'->'.__FUNCTION__.': Invalid context!');
721  $ids = $ctx->_filter_contains($ids);
722  if(!$ids) return $ids;
723  }
724  unset($ctx);
725  return $ids;
726  }
727  // ------------------------------------------------------------------------
728 
732  protected function get_aids_byAttr($attr, $as_keys=false, $actx=NULL) {
733  $aids = self::$_ar_;
734  if(isset($actx) && !$actx) return $aids;
735  if(is_string($attr)) $attr = self::html_parseAttrStr($attr);
736  if($actx)
737  foreach($actx as $aid => $a) {
738  if(!isset($this->attribs[$aid])) continue;
739  $a = $this->attribs[$aid];
740  $good = true;
741  foreach($attr as $n => $v) if(!isset($a[$n]) || $a[$n] !== $v) { $good = false; break; }
742  if($good) $aids[$aid] = $this->attr_idx[$aid];
743  }
744  else
745  foreach($this->attribs as $aid => $a) {
746  $good = true;
747  foreach($attr as $n => $v) if(!isset($a[$n]) || $a[$n] !== $v) { $good = false; break; }
748  if($good) $aids[$aid] = $this->attr_idx[$aid];
749  }
750  return $as_keys ? $aids : array_keys($aids);
751  }
752 
756  protected function get_aids_byClass($cl, $as_keys=false, $actx=NULL) {
757  $aids = self::$_ar_;
758  if(isset($actx) && !$actx) return $aids;
759  if(!is_array($cl)) $cl = preg_split('|\\s+|',trim($cl));
760  if(!$cl) $cl = array_keys($this->class_idx); // the empty set effect
761  foreach($cl as $cl) if(isset($this->class_idx[$cl])) {
762  $aid = $this->class_idx[$cl];
763  if(!$actx) {
764  if(is_array($aid)) foreach($aid as $aid => $cl) $aids[$aid] = $cl;
765  else $aids[$aid] = $this->attr_idx[$aid];
766  }
767  else {
768  if(is_array($aid)) foreach($aid as $aid => $cl) if(isset($actx[$aids])) $aids[$aid] = $cl;
769  else if(isset($actx[$aids])) $aids[$aid] = $this->attr_idx[$aid];
770  }
771  }
772  else return self::$_ar_; // no such class
773  return $as_keys ? $aids : array_keys($aids);
774  }
775 
776  protected function get_aids_byClassAttr($cl, $attr, $as_keys=false, $actx=NULL) {
777  $aids = $this->get_aids_byClass($cl, true, $actx);
778  if(is_string($attr)) $attr = self::html_parseAttrStr($attr);
779  if($attr) foreach($aids as $aid => $ix) {
780  $a = $this->attribs[$aid];
781  $good = count($a) > 1; // has only 'class' attribute
782  if($good) foreach($attr as $n => $v) {
783  if(!isset($a[$n]) || $a[$n] !== $v) {
784  $good = false;
785  break;
786  }
787  }
788  if(!$good) unset($aids[$aid]);
789  }
790  return $as_keys ? $aids : array_keys($aids);
791  }
792 
797  protected function get_ids_byAid($aid, $sort=true, $has_keys=false) {
798  $ret = self::$_ar_;
799  if(!$has_keys) $aid = self::array_select($this->attr_idx, $aid);
800  foreach($aid as $aid => $aix) {
801  if(!is_array($aix)) $aix =array($aix=>$this->ids[$aix]);
802  if(empty($ret)) $ret = $aix;
803  else foreach($aix as $id => $e) $ret[$id] = $e;
804  }
805  if($sort && $ret) ksort($ret);
806  return $ret;
807  }
808 
809  protected function get_ids_byAttr($attr, $sort=true) {
810  $ret = self::$_ar_;
811  if(is_string($attr)) $attr = self::html_parseAttrStr($attr);
812  if(!$attr) return $ret;
813  $sat = $ret;
814  foreach(self::$_index_attribs as $atn) {
815  if(isset($attr[$atn])) {
816  if(empty($this->idx_attr[$atn])) return $ret;
817  $sat[$atn] = $attr[$atn];
818  unset($attr[$atn]);
819  }
820  }
821  if($attr) {
822  $aids = $this->get_aids_byAttr($attr, true);
823  if(!$aids) return $ret;
824  foreach($aids as $aid => $aix) {
825  if(!is_array($aix)) $aix = array($aix=>$this->ids[$aix]);
826  foreach($aix as $id => $e) {
827  if($sat) {
828  $good = true;
829  foreach($sat as $n => $v) {
830  if(!isset($this->idx_attr[$n][$id]) || $this->idx_attr[$n][$id] !== $v) {
831  $good = false;
832  break;
833  }
834  }
835  if($good) $ret[$id] = $e;
836  }
837  else $ret[$id] = $e;
838  }
839  }
840  } else { // !$attr && $sat
841  $av = reset($sat); $an = key($sat); unset($sat[$an]);
842  $aix = $this->idx_attr[$an];
843  foreach($aix as $id => $v) {
844  if($v !== $av) continue;
845  $e = $this->ids[$id];
846  if($sat) {
847  $good = true;
848  foreach($sat as $n => $v) {
849  if(!isset($this->idx_attr[$n][$id]) || $this->idx_attr[$n][$id] !== $v) {
850  $good = false;
851  break;
852  }
853  }
854  if($good) $ret[$id] = $e;
855  }
856  else {
857  $ret[$id] = $e;
858  }
859  }
860  }
861  if($sort) ksort($ret);
862  return $ret;
863  }
864 
865  protected function get_ids_byClass($cl, $sort=true) {
866  $aids = $this->get_aids_byClass($cl, true);
867  return $this->get_ids_byAid($aids, $sort, true);
868  }
869 
870  protected function get_ids_byClassAttr($cl, $attr, $sort=true) {
871  $aids = $this->get_aids_byClassAttr($cl, $attr, true);
872  return $this->get_ids_byAid($aids, $sort, true);
873  }
874 
875  protected function get_attr_byAid($aid, $to_str=false) {
876  if(is_array($aid)) {
877  $ret = self::$_ar_;
878  foreach($aid as $aid) $ret[$aid] = $this->get_attr_byAid($aid, $to_str);
879  } else {
880  if(!isset($this->attribs[$aid])) return self::$_fl_;
881  $ret = $this->attribs[$aid];
882  if($to_str) $ret = self::html_attr2str($ret);
883  }
884  return $ret;
885  }
886 
887  protected function get_attr_byId($id, $attr=NULL, $to_str=false) {
888  $ret = self::$_ar_;
889  if(is_array($id)) {
890  foreach($id as $id => $e) $ret[$id] = $this->get_attr_byId($id, $attr, $to_str);
891  }
892  else {
893  if(!isset($this->ids[$id])) return self::$_fl_;
894  $bu = isset($this->_prop['baseURL']);
895  if(isset($attr)) {
896  if(isset($this->idx_attr[$attr])) $ret = @$this->idx_attr[$attr][$id];
897  else $ret = isset($this->attrs[$id], $this->attribs[$ret=$this->attrs[$id]]) ? @$this->attribs[$ret][$attr] : self::$_nl_;
898  if($ret && $bu && isset(self::$_url_attribs[$attr])) {
899  $ret = $this->url2abs($ret);
900  }
901  }
902  else {
903  if(isset($this->attrs[$id])) $ret = $this->attribs[$this->attrs[$id]];
904  foreach(self::$_index_attribs as $atn) {
905  if(isset($this->idx_attr[$atn][$id])) $ret[$atn] = $this->idx_attr[$atn][$id];
906  }
907 
908  if(!empty($bu)) {
909  foreach(self::$_url_attribs as $n) {
910  if(isset($ret[$n])) $ret[$n] = $this->url2abs($ret[$n]);
911  }
912  }
913  if($to_str) $ret = self::html_attr2str($ret);
914  }
915  }
916  return $ret;
917  }
918 }
get_ids_byAid($aid, $sort=true, $has_keys=false)
_info()
This method is for debugging only.
static abs_url($url, $base)
static is_abs_path($path)
baseURI($href=NULL)
get/set baseURI
_index_comments_html($o)
Index comment tags position in source HTML.
static is_url_path($path)
find($sel, $_attr=NULL, $ctx=NULL)
Definition: hQuery.php:182
html($id=NULL)
Definition: Node.php:142
get_aids_byClass($cl, $as_keys=false, $actx=NULL)
get_aids_byAttr($attr, $as_keys=false, $actx=NULL)