PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 4.4.0
MailPoet – Newsletters, Email Marketing, and Automation v4.4.0
5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / lib-3rd-party / pquery / gan_selector_html.php
mailpoet / lib-3rd-party / pquery Last commit date
third_party 3 years ago IQuery.php 3 years ago LICENSE 4 years ago gan_formatter.php 3 years ago gan_node_html.php 3 years ago gan_parser_html.php 3 years ago gan_selector_html.php 3 years ago gan_tokenizer.php 3 years ago gan_xml2array.php 3 years ago ganon.php 3 years ago index.php 4 years ago pQuery.php 3 years ago
gan_selector_html.php
951 lines
1 <?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing
2 /**
3 * @author Niels A.D.
4 * @author Todd Burry <todd@vanillaforums.com>
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 MailPoetVendor\pQuery;
11
12 if (!defined('ABSPATH')) exit;
13
14
15 /**
16 * Tokenizes a css selector query
17 */
18 class CSSQueryTokenizer extends TokenizerBase {
19
20 /**
21 * Opening bracket token, used for "["
22 */
23 const TOK_BRACKET_OPEN = 100;
24 /**
25 * Closing bracket token, used for "]"
26 */
27 const TOK_BRACKET_CLOSE = 101;
28 /**
29 * Opening brace token, used for "("
30 */
31 const TOK_BRACE_OPEN = 102;
32 /**
33 * Closing brace token, used for ")"
34 */
35 const TOK_BRACE_CLOSE = 103;
36 /**
37 * String token
38 */
39 const TOK_STRING = 104;
40 /**
41 * Colon token, used for ":"
42 */
43 const TOK_COLON = 105;
44 /**
45 * Comma token, used for ","
46 */
47 const TOK_COMMA = 106;
48 /**
49 * "Not" token, used for "!"
50 */
51 const TOK_NOT = 107;
52
53 /**
54 * "All" token, used for "*" in query
55 */
56 const TOK_ALL = 108;
57 /**
58 * Pipe token, used for "|"
59 */
60 const TOK_PIPE = 109;
61 /**
62 * Plus token, used for "+"
63 */
64 const TOK_PLUS = 110;
65 /**
66 * "Sibling" token, used for "~" in query
67 */
68 const TOK_SIBLING = 111;
69 /**
70 * Class token, used for "." in query
71 */
72 const TOK_CLASS = 112;
73 /**
74 * ID token, used for "#" in query
75 */
76 const TOK_ID = 113;
77 /**
78 * Child token, used for ">" in query
79 */
80 const TOK_CHILD = 114;
81
82 /**
83 * Attribute compare prefix token, used for "|="
84 */
85 const TOK_COMPARE_PREFIX = 115;
86 /**
87 * Attribute contains token, used for "*="
88 */
89 const TOK_COMPARE_CONTAINS = 116;
90 /**
91 * Attribute contains word token, used for "~="
92 */
93 const TOK_COMPARE_CONTAINS_WORD = 117;
94 /**
95 * Attribute compare end token, used for "$="
96 */
97 const TOK_COMPARE_ENDS = 118;
98 /**
99 * Attribute equals token, used for "="
100 */
101 const TOK_COMPARE_EQUALS = 119;
102 /**
103 * Attribute not equal token, used for "!="
104 */
105 const TOK_COMPARE_NOT_EQUAL = 120;
106 /**
107 * Attribute compare bigger than token, used for ">="
108 */
109 const TOK_COMPARE_BIGGER_THAN = 121;
110 /**
111 * Attribute compare smaller than token, used for "<="
112 */
113 const TOK_COMPARE_SMALLER_THAN = 122;
114 /**
115 * Attribute compare with regex, used for "%="
116 */
117 const TOK_COMPARE_REGEX = 123;
118 /**
119 * Attribute compare start token, used for "^="
120 */
121 const TOK_COMPARE_STARTS = 124;
122
123 /**
124 * Sets query identifiers
125 * @see TokenizerBase::$identifiers
126 * @access private
127 */
128 var $identifiers = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_-?';
129
130 /**
131 * Map characters to match their tokens
132 * @see TokenizerBase::$custom_char_map
133 * @access private
134 */
135 var $custom_char_map = array(
136 '.' => self::TOK_CLASS,
137 '#' => self::TOK_ID,
138 ',' => self::TOK_COMMA,
139 '>' => 'parse_gt',//self::TOK_CHILD,
140
141 '+' => self::TOK_PLUS,
142 '~' => 'parse_sibling',
143
144 '|' => 'parse_pipe',
145 '*' => 'parse_star',
146 '$' => 'parse_compare',
147 '=' => self::TOK_COMPARE_EQUALS,
148 '!' => 'parse_not',
149 '%' => 'parse_compare',
150 '^' => 'parse_compare',
151 '<' => 'parse_compare',
152
153 '"' => 'parse_string',
154 "'" => 'parse_string',
155 '(' => self::TOK_BRACE_OPEN,
156 ')' => self::TOK_BRACE_CLOSE,
157 '[' => self::TOK_BRACKET_OPEN,
158 ']' => self::TOK_BRACKET_CLOSE,
159 ':' => self::TOK_COLON
160 );
161
162 /**
163 * Parse ">" character
164 * @internal Could be {@link TOK_CHILD} or {@link TOK_COMPARE_BIGGER_THAN}
165 * @return int
166 */
167 protected function parse_gt() {
168 if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
169 ++$this->pos;
170 return ($this->token = self::TOK_COMPARE_BIGGER_THAN);
171 } else {
172 return ($this->token = self::TOK_CHILD);
173 }
174 }
175
176 /**
177 * Parse "~" character
178 * @internal Could be {@link TOK_SIBLING} or {@link TOK_COMPARE_CONTAINS_WORD}
179 * @return int
180 */
181 protected function parse_sibling() {
182 if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
183 ++$this->pos;
184 return ($this->token = self::TOK_COMPARE_CONTAINS_WORD);
185 } else {
186 return ($this->token = self::TOK_SIBLING);
187 }
188 }
189
190 /**
191 * Parse "|" character
192 * @internal Could be {@link TOK_PIPE} or {@link TOK_COMPARE_PREFIX}
193 * @return int
194 */
195 protected function parse_pipe() {
196 if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
197 ++$this->pos;
198 return ($this->token = self::TOK_COMPARE_PREFIX);
199 } else {
200 return ($this->token = self::TOK_PIPE);
201 }
202 }
203
204 /**
205 * Parse "*" character
206 * @internal Could be {@link TOK_ALL} or {@link TOK_COMPARE_CONTAINS}
207 * @return int
208 */
209 protected function parse_star() {
210 if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
211 ++$this->pos;
212 return ($this->token = self::TOK_COMPARE_CONTAINS);
213 } else {
214 return ($this->token = self::TOK_ALL);
215 }
216 }
217
218 /**
219 * Parse "!" character
220 * @internal Could be {@link TOK_NOT} or {@link TOK_COMPARE_NOT_EQUAL}
221 * @return int
222 */
223 protected function parse_not() {
224 if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
225 ++$this->pos;
226 return ($this->token = self::TOK_COMPARE_NOT_EQUAL);
227 } else {
228 return ($this->token = self::TOK_NOT);
229 }
230 }
231
232 /**
233 * Parse several compare characters
234 * @return int
235 */
236 protected function parse_compare() {
237 if ((($this->pos + 1) < $this->size) && ($this->doc[$this->pos + 1] === '=')) {
238 switch($this->doc[$this->pos++]) {
239 case '$':
240 return ($this->token = self::TOK_COMPARE_ENDS);
241 case '%':
242 return ($this->token = self::TOK_COMPARE_REGEX);
243 case '^':
244 return ($this->token = self::TOK_COMPARE_STARTS);
245 case '<':
246 return ($this->token = self::TOK_COMPARE_SMALLER_THAN);
247 }
248 }
249 return false;
250 }
251
252 /**
253 * Parse strings (" and ')
254 * @return int
255 */
256 protected function parse_string() {
257 $char = $this->doc[$this->pos];
258
259 while (true) {
260 if ($this->next_search($char.'\\', false) !== self::TOK_NULL) {
261 if($this->doc[$this->pos] === $char) {
262 break;
263 } else {
264 ++$this->pos;
265 }
266 } else {
267 $this->pos = $this->size - 1;
268 break;
269 }
270 }
271
272 return ($this->token = self::TOK_STRING);
273 }
274
275 }
276
277 /**
278 * Performs a css select query on HTML nodes
279 */
280 class HtmlSelector {
281
282 /**
283 * Parser object
284 * @internal If string, then it will create a new instance as parser
285 * @var CSSQueryTokenizer
286 */
287 var $parser = 'MailPoetVendor\\pQuery\\CSSQueryTokenizer';
288
289 /**
290 * Target of queries
291 * @var DomNode
292 */
293 var $root = null;
294
295 /**
296 * Last performed query, result in {@link $result}
297 * @var string
298 */
299 var $query = '';
300
301 /**
302 * Array of matching nodes
303 * @var array
304 */
305 var $result = array();
306
307 /**
308 * Include root in search, if false the only child nodes are evaluated
309 * @var bool
310 */
311 var $search_root = false;
312
313 /**
314 * Search recursively
315 * @var bool
316 */
317 var $search_recursive = true;
318
319 /**
320 * Extra function map for custom filters
321 * @var array
322 * @internal array('root' => 'filter_root') will cause the
323 * selector to call $this->filter_root at :root
324 * @see DomNode::$filter_map
325 */
326 var $custom_filter_map = array();
327
328 /**
329 * Class constructor
330 * @param DomNode $root {@link $root}
331 * @param string $query
332 * @param bool $search_root {@link $search_root}
333 * @param bool $search_recursive {@link $search_recursive}
334 * @param CSSQueryTokenizer $parser If null, then default class will be used
335 */
336 function __construct($root, $query = '*', $search_root = false, $search_recursive = true, $parser = null) {
337 if ($parser === null) {
338 $parser = new $this->parser();
339 }
340 $this->parser = $parser;
341 $this->root =& $root;
342
343 $this->search_root = $search_root;
344 $this->search_recursive = $search_recursive;
345
346 $this->select($query);
347 }
348
349 #php4 PHP4 class constructor compatibility
350 #function HtmlSelector($root, $query = '*', $search_root = false, $search_recursive = true, $parser = null) {return $this->__construct($root, $query, $search_root, $search_recursive, $parser);}
351 #php4e
352
353 /**
354 * toString method, returns {@link $query}
355 * @return string
356 * @access private
357 */
358 function __toString() {
359 return $this->query;
360 }
361
362 /**
363 * Class magic invoke method, performs {@link select()}
364 * @return array
365 * @access private
366 */
367 function __invoke($query = '*') {
368 return $this->select($query);
369 }
370
371 /**
372 * Perform query
373 * @param string $query
374 * @return array False on failure
375 */
376 function select($query = '*') {
377 $this->parser->setDoc($query);
378 $this->query = $query;
379 return (($this->parse()) ? $this->result : false);
380 }
381
382 /**
383 * Trigger error
384 * @param string $error
385 * @internal %pos% and %tok% will be replace in string with position and token(string)
386 * @access private
387 */
388 protected function error($error) {
389 $error = htmlentities(str_replace(
390 array('%tok%', '%pos%'),
391 array($this->parser->getTokenString(), (int) $this->parser->getPos()),
392 $error
393 ));
394
395 trigger_error($error);
396 }
397
398 /**
399 * Get identifier (parse identifier or string)
400 * @param bool $do_error Error on failure
401 * @return string False on failure
402 * @access private
403 */
404 protected function parse_getIdentifier($do_error = true) {
405 $p =& $this->parser;
406 $tok = $p->token;
407
408 if ($tok === CSSQueryTokenizer::TOK_IDENTIFIER) {
409 return $p->getTokenString();
410 } elseif($tok === CSSQueryTokenizer::TOK_STRING) {
411 return str_replace(array('\\\'', '\\"', '\\\\'), array('\'', '"', '\\'), $p->getTokenString(1, -1));
412 } elseif ($do_error) {
413 $this->error('Expected identifier at %pos%!');
414 }
415 return false;
416 }
417
418 /**
419 * Get query conditions (tag, attribute and filter conditions)
420 * @return array False on failure
421 * @see DomNode::match()
422 * @access private
423 */
424 protected function parse_conditions() {
425 $p =& $this->parser;
426 $tok = $p->token;
427
428 if ($tok === CSSQueryTokenizer::TOK_NULL) {
429 $this->error('Invalid search pattern(1): Empty string!');
430 return false;
431 }
432 $conditions_all = array();
433
434 //Tags
435 while ($tok !== CSSQueryTokenizer::TOK_NULL) {
436 $conditions = array('tags' => array(), 'attributes' => array());
437
438 if ($tok === CSSQueryTokenizer::TOK_ALL) {
439 $tok = $p->next();
440 if (($tok === CSSQueryTokenizer::TOK_PIPE) && ($tok = $p->next()) && ($tok !== CSSQueryTokenizer::TOK_ALL)) {
441 if (($tag = $this->parse_getIdentifier()) === false) {
442 return false;
443 }
444 $conditions['tags'][] = array(
445 'tag' => $tag,
446 'compare' => 'name'
447 );
448 $tok = $p->next_no_whitespace();
449 } else {
450 $conditions['tags'][''] = array(
451 'tag' => '',
452 'match' => false
453 );
454 if ($tok === CSSQueryTokenizer::TOK_ALL) {
455 $tok = $p->next_no_whitespace();
456 }
457 }
458 } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
459 $tok = $p->next();
460 if ($tok === CSSQueryTokenizer::TOK_ALL) {
461 $conditions['tags'][] = array(
462 'tag' => '',
463 'compare' => 'namespace',
464 );
465 } elseif (($tag = $this->parse_getIdentifier()) !== false) {
466 $conditions['tags'][] = array(
467 'tag' => $tag,
468 'compare' => 'total',
469 );
470 } else {
471 return false;
472 }
473 $tok = $p->next_no_whitespace();
474 } elseif ($tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
475 $tok = $p->next_no_whitespace();
476 $last_mode = 'or';
477
478 while (true) {
479 $match = true;
480 $compare = 'total';
481
482 if ($tok === CSSQueryTokenizer::TOK_NOT) {
483 $match = false;
484 $tok = $p->next_no_whitespace();
485 }
486
487 if ($tok === CSSQueryTokenizer::TOK_ALL) {
488 $tok = $p->next();
489 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
490 $this->next();
491 $compare = 'name';
492 if (($tag = $this->parse_getIdentifier()) === false) {
493 return false;
494 }
495 }
496 } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
497 $tok = $p->next();
498 if ($tok === CSSQueryTokenizer::TOK_ALL) {
499 $tag = '';
500 $compare = 'namespace';
501 } elseif (($tag = $this->parse_getIdentifier()) === false) {
502 return false;
503 }
504 $tok = $p->next_no_whitespace();
505 } else {
506 if (($tag = $this->parse_getIdentifier()) === false) {
507 return false;
508 }
509 $tok = $p->next();
510 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
511 $tok = $p->next();
512
513 if ($tok === CSSQueryTokenizer::TOK_ALL) {
514 $compare = 'namespace';
515 } elseif (($tag_name = $this->parse_getIdentifier()) !== false) {
516 $tag = $tag.':'.$tag_name;
517 } else {
518 return false;
519 }
520
521 $tok = $p->next_no_whitespace();
522 }
523 }
524 if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
525 $tok = $p->next_no_whitespace();
526 }
527
528 $conditions['tags'][] = array(
529 'tag' => $tag,
530 'match' => $match,
531 'operator' => $last_mode,
532 'compare' => $compare
533 );
534 switch($tok) {
535 case CSSQueryTokenizer::TOK_COMMA:
536 $tok = $p->next_no_whitespace();
537 $last_mode = 'or';
538 continue 2;
539 case CSSQueryTokenizer::TOK_PLUS:
540 $tok = $p->next_no_whitespace();
541 $last_mode = 'and';
542 continue 2;
543 case CSSQueryTokenizer::TOK_BRACE_CLOSE:
544 $tok = $p->next();
545 break 2;
546 default:
547 $this->error('Expected closing brace or comma at pos %pos%!');
548 return false;
549 }
550 }
551 } elseif (($tag = $this->parse_getIdentifier(false)) !== false) {
552 $tok = $p->next();
553 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
554 $tok = $p->next();
555
556 if ($tok === CSSQueryTokenizer::TOK_ALL) {
557 $conditions['tags'][] = array(
558 'tag' => $tag,
559 'compare' => 'namespace'
560 );
561 } elseif (($tag_name = $this->parse_getIdentifier()) !== false) {
562 $tag = $tag.':'.$tag_name;
563 $conditions['tags'][] = array(
564 'tag' => $tag,
565 'match' => true
566 );
567 } else {
568 return false;
569 }
570
571 $tok = $p->next();
572 } elseif ($tag === 'text' && $tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
573 $pos = $p->getPos();
574 $tok = $p->next();
575 if ($tok === CSSQueryTokenizer::TOK_BRACE_CLOSE) {
576 $conditions['tags'][] = array(
577 'tag' => '~text~',
578 'match' => true
579 );
580 $p->next();
581 } else {
582 $p->setPos($pos);
583 }
584 } else {
585 $conditions['tags'][] = array(
586 'tag' => $tag,
587 'match' => true
588 );
589 }
590 } else {
591 unset($conditions['tags']);
592 }
593
594 //Class
595 $last_mode = 'or';
596 if ($tok === CSSQueryTokenizer::TOK_CLASS) {
597 $p->next();
598 if (($class = $this->parse_getIdentifier()) === false) {
599 return false;
600 }
601
602 $conditions['attributes'][] = array(
603 'attribute' => 'class',
604 'operator_value' => 'contains_word',
605 'value' => $class,
606 'operator_result' => $last_mode
607 );
608 $last_mode = 'and';
609 $tok = $p->next();
610 }
611
612 //ID
613 if ($tok === CSSQueryTokenizer::TOK_ID) {
614 $p->next();
615 if (($id = $this->parse_getIdentifier()) === false) {
616 return false;
617 }
618
619 $conditions['attributes'][] = array(
620 'attribute' => 'id',
621 'operator_value' => 'equals',
622 'value' => $id,
623 'operator_result' => $last_mode
624 );
625 $last_mode = 'and';
626 $tok = $p->next();
627 }
628
629 //Attributes
630 if ($tok === CSSQueryTokenizer::TOK_BRACKET_OPEN) {
631 $tok = $p->next_no_whitespace();
632
633 while (true) {
634 $match = true;
635 $compare = 'total';
636 if ($tok === CSSQueryTokenizer::TOK_NOT) {
637 $match = false;
638 $tok = $p->next_no_whitespace();
639 }
640
641 if ($tok === CSSQueryTokenizer::TOK_ALL) {
642 $tok = $p->next();
643 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
644 $tok = $p->next();
645 if (($attribute = $this->parse_getIdentifier()) === false) {
646 return false;
647 }
648 $compare = 'name';
649 $tok = $p->next();
650 } else {
651 $this->error('Expected pipe at pos %pos%!');
652 return false;
653 }
654 } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
655 $tok = $p->next();
656 if (($tag = $this->parse_getIdentifier()) === false) {
657 return false;
658 }
659 $tok = $p->next_no_whitespace();
660 } elseif (($attribute = $this->parse_getIdentifier()) !== false) {
661 $tok = $p->next();
662 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
663 $tok = $p->next();
664
665 if (($attribute_name = $this->parse_getIdentifier()) !== false) {
666 $attribute = $attribute.':'.$attribute_name;
667 } else {
668 return false;
669 }
670
671 $tok = $p->next();
672 }
673 } else {
674 return false;
675 }
676 if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
677 $tok = $p->next_no_whitespace();
678 }
679
680 $operator_value = '';
681 $val = '';
682 switch($tok) {
683 case CSSQueryTokenizer::TOK_COMPARE_PREFIX:
684 case CSSQueryTokenizer::TOK_COMPARE_CONTAINS:
685 case CSSQueryTokenizer::TOK_COMPARE_CONTAINS_WORD:
686 case CSSQueryTokenizer::TOK_COMPARE_ENDS:
687 case CSSQueryTokenizer::TOK_COMPARE_EQUALS:
688 case CSSQueryTokenizer::TOK_COMPARE_NOT_EQUAL:
689 case CSSQueryTokenizer::TOK_COMPARE_REGEX:
690 case CSSQueryTokenizer::TOK_COMPARE_STARTS:
691 case CSSQueryTokenizer::TOK_COMPARE_BIGGER_THAN:
692 case CSSQueryTokenizer::TOK_COMPARE_SMALLER_THAN:
693 $operator_value = $p->getTokenString(($tok === CSSQueryTokenizer::TOK_COMPARE_EQUALS) ? 0 : -1);
694 $p->next_no_whitespace();
695
696 if (($val = $this->parse_getIdentifier()) === false) {
697 return false;
698 }
699
700 $tok = $p->next_no_whitespace();
701 break;
702 }
703
704 if ($operator_value && $val) {
705 $conditions['attributes'][] = array(
706 'attribute' => $attribute,
707 'operator_value' => $operator_value,
708 'value' => $val,
709 'match' => $match,
710 'operator_result' => $last_mode,
711 'compare' => $compare
712 );
713 } else {
714 $conditions['attributes'][] = array(
715 'attribute' => $attribute,
716 'value' => $match,
717 'operator_result' => $last_mode,
718 'compare' => $compare
719 );
720 }
721
722 switch($tok) {
723 case CSSQueryTokenizer::TOK_COMMA:
724 $tok = $p->next_no_whitespace();
725 $last_mode = 'or';
726 continue 2;
727 case CSSQueryTokenizer::TOK_PLUS:
728 $tok = $p->next_no_whitespace();
729 $last_mode = 'and';
730 continue 2;
731 case CSSQueryTokenizer::TOK_BRACKET_CLOSE:
732 $tok = $p->next();
733 break 2;
734 default:
735 $this->error('Expected closing bracket or comma at pos %pos%!');
736 return false;
737 }
738 }
739 }
740
741 if (count($conditions['attributes']) < 1) {
742 unset($conditions['attributes']);
743 }
744
745 while($tok === CSSQueryTokenizer::TOK_COLON) {
746 if (count($conditions) < 1) {
747 $conditions['tags'] = array(array(
748 'tag' => '',
749 'match' => false
750 ));
751 }
752
753 $tok = $p->next();
754 if (($filter = $this->parse_getIdentifier()) === false) {
755 return false;
756 }
757
758 if (($tok = $p->next()) === CSSQueryTokenizer::TOK_BRACE_OPEN) {
759 $start = $p->pos;
760 $count = 1;
761 while ((($tok = $p->next()) !== CSSQueryTokenizer::TOK_NULL) && !(($tok === CSSQueryTokenizer::TOK_BRACE_CLOSE) && (--$count === 0))) {
762 if ($tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
763 ++$count;
764 }
765 }
766
767
768 if ($tok !== CSSQueryTokenizer::TOK_BRACE_CLOSE) {
769 $this->error('Expected closing brace at pos %pos%!');
770 return false;
771 }
772 $len = $p->pos - 1 - $start;
773 $params = (($len > 0) ? substr($p->doc, $start + 1, $len) : '');
774 $tok = $p->next();
775 } else {
776 $params = '';
777 }
778
779 $conditions['filters'][] = array('filter' => $filter, 'params' => $params);
780 }
781 if (count($conditions) < 1) {
782 $this->error('Invalid search pattern(2): No conditions found!');
783 return false;
784 }
785 $conditions_all[] = $conditions;
786
787 if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
788 $tok = $p->next_no_whitespace();
789 }
790
791 if ($tok === CSSQueryTokenizer::TOK_COMMA) {
792 $tok = $p->next_no_whitespace();
793 continue;
794 } else {
795 break;
796 }
797 }
798
799 return $conditions_all;
800 }
801
802
803 /**
804 * Evaluate root node using custom callback
805 * @param array $conditions {@link parse_conditions()}
806 * @param bool|int $recursive
807 * @param bool $check_root
808 * @return array
809 * @access private
810 */
811 protected function parse_callback($conditions, $recursive = true, $check_root = false) {
812 return ($this->result = $this->root->getChildrenByMatch(
813 $conditions,
814 $recursive,
815 $check_root,
816 $this->custom_filter_map
817 ));
818 }
819
820 /**
821 * Parse first bit of query, only root node has to be evaluated now
822 * @param bool|int $recursive
823 * @return bool
824 * @internal Result of query is set in {@link $result}
825 * @access private
826 */
827 protected function parse_single($recursive = true) {
828 if (($c = $this->parse_conditions()) === false) {
829 return false;
830 }
831
832 $this->parse_callback($c, $recursive, $this->search_root);
833 return true;
834 }
835
836 /**
837 * Evaluate sibling nodes
838 * @return bool
839 * @internal Result of query is set in {@link $result}
840 * @access private
841 */
842 protected function parse_adjacent() {
843 $tmp = $this->result;
844 $this->result = array();
845 if (($c = $this->parse_conditions()) === false) {
846 return false;
847 }
848
849 foreach($tmp as $t) {
850 if (($sibling = $t->getNextSibling()) !== false) {
851 if ($sibling->match($c, true, $this->custom_filter_map)) {
852 $this->result[] = $sibling;
853 }
854 }
855 }
856
857 return true;
858 }
859
860 /**
861 * Evaluate {@link $result}
862 * @param bool $parent Evaluate parent nodes
863 * @param bool|int $recursive
864 * @return bool
865 * @internal Result of query is set in {@link $result}
866 * @access private
867 */
868 protected function parse_result($parent = false, $recursive = true) {
869 $tmp = $this->result;
870 $tmp_res = array();
871 if (($c = $this->parse_conditions()) === false) {
872 return false;
873 }
874
875 foreach(array_keys($tmp) as $t) {
876 $this->root = (($parent) ? $tmp[$t]->parent : $tmp[$t]);
877 $this->parse_callback($c, $recursive);
878 foreach(array_keys($this->result) as $r) {
879 if (!in_array($this->result[$r], $tmp_res, true)) {
880 $tmp_res[] = $this->result[$r];
881 }
882 }
883 }
884 $this->result = $tmp_res;
885 return true;
886 }
887
888 /**
889 * Parse full query
890 * @return bool
891 * @internal Result of query is set in {@link $result}
892 * @access private
893 */
894 protected function parse() {
895 $p =& $this->parser;
896 $p->setPos(0);
897 $this->result = array();
898
899 if (!$this->parse_single()) {
900 return false;
901 }
902
903 while (count($this->result) > 0) {
904 switch($p->token) {
905 case CSSQueryTokenizer::TOK_CHILD:
906 $this->parser->next_no_whitespace();
907 if (!$this->parse_result(false, 1)) {
908 return false;
909 }
910 break;
911
912 case CSSQueryTokenizer::TOK_SIBLING:
913 $this->parser->next_no_whitespace();
914 if (!$this->parse_result(true, 1)) {
915 return false;
916 }
917 break;
918
919 case CSSQueryTokenizer::TOK_PLUS:
920 $this->parser->next_no_whitespace();
921 if (!$this->parse_adjacent()) {
922 return false;
923 }
924 break;
925
926 case CSSQueryTokenizer::TOK_ALL:
927 case CSSQueryTokenizer::TOK_IDENTIFIER:
928 case CSSQueryTokenizer::TOK_STRING:
929 case CSSQueryTokenizer::TOK_BRACE_OPEN:
930 case CSSQueryTokenizer::TOK_BRACKET_OPEN:
931 case CSSQueryTokenizer::TOK_ID:
932 case CSSQueryTokenizer::TOK_CLASS:
933 case CSSQueryTokenizer::TOK_COLON:
934 if (!$this->parse_result()) {
935 return false;
936 }
937 break;
938
939 case CSSQueryTokenizer::TOK_NULL:
940 break 2;
941
942 default:
943 $this->error('Invalid search pattern(3): No result modifier found!');
944 return false;
945 }
946 }
947
948 return true;
949 }
950 }
951