cssparser/rules_and_declarations.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5// https://drafts.csswg.org/css-syntax/#parsing
6
7use super::{BasicParseError, BasicParseErrorKind, Delimiter, ParseError, Parser, Token};
8use crate::cow_rc_str::CowRcStr;
9use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavior, ParserState};
10
11/// Parse `!important`.
12///
13/// Typical usage is `input.try_parse(parse_important).is_ok()`
14/// at the end of a `DeclarationParser::parse_value` implementation.
15pub fn parse_important<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> {
16 input.expect_delim('!')?;
17 input.expect_ident_matching("important")
18}
19
20/// A trait to provide various parsing of declaration values.
21///
22/// For example, there could be different implementations for property declarations in style rules
23/// and for descriptors in `@font-face` rules.
24pub trait DeclarationParser<'i> {
25 /// The finished representation of a declaration.
26 type Declaration;
27
28 /// The error type that is included in the ParseError value that can be returned.
29 type Error: 'i;
30
31 /// Parse the value of a declaration with the given `name`.
32 ///
33 /// Return the finished representation for the declaration
34 /// as returned by `DeclarationListParser::next`,
35 /// or an `Err(..)` to ignore the entire declaration as invalid.
36 ///
37 /// Declaration name matching should be case-insensitive in the ASCII range.
38 /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
39 /// or with the `match_ignore_ascii_case!` macro.
40 ///
41 /// The given `input` is a "delimited" parser
42 /// that ends wherever the declaration value should end.
43 /// (In declaration lists, before the next semicolon or end of the current block.)
44 ///
45 /// If `!important` can be used in a given context,
46 /// `input.try_parse(parse_important).is_ok()` should be used at the end
47 /// of the implementation of this method and the result should be part of the return value.
48 fn parse_value<'t>(
49 &mut self,
50 name: CowRcStr<'i>,
51 input: &mut Parser<'i, 't>,
52 _declaration_start: &ParserState,
53 ) -> Result<Self::Declaration, ParseError<'i, Self::Error>> {
54 Err(input.new_error(BasicParseErrorKind::UnexpectedToken(Token::Ident(name))))
55 }
56}
57
58/// A trait to provide various parsing of at-rules.
59///
60/// For example, there could be different implementations for top-level at-rules
61/// (`@media`, `@font-face`, …)
62/// and for page-margin rules inside `@page`.
63///
64/// Default implementations that reject all at-rules are provided,
65/// so that `impl AtRuleParser<(), ()> for ... {}` can be used
66/// for using `DeclarationListParser` to parse a declarations list with only qualified rules.
67pub trait AtRuleParser<'i> {
68 /// The intermediate representation of prelude of an at-rule.
69 type Prelude;
70
71 /// The finished representation of an at-rule.
72 type AtRule;
73
74 /// The error type that is included in the ParseError value that can be returned.
75 type Error: 'i;
76
77 /// Parse the prelude of an at-rule with the given `name`.
78 ///
79 /// Return the representation of the prelude and the type of at-rule,
80 /// or an `Err(..)` to ignore the entire at-rule as invalid.
81 ///
82 /// The prelude is the part after the at-keyword
83 /// and before the `;` semicolon or `{ /* ... */ }` block.
84 ///
85 /// At-rule name matching should be case-insensitive in the ASCII range.
86 /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
87 /// or with the `match_ignore_ascii_case!` macro.
88 ///
89 /// The given `input` is a "delimited" parser
90 /// that ends wherever the prelude should end.
91 /// (Before the next semicolon, the next `{`, or the end of the current block.)
92 fn parse_prelude<'t>(
93 &mut self,
94 name: CowRcStr<'i>,
95 input: &mut Parser<'i, 't>,
96 ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
97 Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))
98 }
99
100 /// End an at-rule which doesn't have block. Return the finished
101 /// representation of the at-rule.
102 ///
103 /// The location passed in is source location of the start of the prelude.
104 ///
105 /// This is only called when `parse_prelude` returned `WithoutBlock`, and
106 /// either the `;` semicolon indeed follows the prelude, or parser is at
107 /// the end of the input.
108 #[allow(clippy::result_unit_err)]
109 fn rule_without_block(
110 &mut self,
111 prelude: Self::Prelude,
112 start: &ParserState,
113 ) -> Result<Self::AtRule, ()> {
114 let _ = prelude;
115 let _ = start;
116 Err(())
117 }
118
119 /// Parse the content of a `{ /* ... */ }` block for the body of the at-rule.
120 ///
121 /// The location passed in is source location of the start of the prelude.
122 ///
123 /// Return the finished representation of the at-rule
124 /// as returned by `RuleListParser::next` or `DeclarationListParser::next`,
125 /// or an `Err(..)` to ignore the entire at-rule as invalid.
126 ///
127 /// This is only called when `parse_prelude` returned `WithBlock`, and a block
128 /// was indeed found following the prelude.
129 fn parse_block<'t>(
130 &mut self,
131 prelude: Self::Prelude,
132 start: &ParserState,
133 input: &mut Parser<'i, 't>,
134 ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
135 let _ = prelude;
136 let _ = start;
137 Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid))
138 }
139}
140
141/// A trait to provide various parsing of qualified rules.
142///
143/// For example, there could be different implementations for top-level qualified rules (i.e. style
144/// rules with Selectors as prelude) and for qualified rules inside `@keyframes` (keyframe rules
145/// with keyframe selectors as prelude).
146///
147/// Default implementations that reject all qualified rules are provided, so that
148/// `impl QualifiedRuleParser<(), ()> for ... {}` can be used for example for using
149/// `RuleListParser` to parse a rule list with only at-rules (such as inside
150/// `@font-feature-values`).
151pub trait QualifiedRuleParser<'i> {
152 /// The intermediate representation of a qualified rule prelude.
153 type Prelude;
154
155 /// The finished representation of a qualified rule.
156 type QualifiedRule;
157
158 /// The error type that is included in the ParseError value that can be returned.
159 type Error: 'i;
160
161 /// Parse the prelude of a qualified rule. For style rules, this is as Selector list.
162 ///
163 /// Return the representation of the prelude,
164 /// or an `Err(..)` to ignore the entire at-rule as invalid.
165 ///
166 /// The prelude is the part before the `{ /* ... */ }` block.
167 ///
168 /// The given `input` is a "delimited" parser
169 /// that ends where the prelude should end (before the next `{`).
170 fn parse_prelude<'t>(
171 &mut self,
172 input: &mut Parser<'i, 't>,
173 ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
174 Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
175 }
176
177 /// Parse the content of a `{ /* ... */ }` block for the body of the qualified rule.
178 ///
179 /// The location passed in is source location of the start of the prelude.
180 ///
181 /// Return the finished representation of the qualified rule
182 /// as returned by `RuleListParser::next`,
183 /// or an `Err(..)` to ignore the entire at-rule as invalid.
184 fn parse_block<'t>(
185 &mut self,
186 prelude: Self::Prelude,
187 start: &ParserState,
188 input: &mut Parser<'i, 't>,
189 ) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {
190 let _ = prelude;
191 let _ = start;
192 Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
193 }
194}
195
196/// Provides an iterator for rule bodies and declaration lists.
197pub struct RuleBodyParser<'i, 't, 'a, P, I, E> {
198 /// The input given to the parser.
199 pub input: &'a mut Parser<'i, 't>,
200 /// The parser given to `DeclarationListParser::new`
201 pub parser: &'a mut P,
202
203 _phantom: std::marker::PhantomData<(I, E)>,
204}
205
206/// A parser for a rule body item.
207pub trait RuleBodyItemParser<'i, DeclOrRule, Error: 'i>:
208 DeclarationParser<'i, Declaration = DeclOrRule, Error = Error>
209 + QualifiedRuleParser<'i, QualifiedRule = DeclOrRule, Error = Error>
210 + AtRuleParser<'i, AtRule = DeclOrRule, Error = Error>
211{
212 /// Whether we should attempt to parse declarations. If you know you won't, returning false
213 /// here is slightly faster.
214 fn parse_declarations(&self) -> bool;
215 /// Whether we should attempt to parse qualified rules. If you know you won't, returning false
216 /// would be slightly faster.
217 fn parse_qualified(&self) -> bool;
218}
219
220impl<'i, 't, 'a, P, I, E> RuleBodyParser<'i, 't, 'a, P, I, E> {
221 /// Create a new `DeclarationListParser` for the given `input` and `parser`.
222 ///
223 /// Note that all CSS declaration lists can on principle contain at-rules.
224 /// Even if no such valid at-rule exists (yet),
225 /// this affects error handling: at-rules end at `{}` blocks, not just semicolons.
226 ///
227 /// The given `parser` therefore needs to implement
228 /// both `DeclarationParser` and `AtRuleParser` traits.
229 /// However, the latter can be an empty `impl`
230 /// since `AtRuleParser` provides default implementations of its methods.
231 ///
232 /// The return type for finished declarations and at-rules also needs to be the same,
233 /// since `<DeclarationListParser as Iterator>::next` can return either.
234 /// It could be a custom enum.
235 pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self {
236 Self {
237 input,
238 parser,
239 _phantom: std::marker::PhantomData,
240 }
241 }
242}
243
244/// https://drafts.csswg.org/css-syntax/#consume-a-blocks-contents
245impl<'i, I, P, E: 'i> Iterator for RuleBodyParser<'i, '_, '_, P, I, E>
246where
247 P: RuleBodyItemParser<'i, I, E>,
248{
249 type Item = Result<I, (ParseError<'i, E>, &'i str)>;
250
251 fn next(&mut self) -> Option<Self::Item> {
252 loop {
253 self.input.skip_whitespace();
254 let start = self.input.state();
255 match self.input.next_including_whitespace_and_comments().ok()? {
256 Token::CloseCurlyBracket
257 | Token::WhiteSpace(..)
258 | Token::Semicolon
259 | Token::Comment(..) => continue,
260 Token::AtKeyword(ref name) => {
261 let name = name.clone();
262 return Some(parse_at_rule(&start, name, self.input, &mut *self.parser));
263 }
264 // https://drafts.csswg.org/css-syntax/#consume-a-declaration bails out just to
265 // keep parsing as a qualified rule if the token is not an ident, so we implement
266 // that in a slightly more straight-forward way
267 Token::Ident(ref name) if self.parser.parse_declarations() => {
268 let name = name.clone();
269 let parse_qualified = self.parser.parse_qualified();
270 let result = {
271 let error_behavior = if parse_qualified {
272 ParseUntilErrorBehavior::Stop
273 } else {
274 ParseUntilErrorBehavior::Consume
275 };
276 let parser = &mut self.parser;
277 parse_until_after(
278 self.input,
279 Delimiter::Semicolon,
280 error_behavior,
281 |input| {
282 input.expect_colon()?;
283 parser.parse_value(name, input, &start)
284 },
285 )
286 };
287 if result.is_err() && parse_qualified {
288 self.input.reset(&start);
289 // We ignore the resulting error here. The property declaration parse error
290 // is likely to be more relevant.
291 if let Ok(qual) = parse_qualified_rule(
292 &start,
293 self.input,
294 &mut *self.parser,
295 /* nested = */ true,
296 ) {
297 return Some(Ok(qual));
298 }
299 }
300
301 return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
302 }
303 token => {
304 let result = if self.parser.parse_qualified() {
305 self.input.reset(&start);
306 let nested = self.parser.parse_declarations();
307 parse_qualified_rule(&start, self.input, &mut *self.parser, nested)
308 } else {
309 let token = token.clone();
310 self.input.parse_until_after(Delimiter::Semicolon, |_| {
311 Err(start.source_location().new_unexpected_token_error(token))
312 })
313 };
314 return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
315 }
316 }
317 }
318 }
319}
320
321/// Provides an iterator for rule list parsing at the top-level of a stylesheet.
322pub struct StyleSheetParser<'i, 't, 'a, P> {
323 /// The input given.
324 pub input: &'a mut Parser<'i, 't>,
325
326 /// The parser given.
327 pub parser: &'a mut P,
328
329 any_rule_so_far: bool,
330}
331
332impl<'i, 't, 'a, R, P, E: 'i> StyleSheetParser<'i, 't, 'a, P>
333where
334 P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
335 + AtRuleParser<'i, AtRule = R, Error = E>,
336{
337 /// The given `parser` needs to implement both `QualifiedRuleParser` and `AtRuleParser` traits.
338 /// However, either of them can be an empty `impl` since the traits provide default
339 /// implementations of their methods.
340 ///
341 /// The return type for finished qualified rules and at-rules also needs to be the same,
342 /// since `<RuleListParser as Iterator>::next` can return either. It could be a custom enum.
343 pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self {
344 Self {
345 input,
346 parser,
347 any_rule_so_far: false,
348 }
349 }
350}
351
352/// `RuleListParser` is an iterator that yields `Ok(_)` for a rule or an `Err(..)` for an invalid one.
353impl<'i, R, P, E: 'i> Iterator for StyleSheetParser<'i, '_, '_, P>
354where
355 P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
356 + AtRuleParser<'i, AtRule = R, Error = E>,
357{
358 type Item = Result<R, (ParseError<'i, E>, &'i str)>;
359
360 fn next(&mut self) -> Option<Self::Item> {
361 loop {
362 self.input.skip_cdc_and_cdo();
363 let start = self.input.state();
364 let at_keyword = match self.input.next_byte()? {
365 b'@' => match self.input.next_including_whitespace_and_comments() {
366 Ok(Token::AtKeyword(name)) => Some(name.clone()),
367 _ => {
368 self.input.reset(&start);
369 None
370 }
371 },
372 _ => None,
373 };
374
375 if let Some(name) = at_keyword {
376 let first_stylesheet_rule = !self.any_rule_so_far;
377 self.any_rule_so_far = true;
378 if first_stylesheet_rule && name.eq_ignore_ascii_case("charset") {
379 let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
380 let _: Result<(), ParseError<()>> =
381 self.input.parse_until_after(delimiters, |_| Ok(()));
382 } else {
383 return Some(parse_at_rule(
384 &start,
385 name.clone(),
386 self.input,
387 &mut *self.parser,
388 ));
389 }
390 } else {
391 self.any_rule_so_far = true;
392 let result = parse_qualified_rule(
393 &start,
394 self.input,
395 &mut *self.parser,
396 /* nested = */ false,
397 );
398 return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
399 }
400 }
401 }
402}
403
404/// Parse a single declaration, such as an `( /* ... */ )` parenthesis in an `@supports` prelude.
405pub fn parse_one_declaration<'i, 't, P, E>(
406 input: &mut Parser<'i, 't>,
407 parser: &mut P,
408) -> Result<<P as DeclarationParser<'i>>::Declaration, (ParseError<'i, E>, &'i str)>
409where
410 P: DeclarationParser<'i, Error = E>,
411{
412 let start = input.state();
413 let start_position = input.position();
414 input
415 .parse_entirely(|input| {
416 let name = input.expect_ident()?.clone();
417 input.expect_colon()?;
418 parser.parse_value(name, input, &start)
419 })
420 .map_err(|e| (e, input.slice_from(start_position)))
421}
422
423/// Parse a single rule, such as for CSSOM’s `CSSStyleSheet.insertRule`.
424pub fn parse_one_rule<'i, 't, R, P, E>(
425 input: &mut Parser<'i, 't>,
426 parser: &mut P,
427) -> Result<R, ParseError<'i, E>>
428where
429 P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
430 + AtRuleParser<'i, AtRule = R, Error = E>,
431{
432 input.parse_entirely(|input| {
433 input.skip_whitespace();
434 let start = input.state();
435 let at_keyword = if input.next_byte() == Some(b'@') {
436 match *input.next_including_whitespace_and_comments()? {
437 Token::AtKeyword(ref name) => Some(name.clone()),
438 _ => {
439 input.reset(&start);
440 None
441 }
442 }
443 } else {
444 None
445 };
446
447 if let Some(name) = at_keyword {
448 parse_at_rule(&start, name, input, parser).map_err(|e| e.0)
449 } else {
450 parse_qualified_rule(&start, input, parser, /* nested = */ false)
451 }
452 })
453}
454
455fn parse_at_rule<'i, 't, P, E>(
456 start: &ParserState,
457 name: CowRcStr<'i>,
458 input: &mut Parser<'i, 't>,
459 parser: &mut P,
460) -> Result<<P as AtRuleParser<'i>>::AtRule, (ParseError<'i, E>, &'i str)>
461where
462 P: AtRuleParser<'i, Error = E>,
463{
464 let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
465 let result = input.parse_until_before(delimiters, |input| parser.parse_prelude(name, input));
466 match result {
467 Ok(prelude) => {
468 let result = match input.next() {
469 Ok(&Token::Semicolon) | Err(_) => parser
470 .rule_without_block(prelude, start)
471 .map_err(|()| input.new_unexpected_token_error(Token::Semicolon)),
472 Ok(&Token::CurlyBracketBlock) => {
473 parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
474 }
475 Ok(_) => unreachable!(),
476 };
477 result.map_err(|e| (e, input.slice_from(start.position())))
478 }
479 Err(error) => {
480 let end_position = input.position();
481 match input.next() {
482 Ok(&Token::CurlyBracketBlock) | Ok(&Token::Semicolon) | Err(_) => {}
483 _ => unreachable!(),
484 };
485 Err((error, input.slice(start.position()..end_position)))
486 }
487 }
488}
489
490// If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose
491// value starts with "--" followed by a <colon-token>, then...
492fn looks_like_a_custom_property(input: &mut Parser) -> bool {
493 let ident = match input.expect_ident() {
494 Ok(i) => i,
495 Err(..) => return false,
496 };
497 ident.starts_with("--") && input.expect_colon().is_ok()
498}
499
500// https://drafts.csswg.org/css-syntax/#consume-a-qualified-rule
501fn parse_qualified_rule<'i, 't, P, E>(
502 start: &ParserState,
503 input: &mut Parser<'i, 't>,
504 parser: &mut P,
505 nested: bool,
506) -> Result<<P as QualifiedRuleParser<'i>>::QualifiedRule, ParseError<'i, E>>
507where
508 P: QualifiedRuleParser<'i, Error = E>,
509{
510 input.skip_whitespace();
511 let prelude = {
512 let state = input.state();
513 if looks_like_a_custom_property(input) {
514 // If nested is true, consume the remnants of a bad declaration from input, with
515 // nested set to true, and return nothing.
516 // If nested is false, consume a block from input, and return nothing.
517 let delimiters = if nested {
518 Delimiter::Semicolon
519 } else {
520 Delimiter::CurlyBracketBlock
521 };
522 let _: Result<(), ParseError<()>> = input.parse_until_after(delimiters, |_| Ok(()));
523 return Err(state
524 .source_location()
525 .new_error(BasicParseErrorKind::QualifiedRuleInvalid));
526 }
527 let delimiters = if nested {
528 Delimiter::Semicolon | Delimiter::CurlyBracketBlock
529 } else {
530 Delimiter::CurlyBracketBlock
531 };
532 input.reset(&state);
533 input.parse_until_before(delimiters, |input| parser.parse_prelude(input))
534 };
535
536 input.expect_curly_bracket_block()?;
537 // Do this here so that we consume the `{` even if the prelude is `Err`.
538 let prelude = prelude?;
539 parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
540}