PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.8
4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 4.2.1 All 138 releases
learnpress / vendor / symfony / css-selector / Parser / Parser.php

Parser.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.8, at vendor/symfony/css-selector/Parser/Parser.php

360 lines 12.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\CssSelector\Parser;
13
14 use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
15 use Symfony\Component\CssSelector\Node;
16 use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
17
18 /**
19 * CSS selector parser.
20 *
21 * This component is a port of the Python cssselect library,
22 * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.
23 *
24 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
25 *
26 * @internal
27 */
28 class Parser implements ParserInterface
29 {
30 private Tokenizer $tokenizer;
31
32 public function __construct(?Tokenizer $tokenizer = null)
33 {
34 $this->tokenizer = $tokenizer ?? new Tokenizer();
35 }
36
37 public function parse(string $source): array
38 {
39 $reader = new Reader($source);
40 $stream = $this->tokenizer->tokenize($reader);
41
42 return $this->parseSelectorList($stream);
43 }
44
45 /**
46 * Parses the arguments for ":nth-child()" and friends.
47 *
48 * @param Token[] $tokens
49 *
50 * @throws SyntaxErrorException
51 */
52 public static function parseSeries(array $tokens): array
53 {
54 foreach ($tokens as $token) {
55 if ($token->isString()) {
56 throw SyntaxErrorException::stringAsFunctionArgument();
57 }
58 }
59
60 $joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens)));
61
62 $int = function ($string) {
63 if (!is_numeric($string)) {
64 throw SyntaxErrorException::stringAsFunctionArgument();
65 }
66
67 return (int) $string;
68 };
69
70 switch (true) {
71 case 'odd' === $joined:
72 return [2, 1];
73 case 'even' === $joined:
74 return [2, 0];
75 case 'n' === $joined:
76 return [1, 0];
77 case !str_contains($joined, 'n'):
78 return [0, $int($joined)];
79 }
80
81 $split = explode('n', $joined);
82 $first = $split[0] ?? null;
83
84 return [
85 $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1,
86 isset($split[1]) && $split[1] ? $int($split[1]) : 0,
87 ];
88 }
89
90 private function parseSelectorList(TokenStream $stream): array
91 {
92 $stream->skipWhitespace();
93 $selectors = [];
94
95 while (true) {
96 $selectors[] = $this->parserSelectorNode($stream);
97
98 if ($stream->getPeek()->isDelimiter([','])) {
99 $stream->getNext();
100 $stream->skipWhitespace();
101 } else {
102 break;
103 }
104 }
105
106 return $selectors;
107 }
108
109 private function parserSelectorNode(TokenStream $stream): Node\SelectorNode
110 {
111 [$result, $pseudoElement] = $this->parseSimpleSelector($stream);
112
113 while (true) {
114 $stream->skipWhitespace();
115 $peek = $stream->getPeek();
116
117 if ($peek->isFileEnd() || $peek->isDelimiter([','])) {
118 break;
119 }
120
121 if (null !== $pseudoElement) {
122 throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');
123 }
124
125 if ($peek->isDelimiter(['+', '>', '~'])) {
126 $combinator = $stream->getNext()->getValue();
127 $stream->skipWhitespace();
128 } else {
129 $combinator = ' ';
130 }
131
132 [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream);
133 $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
134 }
135
136 return new Node\SelectorNode($result, $pseudoElement);
137 }
138
139 /**
140 * Parses next simple node (hash, class, pseudo, negation).
141 *
142 * @throws SyntaxErrorException
143 */
144 private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array
145 {
146 $stream->skipWhitespace();
147
148 $selectorStart = \count($stream->getUsed());
149 $result = $this->parseElementNode($stream);
150 $pseudoElement = null;
151
152 while (true) {
153 $peek = $stream->getPeek();
154 if ($peek->isWhitespace()
155 || $peek->isFileEnd()
156 || $peek->isDelimiter([',', '+', '>', '~'])
157 || ($insideNegation && $peek->isDelimiter([')']))
158 ) {
159 break;
160 }
161
162 if (null !== $pseudoElement) {
163 throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');
164 }
165
166 if ($peek->isHash()) {
167 $result = new Node\HashNode($result, $stream->getNext()->getValue());
168 } elseif ($peek->isDelimiter(['.'])) {
169 $stream->getNext();
170 $result = new Node\ClassNode($result, $stream->getNextIdentifier());
171 } elseif ($peek->isDelimiter(['['])) {
172 $stream->getNext();
173 $result = $this->parseAttributeNode($result, $stream);
174 } elseif ($peek->isDelimiter([':'])) {
175 $stream->getNext();
176
177 if ($stream->getPeek()->isDelimiter([':'])) {
178 $stream->getNext();
179 $pseudoElement = $stream->getNextIdentifier();
180
181 continue;
182 }
183
184 $identifier = $stream->getNextIdentifier();
185 if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) {
186 // Special case: CSS 2.1 pseudo-elements can have a single ':'.
187 // Any new pseudo-element must have two.
188 $pseudoElement = $identifier;
189
190 continue;
191 }
192
193 if (!$stream->getPeek()->isDelimiter(['('])) {
194 $result = new Node\PseudoNode($result, $identifier);
195 if ('Pseudo[Element[*]:scope]' === $result->__toString()) {
196 $used = \count($stream->getUsed());
197 if (!(2 === $used
198 || 3 === $used && $stream->getUsed()[0]->isWhiteSpace()
199 || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([','])
200 || $used >= 4
201 && $stream->getUsed()[$used - 3]->isWhiteSpace()
202 && $stream->getUsed()[$used - 4]->isDelimiter([','])
203 )) {
204 throw SyntaxErrorException::notAtTheStartOfASelector('scope');
205 }
206 }
207 continue;
208 }
209
210 $stream->getNext();
211 $stream->skipWhitespace();
212
213 if ('not' === strtolower($identifier)) {
214 if ($insideNegation) {
215 throw SyntaxErrorException::nestedNot();
216 }
217
218 [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true);
219 $next = $stream->getNext();
220
221 if (null !== $argumentPseudoElement) {
222 throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()');
223 }
224
225 if (!$next->isDelimiter([')'])) {
226 throw SyntaxErrorException::unexpectedToken('")"', $next);
227 }
228
229 $result = new Node\NegationNode($result, $argument);
230 } else {
231 $arguments = [];
232 $next = null;
233
234 while (true) {
235 $stream->skipWhitespace();
236 $next = $stream->getNext();
237
238 if ($next->isIdentifier()
239 || $next->isString()
240 || $next->isNumber()
241 || $next->isDelimiter(['+', '-'])
242 ) {
243 $arguments[] = $next;
244 } elseif ($next->isDelimiter([')'])) {
245 break;
246 } else {
247 throw SyntaxErrorException::unexpectedToken('an argument', $next);
248 }
249 }
250
251 if (!$arguments) {
252 throw SyntaxErrorException::unexpectedToken('at least one argument', $next);
253 }
254
255 $result = new Node\FunctionNode($result, $identifier, $arguments);
256 }
257 } else {
258 throw SyntaxErrorException::unexpectedToken('selector', $peek);
259 }
260 }
261
262 if (\count($stream->getUsed()) === $selectorStart) {
263 throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek());
264 }
265
266 return [$result, $pseudoElement];
267 }
268
269 private function parseElementNode(TokenStream $stream): Node\ElementNode
270 {
271 $peek = $stream->getPeek();
272
273 if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) {
274 if ($peek->isIdentifier()) {
275 $namespace = $stream->getNext()->getValue();
276 } else {
277 $stream->getNext();
278 $namespace = null;
279 }
280
281 if ($stream->getPeek()->isDelimiter(['|'])) {
282 $stream->getNext();
283 $element = $stream->getNextIdentifierOrStar();
284 } else {
285 $element = $namespace;
286 $namespace = null;
287 }
288 } else {
289 $element = $namespace = null;
290 }
291
292 return new Node\ElementNode($namespace, $element);
293 }
294
295 private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream): Node\AttributeNode
296 {
297 $stream->skipWhitespace();
298 $attribute = $stream->getNextIdentifierOrStar();
299
300 if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) {
301 throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek());
302 }
303
304 if ($stream->getPeek()->isDelimiter(['|'])) {
305 $stream->getNext();
306
307 if ($stream->getPeek()->isDelimiter(['='])) {
308 $namespace = null;
309 $stream->getNext();
310 $operator = '|=';
311 } else {
312 $namespace = $attribute;
313 $attribute = $stream->getNextIdentifier();
314 $operator = null;
315 }
316 } else {
317 $namespace = $operator = null;
318 }
319
320 if (null === $operator) {
321 $stream->skipWhitespace();
322 $next = $stream->getNext();
323
324 if ($next->isDelimiter([']'])) {
325 return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null);
326 } elseif ($next->isDelimiter(['='])) {
327 $operator = '=';
328 } elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!'])
329 && $stream->getPeek()->isDelimiter(['='])
330 ) {
331 $operator = $next->getValue().'=';
332 $stream->getNext();
333 } else {
334 throw SyntaxErrorException::unexpectedToken('operator', $next);
335 }
336 }
337
338 $stream->skipWhitespace();
339 $value = $stream->getNext();
340
341 if ($value->isNumber()) {
342 // if the value is a number, it's casted into a string
343 $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition());
344 }
345
346 if (!($value->isIdentifier() || $value->isString())) {
347 throw SyntaxErrorException::unexpectedToken('string or identifier', $value);
348 }
349
350 $stream->skipWhitespace();
351 $next = $stream->getNext();
352
353 if (!$next->isDelimiter([']'])) {
354 throw SyntaxErrorException::unexpectedToken('"]"', $next);
355 }
356
357 return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue());
358 }
359 }
360