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}