PluginProbe
BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript / 4.1.20
BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript v4.1.20
4.1.20 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.0.30 4.0.29 4.0.28 4.0.27 4.0.26 4.0.24 4.0.25 4.0.23 4.0.22 4.0.21 4.0.19 4.0.18 4.0.17 4.0.16 All 174 releases
searchpro / BerqWP / vendor-prefixed / vendor / symfony / css-selector / Parser / Parser.php

Parser.php in BerqWP – All-In-One Optimization for Core Web Vitals, Cache, CDN, Images, CSS & JavaScript 4.1.20, at BerqWP/vendor-prefixed/vendor/symfony/css-selector/Parser/Parser.php

279 lines 11.1 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 <[email protected]>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace BerqWP_Deps\Symfony\Component\CssSelector\Parser;
12
13 use BerqWP_Deps\Symfony\Component\CssSelector\Exception\SyntaxErrorException;
14 use BerqWP_Deps\Symfony\Component\CssSelector\Node;
15 use BerqWP_Deps\Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
16 /**
17 * CSS selector parser.
18 *
19 * This component is a port of the Python cssselect library,
20 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
21 *
22 * @author Jean-François Simon <[email protected]>
23 *
24 * @internal
25 */
26 class Parser implements ParserInterface
27 {
28 private $tokenizer;
29 public function __construct(?Tokenizer $tokenizer = null)
30 {
31 $this->tokenizer = $tokenizer ?? new Tokenizer();
32 }
33 /**
34 * {@inheritdoc}
35 */
36 public function parse(string $source): array
37 {
38 $reader = new Reader($source);
39 $stream = $this->tokenizer->tokenize($reader);
40 return $this->parseSelectorList($stream);
41 }
42 /**
43 * Parses the arguments for ":nth-child()" and friends.
44 *
45 * @param Token[] $tokens
46 *
47 * @throws SyntaxErrorException
48 */
49 public static function parseSeries(array $tokens): array
50 {
51 foreach ($tokens as $token) {
52 if ($token->isString()) {
53 throw SyntaxErrorException::stringAsFunctionArgument();
54 }
55 }
56 $joined = trim(implode('', array_map(function (Token $token) {
57 return $token->getValue();
58 }, $tokens)));
59 $int = function ($string) {
60 if (!is_numeric($string)) {
61 throw SyntaxErrorException::stringAsFunctionArgument();
62 }
63 return (int) $string;
64 };
65 switch (\true) {
66 case 'odd' === $joined:
67 return [2, 1];
68 case 'even' === $joined:
69 return [2, 0];
70 case 'n' === $joined:
71 return [1, 0];
72 case !str_contains($joined, 'n'):
73 return [0, $int($joined)];
74 }
75 $split = explode('n', $joined);
76 $first = $split[0] ?? null;
77 return [$first ? '-' === $first || '+' === $first ? $int($first . '1') : $int($first) : 1, isset($split[1]) && $split[1] ? $int($split[1]) : 0];
78 }
79 private function parseSelectorList(TokenStream $stream): array
80 {
81 $stream->skipWhitespace();
82 $selectors = [];
83 while (\true) {
84 $selectors[] = $this->parserSelectorNode($stream);
85 if ($stream->getPeek()->isDelimiter([','])) {
86 $stream->getNext();
87 $stream->skipWhitespace();
88 } else {
89 break;
90 }
91 }
92 return $selectors;
93 }
94 private function parserSelectorNode(TokenStream $stream): Node\SelectorNode
95 {
96 [$result, $pseudoElement] = $this->parseSimpleSelector($stream);
97 while (\true) {
98 $stream->skipWhitespace();
99 $peek = $stream->getPeek();
100 if ($peek->isFileEnd() || $peek->isDelimiter([','])) {
101 break;
102 }
103 if (null !== $pseudoElement) {
104 throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');
105 }
106 if ($peek->isDelimiter(['+', '>', '~'])) {
107 $combinator = $stream->getNext()->getValue();
108 $stream->skipWhitespace();
109 } else {
110 $combinator = ' ';
111 }
112 [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream);
113 $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
114 }
115 return new Node\SelectorNode($result, $pseudoElement);
116 }
117 /**
118 * Parses next simple node (hash, class, pseudo, negation).
119 *
120 * @throws SyntaxErrorException
121 */
122 private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = \false): array
123 {
124 $stream->skipWhitespace();
125 $selectorStart = \count($stream->getUsed());
126 $result = $this->parseElementNode($stream);
127 $pseudoElement = null;
128 while (\true) {
129 $peek = $stream->getPeek();
130 if ($peek->isWhitespace() || $peek->isFileEnd() || $peek->isDelimiter([',', '+', '>', '~']) || $insideNegation && $peek->isDelimiter([')'])) {
131 break;
132 }
133 if (null !== $pseudoElement) {
134 throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');
135 }
136 if ($peek->isHash()) {
137 $result = new Node\HashNode($result, $stream->getNext()->getValue());
138 } elseif ($peek->isDelimiter(['.'])) {
139 $stream->getNext();
140 $result = new Node\ClassNode($result, $stream->getNextIdentifier());
141 } elseif ($peek->isDelimiter(['['])) {
142 $stream->getNext();
143 $result = $this->parseAttributeNode($result, $stream);
144 } elseif ($peek->isDelimiter([':'])) {
145 $stream->getNext();
146 if ($stream->getPeek()->isDelimiter([':'])) {
147 $stream->getNext();
148 $pseudoElement = $stream->getNextIdentifier();
149 continue;
150 }
151 $identifier = $stream->getNextIdentifier();
152 if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) {
153 // Special case: CSS 2.1 pseudo-elements can have a single ':'.
154 // Any new pseudo-element must have two.
155 $pseudoElement = $identifier;
156 continue;
157 }
158 if (!$stream->getPeek()->isDelimiter(['('])) {
159 $result = new Node\PseudoNode($result, $identifier);
160 continue;
161 }
162 $stream->getNext();
163 $stream->skipWhitespace();
164 if ('not' === strtolower($identifier)) {
165 if ($insideNegation) {
166 throw SyntaxErrorException::nestedNot();
167 }
168 [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, \true);
169 $next = $stream->getNext();
170 if (null !== $argumentPseudoElement) {
171 throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()');
172 }
173 if (!$next->isDelimiter([')'])) {
174 throw SyntaxErrorException::unexpectedToken('")"', $next);
175 }
176 $result = new Node\NegationNode($result, $argument);
177 } else {
178 $arguments = [];
179 $next = null;
180 while (\true) {
181 $stream->skipWhitespace();
182 $next = $stream->getNext();
183 if ($next->isIdentifier() || $next->isString() || $next->isNumber() || $next->isDelimiter(['+', '-'])) {
184 $arguments[] = $next;
185 } elseif ($next->isDelimiter([')'])) {
186 break;
187 } else {
188 throw SyntaxErrorException::unexpectedToken('an argument', $next);
189 }
190 }
191 if (empty($arguments)) {
192 throw SyntaxErrorException::unexpectedToken('at least one argument', $next);
193 }
194 $result = new Node\FunctionNode($result, $identifier, $arguments);
195 }
196 } else {
197 throw SyntaxErrorException::unexpectedToken('selector', $peek);
198 }
199 }
200 if (\count($stream->getUsed()) === $selectorStart) {
201 throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek());
202 }
203 return [$result, $pseudoElement];
204 }
205 private function parseElementNode(TokenStream $stream): Node\ElementNode
206 {
207 $peek = $stream->getPeek();
208 if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) {
209 if ($peek->isIdentifier()) {
210 $namespace = $stream->getNext()->getValue();
211 } else {
212 $stream->getNext();
213 $namespace = null;
214 }
215 if ($stream->getPeek()->isDelimiter(['|'])) {
216 $stream->getNext();
217 $element = $stream->getNextIdentifierOrStar();
218 } else {
219 $element = $namespace;
220 $namespace = null;
221 }
222 } else {
223 $element = $namespace = null;
224 }
225 return new Node\ElementNode($namespace, $element);
226 }
227 private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream): Node\AttributeNode
228 {
229 $stream->skipWhitespace();
230 $attribute = $stream->getNextIdentifierOrStar();
231 if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) {
232 throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek());
233 }
234 if ($stream->getPeek()->isDelimiter(['|'])) {
235 $stream->getNext();
236 if ($stream->getPeek()->isDelimiter(['='])) {
237 $namespace = null;
238 $stream->getNext();
239 $operator = '|=';
240 } else {
241 $namespace = $attribute;
242 $attribute = $stream->getNextIdentifier();
243 $operator = null;
244 }
245 } else {
246 $namespace = $operator = null;
247 }
248 if (null === $operator) {
249 $stream->skipWhitespace();
250 $next = $stream->getNext();
251 if ($next->isDelimiter([']'])) {
252 return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null);
253 } elseif ($next->isDelimiter(['='])) {
254 $operator = '=';
255 } elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!']) && $stream->getPeek()->isDelimiter(['='])) {
256 $operator = $next->getValue() . '=';
257 $stream->getNext();
258 } else {
259 throw SyntaxErrorException::unexpectedToken('operator', $next);
260 }
261 }
262 $stream->skipWhitespace();
263 $value = $stream->getNext();
264 if ($value->isNumber()) {
265 // if the value is a number, it's casted into a string
266 $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition());
267 }
268 if (!($value->isIdentifier() || $value->isString())) {
269 throw SyntaxErrorException::unexpectedToken('string or identifier', $value);
270 }
271 $stream->skipWhitespace();
272 $next = $stream->getNext();
273 if (!$next->isDelimiter([']'])) {
274 throw SyntaxErrorException::unexpectedToken('"]"', $next);
275 }
276 return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue());
277 }
278 }
279