Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
757 / 757 |
|
100.00% |
61 / 61 |
CRAP | |
100.00% |
1 / 1 |
MatcherFactory | |
100.00% |
757 / 757 |
|
100.00% |
61 / 61 |
133 | |
100.00% |
1 / 1 |
singleton | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
optionalWhitespace | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
significantWhitespace | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
comma | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
ident | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
customIdent | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
string | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
urlstring | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
url | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
cssWideKeywords | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
calcInternal | |
100.00% |
53 / 53 |
|
100.00% |
1 / 1 |
5 | |||
calc | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
rawInteger | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
colorHex | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
integer | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
rawNumber | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
number | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
rawPercentage | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
percentage | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
lengthPercentage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
frequencyPercentage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
anglePercentage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
timePercentage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
numberPercentage | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
dimension | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
zero | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
rawLength | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
length | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
rawAngle | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
angle | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
rawTime | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
time | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
rawFrequency | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
frequency | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
resolution | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
colorFuncs | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
safeColor | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
color | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
image | |
100.00% |
63 / 63 |
|
100.00% |
1 / 1 |
2 | |||
position | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
2 | |||
bgPosition | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
cssMediaQuery | |
100.00% |
99 / 99 |
|
100.00% |
1 / 1 |
5 | |||
cssMediaQueryList | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
cssSupportsCondition | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
2 | |||
cssDeclaration | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
5 | |||
cssSingleEasingFunction | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
cssSelectorList | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
cssSelector | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
cssCombinator | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
cssSimpleSelectorSeq | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
2 | |||
cssTypeSelector | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
cssNamespacePrefix | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
cssOptionalNamespacePrefix | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
cssUniversal | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
cssID | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
cssClass | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
cssAttrib | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
2 | |||
cssPseudo | |
100.00% |
32 / 32 |
|
100.00% |
1 / 1 |
2 | |||
cssANplusB | |
100.00% |
53 / 53 |
|
100.00% |
1 / 1 |
7 | |||
cssNegation | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
2 | |||
colorWords | |
100.00% |
40 / 40 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * @file |
4 | * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0 |
5 | */ |
6 | |
7 | namespace Wikimedia\CSS\Grammar; |
8 | |
9 | use Wikimedia\CSS\Objects\ComponentValueList; |
10 | use Wikimedia\CSS\Objects\Token; |
11 | use Wikimedia\CSS\Parser\Parser; |
12 | use Wikimedia\CSS\Sanitizer\PropertySanitizer; |
13 | |
14 | /** |
15 | * Factory for predefined Grammar matchers |
16 | * @note For security, the attr() and var() functions are not supported, |
17 | * although as a limited exception var() is allowed for color attributes |
18 | * in `::colorFuncs()`. |
19 | */ |
20 | class MatcherFactory { |
21 | /** @var MatcherFactory|null */ |
22 | private static $instance = null; |
23 | |
24 | /** @var (Matcher|Matcher[])[] Cache of constructed matchers */ |
25 | protected $cache = []; |
26 | |
27 | /** @var string[] length units */ |
28 | protected static $lengthUnits = [ |
29 | 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', |
30 | 'cm', 'mm', 'Q', 'in', 'pc', 'pt', 'px' |
31 | ]; |
32 | |
33 | /** @var string[] angle units */ |
34 | protected static $angleUnits = [ 'deg', 'grad', 'rad', 'turn' ]; |
35 | |
36 | /** @var string[] time units */ |
37 | protected static $timeUnits = [ 's', 'ms' ]; |
38 | |
39 | /** @var string[] frequency units */ |
40 | protected static $frequencyUnits = [ 'Hz', 'kHz' ]; |
41 | |
42 | /** |
43 | * Return a static instance of the factory |
44 | * @return MatcherFactory |
45 | */ |
46 | public static function singleton() { |
47 | if ( !self::$instance ) { |
48 | self::$instance = new self(); |
49 | } |
50 | return self::$instance; |
51 | } |
52 | |
53 | /** |
54 | * Matcher for optional whitespace |
55 | * @return Matcher |
56 | */ |
57 | public function optionalWhitespace() { |
58 | if ( !isset( $this->cache[__METHOD__] ) ) { |
59 | $this->cache[__METHOD__] = new WhitespaceMatcher( [ 'significant' => false ] ); |
60 | } |
61 | return $this->cache[__METHOD__]; |
62 | } |
63 | |
64 | /** |
65 | * Matcher for required whitespace |
66 | * @return Matcher |
67 | */ |
68 | public function significantWhitespace() { |
69 | if ( !isset( $this->cache[__METHOD__] ) ) { |
70 | $this->cache[__METHOD__] = new WhitespaceMatcher( [ 'significant' => true ] ); |
71 | } |
72 | return $this->cache[__METHOD__]; |
73 | } |
74 | |
75 | /** |
76 | * Matcher for a comma |
77 | * @return Matcher |
78 | */ |
79 | public function comma() { |
80 | if ( !isset( $this->cache[__METHOD__] ) ) { |
81 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_COMMA ); |
82 | } |
83 | return $this->cache[__METHOD__]; |
84 | } |
85 | |
86 | /** |
87 | * Matcher for an arbitrary identifier |
88 | * @return Matcher |
89 | */ |
90 | public function ident() { |
91 | if ( !isset( $this->cache[__METHOD__] ) ) { |
92 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_IDENT ); |
93 | } |
94 | return $this->cache[__METHOD__]; |
95 | } |
96 | |
97 | /** |
98 | * Matcher for a <custom-ident> |
99 | * |
100 | * Note this doesn't implement the semantic restriction about assigning |
101 | * meaning to various idents in a complex value, as CSS Sanitizer doesn't |
102 | * deal with semantics on that level. |
103 | * |
104 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#identifier-value |
105 | * @param string[] $exclude Additional values to exclude, all-lowercase. |
106 | * @return Matcher |
107 | */ |
108 | public function customIdent( array $exclude = [] ) { |
109 | $exclude = array_merge( [ |
110 | // https://www.w3.org/TR/2019/CR-css-values-3-20190606/#common-keywords |
111 | 'initial', 'inherit', 'unset', 'default', |
112 | // https://www.w3.org/TR/2018/CR-css-cascade-4-20180828/#all-shorthand |
113 | 'revert' |
114 | ], $exclude ); |
115 | return new TokenMatcher( Token::T_IDENT, static function ( Token $t ) use ( $exclude ) { |
116 | return !in_array( strtolower( $t->value() ), $exclude, true ); |
117 | } ); |
118 | } |
119 | |
120 | /** |
121 | * Matcher for a string |
122 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#strings |
123 | * @warning If the string will be used as a URL, use self::urlstring() instead. |
124 | * @return Matcher |
125 | */ |
126 | public function string() { |
127 | if ( !isset( $this->cache[__METHOD__] ) ) { |
128 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_STRING ); |
129 | } |
130 | return $this->cache[__METHOD__]; |
131 | } |
132 | |
133 | /** |
134 | * Matcher for a string containing a URL |
135 | * @param string $type Type of resource referenced, e.g. "image" or "audio". |
136 | * Not used here, but might be used by a subclass to validate the URL more strictly. |
137 | * @return Matcher |
138 | */ |
139 | public function urlstring( $type ) { |
140 | return $this->string(); |
141 | } |
142 | |
143 | /** |
144 | * Matcher for a URL |
145 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#urls |
146 | * @param string $type Type of resource referenced, e.g. "image" or "audio". |
147 | * Not used here, but might be used by a subclass to validate the URL more strictly. |
148 | * @return Matcher |
149 | */ |
150 | public function url( $type ) { |
151 | if ( !isset( $this->cache[__METHOD__] ) ) { |
152 | $this->cache[__METHOD__] = new UrlMatcher(); |
153 | } |
154 | return $this->cache[__METHOD__]; |
155 | } |
156 | |
157 | /** |
158 | * CSS-wide value keywords |
159 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#common-keywords |
160 | * @return Matcher |
161 | */ |
162 | public function cssWideKeywords() { |
163 | if ( !isset( $this->cache[__METHOD__] ) ) { |
164 | $this->cache[__METHOD__] = new KeywordMatcher( [ |
165 | // https://www.w3.org/TR/2019/CR-css-values-3-20190606/#common-keywords |
166 | 'initial', 'inherit', 'unset', |
167 | // added by https://www.w3.org/TR/2018/CR-css-cascade-4-20180828/#all-shorthand |
168 | 'revert' |
169 | ] ); |
170 | } |
171 | return $this->cache[__METHOD__]; |
172 | } |
173 | |
174 | /** |
175 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#calc-notation |
176 | * @param Matcher $typeMatcher Matcher for the type |
177 | * @param string $type Type being matched |
178 | * @return Matcher[] |
179 | */ |
180 | protected function calcInternal( Matcher $typeMatcher, $type ) { |
181 | if ( $type === 'integer' ) { |
182 | $num = $this->rawInteger(); |
183 | } else { |
184 | $num = $this->rawNumber(); |
185 | } |
186 | |
187 | $ows = $this->optionalWhitespace(); |
188 | $ws = $this->significantWhitespace(); |
189 | |
190 | // Definitions are recursive. This will be used by reference and later |
191 | // will be replaced. |
192 | $calcValue = new NothingMatcher(); |
193 | |
194 | if ( $type === 'integer' ) { |
195 | // Division will always resolve to a number, making the expression |
196 | // invalid, so don't allow it. |
197 | $calcProduct = new Juxtaposition( [ |
198 | &$calcValue, |
199 | Quantifier::star( new Juxtaposition( [ $ows, new DelimMatcher( '*' ), $ows, &$calcValue ] ) ) |
200 | ] ); |
201 | } elseif ( $typeMatcher === $this->rawNumber() ) { |
202 | $calcProduct = new Juxtaposition( [ |
203 | &$calcValue, |
204 | Quantifier::star( |
205 | new Juxtaposition( [ $ows, new DelimMatcher( [ '*', '/' ] ), $ows, &$calcValue ] ) |
206 | ), |
207 | ] ); |
208 | } else { |
209 | $calcNumValue = $this->calcInternal( $this->rawNumber(), 'number' )[1]; |
210 | $calcProduct = new Juxtaposition( [ |
211 | &$calcValue, |
212 | Quantifier::star( |
213 | new Alternative( [ |
214 | new Juxtaposition( [ $ows, new DelimMatcher( '*' ), $ows, &$calcValue ] ), |
215 | new Juxtaposition( [ $ows, new DelimMatcher( '/' ), $ows, $calcNumValue, ] ), |
216 | ] ) |
217 | ), |
218 | ] ); |
219 | } |
220 | |
221 | $calcSum = new Juxtaposition( [ |
222 | $ows, |
223 | $calcProduct, |
224 | Quantifier::star( new Juxtaposition( [ |
225 | $ws, new DelimMatcher( [ '+', '-' ] ), $ws, $calcProduct |
226 | ] ) ), |
227 | $ows, |
228 | ] ); |
229 | |
230 | $calcFunc = new FunctionMatcher( 'calc', $calcSum ); |
231 | |
232 | if ( $num === $typeMatcher ) { |
233 | $calcValue = new Alternative( [ |
234 | $typeMatcher, |
235 | new BlockMatcher( Token::T_LEFT_PAREN, $calcSum ), |
236 | $calcFunc, |
237 | ] ); |
238 | } else { |
239 | $calcValue = new Alternative( [ |
240 | $num, |
241 | $typeMatcher, |
242 | new BlockMatcher( Token::T_LEFT_PAREN, $calcSum ), |
243 | $calcFunc, |
244 | ] ); |
245 | } |
246 | |
247 | return [ |
248 | new Alternative( [ $typeMatcher, $calcFunc ] ), |
249 | $calcValue, |
250 | ]; |
251 | } |
252 | |
253 | /** |
254 | * Add calc() support to a basic type matcher |
255 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#calc-notation |
256 | * @param Matcher $typeMatcher Matcher for the type |
257 | * @param string $type Type being matched |
258 | * @return Matcher |
259 | */ |
260 | public function calc( Matcher $typeMatcher, $type ) { |
261 | return $this->calcInternal( $typeMatcher, $type )[0]; |
262 | } |
263 | |
264 | /** |
265 | * Matcher for an integer value, without calc() |
266 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#integers |
267 | * @return Matcher |
268 | */ |
269 | protected function rawInteger() { |
270 | if ( !isset( $this->cache[__METHOD__] ) ) { |
271 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_NUMBER, static function ( Token $t ) { |
272 | // The spec says it must match /^[+-]\d+$/, but the tokenizer |
273 | // should have marked any other number token as a 'number' |
274 | // anyway so let's not bother checking. |
275 | return $t->typeFlag() === 'integer'; |
276 | } ); |
277 | } |
278 | return $this->cache[__METHOD__]; |
279 | } |
280 | |
281 | /** |
282 | * @return TokenMatcher |
283 | */ |
284 | public function colorHex(): TokenMatcher { |
285 | return new TokenMatcher( Token::T_HASH, static function ( Token $t ) { |
286 | return preg_match( '/^([0-9a-f]{3}|[0-9a-f]{6})$/i', $t->value() ); |
287 | } ); |
288 | } |
289 | |
290 | /** |
291 | * Matcher for an integer value |
292 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#integers |
293 | * @return Matcher |
294 | */ |
295 | public function integer() { |
296 | if ( !isset( $this->cache[__METHOD__] ) ) { |
297 | $this->cache[__METHOD__] = $this->calc( $this->rawInteger(), 'integer' ); |
298 | } |
299 | return $this->cache[__METHOD__]; |
300 | } |
301 | |
302 | /** |
303 | * Matcher for a real number, without calc() |
304 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#numbers |
305 | * @return Matcher |
306 | */ |
307 | public function rawNumber() { |
308 | if ( !isset( $this->cache[__METHOD__] ) ) { |
309 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_NUMBER ); |
310 | } |
311 | return $this->cache[__METHOD__]; |
312 | } |
313 | |
314 | /** |
315 | * Matcher for a real number |
316 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#numbers |
317 | * @return Matcher |
318 | */ |
319 | public function number() { |
320 | if ( !isset( $this->cache[__METHOD__] ) ) { |
321 | $this->cache[__METHOD__] = $this->calc( $this->rawNumber(), 'number' ); |
322 | } |
323 | return $this->cache[__METHOD__]; |
324 | } |
325 | |
326 | /** |
327 | * Matcher for a percentage value, without calc() |
328 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#percentages |
329 | * @return Matcher |
330 | */ |
331 | public function rawPercentage() { |
332 | if ( !isset( $this->cache[__METHOD__] ) ) { |
333 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_PERCENTAGE ); |
334 | } |
335 | return $this->cache[__METHOD__]; |
336 | } |
337 | |
338 | /** |
339 | * Matcher for a percentage value |
340 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#percentages |
341 | * @return Matcher |
342 | */ |
343 | public function percentage() { |
344 | if ( !isset( $this->cache[__METHOD__] ) ) { |
345 | $this->cache[__METHOD__] = $this->calc( $this->rawPercentage(), 'percentage' ); |
346 | } |
347 | return $this->cache[__METHOD__]; |
348 | } |
349 | |
350 | /** |
351 | * Matcher for a length-percentage value |
352 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#typedef-length-percentage |
353 | * @return Matcher |
354 | */ |
355 | public function lengthPercentage() { |
356 | if ( !isset( $this->cache[__METHOD__] ) ) { |
357 | $this->cache[__METHOD__] = $this->calc( |
358 | new Alternative( [ $this->rawLength(), $this->rawPercentage() ] ), |
359 | 'length' |
360 | ); |
361 | } |
362 | return $this->cache[__METHOD__]; |
363 | } |
364 | |
365 | /** |
366 | * Matcher for a frequency-percentage value |
367 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#typedef-frequency-percentage |
368 | * @return Matcher |
369 | */ |
370 | public function frequencyPercentage() { |
371 | if ( !isset( $this->cache[__METHOD__] ) ) { |
372 | $this->cache[__METHOD__] = $this->calc( |
373 | new Alternative( [ $this->rawFrequency(), $this->rawPercentage() ] ), |
374 | 'frequency' |
375 | ); |
376 | } |
377 | return $this->cache[__METHOD__]; |
378 | } |
379 | |
380 | /** |
381 | * Matcher for an angle-percentage value |
382 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#typedef-angle-percentage |
383 | * @return Matcher |
384 | */ |
385 | public function anglePercentage() { |
386 | if ( !isset( $this->cache[__METHOD__] ) ) { |
387 | $this->cache[__METHOD__] = $this->calc( |
388 | new Alternative( [ $this->rawAngle(), $this->rawPercentage() ] ), |
389 | 'angle' |
390 | ); |
391 | } |
392 | return $this->cache[__METHOD__]; |
393 | } |
394 | |
395 | /** |
396 | * Matcher for a time-percentage value |
397 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#typedef-time-percentage |
398 | * @return Matcher |
399 | */ |
400 | public function timePercentage() { |
401 | if ( !isset( $this->cache[__METHOD__] ) ) { |
402 | $this->cache[__METHOD__] = $this->calc( |
403 | new Alternative( [ $this->rawTime(), $this->rawPercentage() ] ), |
404 | 'time' |
405 | ); |
406 | } |
407 | return $this->cache[__METHOD__]; |
408 | } |
409 | |
410 | /** |
411 | * Matcher for a number-percentage value |
412 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#typedef-number-percentage |
413 | * @return Matcher |
414 | */ |
415 | public function numberPercentage() { |
416 | if ( !isset( $this->cache[__METHOD__] ) ) { |
417 | $this->cache[__METHOD__] = $this->calc( |
418 | new Alternative( [ $this->rawNumber(), $this->rawPercentage() ] ), |
419 | 'number' |
420 | ); |
421 | } |
422 | return $this->cache[__METHOD__]; |
423 | } |
424 | |
425 | /** |
426 | * Matcher for a dimension value |
427 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#dimensions |
428 | * @return Matcher |
429 | */ |
430 | public function dimension() { |
431 | if ( !isset( $this->cache[__METHOD__] ) ) { |
432 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION ); |
433 | } |
434 | return $this->cache[__METHOD__]; |
435 | } |
436 | |
437 | /** |
438 | * Matches the number 0 |
439 | * @return Matcher |
440 | */ |
441 | public function zero() { |
442 | if ( !isset( $this->cache[__METHOD__] ) ) { |
443 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_NUMBER, static function ( Token $t ) { |
444 | return $t->value() === 0 || $t->value() === 0.0; |
445 | } ); |
446 | } |
447 | return $this->cache[__METHOD__]; |
448 | } |
449 | |
450 | /** |
451 | * Matcher for a length value, without calc() |
452 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#lengths |
453 | * @return Matcher |
454 | */ |
455 | protected function rawLength() { |
456 | if ( !isset( $this->cache[__METHOD__] ) ) { |
457 | $unitsRe = '/^(' . implode( '|', self::$lengthUnits ) . ')$/i'; |
458 | |
459 | $this->cache[__METHOD__] = new Alternative( [ |
460 | $this->zero(), |
461 | new TokenMatcher( Token::T_DIMENSION, static function ( Token $t ) use ( $unitsRe ) { |
462 | return preg_match( $unitsRe, $t->unit() ); |
463 | } ), |
464 | ] ); |
465 | } |
466 | return $this->cache[__METHOD__]; |
467 | } |
468 | |
469 | /** |
470 | * Matcher for a length value |
471 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#lengths |
472 | * @return Matcher |
473 | */ |
474 | public function length() { |
475 | if ( !isset( $this->cache[__METHOD__] ) ) { |
476 | $this->cache[__METHOD__] = $this->calc( $this->rawLength(), 'length' ); |
477 | } |
478 | return $this->cache[__METHOD__]; |
479 | } |
480 | |
481 | /** |
482 | * Matcher for an angle value, without calc() |
483 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#angles |
484 | * @return Matcher |
485 | */ |
486 | protected function rawAngle() { |
487 | if ( !isset( $this->cache[__METHOD__] ) ) { |
488 | $unitsRe = '/^(' . implode( '|', self::$angleUnits ) . ')$/i'; |
489 | |
490 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION, |
491 | static function ( Token $t ) use ( $unitsRe ) { |
492 | return preg_match( $unitsRe, $t->unit() ); |
493 | } |
494 | ); |
495 | } |
496 | return $this->cache[__METHOD__]; |
497 | } |
498 | |
499 | /** |
500 | * Matcher for an angle value |
501 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#angles |
502 | * @return Matcher |
503 | */ |
504 | public function angle() { |
505 | if ( !isset( $this->cache[__METHOD__] ) ) { |
506 | $this->cache[__METHOD__] = $this->calc( $this->rawAngle(), 'angle' ); |
507 | } |
508 | return $this->cache[__METHOD__]; |
509 | } |
510 | |
511 | /** |
512 | * Matcher for a duration (time) value, without calc() |
513 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#time |
514 | * @return Matcher |
515 | */ |
516 | protected function rawTime() { |
517 | if ( !isset( $this->cache[__METHOD__] ) ) { |
518 | $unitsRe = '/^(' . implode( '|', self::$timeUnits ) . ')$/i'; |
519 | |
520 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION, |
521 | static function ( Token $t ) use ( $unitsRe ) { |
522 | return preg_match( $unitsRe, $t->unit() ); |
523 | } |
524 | ); |
525 | } |
526 | return $this->cache[__METHOD__]; |
527 | } |
528 | |
529 | /** |
530 | * Matcher for a duration (time) value |
531 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#time |
532 | * @return Matcher |
533 | */ |
534 | public function time() { |
535 | if ( !isset( $this->cache[__METHOD__] ) ) { |
536 | $this->cache[__METHOD__] = $this->calc( $this->rawTime(), 'time' ); |
537 | } |
538 | return $this->cache[__METHOD__]; |
539 | } |
540 | |
541 | /** |
542 | * Matcher for a frequency value, without calc() |
543 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#frequency |
544 | * @return Matcher |
545 | */ |
546 | protected function rawFrequency() { |
547 | if ( !isset( $this->cache[__METHOD__] ) ) { |
548 | $unitsRe = '/^(' . implode( '|', self::$frequencyUnits ) . ')$/i'; |
549 | |
550 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION, |
551 | static function ( Token $t ) use ( $unitsRe ) { |
552 | return preg_match( $unitsRe, $t->unit() ); |
553 | } |
554 | ); |
555 | } |
556 | return $this->cache[__METHOD__]; |
557 | } |
558 | |
559 | /** |
560 | * Matcher for a frequency value |
561 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#frequency |
562 | * @return Matcher |
563 | */ |
564 | public function frequency() { |
565 | if ( !isset( $this->cache[__METHOD__] ) ) { |
566 | $this->cache[__METHOD__] = $this->calc( $this->rawFrequency(), 'frequency' ); |
567 | } |
568 | return $this->cache[__METHOD__]; |
569 | } |
570 | |
571 | /** |
572 | * Matcher for a resolution value |
573 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#resolution |
574 | * @return Matcher |
575 | */ |
576 | public function resolution() { |
577 | if ( !isset( $this->cache[__METHOD__] ) ) { |
578 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_DIMENSION, static function ( Token $t ) { |
579 | return preg_match( '/^(dpi|dpcm|dppx)$/i', $t->unit() ); |
580 | } ); |
581 | } |
582 | return $this->cache[__METHOD__]; |
583 | } |
584 | |
585 | /** |
586 | * Matchers for color functions |
587 | * @return Matcher[] |
588 | */ |
589 | protected function colorFuncs() { |
590 | if ( !isset( $this->cache[__METHOD__] ) ) { |
591 | $i = $this->integer(); |
592 | $n = $this->number(); |
593 | $p = $this->percentage(); |
594 | $this->cache[__METHOD__] = [ |
595 | new FunctionMatcher( 'rgb', new Alternative( [ |
596 | Quantifier::hash( $i, 3, 3 ), |
597 | Quantifier::hash( $p, 3, 3 ), |
598 | ] ) ), |
599 | new FunctionMatcher( 'rgba', new Alternative( [ |
600 | new Juxtaposition( [ $i, $i, $i, $n ], true ), |
601 | new Juxtaposition( [ $p, $p, $p, $n ], true ), |
602 | ] ) ), |
603 | new FunctionMatcher( 'hsl', new Juxtaposition( [ $n, $p, $p ], true ) ), |
604 | new FunctionMatcher( 'hsla', new Juxtaposition( [ $n, $p, $p, $n ], true ) ), |
605 | ]; |
606 | } |
607 | return $this->cache[__METHOD__]; |
608 | } |
609 | |
610 | /** |
611 | * Matcher for a color value, *not* including a custom property reference. |
612 | * |
613 | * Because custom properties can lead to unexpected behavior (generally |
614 | * a bad thing for security) when concatenated together, this matcher |
615 | * should be used for CSS rules which allow value concatenation. |
616 | * For example, `border-color` allows up to 4 `var(...)` expressions to |
617 | * potentially be concatenated. |
618 | * |
619 | * @see https://www.w3.org/TR/css-variables-1/#custom-property |
620 | * @return Matcher |
621 | */ |
622 | public function safeColor() { |
623 | if ( !isset( $this->cache[__METHOD__] ) ) { |
624 | $this->cache[__METHOD__] = new Alternative( array_merge( [ |
625 | $this->colorWords(), |
626 | $this->colorHex(), |
627 | ], $this->colorFuncs() ) ); |
628 | } |
629 | return $this->cache[__METHOD__]; |
630 | } |
631 | |
632 | /** |
633 | * Matcher for a color value, including a possible custom property |
634 | * reference. |
635 | * |
636 | * @see https://www.w3.org/TR/2018/REC-css-color-3-20180619/#colorunits |
637 | * @return Matcher |
638 | */ |
639 | public function color() { |
640 | if ( !isset( $this->cache[__METHOD__] ) ) { |
641 | $this->cache[__METHOD__] = new Alternative( [ |
642 | $this->safeColor(), |
643 | new FunctionMatcher( 'var', new Juxtaposition( [ |
644 | new CustomPropertyMatcher(), |
645 | Quantifier::optional( new Alternative( [ |
646 | $this->colorWords(), |
647 | $this->colorHex(), |
648 | ] ) ), |
649 | ], true ) ), |
650 | ] ); |
651 | } |
652 | return $this->cache[__METHOD__]; |
653 | } |
654 | |
655 | /** |
656 | * Matcher for an image value |
657 | * @see https://www.w3.org/TR/2019/CR-css-images-3-20191010/#image-values |
658 | * @return Matcher |
659 | */ |
660 | public function image() { |
661 | if ( !isset( $this->cache[__METHOD__] ) ) { |
662 | // https://www.w3.org/TR/2019/CR-css-images-3-20191010/#gradients |
663 | $c = $this->comma(); |
664 | $colorStop = UnorderedGroup::allOf( [ |
665 | $this->color(), |
666 | Quantifier::optional( $this->lengthPercentage() ), |
667 | ] ); |
668 | $colorStopList = new Juxtaposition( [ |
669 | $colorStop, |
670 | Quantifier::hash( new Juxtaposition( [ |
671 | Quantifier::optional( $this->lengthPercentage() ), |
672 | $colorStop |
673 | ], true ) ), |
674 | ], true ); |
675 | $atPosition = new Juxtaposition( [ new KeywordMatcher( 'at' ), $this->position() ] ); |
676 | |
677 | $linearGradient = new Juxtaposition( [ |
678 | Quantifier::optional( new Juxtaposition( [ |
679 | new Alternative( [ |
680 | new Alternative( [ |
681 | $this->zero(), |
682 | $this->angle(), |
683 | ] ), |
684 | new Juxtaposition( [ new KeywordMatcher( 'to' ), UnorderedGroup::someOf( [ |
685 | new KeywordMatcher( [ 'left', 'right' ] ), |
686 | new KeywordMatcher( [ 'top', 'bottom' ] ), |
687 | ] ) ] ) |
688 | ] ), |
689 | $c |
690 | ] ) ), |
691 | $colorStopList, |
692 | ] ); |
693 | $radialGradient = new Juxtaposition( [ |
694 | Quantifier::optional( new Juxtaposition( [ |
695 | new Alternative( [ |
696 | new Juxtaposition( [ |
697 | new Alternative( [ |
698 | UnorderedGroup::someOf( [ new KeywordMatcher( 'circle' ), $this->length() ] ), |
699 | UnorderedGroup::someOf( [ |
700 | new KeywordMatcher( 'ellipse' ), |
701 | Quantifier::count( $this->lengthPercentage(), 2, 2 ) |
702 | ] ), |
703 | UnorderedGroup::someOf( [ |
704 | new KeywordMatcher( [ 'circle', 'ellipse' ] ), |
705 | new KeywordMatcher( [ |
706 | 'closest-corner', 'closest-side', 'farthest-corner', 'farthest-side', |
707 | ] ), |
708 | ] ), |
709 | ] ), |
710 | Quantifier::optional( $atPosition ), |
711 | ] ), |
712 | $atPosition |
713 | ] ), |
714 | $c |
715 | ] ) ), |
716 | $colorStopList, |
717 | ] ); |
718 | |
719 | // Putting it all together |
720 | $this->cache[__METHOD__] = new Alternative( [ |
721 | $this->url( 'image' ), |
722 | new FunctionMatcher( 'linear-gradient', $linearGradient ), |
723 | new FunctionMatcher( 'radial-gradient', $radialGradient ), |
724 | new FunctionMatcher( 'repeating-linear-gradient', $linearGradient ), |
725 | new FunctionMatcher( 'repeating-radial-gradient', $radialGradient ), |
726 | ] ); |
727 | } |
728 | return $this->cache[__METHOD__]; |
729 | } |
730 | |
731 | /** |
732 | * Matcher for a position value |
733 | * @see https://www.w3.org/TR/2019/CR-css-values-3-20190606/#typedef-position |
734 | * @return Matcher |
735 | */ |
736 | public function position() { |
737 | if ( !isset( $this->cache[__METHOD__] ) ) { |
738 | $lp = $this->lengthPercentage(); |
739 | $center = new KeywordMatcher( 'center' ); |
740 | $leftRight = new KeywordMatcher( [ 'left', 'right' ] ); |
741 | $topBottom = new KeywordMatcher( [ 'top', 'bottom' ] ); |
742 | |
743 | $this->cache[__METHOD__] = new Alternative( [ |
744 | UnorderedGroup::someOf( [ |
745 | new Alternative( [ $center, $leftRight ] ), |
746 | new Alternative( [ $center, $topBottom ] ), |
747 | ] ), |
748 | new Juxtaposition( [ |
749 | new Alternative( [ $center, $leftRight, $lp ] ), |
750 | Quantifier::optional( new Alternative( [ $center, $topBottom, $lp ] ) ), |
751 | ] ), |
752 | |
753 | UnorderedGroup::allOf( [ |
754 | new Juxtaposition( [ $leftRight, $lp ] ), |
755 | new Juxtaposition( [ $topBottom, $lp ] ), |
756 | ] ), |
757 | ] ); |
758 | } |
759 | return $this->cache[__METHOD__]; |
760 | } |
761 | |
762 | /** |
763 | * Matcher for a bg-position value |
764 | * @see https://www.w3.org/TR/2017/CR-css-backgrounds-3-20171017/#typedef-bg-position |
765 | * @return Matcher |
766 | */ |
767 | public function bgPosition() { |
768 | if ( !isset( $this->cache[__METHOD__] ) ) { |
769 | $lp = $this->lengthPercentage(); |
770 | $olp = Quantifier::optional( $lp ); |
771 | $center = new KeywordMatcher( 'center' ); |
772 | $leftRight = new KeywordMatcher( [ 'left', 'right' ] ); |
773 | $topBottom = new KeywordMatcher( [ 'top', 'bottom' ] ); |
774 | |
775 | $this->cache[__METHOD__] = new Alternative( [ |
776 | new Alternative( [ $center, $leftRight, $topBottom, $lp ] ), |
777 | new Juxtaposition( [ |
778 | new Alternative( [ $center, $leftRight, $lp ] ), |
779 | new Alternative( [ $center, $topBottom, $lp ] ), |
780 | ] ), |
781 | UnorderedGroup::allOf( [ |
782 | new Alternative( [ $center, new Juxtaposition( [ $leftRight, $olp ] ) ] ), |
783 | new Alternative( [ $center, new Juxtaposition( [ $topBottom, $olp ] ) ] ), |
784 | ] ), |
785 | ] ); |
786 | } |
787 | return $this->cache[__METHOD__]; |
788 | } |
789 | |
790 | /** |
791 | * Matcher for a CSS media query |
792 | * @see https://www.w3.org/TR/2017/CR-mediaqueries-4-20170905/#mq-syntax |
793 | * @param bool $strict Only allow defined query types |
794 | * @return Matcher |
795 | */ |
796 | public function cssMediaQuery( $strict = true ) { |
797 | $key = __METHOD__ . ':' . ( $strict ? 'strict' : 'unstrict' ); |
798 | if ( !isset( $this->cache[$key] ) ) { |
799 | if ( $strict ) { |
800 | $generalEnclosed = new NothingMatcher(); |
801 | |
802 | $mediaType = new KeywordMatcher( [ |
803 | 'all', 'print', 'screen', 'speech', |
804 | // deprecated |
805 | 'tty', 'tv', 'projection', 'handheld', 'braille', 'embossed', 'aural' |
806 | ] ); |
807 | |
808 | $rangeFeatures = [ |
809 | 'width', 'height', 'aspect-ratio', 'resolution', 'color', 'color-index', 'monochrome', |
810 | // deprecated |
811 | 'device-width', 'device-height', 'device-aspect-ratio' |
812 | ]; |
813 | $discreteFeatures = [ |
814 | 'orientation', 'scan', 'grid', 'update', 'overflow-block', 'overflow-inline', 'color-gamut', |
815 | 'pointer', 'hover', 'any-pointer', 'any-hover', 'scripting', 'prefers-color-scheme' |
816 | ]; |
817 | $mfName = new KeywordMatcher( array_merge( |
818 | $rangeFeatures, |
819 | array_map( static function ( $f ) { |
820 | return "min-$f"; |
821 | }, $rangeFeatures ), |
822 | array_map( static function ( $f ) { |
823 | return "max-$f"; |
824 | }, $rangeFeatures ), |
825 | $discreteFeatures |
826 | ) ); |
827 | } else { |
828 | $anythingPlus = new AnythingMatcher( [ 'quantifier' => '+' ] ); |
829 | $generalEnclosed = new Alternative( [ |
830 | new FunctionMatcher( null, $anythingPlus ), |
831 | new BlockMatcher( Token::T_LEFT_PAREN, |
832 | new Juxtaposition( [ $this->ident(), $anythingPlus ] ) |
833 | ), |
834 | ] ); |
835 | $mediaType = $this->ident(); |
836 | $mfName = $this->ident(); |
837 | } |
838 | |
839 | $posInt = $this->calc( |
840 | new TokenMatcher( Token::T_NUMBER, static function ( Token $t ) { |
841 | return $t->typeFlag() === 'integer' && preg_match( '/^\+?\d+$/', $t->representation() ); |
842 | } ), |
843 | 'integer' |
844 | ); |
845 | $eq = new DelimMatcher( '=' ); |
846 | $oeq = Quantifier::optional( new Juxtaposition( [ new NoWhitespace, $eq ] ) ); |
847 | $ltgteq = Quantifier::optional( new Alternative( [ |
848 | $eq, |
849 | new Juxtaposition( [ new DelimMatcher( [ '<', '>' ] ), $oeq ] ), |
850 | ] ) ); |
851 | $lteq = new Juxtaposition( [ new DelimMatcher( '<' ), $oeq ] ); |
852 | $gteq = new Juxtaposition( [ new DelimMatcher( '>' ), $oeq ] ); |
853 | $mfValue = new Alternative( [ |
854 | $this->number(), |
855 | $this->dimension(), |
856 | $this->ident(), |
857 | new KeywordMatcher( [ 'light', 'dark' ] ), |
858 | new Juxtaposition( [ $posInt, new DelimMatcher( '/' ), $posInt ] ), |
859 | ] ); |
860 | |
861 | // temporary |
862 | $mediaInParens = new NothingMatcher(); |
863 | $mediaNot = new Juxtaposition( [ new KeywordMatcher( 'not' ), &$mediaInParens ] ); |
864 | $mediaAnd = new Juxtaposition( [ new KeywordMatcher( 'and' ), &$mediaInParens ] ); |
865 | $mediaOr = new Juxtaposition( [ new KeywordMatcher( 'or' ), &$mediaInParens ] ); |
866 | $mediaCondition = new Alternative( [ |
867 | $mediaNot, |
868 | new Juxtaposition( [ |
869 | &$mediaInParens, |
870 | new Alternative( [ |
871 | Quantifier::star( $mediaAnd ), |
872 | Quantifier::star( $mediaOr ), |
873 | ] ) |
874 | ] ), |
875 | ] ); |
876 | $mediaConditionWithoutOr = new Alternative( [ |
877 | $mediaNot, |
878 | new Juxtaposition( [ &$mediaInParens, Quantifier::star( $mediaAnd ) ] ), |
879 | ] ); |
880 | $mediaFeature = new BlockMatcher( Token::T_LEFT_PAREN, new Alternative( [ |
881 | // <mf-plain> |
882 | new Juxtaposition( [ $mfName, new TokenMatcher( Token::T_COLON ), $mfValue ] ), |
883 | // <mf-boolean> |
884 | $mfName, |
885 | // <mf-range>, 1st alternative |
886 | new Juxtaposition( [ $mfName, $ltgteq, $mfValue ] ), |
887 | // <mf-range>, 2nd alternative |
888 | new Juxtaposition( [ $mfValue, $ltgteq, $mfName ] ), |
889 | // <mf-range>, 3rd alt |
890 | new Juxtaposition( [ $mfValue, $lteq, $mfName, $lteq, $mfValue ] ), |
891 | // <mf-range>, 4th alt |
892 | new Juxtaposition( [ $mfValue, $gteq, $mfName, $gteq, $mfValue ] ), |
893 | ] ) ); |
894 | $mediaInParens = new Alternative( [ |
895 | new BlockMatcher( Token::T_LEFT_PAREN, $mediaCondition ), |
896 | $mediaFeature, |
897 | $generalEnclosed, |
898 | ] ); |
899 | |
900 | $this->cache[$key] = new Alternative( [ |
901 | $mediaCondition, |
902 | new Juxtaposition( [ |
903 | Quantifier::optional( new KeywordMatcher( [ 'not', 'only' ] ) ), |
904 | $mediaType, |
905 | Quantifier::optional( new Juxtaposition( [ |
906 | new KeywordMatcher( 'and' ), |
907 | $mediaConditionWithoutOr, |
908 | ] ) ) |
909 | ] ) |
910 | ] ); |
911 | } |
912 | |
913 | return $this->cache[$key]; |
914 | } |
915 | |
916 | /** |
917 | * Matcher for a CSS media query list |
918 | * @see https://www.w3.org/TR/2017/CR-mediaqueries-4-20170905/#mq-syntax |
919 | * @param bool $strict Only allow defined query types |
920 | * @return Matcher |
921 | */ |
922 | public function cssMediaQueryList( $strict = true ) { |
923 | $key = __METHOD__ . ':' . ( $strict ? 'strict' : 'unstrict' ); |
924 | if ( !isset( $this->cache[$key] ) ) { |
925 | $this->cache[$key] = Quantifier::hash( $this->cssMediaQuery( $strict ), 0, INF ); |
926 | } |
927 | |
928 | return $this->cache[$key]; |
929 | } |
930 | |
931 | /** |
932 | * Matcher for a "supports-condition" |
933 | * @see https://www.w3.org/TR/2013/CR-css3-conditional-20130404/#supports_condition |
934 | * @param PropertySanitizer|null $declarationSanitizer Check declarations against this Sanitizer |
935 | * @param bool $strict Only accept defined syntax. Default true. |
936 | * @return Matcher |
937 | */ |
938 | public function cssSupportsCondition( |
939 | PropertySanitizer $declarationSanitizer = null, $strict = true |
940 | ) { |
941 | $ws = $this->significantWhitespace(); |
942 | $anythingPlus = new AnythingMatcher( [ 'quantifier' => '+' ] ); |
943 | |
944 | if ( $strict ) { |
945 | $generalEnclosed = new NothingMatcher(); |
946 | } else { |
947 | $generalEnclosed = new Alternative( [ |
948 | new FunctionMatcher( null, $anythingPlus ), |
949 | new BlockMatcher( Token::T_LEFT_PAREN, new Juxtaposition( [ $this->ident(), $anythingPlus ] ) ), |
950 | ] ); |
951 | } |
952 | |
953 | // temp |
954 | $supportsConditionBlock = new NothingMatcher(); |
955 | $supportsConditionInParens = new Alternative( [ |
956 | &$supportsConditionBlock, |
957 | new BlockMatcher( Token::T_LEFT_PAREN, $this->cssDeclaration( $declarationSanitizer ) ), |
958 | $generalEnclosed, |
959 | ] ); |
960 | $supportsCondition = new Alternative( [ |
961 | new Juxtaposition( [ new KeywordMatcher( 'not' ), $ws, $supportsConditionInParens ] ), |
962 | new Juxtaposition( [ $supportsConditionInParens, Quantifier::plus( new Juxtaposition( [ |
963 | $ws, new KeywordMatcher( 'and' ), $ws, $supportsConditionInParens |
964 | ] ) ) ] ), |
965 | new Juxtaposition( [ $supportsConditionInParens, Quantifier::plus( new Juxtaposition( [ |
966 | $ws, new KeywordMatcher( 'or' ), $ws, $supportsConditionInParens |
967 | ] ) ) ] ), |
968 | $supportsConditionInParens, |
969 | ] ); |
970 | $supportsConditionBlock = new BlockMatcher( Token::T_LEFT_PAREN, $supportsCondition ); |
971 | |
972 | return $supportsCondition; |
973 | } |
974 | |
975 | /** |
976 | * Matcher for a declaration |
977 | * @param PropertySanitizer|null $declarationSanitizer Check declarations against this Sanitizer |
978 | * @return Matcher |
979 | */ |
980 | public function cssDeclaration( PropertySanitizer $declarationSanitizer = null ) { |
981 | $anythingPlus = new AnythingMatcher( [ 'quantifier' => '+' ] ); |
982 | |
983 | return new CheckedMatcher( |
984 | $anythingPlus, |
985 | static function ( ComponentValueList $list, GrammarMatch $match, array $options ) |
986 | use ( $declarationSanitizer ) |
987 | { |
988 | $cvlist = new ComponentValueList( $match->getValues() ); |
989 | $parser = Parser::newFromTokens( $cvlist->toTokenArray() ); |
990 | $declaration = $parser->parseDeclaration(); |
991 | if ( !$declaration || $parser->getParseErrors() ) { |
992 | return false; |
993 | } |
994 | if ( !$declarationSanitizer ) { |
995 | return true; |
996 | } |
997 | $reset = $declarationSanitizer->stashSanitizationErrors(); |
998 | $ret = $declarationSanitizer->sanitize( $declaration ); |
999 | $errors = $declarationSanitizer->getSanitizationErrors(); |
1000 | unset( $reset ); |
1001 | return $ret === $declaration && !$errors; |
1002 | } |
1003 | ); |
1004 | } |
1005 | |
1006 | /** |
1007 | * Matcher for single easing functions from CSS Easing Functions Level 1 |
1008 | * @see https://www.w3.org/TR/2019/CR-css-easing-1-20190430/#typedef-easing-function |
1009 | * @return Matcher |
1010 | */ |
1011 | public function cssSingleEasingFunction() { |
1012 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1013 | $this->cache[__METHOD__] = new Alternative( [ |
1014 | new KeywordMatcher( [ |
1015 | 'ease', 'linear', 'ease-in', 'ease-out', 'ease-in-out', 'step-start', 'step-end' |
1016 | ] ), |
1017 | new FunctionMatcher( 'steps', new Juxtaposition( [ |
1018 | $this->integer(), |
1019 | Quantifier::optional( new KeywordMatcher( [ |
1020 | 'jump-start', 'jump-end', 'jump-none', 'jump-both', 'start', 'end' |
1021 | ] ) ), |
1022 | ], true ) ), |
1023 | new FunctionMatcher( 'cubic-bezier', Quantifier::hash( $this->number(), 4, 4 ) ), |
1024 | ] ); |
1025 | } |
1026 | |
1027 | return $this->cache[__METHOD__]; |
1028 | } |
1029 | |
1030 | /** |
1031 | * @name CSS Selectors Level 3 |
1032 | * @{ |
1033 | * |
1034 | * https://www.w3.org/TR/2018/REC-selectors-3-20181106/#w3cselgrammar |
1035 | */ |
1036 | |
1037 | /** |
1038 | * List of selectors (selectors_group) |
1039 | * |
1040 | * selector [ COMMA S* selector ]* |
1041 | * |
1042 | * Capturing is set up for the `selector`s. |
1043 | * |
1044 | * @return Matcher |
1045 | */ |
1046 | public function cssSelectorList() { |
1047 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1048 | // Technically the spec doesn't allow whitespace before the comma, |
1049 | // but I'd guess every browser does. So just use Quantifier::hash. |
1050 | $selector = $this->cssSelector()->capture( 'selector' ); |
1051 | $this->cache[__METHOD__] = Quantifier::hash( $selector ); |
1052 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1053 | } |
1054 | return $this->cache[__METHOD__]; |
1055 | } |
1056 | |
1057 | /** |
1058 | * A single selector (selector) |
1059 | * |
1060 | * simple_selector_sequence [ combinator simple_selector_sequence ]* |
1061 | * |
1062 | * Capturing is set up for the `simple_selector_sequence`s (as 'simple') and `combinator`. |
1063 | * |
1064 | * @return Matcher |
1065 | */ |
1066 | public function cssSelector() { |
1067 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1068 | $simple = $this->cssSimpleSelectorSeq()->capture( 'simple' ); |
1069 | $this->cache[__METHOD__] = new Juxtaposition( [ |
1070 | $simple, |
1071 | Quantifier::star( new Juxtaposition( [ |
1072 | $this->cssCombinator()->capture( 'combinator' ), |
1073 | $simple, |
1074 | ] ) ) |
1075 | ] ); |
1076 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1077 | } |
1078 | return $this->cache[__METHOD__]; |
1079 | } |
1080 | |
1081 | /** |
1082 | * A CSS combinator (combinator) |
1083 | * |
1084 | * PLUS S* | GREATER S* | TILDE S* | S+ |
1085 | * |
1086 | * (combinators can be surrounded by whitespace) |
1087 | * |
1088 | * @return Matcher |
1089 | */ |
1090 | public function cssCombinator() { |
1091 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1092 | $this->cache[__METHOD__] = new Alternative( [ |
1093 | new Juxtaposition( [ |
1094 | $this->optionalWhitespace(), |
1095 | new DelimMatcher( [ '+', '>', '~' ] ), |
1096 | $this->optionalWhitespace(), |
1097 | ] ), |
1098 | $this->significantWhitespace(), |
1099 | ] ); |
1100 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1101 | } |
1102 | return $this->cache[__METHOD__]; |
1103 | } |
1104 | |
1105 | /** |
1106 | * A simple selector sequence (simple_selector_sequence) |
1107 | * |
1108 | * [ type_selector | universal ] |
1109 | * [ HASH | class | attrib | pseudo | negation ]* |
1110 | * | [ HASH | class | attrib | pseudo | negation ]+ |
1111 | * |
1112 | * The following captures are set: |
1113 | * - element: [ type_selector | universal ] |
1114 | * - id: HASH |
1115 | * - class: class |
1116 | * - attrib: attrib |
1117 | * - pseudo: pseudo |
1118 | * - negation: negation |
1119 | * |
1120 | * @return Matcher |
1121 | */ |
1122 | public function cssSimpleSelectorSeq() { |
1123 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1124 | $hashEtc = new Alternative( [ |
1125 | $this->cssID()->capture( 'id' ), |
1126 | $this->cssClass()->capture( 'class' ), |
1127 | $this->cssAttrib()->capture( 'attrib' ), |
1128 | $this->cssPseudo()->capture( 'pseudo' ), |
1129 | $this->cssNegation()->capture( 'negation' ), |
1130 | ] ); |
1131 | |
1132 | $this->cache[__METHOD__] = new Alternative( [ |
1133 | new Juxtaposition( [ |
1134 | Alternative::create( [ |
1135 | $this->cssTypeSelector(), |
1136 | $this->cssUniversal(), |
1137 | ] )->capture( 'element' ), |
1138 | Quantifier::star( $hashEtc ) |
1139 | ] ), |
1140 | Quantifier::plus( $hashEtc ) |
1141 | ] ); |
1142 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1143 | } |
1144 | return $this->cache[__METHOD__]; |
1145 | } |
1146 | |
1147 | /** |
1148 | * A type selector, i.e. a tag name (type_selector) |
1149 | * |
1150 | * [ namespace_prefix ] ? element_name |
1151 | * |
1152 | * where element_name is |
1153 | * |
1154 | * IDENT |
1155 | * |
1156 | * @return Matcher |
1157 | */ |
1158 | public function cssTypeSelector() { |
1159 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1160 | $this->cache[__METHOD__] = new Juxtaposition( [ |
1161 | $this->cssOptionalNamespacePrefix(), |
1162 | new TokenMatcher( Token::T_IDENT ) |
1163 | ] ); |
1164 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1165 | } |
1166 | return $this->cache[__METHOD__]; |
1167 | } |
1168 | |
1169 | /** |
1170 | * A namespace prefix (namespace_prefix) |
1171 | * |
1172 | * [ IDENT | '*' ]? '|' |
1173 | * |
1174 | * @return Matcher |
1175 | */ |
1176 | public function cssNamespacePrefix() { |
1177 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1178 | $this->cache[__METHOD__] = new Juxtaposition( [ |
1179 | Quantifier::optional( new Alternative( [ |
1180 | $this->ident(), |
1181 | new DelimMatcher( [ '*' ] ), |
1182 | ] ) ), |
1183 | new DelimMatcher( [ '|' ] ), |
1184 | ] ); |
1185 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1186 | } |
1187 | return $this->cache[__METHOD__]; |
1188 | } |
1189 | |
1190 | /** |
1191 | * An optional namespace prefix |
1192 | * |
1193 | * [ namespace_prefix ]? |
1194 | * |
1195 | * @return Matcher |
1196 | */ |
1197 | private function cssOptionalNamespacePrefix() { |
1198 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1199 | $this->cache[__METHOD__] = Quantifier::optional( $this->cssNamespacePrefix() ); |
1200 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1201 | } |
1202 | return $this->cache[__METHOD__]; |
1203 | } |
1204 | |
1205 | /** |
1206 | * The universal selector (universal) |
1207 | * |
1208 | * [ namespace_prefix ]? '*' |
1209 | * |
1210 | * @return Matcher |
1211 | */ |
1212 | public function cssUniversal() { |
1213 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1214 | $this->cache[__METHOD__] = new Juxtaposition( [ |
1215 | $this->cssOptionalNamespacePrefix(), |
1216 | new DelimMatcher( [ '*' ] ) |
1217 | ] ); |
1218 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1219 | } |
1220 | return $this->cache[__METHOD__]; |
1221 | } |
1222 | |
1223 | /** |
1224 | * An ID selector |
1225 | * |
1226 | * HASH |
1227 | * |
1228 | * @return Matcher |
1229 | */ |
1230 | public function cssID() { |
1231 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1232 | $this->cache[__METHOD__] = new TokenMatcher( Token::T_HASH, static function ( Token $t ) { |
1233 | return $t->typeFlag() === 'id'; |
1234 | } ); |
1235 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1236 | } |
1237 | return $this->cache[__METHOD__]; |
1238 | } |
1239 | |
1240 | /** |
1241 | * A class selector (class) |
1242 | * |
1243 | * '.' IDENT |
1244 | * |
1245 | * @return Matcher |
1246 | */ |
1247 | public function cssClass() { |
1248 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1249 | $this->cache[__METHOD__] = new Juxtaposition( [ |
1250 | new DelimMatcher( [ '.' ] ), |
1251 | $this->ident() |
1252 | ] ); |
1253 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1254 | } |
1255 | return $this->cache[__METHOD__]; |
1256 | } |
1257 | |
1258 | /** |
1259 | * An attribute selector (attrib) |
1260 | * |
1261 | * '[' S* [ namespace_prefix ]? IDENT S* |
1262 | * [ [ PREFIXMATCH | |
1263 | * SUFFIXMATCH | |
1264 | * SUBSTRINGMATCH | |
1265 | * '=' | |
1266 | * INCLUDES | |
1267 | * DASHMATCH ] S* [ IDENT | STRING ] S* |
1268 | * ]? ']' |
1269 | * |
1270 | * Captures are set for the attribute, test, and value. Note that these |
1271 | * captures will probably be relative to the contents of the SimpleBlock |
1272 | * that this matcher matches! |
1273 | * |
1274 | * @return Matcher |
1275 | */ |
1276 | public function cssAttrib() { |
1277 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1278 | // An attribute is going to be parsed by the parser as a |
1279 | // SimpleBlock, so that's what we need to look for here. |
1280 | |
1281 | $this->cache[__METHOD__] = new BlockMatcher( Token::T_LEFT_BRACKET, |
1282 | new Juxtaposition( [ |
1283 | $this->optionalWhitespace(), |
1284 | Juxtaposition::create( [ |
1285 | $this->cssOptionalNamespacePrefix(), |
1286 | $this->ident(), |
1287 | ] )->capture( 'attribute' ), |
1288 | $this->optionalWhitespace(), |
1289 | Quantifier::optional( new Juxtaposition( [ |
1290 | // Sigh. They removed various tokens from CSS Syntax 3, but didn't update the grammar |
1291 | // in CSS Selectors 3. Wing it with a hint from CSS Selectors 4's <attr-matcher> |
1292 | ( new Juxtaposition( [ |
1293 | Quantifier::optional( new DelimMatcher( [ '^', '$', '*', '~', '|' ] ) ), |
1294 | new DelimMatcher( [ '=' ] ), |
1295 | ] ) )->capture( 'test' ), |
1296 | $this->optionalWhitespace(), |
1297 | Alternative::create( [ |
1298 | $this->ident(), |
1299 | $this->string(), |
1300 | ] )->capture( 'value' ), |
1301 | $this->optionalWhitespace(), |
1302 | ] ) ), |
1303 | ] ) |
1304 | ); |
1305 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1306 | } |
1307 | return $this->cache[__METHOD__]; |
1308 | } |
1309 | |
1310 | /** |
1311 | * A pseudo-class or pseudo-element (pseudo) |
1312 | * |
1313 | * ':' ':'? [ IDENT | functional_pseudo ] |
1314 | * |
1315 | * Where functional_pseudo is |
1316 | * |
1317 | * FUNCTION S* expression ')' |
1318 | * |
1319 | * Although this actually only matches the pseudo-selectors defined in the |
1320 | * following sources: |
1321 | * - https://www.w3.org/TR/2018/REC-selectors-3-20181106/#pseudo-classes |
1322 | * - https://www.w3.org/TR/2019/WD-css-pseudo-4-20190225/ |
1323 | * |
1324 | * @return Matcher |
1325 | */ |
1326 | public function cssPseudo() { |
1327 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1328 | $colon = new TokenMatcher( Token::T_COLON ); |
1329 | $ows = $this->optionalWhitespace(); |
1330 | $anplusb = new Juxtaposition( [ $ows, $this->cssANplusB(), $ows ] ); |
1331 | $this->cache[__METHOD__] = new Alternative( [ |
1332 | new Juxtaposition( [ |
1333 | $colon, |
1334 | new Alternative( [ |
1335 | new KeywordMatcher( [ |
1336 | 'link', 'visited', 'hover', 'active', 'focus', 'target', 'enabled', 'disabled', 'checked', |
1337 | 'indeterminate', 'root', 'first-child', 'last-child', 'first-of-type', |
1338 | 'last-of-type', 'only-child', 'only-of-type', 'empty', |
1339 | // CSS2-compat elements with class syntax |
1340 | 'first-line', 'first-letter', 'before', 'after', |
1341 | ] ), |
1342 | new FunctionMatcher( 'lang', new Juxtaposition( [ $ows, $this->ident(), $ows ] ) ), |
1343 | new FunctionMatcher( 'nth-child', $anplusb ), |
1344 | new FunctionMatcher( 'nth-last-child', $anplusb ), |
1345 | new FunctionMatcher( 'nth-of-type', $anplusb ), |
1346 | new FunctionMatcher( 'nth-last-of-type', $anplusb ), |
1347 | ] ), |
1348 | ] ), |
1349 | new Juxtaposition( [ |
1350 | $colon, |
1351 | $colon, |
1352 | new KeywordMatcher( [ |
1353 | 'first-line', 'first-letter', 'before', 'after', 'selection', 'inactive-selection', |
1354 | 'spelling-error', 'grammar-error', 'marker', 'placeholder' |
1355 | ] ), |
1356 | ] ), |
1357 | ] ); |
1358 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1359 | } |
1360 | return $this->cache[__METHOD__]; |
1361 | } |
1362 | |
1363 | /** |
1364 | * An "AN+B" form |
1365 | * |
1366 | * https://www.w3.org/TR/2019/CR-css-syntax-3-20190716/#anb-microsyntax |
1367 | * |
1368 | * @return Matcher |
1369 | */ |
1370 | public function cssANplusB() { |
1371 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1372 | // Quoth the spec: |
1373 | // > The An+B notation was originally defined using a slightly |
1374 | // > different tokenizer than the rest of CSS, resulting in a |
1375 | // > somewhat odd definition when expressed in terms of CSS tokens. |
1376 | // That's a bit of an understatement |
1377 | |
1378 | $plusQ = Quantifier::optional( new DelimMatcher( [ '+' ] ) ); |
1379 | $n = new KeywordMatcher( [ 'n' ] ); |
1380 | $dashN = new KeywordMatcher( [ '-n' ] ); |
1381 | $nDash = new KeywordMatcher( [ 'n-' ] ); |
1382 | $plusQN = new Juxtaposition( [ $plusQ, $n ] ); |
1383 | $plusQNDash = new Juxtaposition( [ $plusQ, $nDash ] ); |
1384 | $nDimension = new TokenMatcher( Token::T_DIMENSION, static function ( Token $t ) { |
1385 | return $t->typeFlag() === 'integer' && !strcasecmp( $t->unit(), 'n' ); |
1386 | } ); |
1387 | $nDashDimension = new TokenMatcher( Token::T_DIMENSION, static function ( Token $t ) { |
1388 | return $t->typeFlag() === 'integer' && !strcasecmp( $t->unit(), 'n-' ); |
1389 | } ); |
1390 | $nDashDigitDimension = new TokenMatcher( Token::T_DIMENSION, static function ( Token $t ) { |
1391 | return $t->typeFlag() === 'integer' && preg_match( '/^n-\d+$/i', $t->unit() ); |
1392 | } ); |
1393 | $nDashDigitIdent = new TokenMatcher( Token::T_IDENT, static function ( Token $t ) { |
1394 | return preg_match( '/^n-\d+$/i', $t->value() ); |
1395 | } ); |
1396 | $dashNDashDigitIdent = new TokenMatcher( Token::T_IDENT, static function ( Token $t ) { |
1397 | return preg_match( '/^-n-\d+$/i', $t->value() ); |
1398 | } ); |
1399 | $signedInt = new TokenMatcher( Token::T_NUMBER, static function ( Token $t ) { |
1400 | return $t->typeFlag() === 'integer' && preg_match( '/^[+-]/', $t->representation() ); |
1401 | } ); |
1402 | $signlessInt = new TokenMatcher( Token::T_NUMBER, static function ( Token $t ) { |
1403 | return $t->typeFlag() === 'integer' && preg_match( '/^\d/', $t->representation() ); |
1404 | } ); |
1405 | $plusOrMinus = new DelimMatcher( [ '+', '-' ] ); |
1406 | $S = $this->optionalWhitespace(); |
1407 | |
1408 | $this->cache[__METHOD__] = new Alternative( [ |
1409 | new KeywordMatcher( [ 'odd', 'even' ] ), |
1410 | new TokenMatcher( Token::T_NUMBER, static function ( Token $t ) { |
1411 | return $t->typeFlag() === 'integer'; |
1412 | } ), |
1413 | $nDimension, |
1414 | $plusQN, |
1415 | $dashN, |
1416 | $nDashDigitDimension, |
1417 | new Juxtaposition( [ $plusQ, $nDashDigitIdent ] ), |
1418 | $dashNDashDigitIdent, |
1419 | new Juxtaposition( [ $nDimension, $S, $signedInt ] ), |
1420 | new Juxtaposition( [ $plusQN, $S, $signedInt ] ), |
1421 | new Juxtaposition( [ $dashN, $S, $signedInt ] ), |
1422 | new Juxtaposition( [ $nDashDimension, $S, $signlessInt ] ), |
1423 | new Juxtaposition( [ $plusQNDash, $S, $signlessInt ] ), |
1424 | new Juxtaposition( [ new KeywordMatcher( [ '-n-' ] ), $S, $signlessInt ] ), |
1425 | new Juxtaposition( [ $nDimension, $S, $plusOrMinus, $S, $signlessInt ] ), |
1426 | new Juxtaposition( [ $plusQN, $S, $plusOrMinus, $S, $signlessInt ] ), |
1427 | new Juxtaposition( [ $dashN, $S, $plusOrMinus, $S, $signlessInt ] ) |
1428 | ] ); |
1429 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1430 | } |
1431 | return $this->cache[__METHOD__]; |
1432 | } |
1433 | |
1434 | /** |
1435 | * A negation (negation) |
1436 | * |
1437 | * ':' not( S* [ type_selector | universal | HASH | class | attrib | pseudo ] S* ')' |
1438 | * |
1439 | * @return Matcher |
1440 | */ |
1441 | public function cssNegation() { |
1442 | if ( !isset( $this->cache[__METHOD__] ) ) { |
1443 | // A negation is going to be parsed by the parser as a colon |
1444 | // followed by a CSSFunction, so that's what we need to look for |
1445 | // here. |
1446 | |
1447 | $this->cache[__METHOD__] = new Juxtaposition( [ |
1448 | new TokenMatcher( Token::T_COLON ), |
1449 | new FunctionMatcher( 'not', |
1450 | new Juxtaposition( [ |
1451 | $this->optionalWhitespace(), |
1452 | new Alternative( [ |
1453 | $this->cssTypeSelector(), |
1454 | $this->cssUniversal(), |
1455 | $this->cssID(), |
1456 | $this->cssClass(), |
1457 | $this->cssAttrib(), |
1458 | $this->cssPseudo(), |
1459 | ] ), |
1460 | $this->optionalWhitespace(), |
1461 | ] ) |
1462 | ) |
1463 | ] ); |
1464 | $this->cache[__METHOD__]->setDefaultOptions( [ 'skip-whitespace' => false ] ); |
1465 | } |
1466 | return $this->cache[__METHOD__]; |
1467 | } |
1468 | |
1469 | /** |
1470 | * @return KeywordMatcher |
1471 | */ |
1472 | public function colorWords(): KeywordMatcher { |
1473 | return new KeywordMatcher( [ |
1474 | // Basic colors |
1475 | 'aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', |
1476 | 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', |
1477 | 'silver', 'teal', 'white', 'yellow', |
1478 | // Extended colors |
1479 | 'aliceblue', 'antiquewhite', 'aquamarine', 'azure', |
1480 | 'beige', 'bisque', 'blanchedalmond', 'blueviolet', 'brown', |
1481 | 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', |
1482 | 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', |
1483 | 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', |
1484 | 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', |
1485 | 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', |
1486 | 'darksalmon', 'darkseagreen', 'darkslateblue', |
1487 | 'darkslategray', 'darkslategrey', 'darkturquoise', |
1488 | 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', |
1489 | 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', |
1490 | 'forestgreen', 'gainsboro', 'ghostwhite', 'gold', |
1491 | 'goldenrod', 'greenyellow', 'grey', 'honeydew', 'hotpink', |
1492 | 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', |
1493 | 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', |
1494 | 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', |
1495 | 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', |
1496 | 'lightsalmon', 'lightseagreen', 'lightskyblue', |
1497 | 'lightslategray', 'lightslategrey', 'lightsteelblue', |
1498 | 'lightyellow', 'limegreen', 'linen', 'magenta', |
1499 | 'mediumaquamarine', 'mediumblue', 'mediumorchid', |
1500 | 'mediumpurple', 'mediumseagreen', 'mediumslateblue', |
1501 | 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', |
1502 | 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', |
1503 | 'navajowhite', 'oldlace', 'olivedrab', 'orange', |
1504 | 'orangered', 'orchid', 'palegoldenrod', 'palegreen', |
1505 | 'paleturquoise', 'palevioletred', 'papayawhip', |
1506 | 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', |
1507 | 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', |
1508 | 'sandybrown', 'seagreen', 'seashell', 'sienna', 'skyblue', |
1509 | 'slateblue', 'slategray', 'slategrey', 'snow', |
1510 | 'springgreen', 'steelblue', 'tan', 'thistle', 'tomato', |
1511 | 'turquoise', 'violet', 'wheat', 'whitesmoke', |
1512 | 'yellowgreen', |
1513 | // Other keywords. Intentionally omitting the deprecated system colors. |
1514 | 'transparent', 'currentColor', |
1515 | ] ); |
1516 | } |
1517 | |
1518 | /** @} */ |
1519 | |
1520 | } |
1521 | |
1522 | /** |
1523 | * For really cool vim folding this needs to be at the end: |
1524 | * vim: foldmarker=@{,@} foldmethod=marker |
1525 | */ |