tburry /
pquery
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * @author Niels A.D. |
||
| 4 | * @author Todd Burry <[email protected]> |
||
| 5 | * @copyright 2010 Niels A.D., 2014 Todd Burry |
||
| 6 | * @license http://opensource.org/licenses/LGPL-2.1 LGPL-2.1 |
||
| 7 | * @package pQuery |
||
| 8 | */ |
||
| 9 | |||
| 10 | namespace pQuery; |
||
| 11 | |||
| 12 | /** |
||
| 13 | * Tokenizes a css selector query |
||
| 14 | */ |
||
| 15 | class CSSQueryTokenizer extends TokenizerBase {
|
||
| 16 | |||
| 17 | /** |
||
| 18 | * Opening bracket token, used for "[" |
||
| 19 | */ |
||
| 20 | const TOK_BRACKET_OPEN = 100; |
||
| 21 | /** |
||
| 22 | * Closing bracket token, used for "]" |
||
| 23 | */ |
||
| 24 | const TOK_BRACKET_CLOSE = 101; |
||
| 25 | /** |
||
| 26 | * Opening brace token, used for "("
|
||
| 27 | */ |
||
| 28 | const TOK_BRACE_OPEN = 102; |
||
| 29 | /** |
||
| 30 | * Closing brace token, used for ")" |
||
| 31 | */ |
||
| 32 | const TOK_BRACE_CLOSE = 103; |
||
| 33 | /** |
||
| 34 | * String token |
||
| 35 | */ |
||
| 36 | const TOK_STRING = 104; |
||
| 37 | /** |
||
| 38 | * Colon token, used for ":" |
||
| 39 | */ |
||
| 40 | const TOK_COLON = 105; |
||
| 41 | /** |
||
| 42 | * Comma token, used for "," |
||
| 43 | */ |
||
| 44 | const TOK_COMMA = 106; |
||
| 45 | /** |
||
| 46 | * "Not" token, used for "!" |
||
| 47 | */ |
||
| 48 | const TOK_NOT = 107; |
||
| 49 | |||
| 50 | /** |
||
| 51 | * "All" token, used for "*" in query |
||
| 52 | */ |
||
| 53 | const TOK_ALL = 108; |
||
| 54 | /** |
||
| 55 | * Pipe token, used for "|" |
||
| 56 | */ |
||
| 57 | const TOK_PIPE = 109; |
||
| 58 | /** |
||
| 59 | * Plus token, used for "+" |
||
| 60 | */ |
||
| 61 | const TOK_PLUS = 110; |
||
| 62 | /** |
||
| 63 | * "Sibling" token, used for "~" in query |
||
| 64 | */ |
||
| 65 | const TOK_SIBLING = 111; |
||
| 66 | /** |
||
| 67 | * Class token, used for "." in query |
||
| 68 | */ |
||
| 69 | const TOK_CLASS = 112; |
||
| 70 | /** |
||
| 71 | * ID token, used for "#" in query |
||
| 72 | */ |
||
| 73 | const TOK_ID = 113; |
||
| 74 | /** |
||
| 75 | * Child token, used for ">" in query |
||
| 76 | */ |
||
| 77 | const TOK_CHILD = 114; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * Attribute compare prefix token, used for "|=" |
||
| 81 | */ |
||
| 82 | const TOK_COMPARE_PREFIX = 115; |
||
| 83 | /** |
||
| 84 | * Attribute contains token, used for "*=" |
||
| 85 | */ |
||
| 86 | const TOK_COMPARE_CONTAINS = 116; |
||
| 87 | /** |
||
| 88 | * Attribute contains word token, used for "~=" |
||
| 89 | */ |
||
| 90 | const TOK_COMPARE_CONTAINS_WORD = 117; |
||
| 91 | /** |
||
| 92 | * Attribute compare end token, used for "$=" |
||
| 93 | */ |
||
| 94 | const TOK_COMPARE_ENDS = 118; |
||
| 95 | /** |
||
| 96 | * Attribute equals token, used for "=" |
||
| 97 | */ |
||
| 98 | const TOK_COMPARE_EQUALS = 119; |
||
| 99 | /** |
||
| 100 | * Attribute not equal token, used for "!=" |
||
| 101 | */ |
||
| 102 | const TOK_COMPARE_NOT_EQUAL = 120; |
||
| 103 | /** |
||
| 104 | * Attribute compare bigger than token, used for ">=" |
||
| 105 | */ |
||
| 106 | const TOK_COMPARE_BIGGER_THAN = 121; |
||
| 107 | /** |
||
| 108 | * Attribute compare smaller than token, used for "<=" |
||
| 109 | */ |
||
| 110 | const TOK_COMPARE_SMALLER_THAN = 122; |
||
| 111 | /** |
||
| 112 | * Attribute compare with regex, used for "%=" |
||
| 113 | */ |
||
| 114 | const TOK_COMPARE_REGEX = 123; |
||
| 115 | /** |
||
| 116 | * Attribute compare start token, used for "^=" |
||
| 117 | */ |
||
| 118 | const TOK_COMPARE_STARTS = 124; |
||
| 119 | |||
| 120 | /** |
||
| 121 | * Sets query identifiers |
||
| 122 | * @see TokenizerBase::$identifiers |
||
| 123 | * @access private |
||
| 124 | */ |
||
| 125 | var $identifiers = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_-?'; |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Map characters to match their tokens |
||
| 129 | * @see TokenizerBase::$custom_char_map |
||
| 130 | * @access private |
||
| 131 | */ |
||
| 132 | var $custom_char_map = array( |
||
| 133 | '.' => self::TOK_CLASS, |
||
| 134 | '#' => self::TOK_ID, |
||
| 135 | ',' => self::TOK_COMMA, |
||
| 136 | '>' => 'parse_gt',//self::TOK_CHILD, |
||
| 137 | |||
| 138 | '+' => self::TOK_PLUS, |
||
| 139 | '~' => 'parse_sibling', |
||
| 140 | |||
| 141 | '|' => 'parse_pipe', |
||
| 142 | '*' => 'parse_star', |
||
| 143 | '$' => 'parse_compare', |
||
| 144 | '=' => self::TOK_COMPARE_EQUALS, |
||
| 145 | '!' => 'parse_not', |
||
| 146 | '%' => 'parse_compare', |
||
| 147 | '^' => 'parse_compare', |
||
| 148 | '<' => 'parse_compare', |
||
| 149 | |||
| 150 | '"' => 'parse_string', |
||
| 151 | "'" => 'parse_string', |
||
| 152 | '(' => self::TOK_BRACE_OPEN,
|
||
| 153 | ')' => self::TOK_BRACE_CLOSE, |
||
| 154 | '[' => self::TOK_BRACKET_OPEN, |
||
| 155 | ']' => self::TOK_BRACKET_CLOSE, |
||
| 156 | ':' => self::TOK_COLON |
||
| 157 | ); |
||
| 158 | |||
| 159 | /** |
||
| 160 | * Parse ">" character |
||
| 161 | * @internal Could be {@link TOK_CHILD} or {@link TOK_COMPARE_BIGGER_THAN}
|
||
| 162 | * @return int |
||
| 163 | */ |
||
| 164 | protected function parse_gt() {
|
||
| 165 | if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
|
||
| 166 | ++$this->pos; |
||
| 167 | return ($this->token = self::TOK_COMPARE_BIGGER_THAN); |
||
| 168 | } else {
|
||
| 169 | return ($this->token = self::TOK_CHILD); |
||
| 170 | } |
||
| 171 | } |
||
| 172 | |||
| 173 | /** |
||
| 174 | * Parse "~" character |
||
| 175 | * @internal Could be {@link TOK_SIBLING} or {@link TOK_COMPARE_CONTAINS_WORD}
|
||
| 176 | * @return int |
||
| 177 | */ |
||
| 178 | protected function parse_sibling() {
|
||
| 179 | if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
|
||
| 180 | ++$this->pos; |
||
| 181 | return ($this->token = self::TOK_COMPARE_CONTAINS_WORD); |
||
| 182 | } else {
|
||
| 183 | return ($this->token = self::TOK_SIBLING); |
||
| 184 | } |
||
| 185 | } |
||
| 186 | |||
| 187 | /** |
||
| 188 | * Parse "|" character |
||
| 189 | * @internal Could be {@link TOK_PIPE} or {@link TOK_COMPARE_PREFIX}
|
||
| 190 | * @return int |
||
| 191 | */ |
||
| 192 | 1 | protected function parse_pipe() {
|
|
| 193 | 1 | if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
|
|
| 194 | 1 | ++$this->pos; |
|
| 195 | 1 | return ($this->token = self::TOK_COMPARE_PREFIX); |
|
| 196 | } else {
|
||
| 197 | return ($this->token = self::TOK_PIPE); |
||
| 198 | } |
||
| 199 | } |
||
| 200 | |||
| 201 | /** |
||
| 202 | * Parse "*" character |
||
| 203 | * @internal Could be {@link TOK_ALL} or {@link TOK_COMPARE_CONTAINS}
|
||
| 204 | * @return int |
||
| 205 | */ |
||
| 206 | 2 | protected function parse_star() {
|
|
| 207 | 2 | if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
|
|
| 208 | ++$this->pos; |
||
| 209 | return ($this->token = self::TOK_COMPARE_CONTAINS); |
||
| 210 | } else {
|
||
| 211 | 2 | return ($this->token = self::TOK_ALL); |
|
| 212 | } |
||
| 213 | } |
||
| 214 | |||
| 215 | /** |
||
| 216 | * Parse "!" character |
||
| 217 | * @internal Could be {@link TOK_NOT} or {@link TOK_COMPARE_NOT_EQUAL}
|
||
| 218 | * @return int |
||
| 219 | */ |
||
| 220 | protected function parse_not() {
|
||
| 221 | if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
|
||
| 222 | ++$this->pos; |
||
| 223 | return ($this->token = self::TOK_COMPARE_NOT_EQUAL); |
||
| 224 | } else {
|
||
| 225 | return ($this->token = self::TOK_NOT); |
||
| 226 | } |
||
| 227 | } |
||
| 228 | |||
| 229 | /** |
||
| 230 | * Parse several compare characters |
||
| 231 | * @return int |
||
| 232 | */ |
||
| 233 | protected function parse_compare() {
|
||
| 234 | if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
|
||
| 235 | switch($this->doc[$this->pos++]) {
|
||
| 236 | case '$': |
||
| 237 | return ($this->token = self::TOK_COMPARE_ENDS); |
||
| 238 | case '%': |
||
| 239 | return ($this->token = self::TOK_COMPARE_REGEX); |
||
| 240 | case '^': |
||
| 241 | return ($this->token = self::TOK_COMPARE_STARTS); |
||
| 242 | case '<': |
||
| 243 | return ($this->token = self::TOK_COMPARE_SMALLER_THAN); |
||
| 244 | } |
||
| 245 | } |
||
| 246 | return false; |
||
| 247 | } |
||
| 248 | |||
| 249 | /** |
||
| 250 | * Parse strings (" and ')
|
||
| 251 | * @return int |
||
| 252 | */ |
||
| 253 | 5 | protected function parse_string() {
|
|
| 254 | 5 | $char = $this->doc[$this->pos]; |
|
| 255 | |||
| 256 | 5 | while (true) {
|
|
| 257 | 5 | if ($this->next_search($char.'\\', false) !== self::TOK_NULL) {
|
|
| 258 | 5 | if($this->doc[$this->pos] === $char) {
|
|
| 259 | 5 | break; |
|
| 260 | } else {
|
||
| 261 | ++$this->pos; |
||
| 262 | } |
||
| 263 | } else {
|
||
| 264 | $this->pos = $this->size - 1; |
||
| 265 | break; |
||
| 266 | } |
||
| 267 | } |
||
| 268 | |||
| 269 | 5 | return ($this->token = self::TOK_STRING); |
|
| 270 | } |
||
| 271 | |||
| 272 | } |
||
| 273 | |||
| 274 | /** |
||
| 275 | * Performs a css select query on HTML nodes |
||
| 276 | */ |
||
| 277 | class HtmlSelector {
|
||
| 278 | |||
| 279 | /** |
||
| 280 | * Parser object |
||
| 281 | * @internal If string, then it will create a new instance as parser |
||
| 282 | * @var CSSQueryTokenizer |
||
| 283 | */ |
||
| 284 | var $parser = 'pQuery\\CSSQueryTokenizer'; |
||
| 285 | |||
| 286 | /** |
||
| 287 | * Target of queries |
||
| 288 | * @var DomNode |
||
| 289 | */ |
||
| 290 | var $root = null; |
||
| 291 | |||
| 292 | /** |
||
| 293 | * Last performed query, result in {@link $result}
|
||
| 294 | * @var string |
||
| 295 | */ |
||
| 296 | var $query = ''; |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Array of matching nodes |
||
| 300 | * @var array |
||
| 301 | */ |
||
| 302 | var $result = array(); |
||
| 303 | |||
| 304 | /** |
||
| 305 | * Include root in search, if false the only child nodes are evaluated |
||
| 306 | * @var bool |
||
| 307 | */ |
||
| 308 | var $search_root = false; |
||
| 309 | |||
| 310 | /** |
||
| 311 | * Search recursively |
||
| 312 | * @var bool |
||
| 313 | */ |
||
| 314 | var $search_recursive = true; |
||
| 315 | |||
| 316 | /** |
||
| 317 | * Extra function map for custom filters |
||
| 318 | * @var array |
||
| 319 | * @internal array('root' => 'filter_root') will cause the
|
||
| 320 | * selector to call $this->filter_root at :root |
||
| 321 | * @see DomNode::$filter_map |
||
| 322 | */ |
||
| 323 | var $custom_filter_map = array(); |
||
| 324 | |||
| 325 | /** |
||
| 326 | * Class constructor |
||
| 327 | * @param DomNode $root {@link $root}
|
||
| 328 | * @param string $query |
||
| 329 | * @param bool $search_root {@link $search_root}
|
||
| 330 | * @param bool $search_recursive {@link $search_recursive}
|
||
| 331 | * @param CSSQueryTokenizer $parser If null, then default class will be used |
||
| 332 | */ |
||
| 333 | 37 | function __construct($root, $query = '*', $search_root = false, $search_recursive = true, $parser = null) {
|
|
| 334 | 37 | if ($parser === null) {
|
|
| 335 | 37 | $parser = new $this->parser(); |
|
| 336 | 37 | } |
|
| 337 | 37 | $this->parser = $parser; |
|
| 338 | 37 | $this->root =& $root; |
|
| 339 | |||
| 340 | 37 | $this->search_root = $search_root; |
|
| 341 | 37 | $this->search_recursive = $search_recursive; |
|
| 342 | |||
| 343 | 37 | $this->select($query); |
|
| 344 | 37 | } |
|
| 345 | |||
| 346 | #php4 PHP4 class constructor compatibility |
||
| 347 | #function HtmlSelector($root, $query = '*', $search_root = false, $search_recursive = true, $parser = null) {return $this->__construct($root, $query, $search_root, $search_recursive, $parser);}
|
||
|
0 ignored issues
–
show
|
|||
| 348 | #php4e |
||
| 349 | |||
| 350 | /** |
||
| 351 | * toString method, returns {@link $query}
|
||
| 352 | * @return string |
||
| 353 | * @access private |
||
| 354 | */ |
||
| 355 | function __toString() {
|
||
| 356 | return $this->query; |
||
| 357 | } |
||
| 358 | |||
| 359 | /** |
||
| 360 | * Class magic invoke method, performs {@link select()}
|
||
| 361 | * @return array |
||
| 362 | * @access private |
||
| 363 | */ |
||
| 364 | function __invoke($query = '*') {
|
||
| 365 | return $this->select($query); |
||
| 366 | } |
||
| 367 | |||
| 368 | /** |
||
| 369 | * Perform query |
||
| 370 | * @param string $query |
||
| 371 | * @return array False on failure |
||
| 372 | */ |
||
| 373 | 37 | function select($query = '*') {
|
|
| 374 | 37 | $this->parser->setDoc($query); |
|
| 375 | 37 | $this->query = $query; |
|
| 376 | 37 | return (($this->parse()) ? $this->result : false); |
|
| 377 | } |
||
| 378 | |||
| 379 | /** |
||
| 380 | * Trigger error |
||
| 381 | * @param string $error |
||
| 382 | * @internal %pos% and %tok% will be replace in string with position and token(string) |
||
| 383 | * @access private |
||
| 384 | */ |
||
| 385 | protected function error($error) {
|
||
| 386 | $error = htmlentities(str_replace( |
||
| 387 | array('%tok%', '%pos%'),
|
||
| 388 | array($this->parser->getTokenString(), (int) $this->parser->getPos()), |
||
| 389 | $error |
||
| 390 | )); |
||
| 391 | |||
| 392 | trigger_error($error); |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * Get identifier (parse identifier or string) |
||
| 397 | * @param bool $do_error Error on failure |
||
| 398 | * @return string False on failure |
||
| 399 | * @access private |
||
| 400 | */ |
||
| 401 | 36 | protected function parse_getIdentifier($do_error = true) {
|
|
| 402 | 36 | $p =& $this->parser; |
|
| 403 | 36 | $tok = $p->token; |
|
| 404 | |||
| 405 | 36 | if ($tok === CSSQueryTokenizer::TOK_IDENTIFIER) {
|
|
| 406 | 36 | return $p->getTokenString(); |
|
| 407 | 17 | } elseif($tok === CSSQueryTokenizer::TOK_STRING) {
|
|
| 408 | 5 | return str_replace(array('\\\'', '\\"', '\\\\'), array('\'', '"', '\\'), $p->getTokenString(1, -1));
|
|
| 409 | 17 | } elseif ($do_error) {
|
|
| 410 | $this->error('Expected identifier at %pos%!');
|
||
| 411 | } |
||
| 412 | 17 | return false; |
|
| 413 | } |
||
| 414 | |||
| 415 | /** |
||
| 416 | * Get query conditions (tag, attribute and filter conditions) |
||
| 417 | * @return array False on failure |
||
| 418 | * @see DomNode::match() |
||
| 419 | * @access private |
||
| 420 | */ |
||
| 421 | 37 | protected function parse_conditions() {
|
|
| 422 | 37 | $p =& $this->parser; |
|
| 423 | 37 | $tok = $p->token; |
|
| 424 | |||
| 425 | 37 | if ($tok === CSSQueryTokenizer::TOK_NULL) {
|
|
| 426 | $this->error('Invalid search pattern(1): Empty string!');
|
||
| 427 | return false; |
||
| 428 | } |
||
| 429 | 37 | $conditions_all = array(); |
|
| 430 | |||
| 431 | //Tags |
||
| 432 | 37 | while ($tok !== CSSQueryTokenizer::TOK_NULL) {
|
|
| 433 | 37 | $conditions = array('tags' => array(), 'attributes' => array());
|
|
| 434 | |||
| 435 | 37 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
|
| 436 | 2 | $tok = $p->next(); |
|
| 437 | 2 | if (($tok === CSSQueryTokenizer::TOK_PIPE) && ($tok = $p->next()) && ($tok !== CSSQueryTokenizer::TOK_ALL)) {
|
|
| 438 | if (($tag = $this->parse_getIdentifier()) === false) {
|
||
| 439 | return false; |
||
| 440 | } |
||
| 441 | $conditions['tags'][] = array( |
||
| 442 | 'tag' => $tag, |
||
| 443 | 'compare' => 'name' |
||
| 444 | ); |
||
| 445 | $tok = $p->next_no_whitespace(); |
||
| 446 | } else {
|
||
| 447 | 2 | $conditions['tags'][''] = array( |
|
| 448 | 2 | 'tag' => '', |
|
| 449 | 'match' => false |
||
| 450 | 2 | ); |
|
| 451 | 2 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
|
| 452 | $tok = $p->next_no_whitespace(); |
||
| 453 | } |
||
| 454 | } |
||
| 455 | 37 | } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
|
| 456 | $tok = $p->next(); |
||
| 457 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
||
| 458 | $conditions['tags'][] = array( |
||
| 459 | 'tag' => '', |
||
| 460 | 'compare' => 'namespace', |
||
| 461 | ); |
||
| 462 | } elseif (($tag = $this->parse_getIdentifier()) !== false) {
|
||
| 463 | $conditions['tags'][] = array( |
||
| 464 | 'tag' => $tag, |
||
| 465 | 'compare' => 'total', |
||
| 466 | ); |
||
| 467 | } else {
|
||
| 468 | return false; |
||
| 469 | } |
||
| 470 | $tok = $p->next_no_whitespace(); |
||
| 471 | 36 | } elseif ($tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
|
|
| 472 | $tok = $p->next_no_whitespace(); |
||
| 473 | $last_mode = 'or'; |
||
| 474 | |||
| 475 | while (true) {
|
||
| 476 | $match = true; |
||
| 477 | $compare = 'total'; |
||
| 478 | |||
| 479 | if ($tok === CSSQueryTokenizer::TOK_NOT) {
|
||
| 480 | $match = false; |
||
| 481 | $tok = $p->next_no_whitespace(); |
||
| 482 | } |
||
| 483 | |||
| 484 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
||
| 485 | $tok = $p->next(); |
||
| 486 | if ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
||
| 487 | $this->next(); |
||
| 488 | $compare = 'name'; |
||
| 489 | if (($tag = $this->parse_getIdentifier()) === false) {
|
||
| 490 | return false; |
||
| 491 | } |
||
| 492 | } |
||
| 493 | } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
||
| 494 | $tok = $p->next(); |
||
| 495 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
||
| 496 | $tag = ''; |
||
| 497 | $compare = 'namespace'; |
||
| 498 | } elseif (($tag = $this->parse_getIdentifier()) === false) {
|
||
| 499 | return false; |
||
| 500 | } |
||
| 501 | $tok = $p->next_no_whitespace(); |
||
| 502 | } else {
|
||
| 503 | if (($tag = $this->parse_getIdentifier()) === false) {
|
||
| 504 | return false; |
||
| 505 | } |
||
| 506 | $tok = $p->next(); |
||
| 507 | if ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
||
| 508 | $tok = $p->next(); |
||
| 509 | |||
| 510 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
||
| 511 | $compare = 'namespace'; |
||
| 512 | } elseif (($tag_name = $this->parse_getIdentifier()) !== false) {
|
||
| 513 | $tag = $tag.':'.$tag_name; |
||
| 514 | } else {
|
||
| 515 | return false; |
||
| 516 | } |
||
| 517 | |||
| 518 | $tok = $p->next_no_whitespace(); |
||
| 519 | } |
||
| 520 | } |
||
| 521 | if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
|
||
| 522 | $tok = $p->next_no_whitespace(); |
||
| 523 | } |
||
| 524 | |||
| 525 | $conditions['tags'][] = array( |
||
| 526 | 'tag' => $tag, |
||
| 527 | 'match' => $match, |
||
| 528 | 'operator' => $last_mode, |
||
| 529 | 'compare' => $compare |
||
| 530 | ); |
||
| 531 | switch($tok) {
|
||
| 532 | case CSSQueryTokenizer::TOK_COMMA: |
||
| 533 | $tok = $p->next_no_whitespace(); |
||
| 534 | $last_mode = 'or'; |
||
| 535 | continue 2; |
||
| 536 | case CSSQueryTokenizer::TOK_PLUS: |
||
| 537 | $tok = $p->next_no_whitespace(); |
||
| 538 | $last_mode = 'and'; |
||
| 539 | continue 2; |
||
| 540 | case CSSQueryTokenizer::TOK_BRACE_CLOSE: |
||
| 541 | $tok = $p->next(); |
||
| 542 | break 2; |
||
| 543 | default: |
||
| 544 | $this->error('Expected closing brace or comma at pos %pos%!');
|
||
| 545 | return false; |
||
| 546 | } |
||
| 547 | } |
||
| 548 | 36 | } elseif (($tag = $this->parse_getIdentifier(false)) !== false) {
|
|
| 549 | 22 | $tok = $p->next(); |
|
| 550 | 22 | if ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
|
| 551 | $tok = $p->next(); |
||
| 552 | |||
| 553 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
||
| 554 | $conditions['tags'][] = array( |
||
| 555 | 'tag' => $tag, |
||
| 556 | 'compare' => 'namespace' |
||
| 557 | ); |
||
| 558 | } elseif (($tag_name = $this->parse_getIdentifier()) !== false) {
|
||
| 559 | $tag = $tag.':'.$tag_name; |
||
| 560 | $conditions['tags'][] = array( |
||
| 561 | 'tag' => $tag, |
||
| 562 | 'match' => true |
||
| 563 | ); |
||
| 564 | } else {
|
||
| 565 | return false; |
||
| 566 | } |
||
| 567 | |||
| 568 | $tok = $p->next(); |
||
| 569 | 22 | } elseif ($tag === 'text' && $tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
|
|
| 570 | 1 | $pos = $p->getPos(); |
|
| 571 | 1 | $tok = $p->next(); |
|
| 572 | 1 | if ($tok === CSSQueryTokenizer::TOK_BRACE_CLOSE) {
|
|
| 573 | 1 | $conditions['tags'][] = array( |
|
| 574 | 1 | 'tag' => '~text~', |
|
| 575 | 'match' => true |
||
| 576 | 1 | ); |
|
| 577 | 1 | $p->next(); |
|
| 578 | 1 | } else {
|
|
| 579 | $p->setPos($pos); |
||
| 580 | } |
||
| 581 | 1 | } else {
|
|
| 582 | 21 | $conditions['tags'][] = array( |
|
| 583 | 21 | 'tag' => $tag, |
|
| 584 | 'match' => true |
||
| 585 | 21 | ); |
|
| 586 | } |
||
| 587 | 22 | } else {
|
|
| 588 | 17 | unset($conditions['tags']); |
|
| 589 | } |
||
| 590 | |||
| 591 | //Class |
||
| 592 | 37 | $last_mode = 'or'; |
|
| 593 | 37 | if ($tok === CSSQueryTokenizer::TOK_CLASS) {
|
|
| 594 | 12 | $p->next(); |
|
| 595 | 12 | if (($class = $this->parse_getIdentifier()) === false) {
|
|
| 596 | return false; |
||
| 597 | } |
||
| 598 | |||
| 599 | 12 | $conditions['attributes'][] = array( |
|
| 600 | 12 | 'attribute' => 'class', |
|
| 601 | 12 | 'operator_value' => 'contains_word', |
|
| 602 | 12 | 'value' => $class, |
|
| 603 | 'operator_result' => $last_mode |
||
| 604 | 12 | ); |
|
| 605 | 12 | $last_mode = 'and'; |
|
| 606 | 12 | $tok = $p->next(); |
|
| 607 | 12 | } |
|
| 608 | |||
| 609 | //ID |
||
| 610 | 37 | if ($tok === CSSQueryTokenizer::TOK_ID) {
|
|
| 611 | 1 | $p->next(); |
|
| 612 | 1 | if (($id = $this->parse_getIdentifier()) === false) {
|
|
| 613 | return false; |
||
| 614 | } |
||
| 615 | |||
| 616 | 1 | $conditions['attributes'][] = array( |
|
| 617 | 1 | 'attribute' => 'id', |
|
| 618 | 1 | 'operator_value' => 'equals', |
|
| 619 | 1 | 'value' => $id, |
|
| 620 | 'operator_result' => $last_mode |
||
| 621 | 1 | ); |
|
| 622 | 1 | $last_mode = 'and'; |
|
| 623 | 1 | $tok = $p->next(); |
|
| 624 | 1 | } |
|
| 625 | |||
| 626 | //Attributes |
||
| 627 | 37 | if ($tok === CSSQueryTokenizer::TOK_BRACKET_OPEN) {
|
|
| 628 | 6 | $tok = $p->next_no_whitespace(); |
|
| 629 | |||
| 630 | 6 | while (true) {
|
|
| 631 | 6 | $match = true; |
|
| 632 | 6 | $compare = 'total'; |
|
| 633 | 6 | if ($tok === CSSQueryTokenizer::TOK_NOT) {
|
|
| 634 | $match = false; |
||
| 635 | $tok = $p->next_no_whitespace(); |
||
| 636 | } |
||
| 637 | |||
| 638 | 6 | if ($tok === CSSQueryTokenizer::TOK_ALL) {
|
|
| 639 | $tok = $p->next(); |
||
| 640 | if ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
||
| 641 | $tok = $p->next(); |
||
| 642 | if (($attribute = $this->parse_getIdentifier()) === false) {
|
||
| 643 | return false; |
||
| 644 | } |
||
| 645 | $compare = 'name'; |
||
| 646 | $tok = $p->next(); |
||
| 647 | } else {
|
||
| 648 | $this->error('Expected pipe at pos %pos%!');
|
||
| 649 | return false; |
||
| 650 | } |
||
| 651 | 6 | } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
|
| 652 | $tok = $p->next(); |
||
| 653 | if (($tag = $this->parse_getIdentifier()) === false) {
|
||
| 654 | return false; |
||
| 655 | } |
||
| 656 | $tok = $p->next_no_whitespace(); |
||
| 657 | 6 | } elseif (($attribute = $this->parse_getIdentifier()) !== false) {
|
|
| 658 | 6 | $tok = $p->next(); |
|
| 659 | 6 | if ($tok === CSSQueryTokenizer::TOK_PIPE) {
|
|
| 660 | $tok = $p->next(); |
||
| 661 | |||
| 662 | if (($attribute_name = $this->parse_getIdentifier()) !== false) {
|
||
| 663 | $attribute = $attribute.':'.$attribute_name; |
||
| 664 | } else {
|
||
| 665 | return false; |
||
| 666 | } |
||
| 667 | |||
| 668 | $tok = $p->next(); |
||
| 669 | } |
||
| 670 | 6 | } else {
|
|
| 671 | return false; |
||
| 672 | } |
||
| 673 | 6 | if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
|
|
| 674 | $tok = $p->next_no_whitespace(); |
||
| 675 | } |
||
| 676 | |||
| 677 | 6 | $operator_value = ''; |
|
| 678 | 6 | $val = ''; |
|
| 679 | switch($tok) {
|
||
| 680 | 6 | case CSSQueryTokenizer::TOK_COMPARE_PREFIX: |
|
| 681 | 6 | case CSSQueryTokenizer::TOK_COMPARE_CONTAINS: |
|
| 682 | 6 | case CSSQueryTokenizer::TOK_COMPARE_CONTAINS_WORD: |
|
| 683 | 6 | case CSSQueryTokenizer::TOK_COMPARE_ENDS: |
|
| 684 | 6 | case CSSQueryTokenizer::TOK_COMPARE_EQUALS: |
|
| 685 | 6 | case CSSQueryTokenizer::TOK_COMPARE_NOT_EQUAL: |
|
| 686 | 6 | case CSSQueryTokenizer::TOK_COMPARE_REGEX: |
|
| 687 | 6 | case CSSQueryTokenizer::TOK_COMPARE_STARTS: |
|
| 688 | 6 | case CSSQueryTokenizer::TOK_COMPARE_BIGGER_THAN: |
|
| 689 | 6 | case CSSQueryTokenizer::TOK_COMPARE_SMALLER_THAN: |
|
| 690 | 6 | $operator_value = $p->getTokenString(($tok === CSSQueryTokenizer::TOK_COMPARE_EQUALS) ? 0 : -1); |
|
| 691 | 6 | $p->next_no_whitespace(); |
|
| 692 | |||
| 693 | 6 | if (($val = $this->parse_getIdentifier()) === false) {
|
|
| 694 | return false; |
||
| 695 | } |
||
| 696 | |||
| 697 | 6 | $tok = $p->next_no_whitespace(); |
|
| 698 | 6 | break; |
|
| 699 | } |
||
| 700 | |||
| 701 | 6 | if ($operator_value && $val) {
|
|
| 702 | 6 | $conditions['attributes'][] = array( |
|
| 703 | 6 | 'attribute' => $attribute, |
|
| 704 | 6 | 'operator_value' => $operator_value, |
|
| 705 | 6 | 'value' => $val, |
|
| 706 | 6 | 'match' => $match, |
|
| 707 | 6 | 'operator_result' => $last_mode, |
|
| 708 | 'compare' => $compare |
||
| 709 | 6 | ); |
|
| 710 | 6 | } else {
|
|
| 711 | $conditions['attributes'][] = array( |
||
| 712 | 'attribute' => $attribute, |
||
| 713 | 'value' => $match, |
||
| 714 | 'operator_result' => $last_mode, |
||
| 715 | 'compare' => $compare |
||
| 716 | ); |
||
| 717 | } |
||
| 718 | |||
| 719 | switch($tok) {
|
||
| 720 | 6 | case CSSQueryTokenizer::TOK_COMMA: |
|
| 721 | $tok = $p->next_no_whitespace(); |
||
| 722 | $last_mode = 'or'; |
||
| 723 | continue 2; |
||
| 724 | 6 | case CSSQueryTokenizer::TOK_PLUS: |
|
| 725 | $tok = $p->next_no_whitespace(); |
||
| 726 | $last_mode = 'and'; |
||
| 727 | continue 2; |
||
| 728 | 6 | case CSSQueryTokenizer::TOK_BRACKET_CLOSE: |
|
| 729 | 6 | $tok = $p->next(); |
|
| 730 | 6 | break 2; |
|
| 731 | default: |
||
| 732 | $this->error('Expected closing bracket or comma at pos %pos%!');
|
||
| 733 | return false; |
||
| 734 | } |
||
| 735 | } |
||
| 736 | 6 | } |
|
| 737 | |||
| 738 | 37 | if (count($conditions['attributes']) < 1) {
|
|
| 739 | 23 | unset($conditions['attributes']); |
|
| 740 | 23 | } |
|
| 741 | |||
| 742 | 37 | while($tok === CSSQueryTokenizer::TOK_COLON) {
|
|
| 743 | 3 | if (count($conditions) < 1) {
|
|
| 744 | 2 | $conditions['tags'] = array(array( |
|
| 745 | 2 | 'tag' => '', |
|
| 746 | 'match' => false |
||
| 747 | 2 | )); |
|
| 748 | 2 | } |
|
| 749 | |||
| 750 | 3 | $tok = $p->next(); |
|
| 751 | 3 | if (($filter = $this->parse_getIdentifier()) === false) {
|
|
| 752 | return false; |
||
| 753 | } |
||
| 754 | |||
| 755 | 3 | if (($tok = $p->next()) === CSSQueryTokenizer::TOK_BRACE_OPEN) {
|
|
| 756 | $start = $p->pos; |
||
| 757 | $count = 1; |
||
| 758 | while ((($tok = $p->next()) !== CSSQueryTokenizer::TOK_NULL) && !(($tok === CSSQueryTokenizer::TOK_BRACE_CLOSE) && (--$count === 0))) {
|
||
| 759 | if ($tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
|
||
| 760 | ++$count; |
||
| 761 | } |
||
| 762 | } |
||
| 763 | |||
| 764 | |||
| 765 | if ($tok !== CSSQueryTokenizer::TOK_BRACE_CLOSE) {
|
||
| 766 | $this->error('Expected closing brace at pos %pos%!');
|
||
| 767 | return false; |
||
| 768 | } |
||
| 769 | $len = $p->pos - 1 - $start; |
||
| 770 | $params = (($len > 0) ? substr($p->doc, $start + 1, $len) : ''); |
||
| 771 | $tok = $p->next(); |
||
| 772 | } else {
|
||
| 773 | 3 | $params = ''; |
|
| 774 | } |
||
| 775 | |||
| 776 | 3 | $conditions['filters'][] = array('filter' => $filter, 'params' => $params);
|
|
| 777 | 3 | } |
|
| 778 | 37 | if (count($conditions) < 1) {
|
|
| 779 | $this->error('Invalid search pattern(2): No conditions found!');
|
||
| 780 | return false; |
||
| 781 | } |
||
| 782 | 37 | $conditions_all[] = $conditions; |
|
| 783 | |||
| 784 | 37 | if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
|
|
| 785 | $tok = $p->next_no_whitespace(); |
||
| 786 | } |
||
| 787 | |||
| 788 | 37 | if ($tok === CSSQueryTokenizer::TOK_COMMA) {
|
|
| 789 | 2 | $tok = $p->next_no_whitespace(); |
|
| 790 | 2 | continue; |
|
| 791 | } else {
|
||
| 792 | 37 | break; |
|
| 793 | } |
||
| 794 | } |
||
| 795 | |||
| 796 | 37 | return $conditions_all; |
|
| 797 | } |
||
| 798 | |||
| 799 | |||
| 800 | /** |
||
| 801 | * Evaluate root node using custom callback |
||
| 802 | * @param array $conditions {@link parse_conditions()}
|
||
| 803 | * @param bool|int $recursive |
||
| 804 | * @param bool $check_root |
||
| 805 | * @return array |
||
| 806 | * @access private |
||
| 807 | */ |
||
| 808 | 37 | protected function parse_callback($conditions, $recursive = true, $check_root = false) {
|
|
| 809 | 37 | return ($this->result = $this->root->getChildrenByMatch( |
|
| 810 | 37 | $conditions, |
|
| 811 | 37 | $recursive, |
|
| 812 | 37 | $check_root, |
|
| 813 | 37 | $this->custom_filter_map |
|
| 814 | 37 | )); |
|
| 815 | } |
||
| 816 | |||
| 817 | /** |
||
| 818 | * Parse first bit of query, only root node has to be evaluated now |
||
| 819 | * @param bool|int $recursive |
||
| 820 | * @return bool |
||
| 821 | * @internal Result of query is set in {@link $result}
|
||
| 822 | * @access private |
||
| 823 | */ |
||
| 824 | 37 | protected function parse_single($recursive = true) {
|
|
| 825 | 37 | if (($c = $this->parse_conditions()) === false) {
|
|
| 826 | return false; |
||
| 827 | } |
||
| 828 | |||
| 829 | 37 | $this->parse_callback($c, $recursive, $this->search_root); |
|
| 830 | 37 | return true; |
|
| 831 | } |
||
| 832 | |||
| 833 | /** |
||
| 834 | * Evaluate sibling nodes |
||
| 835 | * @return bool |
||
| 836 | * @internal Result of query is set in {@link $result}
|
||
| 837 | * @access private |
||
| 838 | */ |
||
| 839 | protected function parse_adjacent() {
|
||
| 840 | $tmp = $this->result; |
||
| 841 | $this->result = array(); |
||
| 842 | if (($c = $this->parse_conditions()) === false) {
|
||
| 843 | return false; |
||
| 844 | } |
||
| 845 | |||
| 846 | foreach($tmp as $t) {
|
||
| 847 | if (($sibling = $t->getNextSibling()) !== false) {
|
||
| 848 | if ($sibling->match($c, true, $this->custom_filter_map)) {
|
||
| 849 | $this->result[] = $sibling; |
||
| 850 | } |
||
| 851 | } |
||
| 852 | } |
||
| 853 | |||
| 854 | return true; |
||
| 855 | } |
||
| 856 | |||
| 857 | /** |
||
| 858 | * Evaluate {@link $result}
|
||
| 859 | * @param bool $parent Evaluate parent nodes |
||
| 860 | * @param bool|int $recursive |
||
| 861 | * @return bool |
||
| 862 | * @internal Result of query is set in {@link $result}
|
||
| 863 | * @access private |
||
| 864 | */ |
||
| 865 | protected function parse_result($parent = false, $recursive = true) {
|
||
| 866 | $tmp = $this->result; |
||
| 867 | $tmp_res = array(); |
||
| 868 | if (($c = $this->parse_conditions()) === false) {
|
||
| 869 | return false; |
||
| 870 | } |
||
| 871 | |||
| 872 | foreach(array_keys($tmp) as $t) {
|
||
| 873 | $this->root = (($parent) ? $tmp[$t]->parent : $tmp[$t]); |
||
| 874 | $this->parse_callback($c, $recursive); |
||
| 875 | foreach(array_keys($this->result) as $r) {
|
||
| 876 | if (!in_array($this->result[$r], $tmp_res, true)) {
|
||
| 877 | $tmp_res[] = $this->result[$r]; |
||
| 878 | } |
||
| 879 | } |
||
| 880 | } |
||
| 881 | $this->result = $tmp_res; |
||
| 882 | return true; |
||
| 883 | } |
||
| 884 | |||
| 885 | /** |
||
| 886 | * Parse full query |
||
| 887 | * @return bool |
||
| 888 | * @internal Result of query is set in {@link $result}
|
||
| 889 | * @access private |
||
| 890 | */ |
||
| 891 | 37 | protected function parse() {
|
|
| 892 | 37 | $p =& $this->parser; |
|
| 893 | 37 | $p->setPos(0); |
|
| 894 | 37 | $this->result = array(); |
|
| 895 | |||
| 896 | 37 | if (!$this->parse_single()) {
|
|
| 897 | return false; |
||
| 898 | } |
||
| 899 | |||
| 900 | 37 | while (count($this->result) > 0) {
|
|
| 901 | 37 | switch($p->token) {
|
|
| 902 | 37 | case CSSQueryTokenizer::TOK_CHILD: |
|
| 903 | $this->parser->next_no_whitespace(); |
||
| 904 | if (!$this->parse_result(false, 1)) {
|
||
| 905 | return false; |
||
| 906 | } |
||
| 907 | break; |
||
| 908 | |||
| 909 | 37 | case CSSQueryTokenizer::TOK_SIBLING: |
|
| 910 | $this->parser->next_no_whitespace(); |
||
| 911 | if (!$this->parse_result(true, 1)) {
|
||
| 912 | return false; |
||
| 913 | } |
||
| 914 | break; |
||
| 915 | |||
| 916 | 37 | case CSSQueryTokenizer::TOK_PLUS: |
|
| 917 | $this->parser->next_no_whitespace(); |
||
| 918 | if (!$this->parse_adjacent()) {
|
||
| 919 | return false; |
||
| 920 | } |
||
| 921 | break; |
||
| 922 | |||
| 923 | 37 | case CSSQueryTokenizer::TOK_ALL: |
|
| 924 | 37 | case CSSQueryTokenizer::TOK_IDENTIFIER: |
|
| 925 | 37 | case CSSQueryTokenizer::TOK_STRING: |
|
| 926 | 37 | case CSSQueryTokenizer::TOK_BRACE_OPEN: |
|
| 927 | 37 | case CSSQueryTokenizer::TOK_BRACKET_OPEN: |
|
| 928 | 37 | case CSSQueryTokenizer::TOK_ID: |
|
| 929 | 37 | case CSSQueryTokenizer::TOK_CLASS: |
|
| 930 | 37 | case CSSQueryTokenizer::TOK_COLON: |
|
| 931 | if (!$this->parse_result()) {
|
||
| 932 | return false; |
||
| 933 | } |
||
| 934 | break; |
||
| 935 | |||
| 936 | 37 | case CSSQueryTokenizer::TOK_NULL: |
|
| 937 | 37 | break 2; |
|
| 938 | |||
| 939 | default: |
||
| 940 | $this->error('Invalid search pattern(3): No result modifier found!');
|
||
| 941 | return false; |
||
| 942 | } |
||
| 943 | } |
||
| 944 | |||
| 945 | 37 | return true; |
|
| 946 | } |
||
| 947 | } |
||
| 948 | |||
| 949 | ?> |
||
| 950 |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.