Skip to main content

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