Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
use super::{BasicParseError, BasicParseErrorKind, Delimiter, ParseError, Parser, Token};
use crate::cow_rc_str::CowRcStr;
use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavior, ParserState};
/// Parse `!important`.
///
/// Typical usage is `input.try_parse(parse_important).is_ok()`
/// at the end of a `DeclarationParser::parse_value` implementation.
pub fn parse_important<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> {
    input.expect_delim('!')?;
    input.expect_ident_matching("important")
}
/// A trait to provide various parsing of declaration values.
///
/// For example, there could be different implementations for property declarations in style rules
/// and for descriptors in `@font-face` rules.
pub trait DeclarationParser<'i> {
    /// The finished representation of a declaration.
    type Declaration;
    /// The error type that is included in the ParseError value that can be returned.
    type Error: 'i;
    /// Parse the value of a declaration with the given `name`.
    ///
    /// Return the finished representation for the declaration
    /// as returned by `DeclarationListParser::next`,
    /// or an `Err(..)` to ignore the entire declaration as invalid.
    ///
    /// Declaration name matching should be case-insensitive in the ASCII range.
    /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
    /// or with the `match_ignore_ascii_case!` macro.
    ///
    /// The given `input` is a "delimited" parser
    /// that ends wherever the declaration value should end.
    /// (In declaration lists, before the next semicolon or end of the current block.)
    ///
    /// If `!important` can be used in a given context,
    /// `input.try_parse(parse_important).is_ok()` should be used at the end
    /// of the implementation of this method and the result should be part of the return value.
    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
        _declaration_start: &ParserState,
    ) -> Result<Self::Declaration, ParseError<'i, Self::Error>> {
        Err(input.new_error(BasicParseErrorKind::UnexpectedToken(Token::Ident(name))))
    }
}
/// A trait to provide various parsing of at-rules.
///
/// For example, there could be different implementations for top-level at-rules
/// (`@media`, `@font-face`, …)
/// and for page-margin rules inside `@page`.
///
/// Default implementations that reject all at-rules are provided,
/// so that `impl AtRuleParser<(), ()> for ... {}` can be used
/// for using `DeclarationListParser` to parse a declarations list with only qualified rules.
pub trait AtRuleParser<'i> {
    /// The intermediate representation of prelude of an at-rule.
    type Prelude;
    /// The finished representation of an at-rule.
    type AtRule;
    /// The error type that is included in the ParseError value that can be returned.
    type Error: 'i;
    /// Parse the prelude of an at-rule with the given `name`.
    ///
    /// Return the representation of the prelude and the type of at-rule,
    /// or an `Err(..)` to ignore the entire at-rule as invalid.
    ///
    /// The prelude is the part after the at-keyword
    /// and before the `;` semicolon or `{ /* ... */ }` block.
    ///
    /// At-rule name matching should be case-insensitive in the ASCII range.
    /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
    /// or with the `match_ignore_ascii_case!` macro.
    ///
    /// The given `input` is a "delimited" parser
    /// that ends wherever the prelude should end.
    /// (Before the next semicolon, the next `{`, or the end of the current block.)
    fn parse_prelude<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
        Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name)))
    }
    /// End an at-rule which doesn't have block. Return the finished
    /// representation of the at-rule.
    ///
    /// The location passed in is source location of the start of the prelude.
    ///
    /// This is only called when `parse_prelude` returned `WithoutBlock`, and
    /// either the `;` semicolon indeed follows the prelude, or parser is at
    /// the end of the input.
    #[allow(clippy::result_unit_err)]
    fn rule_without_block(
        &mut self,
        prelude: Self::Prelude,
        start: &ParserState,
    ) -> Result<Self::AtRule, ()> {
        let _ = prelude;
        let _ = start;
        Err(())
    }
    /// Parse the content of a `{ /* ... */ }` block for the body of the at-rule.
    ///
    /// The location passed in is source location of the start of the prelude.
    ///
    /// Return the finished representation of the at-rule
    /// as returned by `RuleListParser::next` or `DeclarationListParser::next`,
    /// or an `Err(..)` to ignore the entire at-rule as invalid.
    ///
    /// This is only called when `parse_prelude` returned `WithBlock`, and a block
    /// was indeed found following the prelude.
    fn parse_block<'t>(
        &mut self,
        prelude: Self::Prelude,
        start: &ParserState,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
        let _ = prelude;
        let _ = start;
        Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid))
    }
}
/// A trait to provide various parsing of qualified rules.
///
/// For example, there could be different implementations for top-level qualified rules (i.e. style
/// rules with Selectors as prelude) and for qualified rules inside `@keyframes` (keyframe rules
/// with keyframe selectors as prelude).
///
/// Default implementations that reject all qualified rules are provided, so that
/// `impl QualifiedRuleParser<(), ()> for ... {}` can be used for example for using
/// `RuleListParser` to parse a rule list with only at-rules (such as inside
/// `@font-feature-values`).
pub trait QualifiedRuleParser<'i> {
    /// The intermediate representation of a qualified rule prelude.
    type Prelude;
    /// The finished representation of a qualified rule.
    type QualifiedRule;
    /// The error type that is included in the ParseError value that can be returned.
    type Error: 'i;
    /// Parse the prelude of a qualified rule. For style rules, this is as Selector list.
    ///
    /// Return the representation of the prelude,
    /// or an `Err(..)` to ignore the entire at-rule as invalid.
    ///
    /// The prelude is the part before the `{ /* ... */ }` block.
    ///
    /// The given `input` is a "delimited" parser
    /// that ends where the prelude should end (before the next `{`).
    fn parse_prelude<'t>(
        &mut self,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
        Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
    }
    /// Parse the content of a `{ /* ... */ }` block for the body of the qualified rule.
    ///
    /// The location passed in is source location of the start of the prelude.
    ///
    /// Return the finished representation of the qualified rule
    /// as returned by `RuleListParser::next`,
    /// or an `Err(..)` to ignore the entire at-rule as invalid.
    fn parse_block<'t>(
        &mut self,
        prelude: Self::Prelude,
        start: &ParserState,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {
        let _ = prelude;
        let _ = start;
        Err(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))
    }
}
/// Provides an iterator for rule bodies and declaration lists.
pub struct RuleBodyParser<'i, 't, 'a, P, I, E> {
    /// The input given to the parser.
    pub input: &'a mut Parser<'i, 't>,
    /// The parser given to `DeclarationListParser::new`
    pub parser: &'a mut P,
    _phantom: std::marker::PhantomData<(I, E)>,
}
/// A parser for a rule body item.
pub trait RuleBodyItemParser<'i, DeclOrRule, Error: 'i>:
    DeclarationParser<'i, Declaration = DeclOrRule, Error = Error>
    + QualifiedRuleParser<'i, QualifiedRule = DeclOrRule, Error = Error>
    + AtRuleParser<'i, AtRule = DeclOrRule, Error = Error>
{
    /// Whether we should attempt to parse declarations. If you know you won't, returning false
    /// here is slightly faster.
    fn parse_declarations(&self) -> bool;
    /// Whether we should attempt to parse qualified rules. If you know you won't, returning false
    /// would be slightly faster.
    fn parse_qualified(&self) -> bool;
}
impl<'i, 't, 'a, P, I, E> RuleBodyParser<'i, 't, 'a, P, I, E> {
    /// Create a new `DeclarationListParser` for the given `input` and `parser`.
    ///
    /// Note that all CSS declaration lists can on principle contain at-rules.
    /// Even if no such valid at-rule exists (yet),
    /// this affects error handling: at-rules end at `{}` blocks, not just semicolons.
    ///
    /// The given `parser` therefore needs to implement
    /// both `DeclarationParser` and `AtRuleParser` traits.
    /// However, the latter can be an empty `impl`
    /// since `AtRuleParser` provides default implementations of its methods.
    ///
    /// The return type for finished declarations and at-rules also needs to be the same,
    /// since `<DeclarationListParser as Iterator>::next` can return either.
    /// It could be a custom enum.
    pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self {
        Self {
            input,
            parser,
            _phantom: std::marker::PhantomData,
        }
    }
}
impl<'i, I, P, E: 'i> Iterator for RuleBodyParser<'i, '_, '_, P, I, E>
where
    P: RuleBodyItemParser<'i, I, E>,
{
    type Item = Result<I, (ParseError<'i, E>, &'i str)>;
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            self.input.skip_whitespace();
            let start = self.input.state();
            match self.input.next_including_whitespace_and_comments().ok()? {
                Token::CloseCurlyBracket
                | Token::WhiteSpace(..)
                | Token::Semicolon
                | Token::Comment(..) => continue,
                Token::AtKeyword(ref name) => {
                    let name = name.clone();
                    return Some(parse_at_rule(&start, name, self.input, &mut *self.parser));
                }
                // keep parsing as a qualified rule if the token is not an ident, so we implement
                // that in a slightly more straight-forward way
                Token::Ident(ref name) if self.parser.parse_declarations() => {
                    let name = name.clone();
                    let parse_qualified = self.parser.parse_qualified();
                    let result = {
                        let error_behavior = if parse_qualified {
                            ParseUntilErrorBehavior::Stop
                        } else {
                            ParseUntilErrorBehavior::Consume
                        };
                        let parser = &mut self.parser;
                        parse_until_after(
                            self.input,
                            Delimiter::Semicolon,
                            error_behavior,
                            |input| {
                                input.expect_colon()?;
                                parser.parse_value(name, input, &start)
                            },
                        )
                    };
                    if result.is_err() && parse_qualified {
                        self.input.reset(&start);
                        // We ignore the resulting error here. The property declaration parse error
                        // is likely to be more relevant.
                        if let Ok(qual) = parse_qualified_rule(
                            &start,
                            self.input,
                            &mut *self.parser,
                            /* nested = */ true,
                        ) {
                            return Some(Ok(qual));
                        }
                    }
                    return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
                }
                token => {
                    let result = if self.parser.parse_qualified() {
                        self.input.reset(&start);
                        let nested = self.parser.parse_declarations();
                        parse_qualified_rule(&start, self.input, &mut *self.parser, nested)
                    } else {
                        let token = token.clone();
                        self.input.parse_until_after(Delimiter::Semicolon, |_| {
                            Err(start.source_location().new_unexpected_token_error(token))
                        })
                    };
                    return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
                }
            }
        }
    }
}
/// Provides an iterator for rule list parsing at the top-level of a stylesheet.
pub struct StyleSheetParser<'i, 't, 'a, P> {
    /// The input given.
    pub input: &'a mut Parser<'i, 't>,
    /// The parser given.
    pub parser: &'a mut P,
    any_rule_so_far: bool,
}
impl<'i, 't, 'a, R, P, E: 'i> StyleSheetParser<'i, 't, 'a, P>
where
    P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
        + AtRuleParser<'i, AtRule = R, Error = E>,
{
    /// The given `parser` needs to implement both `QualifiedRuleParser` and `AtRuleParser` traits.
    /// However, either of them can be an empty `impl` since the traits provide default
    /// implementations of their methods.
    ///
    /// The return type for finished qualified rules and at-rules also needs to be the same,
    /// since `<RuleListParser as Iterator>::next` can return either. It could be a custom enum.
    pub fn new(input: &'a mut Parser<'i, 't>, parser: &'a mut P) -> Self {
        Self {
            input,
            parser,
            any_rule_so_far: false,
        }
    }
}
/// `RuleListParser` is an iterator that yields `Ok(_)` for a rule or an `Err(..)` for an invalid one.
impl<'i, R, P, E: 'i> Iterator for StyleSheetParser<'i, '_, '_, P>
where
    P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
        + AtRuleParser<'i, AtRule = R, Error = E>,
{
    type Item = Result<R, (ParseError<'i, E>, &'i str)>;
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            self.input.skip_cdc_and_cdo();
            let start = self.input.state();
            let at_keyword = match self.input.next_byte()? {
                b'@' => match self.input.next_including_whitespace_and_comments() {
                    Ok(Token::AtKeyword(name)) => Some(name.clone()),
                    _ => {
                        self.input.reset(&start);
                        None
                    }
                },
                _ => None,
            };
            if let Some(name) = at_keyword {
                let first_stylesheet_rule = !self.any_rule_so_far;
                self.any_rule_so_far = true;
                if first_stylesheet_rule && name.eq_ignore_ascii_case("charset") {
                    let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
                    let _: Result<(), ParseError<()>> =
                        self.input.parse_until_after(delimiters, |_| Ok(()));
                } else {
                    return Some(parse_at_rule(
                        &start,
                        name.clone(),
                        self.input,
                        &mut *self.parser,
                    ));
                }
            } else {
                self.any_rule_so_far = true;
                let result = parse_qualified_rule(
                    &start,
                    self.input,
                    &mut *self.parser,
                    /* nested = */ false,
                );
                return Some(result.map_err(|e| (e, self.input.slice_from(start.position()))));
            }
        }
    }
}
/// Parse a single declaration, such as an `( /* ... */ )` parenthesis in an `@supports` prelude.
pub fn parse_one_declaration<'i, 't, P, E>(
    input: &mut Parser<'i, 't>,
    parser: &mut P,
) -> Result<<P as DeclarationParser<'i>>::Declaration, (ParseError<'i, E>, &'i str)>
where
    P: DeclarationParser<'i, Error = E>,
{
    let start = input.state();
    let start_position = input.position();
    input
        .parse_entirely(|input| {
            let name = input.expect_ident()?.clone();
            input.expect_colon()?;
            parser.parse_value(name, input, &start)
        })
        .map_err(|e| (e, input.slice_from(start_position)))
}
/// Parse a single rule, such as for CSSOM’s `CSSStyleSheet.insertRule`.
pub fn parse_one_rule<'i, 't, R, P, E>(
    input: &mut Parser<'i, 't>,
    parser: &mut P,
) -> Result<R, ParseError<'i, E>>
where
    P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
        + AtRuleParser<'i, AtRule = R, Error = E>,
{
    input.parse_entirely(|input| {
        input.skip_whitespace();
        let start = input.state();
        let at_keyword = if input.next_byte() == Some(b'@') {
            match *input.next_including_whitespace_and_comments()? {
                Token::AtKeyword(ref name) => Some(name.clone()),
                _ => {
                    input.reset(&start);
                    None
                }
            }
        } else {
            None
        };
        if let Some(name) = at_keyword {
            parse_at_rule(&start, name, input, parser).map_err(|e| e.0)
        } else {
            parse_qualified_rule(&start, input, parser, /* nested = */ false)
        }
    })
}
fn parse_at_rule<'i, 't, P, E>(
    start: &ParserState,
    name: CowRcStr<'i>,
    input: &mut Parser<'i, 't>,
    parser: &mut P,
) -> Result<<P as AtRuleParser<'i>>::AtRule, (ParseError<'i, E>, &'i str)>
where
    P: AtRuleParser<'i, Error = E>,
{
    let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
    let result = input.parse_until_before(delimiters, |input| parser.parse_prelude(name, input));
    match result {
        Ok(prelude) => {
            let result = match input.next() {
                Ok(&Token::Semicolon) | Err(_) => parser
                    .rule_without_block(prelude, start)
                    .map_err(|()| input.new_unexpected_token_error(Token::Semicolon)),
                Ok(&Token::CurlyBracketBlock) => {
                    parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
                }
                Ok(_) => unreachable!(),
            };
            result.map_err(|e| (e, input.slice_from(start.position())))
        }
        Err(error) => {
            let end_position = input.position();
            match input.next() {
                Ok(&Token::CurlyBracketBlock) | Ok(&Token::Semicolon) | Err(_) => {}
                _ => unreachable!(),
            };
            Err((error, input.slice(start.position()..end_position)))
        }
    }
}
//  If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose
//  value starts with "--" followed by a <colon-token>, then...
fn looks_like_a_custom_property(input: &mut Parser) -> bool {
    let ident = match input.expect_ident() {
        Ok(i) => i,
        Err(..) => return false,
    };
    ident.starts_with("--") && input.expect_colon().is_ok()
}
fn parse_qualified_rule<'i, 't, P, E>(
    start: &ParserState,
    input: &mut Parser<'i, 't>,
    parser: &mut P,
    nested: bool,
) -> Result<<P as QualifiedRuleParser<'i>>::QualifiedRule, ParseError<'i, E>>
where
    P: QualifiedRuleParser<'i, Error = E>,
{
    input.skip_whitespace();
    let prelude = {
        let state = input.state();
        if looks_like_a_custom_property(input) {
            // If nested is true, consume the remnants of a bad declaration from input, with
            // nested set to true, and return nothing.
            // If nested is false, consume a block from input, and return nothing.
            let delimiters = if nested {
                Delimiter::Semicolon
            } else {
                Delimiter::CurlyBracketBlock
            };
            let _: Result<(), ParseError<()>> = input.parse_until_after(delimiters, |_| Ok(()));
            return Err(state
                .source_location()
                .new_error(BasicParseErrorKind::QualifiedRuleInvalid));
        }
        let delimiters = if nested {
            Delimiter::Semicolon | Delimiter::CurlyBracketBlock
        } else {
            Delimiter::CurlyBracketBlock
        };
        input.reset(&state);
        input.parse_until_before(delimiters, |input| parser.parse_prelude(input))
    };
    input.expect_curly_bracket_block()?;
    // Do this here so that we consume the `{` even if the prelude is `Err`.
    let prelude = prelude?;
    parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
}