PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.34.2
MailPoet – Newsletters, Email Marketing, and Automation v5.34.2
5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.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 1 year ago IQuery.php 3 years ago LICENSE 4 years ago gan_formatter.php 3 years ago gan_node_html.php 5 months ago gan_parser_html.php 3 years ago gan_selector_html.php 5 months ago gan_tokenizer.php 1 year ago gan_xml2array.php 3 years ago ganon.php 5 months ago index.php 3 years ago pQuery.php 3 years ago
gan_selector_html.php
952 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 ), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401);
394
395 // phpcs:ignore QITStandard.PHP.DebugCode.DebugFunctionFound
396 trigger_error(esc_html($error));
397 }
398
399 /**
400 * Get identifier (parse identifier or string)
401 * @param bool $do_error Error on failure
402 * @return string False on failure
403 * @access private
404 */
405 protected function parse_getIdentifier($do_error = true) {
406 $p =& $this->parser;
407 $tok = $p->token;
408
409 if ($tok === CSSQueryTokenizer::TOK_IDENTIFIER) {
410 return $p->getTokenString();
411 } elseif($tok === CSSQueryTokenizer::TOK_STRING) {
412 return str_replace(array('\\\'', '\\"', '\\\\'), array('\'', '"', '\\'), $p->getTokenString(1, -1));
413 } elseif ($do_error) {
414 $this->error('Expected identifier at %pos%!');
415 }
416 return false;
417 }
418
419 /**
420 * Get query conditions (tag, attribute and filter conditions)
421 * @return array False on failure
422 * @see DomNode::match()
423 * @access private
424 */
425 protected function parse_conditions() {
426 $p =& $this->parser;
427 $tok = $p->token;
428
429 if ($tok === CSSQueryTokenizer::TOK_NULL) {
430 $this->error('Invalid search pattern(1): Empty string!');
431 return false;
432 }
433 $conditions_all = array();
434
435 //Tags
436 while ($tok !== CSSQueryTokenizer::TOK_NULL) {
437 $conditions = array('tags' => array(), 'attributes' => array());
438
439 if ($tok === CSSQueryTokenizer::TOK_ALL) {
440 $tok = $p->next();
441 if (($tok === CSSQueryTokenizer::TOK_PIPE) && ($tok = $p->next()) && ($tok !== CSSQueryTokenizer::TOK_ALL)) {
442 if (($tag = $this->parse_getIdentifier()) === false) {
443 return false;
444 }
445 $conditions['tags'][] = array(
446 'tag' => $tag,
447 'compare' => 'name'
448 );
449 $tok = $p->next_no_whitespace();
450 } else {
451 $conditions['tags'][''] = array(
452 'tag' => '',
453 'match' => false
454 );
455 if ($tok === CSSQueryTokenizer::TOK_ALL) {
456 $tok = $p->next_no_whitespace();
457 }
458 }
459 } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
460 $tok = $p->next();
461 if ($tok === CSSQueryTokenizer::TOK_ALL) {
462 $conditions['tags'][] = array(
463 'tag' => '',
464 'compare' => 'namespace',
465 );
466 } elseif (($tag = $this->parse_getIdentifier()) !== false) {
467 $conditions['tags'][] = array(
468 'tag' => $tag,
469 'compare' => 'total',
470 );
471 } else {
472 return false;
473 }
474 $tok = $p->next_no_whitespace();
475 } elseif ($tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
476 $tok = $p->next_no_whitespace();
477 $last_mode = 'or';
478
479 while (true) {
480 $match = true;
481 $compare = 'total';
482
483 if ($tok === CSSQueryTokenizer::TOK_NOT) {
484 $match = false;
485 $tok = $p->next_no_whitespace();
486 }
487
488 if ($tok === CSSQueryTokenizer::TOK_ALL) {
489 $tok = $p->next();
490 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
491 $this->next();
492 $compare = 'name';
493 if (($tag = $this->parse_getIdentifier()) === false) {
494 return false;
495 }
496 }
497 } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
498 $tok = $p->next();
499 if ($tok === CSSQueryTokenizer::TOK_ALL) {
500 $tag = '';
501 $compare = 'namespace';
502 } elseif (($tag = $this->parse_getIdentifier()) === false) {
503 return false;
504 }
505 $tok = $p->next_no_whitespace();
506 } else {
507 if (($tag = $this->parse_getIdentifier()) === false) {
508 return false;
509 }
510 $tok = $p->next();
511 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
512 $tok = $p->next();
513
514 if ($tok === CSSQueryTokenizer::TOK_ALL) {
515 $compare = 'namespace';
516 } elseif (($tag_name = $this->parse_getIdentifier()) !== false) {
517 $tag = $tag.':'.$tag_name;
518 } else {
519 return false;
520 }
521
522 $tok = $p->next_no_whitespace();
523 }
524 }
525 if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
526 $tok = $p->next_no_whitespace();
527 }
528
529 $conditions['tags'][] = array(
530 'tag' => $tag,
531 'match' => $match,
532 'operator' => $last_mode,
533 'compare' => $compare
534 );
535 switch($tok) {
536 case CSSQueryTokenizer::TOK_COMMA:
537 $tok = $p->next_no_whitespace();
538 $last_mode = 'or';
539 continue 2;
540 case CSSQueryTokenizer::TOK_PLUS:
541 $tok = $p->next_no_whitespace();
542 $last_mode = 'and';
543 continue 2;
544 case CSSQueryTokenizer::TOK_BRACE_CLOSE:
545 $tok = $p->next();
546 break 2;
547 default:
548 $this->error('Expected closing brace or comma at pos %pos%!');
549 return false;
550 }
551 }
552 } elseif (($tag = $this->parse_getIdentifier(false)) !== false) {
553 $tok = $p->next();
554 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
555 $tok = $p->next();
556
557 if ($tok === CSSQueryTokenizer::TOK_ALL) {
558 $conditions['tags'][] = array(
559 'tag' => $tag,
560 'compare' => 'namespace'
561 );
562 } elseif (($tag_name = $this->parse_getIdentifier()) !== false) {
563 $tag = $tag.':'.$tag_name;
564 $conditions['tags'][] = array(
565 'tag' => $tag,
566 'match' => true
567 );
568 } else {
569 return false;
570 }
571
572 $tok = $p->next();
573 } elseif ($tag === 'text' && $tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
574 $pos = $p->getPos();
575 $tok = $p->next();
576 if ($tok === CSSQueryTokenizer::TOK_BRACE_CLOSE) {
577 $conditions['tags'][] = array(
578 'tag' => '~text~',
579 'match' => true
580 );
581 $p->next();
582 } else {
583 $p->setPos($pos);
584 }
585 } else {
586 $conditions['tags'][] = array(
587 'tag' => $tag,
588 'match' => true
589 );
590 }
591 } else {
592 unset($conditions['tags']);
593 }
594
595 //Class
596 $last_mode = 'or';
597 if ($tok === CSSQueryTokenizer::TOK_CLASS) {
598 $p->next();
599 if (($class = $this->parse_getIdentifier()) === false) {
600 return false;
601 }
602
603 $conditions['attributes'][] = array(
604 'attribute' => 'class',
605 'operator_value' => 'contains_word',
606 'value' => $class,
607 'operator_result' => $last_mode
608 );
609 $last_mode = 'and';
610 $tok = $p->next();
611 }
612
613 //ID
614 if ($tok === CSSQueryTokenizer::TOK_ID) {
615 $p->next();
616 if (($id = $this->parse_getIdentifier()) === false) {
617 return false;
618 }
619
620 $conditions['attributes'][] = array(
621 'attribute' => 'id',
622 'operator_value' => 'equals',
623 'value' => $id,
624 'operator_result' => $last_mode
625 );
626 $last_mode = 'and';
627 $tok = $p->next();
628 }
629
630 //Attributes
631 if ($tok === CSSQueryTokenizer::TOK_BRACKET_OPEN) {
632 $tok = $p->next_no_whitespace();
633
634 while (true) {
635 $match = true;
636 $compare = 'total';
637 if ($tok === CSSQueryTokenizer::TOK_NOT) {
638 $match = false;
639 $tok = $p->next_no_whitespace();
640 }
641
642 if ($tok === CSSQueryTokenizer::TOK_ALL) {
643 $tok = $p->next();
644 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
645 $tok = $p->next();
646 if (($attribute = $this->parse_getIdentifier()) === false) {
647 return false;
648 }
649 $compare = 'name';
650 $tok = $p->next();
651 } else {
652 $this->error('Expected pipe at pos %pos%!');
653 return false;
654 }
655 } elseif ($tok === CSSQueryTokenizer::TOK_PIPE) {
656 $tok = $p->next();
657 if (($tag = $this->parse_getIdentifier()) === false) {
658 return false;
659 }
660 $tok = $p->next_no_whitespace();
661 } elseif (($attribute = $this->parse_getIdentifier()) !== false) {
662 $tok = $p->next();
663 if ($tok === CSSQueryTokenizer::TOK_PIPE) {
664 $tok = $p->next();
665
666 if (($attribute_name = $this->parse_getIdentifier()) !== false) {
667 $attribute = $attribute.':'.$attribute_name;
668 } else {
669 return false;
670 }
671
672 $tok = $p->next();
673 }
674 } else {
675 return false;
676 }
677 if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
678 $tok = $p->next_no_whitespace();
679 }
680
681 $operator_value = '';
682 $val = '';
683 switch($tok) {
684 case CSSQueryTokenizer::TOK_COMPARE_PREFIX:
685 case CSSQueryTokenizer::TOK_COMPARE_CONTAINS:
686 case CSSQueryTokenizer::TOK_COMPARE_CONTAINS_WORD:
687 case CSSQueryTokenizer::TOK_COMPARE_ENDS:
688 case CSSQueryTokenizer::TOK_COMPARE_EQUALS:
689 case CSSQueryTokenizer::TOK_COMPARE_NOT_EQUAL:
690 case CSSQueryTokenizer::TOK_COMPARE_REGEX:
691 case CSSQueryTokenizer::TOK_COMPARE_STARTS:
692 case CSSQueryTokenizer::TOK_COMPARE_BIGGER_THAN:
693 case CSSQueryTokenizer::TOK_COMPARE_SMALLER_THAN:
694 $operator_value = $p->getTokenString(($tok === CSSQueryTokenizer::TOK_COMPARE_EQUALS) ? 0 : -1);
695 $p->next_no_whitespace();
696
697 if (($val = $this->parse_getIdentifier()) === false) {
698 return false;
699 }
700
701 $tok = $p->next_no_whitespace();
702 break;
703 }
704
705 if ($operator_value && $val) {
706 $conditions['attributes'][] = array(
707 'attribute' => $attribute,
708 'operator_value' => $operator_value,
709 'value' => $val,
710 'match' => $match,
711 'operator_result' => $last_mode,
712 'compare' => $compare
713 );
714 } else {
715 $conditions['attributes'][] = array(
716 'attribute' => $attribute,
717 'value' => $match,
718 'operator_result' => $last_mode,
719 'compare' => $compare
720 );
721 }
722
723 switch($tok) {
724 case CSSQueryTokenizer::TOK_COMMA:
725 $tok = $p->next_no_whitespace();
726 $last_mode = 'or';
727 continue 2;
728 case CSSQueryTokenizer::TOK_PLUS:
729 $tok = $p->next_no_whitespace();
730 $last_mode = 'and';
731 continue 2;
732 case CSSQueryTokenizer::TOK_BRACKET_CLOSE:
733 $tok = $p->next();
734 break 2;
735 default:
736 $this->error('Expected closing bracket or comma at pos %pos%!');
737 return false;
738 }
739 }
740 }
741
742 if (count($conditions['attributes']) < 1) {
743 unset($conditions['attributes']);
744 }
745
746 while($tok === CSSQueryTokenizer::TOK_COLON) {
747 if (count($conditions) < 1) {
748 $conditions['tags'] = array(array(
749 'tag' => '',
750 'match' => false
751 ));
752 }
753
754 $tok = $p->next();
755 if (($filter = $this->parse_getIdentifier()) === false) {
756 return false;
757 }
758
759 if (($tok = $p->next()) === CSSQueryTokenizer::TOK_BRACE_OPEN) {
760 $start = $p->pos;
761 $count = 1;
762 while ((($tok = $p->next()) !== CSSQueryTokenizer::TOK_NULL) && !(($tok === CSSQueryTokenizer::TOK_BRACE_CLOSE) && (--$count === 0))) {
763 if ($tok === CSSQueryTokenizer::TOK_BRACE_OPEN) {
764 ++$count;
765 }
766 }
767
768
769 if ($tok !== CSSQueryTokenizer::TOK_BRACE_CLOSE) {
770 $this->error('Expected closing brace at pos %pos%!');
771 return false;
772 }
773 $len = $p->pos - 1 - $start;
774 $params = (($len > 0) ? substr($p->doc, $start + 1, $len) : '');
775 $tok = $p->next();
776 } else {
777 $params = '';
778 }
779
780 $conditions['filters'][] = array('filter' => $filter, 'params' => $params);
781 }
782 if (count($conditions) < 1) {
783 $this->error('Invalid search pattern(2): No conditions found!');
784 return false;
785 }
786 $conditions_all[] = $conditions;
787
788 if ($tok === CSSQueryTokenizer::TOK_WHITESPACE) {
789 $tok = $p->next_no_whitespace();
790 }
791
792 if ($tok === CSSQueryTokenizer::TOK_COMMA) {
793 $tok = $p->next_no_whitespace();
794 continue;
795 } else {
796 break;
797 }
798 }
799
800 return $conditions_all;
801 }
802
803
804 /**
805 * Evaluate root node using custom callback
806 * @param array $conditions {@link parse_conditions()}
807 * @param bool|int $recursive
808 * @param bool $check_root
809 * @return array
810 * @access private
811 */
812 protected function parse_callback($conditions, $recursive = true, $check_root = false) {
813 return ($this->result = $this->root->getChildrenByMatch(
814 $conditions,
815 $recursive,
816 $check_root,
817 $this->custom_filter_map
818 ));
819 }
820
821 /**
822 * Parse first bit of query, only root node has to be evaluated now
823 * @param bool|int $recursive
824 * @return bool
825 * @internal Result of query is set in {@link $result}
826 * @access private
827 */
828 protected function parse_single($recursive = true) {
829 if (($c = $this->parse_conditions()) === false) {
830 return false;
831 }
832
833 $this->parse_callback($c, $recursive, $this->search_root);
834 return true;
835 }
836
837 /**
838 * Evaluate sibling nodes
839 * @return bool
840 * @internal Result of query is set in {@link $result}
841 * @access private
842 */
843 protected function parse_adjacent() {
844 $tmp = $this->result;
845 $this->result = array();
846 if (($c = $this->parse_conditions()) === false) {
847 return false;
848 }
849
850 foreach($tmp as $t) {
851 if (($sibling = $t->getNextSibling()) !== false) {
852 if ($sibling->match($c, true, $this->custom_filter_map)) {
853 $this->result[] = $sibling;
854 }
855 }
856 }
857
858 return true;
859 }
860
861 /**
862 * Evaluate {@link $result}
863 * @param bool $parent Evaluate parent nodes
864 * @param bool|int $recursive
865 * @return bool
866 * @internal Result of query is set in {@link $result}
867 * @access private
868 */
869 protected function parse_result($parent = false, $recursive = true) {
870 $tmp = $this->result;
871 $tmp_res = array();
872 if (($c = $this->parse_conditions()) === false) {
873 return false;
874 }
875
876 foreach(array_keys($tmp) as $t) {
877 $this->root = (($parent) ? $tmp[$t]->parent : $tmp[$t]);
878 $this->parse_callback($c, $recursive);
879 foreach(array_keys($this->result) as $r) {
880 if (!in_array($this->result[$r], $tmp_res, true)) {
881 $tmp_res[] = $this->result[$r];
882 }
883 }
884 }
885 $this->result = $tmp_res;
886 return true;
887 }
888
889 /**
890 * Parse full query
891 * @return bool
892 * @internal Result of query is set in {@link $result}
893 * @access private
894 */
895 protected function parse() {
896 $p =& $this->parser;
897 $p->setPos(0);
898 $this->result = array();
899
900 if (!$this->parse_single()) {
901 return false;
902 }
903
904 while (count($this->result) > 0) {
905 switch($p->token) {
906 case CSSQueryTokenizer::TOK_CHILD:
907 $this->parser->next_no_whitespace();
908 if (!$this->parse_result(false, 1)) {
909 return false;
910 }
911 break;
912
913 case CSSQueryTokenizer::TOK_SIBLING:
914 $this->parser->next_no_whitespace();
915 if (!$this->parse_result(true, 1)) {
916 return false;
917 }
918 break;
919
920 case CSSQueryTokenizer::TOK_PLUS:
921 $this->parser->next_no_whitespace();
922 if (!$this->parse_adjacent()) {
923 return false;
924 }
925 break;
926
927 case CSSQueryTokenizer::TOK_ALL:
928 case CSSQueryTokenizer::TOK_IDENTIFIER:
929 case CSSQueryTokenizer::TOK_STRING:
930 case CSSQueryTokenizer::TOK_BRACE_OPEN:
931 case CSSQueryTokenizer::TOK_BRACKET_OPEN:
932 case CSSQueryTokenizer::TOK_ID:
933 case CSSQueryTokenizer::TOK_CLASS:
934 case CSSQueryTokenizer::TOK_COLON:
935 if (!$this->parse_result()) {
936 return false;
937 }
938 break;
939
940 case CSSQueryTokenizer::TOK_NULL:
941 break 2;
942
943 default:
944 $this->error('Invalid search pattern(3): No result modifier found!');
945 return false;
946 }
947 }
948
949 return true;
950 }
951 }
952