blob: af93d978e1329a32418854133647eb6b81ac27ee [file] [log] [blame]
use crate::declaration::{parse_declaration, DeclarationBlock, DeclarationList};
use crate::error::{Error, ParserError, PrinterError};
use crate::media_query::*;
use crate::printer::Printer;
use crate::properties::custom::TokenList;
use crate::rules::container::{ContainerCondition, ContainerName, ContainerRule};
use crate::rules::font_feature_values::FontFeatureValuesRule;
use crate::rules::font_palette_values::FontPaletteValuesRule;
use crate::rules::layer::{LayerBlockRule, LayerStatementRule};
use crate::rules::nesting::NestedDeclarationsRule;
use crate::rules::property::PropertyRule;
use crate::rules::scope::ScopeRule;
use crate::rules::starting_style::StartingStyleRule;
use crate::rules::view_transition::ViewTransitionRule;
use crate::rules::viewport::ViewportRule;
use crate::properties::font::FamilyName;
use crate::rules::{
counter_style::CounterStyleRule,
custom_media::CustomMediaRule,
document::MozDocumentRule,
font_face::{FontFaceDeclarationParser, FontFaceRule},
import::ImportRule,
keyframes::{KeyframeListParser, KeyframesName, KeyframesRule},
layer::LayerName,
media::MediaRule,
namespace::NamespaceRule,
nesting::NestingRule,
page::{PageRule, PageSelector},
style::StyleRule,
supports::{SupportsCondition, SupportsRule},
unknown::UnknownAtRule,
CssRule, CssRuleList, Location,
};
use crate::selector::{SelectorList, SelectorParser};
use crate::traits::{Parse, ParseWithOptions};
use crate::values::ident::{CustomIdent, DashedIdent};
use crate::values::string::CowArcStr;
use crate::vendor_prefix::VendorPrefix;
#[cfg(feature = "visitor")]
use crate::visitor::{Visit, VisitTypes, Visitor};
use bitflags::bitflags;
use cssparser::*;
use parcel_selectors::parser::{NestingRequirement, ParseErrorRecovery};
use std::sync::{Arc, RwLock};
bitflags! {
/// Parser feature flags to enable.
#[derive(Clone, Debug, Default)]
pub struct ParserFlags: u8 {
/// Whether the enable the [CSS nesting](https://www.w3.org/TR/css-nesting-1/) draft syntax.
const NESTING = 1 << 0;
/// Whether to enable the [custom media](https://drafts.csswg.org/mediaqueries-5/#custom-mq) draft syntax.
const CUSTOM_MEDIA = 1 << 1;
/// Whether to enable the non-standard >>> and /deep/ selector combinators used by Vue and Angular.
const DEEP_SELECTOR_COMBINATOR = 1 << 2;
}
}
/// CSS parsing options.
#[derive(Clone, Debug, Default)]
pub struct ParserOptions<'o, 'i> {
/// Filename to use in error messages.
pub filename: String,
/// Whether the enable [CSS modules](https://github.com/css-modules/css-modules).
pub css_modules: Option<crate::css_modules::Config<'o>>,
/// The source index to assign to all parsed rules. Impacts the source map when
/// the style sheet is serialized.
pub source_index: u32,
/// Whether to ignore invalid rules and declarations rather than erroring.
pub error_recovery: bool,
/// A list that will be appended to when a warning occurs.
pub warnings: Option<Arc<RwLock<Vec<Error<ParserError<'i>>>>>>,
/// Feature flags to enable.
pub flags: ParserFlags,
}
impl<'o, 'i> ParserOptions<'o, 'i> {
#[inline]
pub(crate) fn warn(&self, warning: ParseError<'i, ParserError<'i>>) {
if let Some(warnings) = &self.warnings {
if let Ok(mut warnings) = warnings.write() {
warnings.push(Error::from(warning, self.filename.clone()));
}
}
}
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct DefaultAtRuleParser;
impl<'i> crate::traits::AtRuleParser<'i> for DefaultAtRuleParser {
type AtRule = DefaultAtRule;
type Error = ();
type Prelude = ();
}
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct DefaultAtRule;
impl crate::traits::ToCss for DefaultAtRule {
fn to_css<W: std::fmt::Write>(&self, _: &mut Printer<W>) -> Result<(), PrinterError> {
Err(PrinterError {
kind: crate::error::PrinterErrorKind::FmtError,
loc: None,
})
}
}
#[cfg(feature = "into_owned")]
impl<'any> static_self::IntoOwned<'any> for DefaultAtRule {
type Owned = Self;
fn into_owned(self) -> Self {
self
}
}
#[cfg(feature = "visitor")]
#[cfg_attr(docsrs, doc(cfg(feature = "visitor")))]
impl<'i, V: Visitor<'i, DefaultAtRule>> Visit<'i, DefaultAtRule, V> for DefaultAtRule {
const CHILD_TYPES: VisitTypes = VisitTypes::empty();
fn visit_children(&mut self, _: &mut V) -> Result<(), V::Error> {
Ok(())
}
}
#[derive(PartialEq, PartialOrd)]
enum State {
Start = 1,
Layers = 2,
Imports = 3,
Namespaces = 4,
Body = 5,
}
/// The parser for the top-level rules in a stylesheet.
pub struct TopLevelRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> {
pub options: &'a ParserOptions<'o, 'i>,
state: State,
at_rule_parser: &'a mut T,
rules: &'a mut CssRuleList<'i, T::AtRule>,
}
impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> TopLevelRuleParser<'a, 'o, 'i, T> {
pub fn new(
options: &'a ParserOptions<'o, 'i>,
at_rule_parser: &'a mut T,
rules: &'a mut CssRuleList<'i, T::AtRule>,
) -> Self {
TopLevelRuleParser {
options,
state: State::Start,
at_rule_parser,
rules,
}
}
pub fn nested<'x: 'b>(&'x mut self) -> NestedRuleParser<'x, 'o, 'i, T> {
NestedRuleParser {
options: &self.options,
at_rule_parser: self.at_rule_parser,
declarations: DeclarationList::new(),
important_declarations: DeclarationList::new(),
rules: &mut self.rules,
is_in_style_rule: false,
allow_declarations: false,
}
}
}
/// A rule prelude for at-rule with block.
#[derive(Debug)]
#[allow(dead_code)]
pub enum AtRulePrelude<'i, T> {
/// A @font-face rule prelude.
FontFace,
/// A @font-feature-values rule prelude, with its FamilyName list.
FontFeatureValues(Vec<FamilyName<'i>>),
/// A @font-palette-values rule prelude, with its name.
FontPaletteValues(DashedIdent<'i>),
/// A @counter-style rule prelude, with its counter style name.
CounterStyle(CustomIdent<'i>),
/// A @media rule prelude, with its media queries.
Media(MediaList<'i>),
/// A @custom-media rule prelude.
CustomMedia(DashedIdent<'i>, MediaList<'i>),
/// An @supports rule, with its conditional
Supports(SupportsCondition<'i>),
/// A @viewport rule prelude.
Viewport(VendorPrefix),
/// A @keyframes rule, with its animation name and vendor prefix if exists.
Keyframes(KeyframesName<'i>, VendorPrefix),
/// A @page rule prelude.
Page(Vec<PageSelector<'i>>),
/// A @-moz-document rule.
MozDocument,
/// A @import rule prelude.
Import(
CowRcStr<'i>,
MediaList<'i>,
Option<SupportsCondition<'i>>,
Option<Option<LayerName<'i>>>,
),
/// A @namespace rule prelude.
Namespace(Option<CowRcStr<'i>>, CowRcStr<'i>),
/// A @charset rule prelude.
Charset,
/// A @nest prelude.
Nest(SelectorList<'i>),
/// An @layer prelude.
Layer(Vec<LayerName<'i>>),
/// An @property prelude.
Property(DashedIdent<'i>),
/// A @container prelude.
Container(Option<ContainerName<'i>>, ContainerCondition<'i>),
/// A @starting-style prelude.
StartingStyle,
/// A @scope rule prelude.
Scope(Option<SelectorList<'i>>, Option<SelectorList<'i>>),
/// A @view-transition rule prelude.
ViewTransition,
/// An unknown prelude.
Unknown(CowArcStr<'i>, TokenList<'i>),
/// A custom prelude.
Custom(T),
}
impl<'i, T> AtRulePrelude<'i, T> {
// https://drafts.csswg.org/css-nesting/#conditionals
// In addition to nested style rules, this specification allows nested group rules inside
// of style rules: any at-rule whose body contains style rules can be nested inside of a
// style rule as well.
fn allowed_in_style_rule(&self) -> bool {
match *self {
Self::Media(..)
| Self::Supports(..)
| Self::Container(..)
| Self::MozDocument
| Self::Layer(..)
| Self::StartingStyle
| Self::Scope(..)
| Self::Nest(..)
| Self::Unknown(..)
| Self::Custom(..) => true,
Self::Namespace(..)
| Self::FontFace
| Self::FontFeatureValues(..)
| Self::FontPaletteValues(..)
| Self::CounterStyle(..)
| Self::Keyframes(..)
| Self::Page(..)
| Self::Property(..)
| Self::Import(..)
| Self::CustomMedia(..)
| Self::Viewport(..)
| Self::Charset
| Self::ViewTransition => false,
}
}
}
impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for TopLevelRuleParser<'a, 'o, 'i, T> {
type Prelude = AtRulePrelude<'i, T::Prelude>;
type AtRule = ();
type Error = ParserError<'i>;
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
match_ignore_ascii_case! { &*name,
"import" => {
if self.state > State::Imports {
return Err(input.new_custom_error(ParserError::UnexpectedImportRule))
}
let url_string = input.expect_url_or_string()?.clone();
let layer = if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() {
Some(None)
} else if input.try_parse(|input| input.expect_function_matching("layer")).is_ok() {
let name = input.parse_nested_block(LayerName::parse).map(|name| Some(name))?;
Some(name)
} else {
None
};
let supports = if input.try_parse(|input| input.expect_function_matching("supports")).is_ok() {
Some(input.parse_nested_block(|input| {
input.try_parse(SupportsCondition::parse).or_else(|_| SupportsCondition::parse_declaration(input))
})?)
} else {
None
};
let media = MediaList::parse(input, &self.options)?;
return Ok(AtRulePrelude::Import(url_string, media, supports, layer));
},
"namespace" => {
if self.state > State::Namespaces {
return Err(input.new_custom_error(ParserError::UnexpectedNamespaceRule))
}
let prefix = input.try_parse(|input| input.expect_ident_cloned()).ok();
let namespace = input.expect_url_or_string()?;
let prelude = AtRulePrelude::Namespace(prefix, namespace);
return Ok(prelude);
},
"charset" => {
// @charset is removed by rust-cssparser if it’s the first rule in the stylesheet.
// Anything left is technically invalid, however, users often concatenate CSS files
// together, so we are more lenient and simply ignore @charset rules in the middle of a file.
input.expect_string()?;
return Ok(AtRulePrelude::Charset)
},
"custom-media" if self.options.flags.contains(ParserFlags::CUSTOM_MEDIA) => {
let name = DashedIdent::parse(input)?;
let media = MediaList::parse(input, &self.options)?;
return Ok(AtRulePrelude::CustomMedia(name, media))
},
"property" => {
let name = DashedIdent::parse(input)?;
return Ok(AtRulePrelude::Property(name))
},
_ => {}
}
AtRuleParser::parse_prelude(&mut self.nested(), name, input)
}
#[inline]
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
self.state = State::Body;
AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)
}
#[inline]
fn rule_without_block(
&mut self,
prelude: AtRulePrelude<'i, T::Prelude>,
start: &ParserState,
) -> Result<Self::AtRule, ()> {
let loc = start.source_location();
let loc = Location {
source_index: self.options.source_index,
line: loc.line,
column: loc.column,
};
match prelude {
AtRulePrelude::Import(url, media, supports, layer) => {
self.state = State::Imports;
self.rules.0.push(CssRule::Import(ImportRule {
url: url.into(),
layer,
supports,
media,
loc,
}));
Ok(())
}
AtRulePrelude::Namespace(prefix, url) => {
self.state = State::Namespaces;
self.rules.0.push(CssRule::Namespace(NamespaceRule {
prefix: prefix.map(|x| x.into()),
url: url.into(),
loc,
}));
Ok(())
}
AtRulePrelude::CustomMedia(name, query) => {
self.state = State::Body;
self.rules.0.push(CssRule::CustomMedia(CustomMediaRule { name, query, loc }));
Ok(())
}
AtRulePrelude::Layer(_) => {
// @layer statements are allowed before @import rules, but cannot be interleaved.
if self.state <= State::Layers {
self.state = State::Layers;
} else {
self.state = State::Body;
}
AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)
}
AtRulePrelude::Charset => Ok(()),
AtRulePrelude::Unknown(name, prelude) => {
self.rules.0.push(CssRule::Unknown(UnknownAtRule {
name,
prelude,
block: None,
loc,
}));
Ok(())
}
AtRulePrelude::Custom(_) => {
self.state = State::Body;
AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)
}
_ => Err(()),
}
}
}
impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
for TopLevelRuleParser<'a, 'o, 'i, T>
{
type Prelude = SelectorList<'i>;
type QualifiedRule = ();
type Error = ParserError<'i>;
#[inline]
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
self.state = State::Body;
QualifiedRuleParser::parse_prelude(&mut self.nested(), input)
}
#[inline]
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {
QualifiedRuleParser::parse_block(&mut self.nested(), prelude, start, input)
}
}
pub struct NestedRuleParser<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> {
pub options: &'a ParserOptions<'o, 'i>,
pub at_rule_parser: &'a mut T,
declarations: DeclarationList<'i>,
important_declarations: DeclarationList<'i>,
rules: &'a mut CssRuleList<'i, T::AtRule>,
is_in_style_rule: bool,
allow_declarations: bool,
}
impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> NestedRuleParser<'a, 'o, 'i, T> {
pub fn parse_nested<'t>(
&mut self,
input: &mut Parser<'i, 't>,
is_style_rule: bool,
) -> Result<(DeclarationBlock<'i>, CssRuleList<'i, T::AtRule>), ParseError<'i, ParserError<'i>>> {
let mut rules = CssRuleList(vec![]);
let mut nested_parser = NestedRuleParser {
options: self.options,
at_rule_parser: self.at_rule_parser,
declarations: DeclarationList::new(),
important_declarations: DeclarationList::new(),
rules: &mut rules,
is_in_style_rule: self.is_in_style_rule || is_style_rule,
allow_declarations: self.allow_declarations || self.is_in_style_rule || is_style_rule,
};
let parse_declarations = nested_parser.parse_declarations();
let mut errors = Vec::new();
let mut iter = RuleBodyParser::new(input, &mut nested_parser);
while let Some(result) = iter.next() {
match result {
Ok(()) => {}
Err((e, _)) => {
if parse_declarations {
iter.parser.declarations.clear();
iter.parser.important_declarations.clear();
errors.push(e);
} else {
if iter.parser.options.error_recovery {
iter.parser.options.warn(e);
continue;
}
return Err(e);
}
}
}
}
if parse_declarations {
if !errors.is_empty() {
if self.options.error_recovery {
for err in errors {
self.options.warn(err);
}
} else {
return Err(errors.remove(0));
}
}
}
Ok((
DeclarationBlock {
declarations: nested_parser.declarations,
important_declarations: nested_parser.important_declarations,
},
rules,
))
}
fn parse_style_block<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
let loc = input.current_source_location();
let loc = Location {
source_index: self.options.source_index,
line: loc.line,
column: loc.column,
};
// Declarations can be immediately within @media and @supports blocks that are nested within a parent style rule.
// These are wrapped in an (invisible) NestedDeclarationsRule.
let (declarations, mut rules) = self.parse_nested(input, false)?;
if declarations.len() > 0 {
rules.0.insert(
0,
CssRule::NestedDeclarations(NestedDeclarationsRule { declarations, loc }),
)
}
Ok(rules)
}
fn loc(&self, start: &ParserState) -> Location {
let loc = start.source_location();
Location {
source_index: self.options.source_index,
line: loc.line,
column: loc.column,
}
}
}
impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> AtRuleParser<'i> for NestedRuleParser<'a, 'o, 'i, T> {
type Prelude = AtRulePrelude<'i, T::Prelude>;
type AtRule = ();
type Error = ParserError<'i>;
fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
let result = match_ignore_ascii_case! { &*name,
"media" => {
let media = MediaList::parse(input, &self.options)?;
AtRulePrelude::Media(media)
},
"supports" => {
let cond = SupportsCondition::parse(input, )?;
AtRulePrelude::Supports(cond)
},
"font-face" => {
AtRulePrelude::FontFace
},
// "font-feature-values" => {
// if !cfg!(feature = "gecko") {
// // Support for this rule is not fully implemented in Servo yet.
// return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
// }
// let family_names = parse_family_name_list(self.context, input)?;
// Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::FontFeatureValues(family_names)))
// },
"font-feature-values" => {
let names = match Vec::<FamilyName>::parse(input) {
Ok(names) => names,
Err(e) => return Err(e)
};
AtRulePrelude::FontFeatureValues(names)
},
"font-palette-values" => {
let name = DashedIdent::parse(input)?;
AtRulePrelude::FontPaletteValues(name)
},
"counter-style" => {
let name = CustomIdent::parse(input)?;
AtRulePrelude::CounterStyle(name)
},
"viewport" | "-ms-viewport" => {
let prefix = if starts_with_ignore_ascii_case(&*name, "-ms") {
VendorPrefix::Ms
} else {
VendorPrefix::None
};
AtRulePrelude::Viewport(prefix)
},
"keyframes" | "-webkit-keyframes" | "-moz-keyframes" | "-o-keyframes" | "-ms-keyframes" => {
let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") {
VendorPrefix::WebKit
} else if starts_with_ignore_ascii_case(&*name, "-moz-") {
VendorPrefix::Moz
} else if starts_with_ignore_ascii_case(&*name, "-o-") {
VendorPrefix::O
} else if starts_with_ignore_ascii_case(&*name, "-ms-") {
VendorPrefix::Ms
} else {
VendorPrefix::None
};
let name = input.try_parse(KeyframesName::parse)?;
AtRulePrelude::Keyframes(name, prefix)
},
"page" => {
let selectors = input.try_parse(|input| input.parse_comma_separated(PageSelector::parse)).unwrap_or_default();
AtRulePrelude::Page(selectors)
},
"-moz-document" => {
// Firefox only supports the url-prefix() function with no arguments as a legacy CSS hack.
// See https://css-tricks.com/snippets/css/css-hacks-targeting-firefox/
input.expect_function_matching("url-prefix")?;
input.parse_nested_block(|input| {
// Firefox also allows an empty string as an argument...
// https://github.com/mozilla/gecko-dev/blob/0077f2248712a1b45bf02f0f866449f663538164/servo/components/style/stylesheets/document_rule.rs#L303
let _ = input.try_parse(|input| -> Result<(), ParseError<'i, Self::Error>> {
let s = input.expect_string()?;
if !s.is_empty() {
return Err(input.new_custom_error(ParserError::InvalidValue))
}
Ok(())
});
input.expect_exhausted()?;
Ok(())
})?;
AtRulePrelude::MozDocument
},
"layer" => {
let names = match Vec::<LayerName>::parse(input) {
Ok(names) => names,
Err(ParseError { kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), .. }) => Vec::new(),
Err(e) => return Err(e)
};
AtRulePrelude::Layer(names)
},
"container" => {
let name = input.try_parse(ContainerName::parse).ok();
let condition = ContainerCondition::parse_with_options(input, &self.options)?;
AtRulePrelude::Container(name, condition)
},
"starting-style" => {
AtRulePrelude::StartingStyle
},
"scope" => {
let selector_parser = SelectorParser {
is_nesting_allowed: true,
options: &self.options,
};
let scope_start = if input.try_parse(|input| input.expect_parenthesis_block()).is_ok() {
Some(input.parse_nested_block(|input| {
// https://drafts.csswg.org/css-cascade-6/#scoped-rules
// TODO: disallow pseudo elements?
SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None)
})?)
} else {
None
};
let scope_end = if input.try_parse(|input| input.expect_ident_matching("to")).is_ok() {
input.expect_parenthesis_block()?;
Some(input.parse_nested_block(|input| {
SelectorList::parse_relative(&selector_parser, input, ParseErrorRecovery::IgnoreInvalidSelector, NestingRequirement::None)
})?)
} else {
None
};
AtRulePrelude::Scope(scope_start, scope_end)
},
"view-transition" => {
AtRulePrelude::ViewTransition
},
"nest" if self.is_in_style_rule => {
self.options.warn(input.new_custom_error(ParserError::DeprecatedNestRule));
let selector_parser = SelectorParser {
is_nesting_allowed: true,
options: &self.options,
};
let selectors = SelectorList::parse(&selector_parser, input, ParseErrorRecovery::DiscardList, NestingRequirement::Contained)?;
AtRulePrelude::Nest(selectors)
},
"value" if self.options.css_modules.is_some() => {
return Err(input.new_custom_error(ParserError::DeprecatedCssModulesValueRule));
},
_ => parse_custom_at_rule_prelude(&name, input, self.options, self.at_rule_parser)?
};
if self.is_in_style_rule && !result.allowed_in_style_rule() {
return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())));
}
Ok(result)
}
#[inline]
fn rule_without_block(
&mut self,
prelude: AtRulePrelude<'i, T::Prelude>,
start: &ParserState,
) -> Result<Self::AtRule, ()> {
let loc = self.loc(start);
match prelude {
AtRulePrelude::Layer(names) => {
if self.is_in_style_rule || names.is_empty() {
return Err(());
}
self.rules.0.push(CssRule::LayerStatement(LayerStatementRule { names, loc }));
Ok(())
}
AtRulePrelude::Unknown(name, prelude) => {
self.rules.0.push(CssRule::Unknown(UnknownAtRule {
name,
prelude,
block: None,
loc,
}));
Ok(())
}
AtRulePrelude::Custom(prelude) => {
self.rules.0.push(parse_custom_at_rule_without_block(
prelude,
start,
self.options,
self.at_rule_parser,
self.is_in_style_rule,
)?);
Ok(())
}
_ => Err(()),
}
}
fn parse_block<'t>(
&mut self,
prelude: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i, Self::Error>> {
let loc = self.loc(start);
match prelude {
AtRulePrelude::FontFace => {
let mut decl_parser = FontFaceDeclarationParser;
let mut parser = RuleBodyParser::new(input, &mut decl_parser);
let mut properties = vec![];
while let Some(decl) = parser.next() {
if let Ok(decl) = decl {
properties.push(decl);
}
}
self.rules.0.push(CssRule::FontFace(FontFaceRule { properties, loc }));
Ok(())
}
// AtRuleBlockPrelude::FontFeatureValues(family_names) => {
// let context = ParserContext::new_with_rule_type(
// self.context,
// CssRuleType::FontFeatureValues,
// self.namespaces,
// );
// Ok(CssRule::FontFeatureValues(Arc::new(self.shared_lock.wrap(
// FontFeatureValuesRule::parse(
// &context,
// input,
// family_names,
// start.source_location(),
// ),
// ))))
// },
AtRulePrelude::FontPaletteValues(name) => {
let rule = FontPaletteValuesRule::parse(name, input, loc)?;
self.rules.0.push(CssRule::FontPaletteValues(rule));
Ok(())
}
AtRulePrelude::CounterStyle(name) => {
self.rules.0.push(CssRule::CounterStyle(CounterStyleRule {
name,
declarations: DeclarationBlock::parse(input, self.options)?,
loc,
}));
Ok(())
}
AtRulePrelude::Media(query) => {
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::Media(MediaRule { query, rules, loc }));
Ok(())
}
AtRulePrelude::Supports(condition) => {
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::Supports(SupportsRule { condition, rules, loc }));
Ok(())
}
AtRulePrelude::Container(name, condition) => {
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::Container(ContainerRule {
name,
condition,
rules,
loc,
}));
Ok(())
}
AtRulePrelude::Scope(scope_start, scope_end) => {
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::Scope(ScopeRule {
scope_start,
scope_end,
rules,
loc,
}));
Ok(())
}
AtRulePrelude::Viewport(vendor_prefix) => {
self.rules.0.push(CssRule::Viewport(ViewportRule {
vendor_prefix,
// TODO: parse viewport descriptors rather than properties
// https://drafts.csswg.org/css-device-adapt/#viewport-desc
declarations: DeclarationBlock::parse(input, self.options)?,
loc,
}));
Ok(())
}
AtRulePrelude::Keyframes(name, vendor_prefix) => {
let mut parser = KeyframeListParser;
let iter = RuleBodyParser::new(input, &mut parser);
self.rules.0.push(CssRule::Keyframes(KeyframesRule {
name,
keyframes: iter.filter_map(Result::ok).collect(),
vendor_prefix,
loc,
}));
Ok(())
}
AtRulePrelude::Page(selectors) => {
let rule = PageRule::parse(selectors, input, loc, self.options)?;
self.rules.0.push(CssRule::Page(rule));
Ok(())
}
AtRulePrelude::MozDocument => {
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::MozDocument(MozDocumentRule { rules, loc }));
Ok(())
}
AtRulePrelude::Layer(names) => {
let name = if names.is_empty() {
None
} else if names.len() == 1 {
names.into_iter().next()
} else {
return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
};
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::LayerBlock(LayerBlockRule { name, rules, loc }));
Ok(())
}
AtRulePrelude::Property(name) => {
self.rules.0.push(CssRule::Property(PropertyRule::parse(name, input, loc)?));
Ok(())
}
AtRulePrelude::Import(..)
| AtRulePrelude::Namespace(..)
| AtRulePrelude::CustomMedia(..)
| AtRulePrelude::Charset => {
// These rules don't have blocks.
Err(input.new_unexpected_token_error(Token::CurlyBracketBlock))
}
AtRulePrelude::StartingStyle => {
let rules = self.parse_style_block(input)?;
self.rules.0.push(CssRule::StartingStyle(StartingStyleRule { rules, loc }));
Ok(())
}
AtRulePrelude::ViewTransition => {
self
.rules
.0
.push(CssRule::ViewTransition(ViewTransitionRule::parse(input, loc)?));
Ok(())
}
AtRulePrelude::Nest(selectors) => {
let (declarations, rules) = self.parse_nested(input, true)?;
self.rules.0.push(CssRule::Nesting(NestingRule {
style: StyleRule {
selectors,
declarations,
vendor_prefix: VendorPrefix::empty(),
rules,
loc,
},
loc,
}));
Ok(())
}
AtRulePrelude::FontFeatureValues(family_names) => {
let rule = FontFeatureValuesRule::parse(family_names, input, loc, self.options)?;
self.rules.0.push(CssRule::FontFeatureValues(rule));
Ok(())
}
AtRulePrelude::Unknown(name, prelude) => {
self.rules.0.push(CssRule::Unknown(UnknownAtRule {
name,
prelude,
block: Some(TokenList::parse(input, &self.options, 0)?),
loc,
}));
Ok(())
}
AtRulePrelude::Custom(prelude) => {
self.rules.0.push(parse_custom_at_rule_body(
prelude,
input,
start,
self.options,
self.at_rule_parser,
self.is_in_style_rule,
)?);
Ok(())
}
}
}
}
impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> QualifiedRuleParser<'i>
for NestedRuleParser<'a, 'o, 'i, T>
{
type Prelude = SelectorList<'i>;
type QualifiedRule = ();
type Error = ParserError<'i>;
fn parse_prelude<'t>(
&mut self,
input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
let selector_parser = SelectorParser {
is_nesting_allowed: true,
options: &self.options,
};
if self.is_in_style_rule {
SelectorList::parse_relative(
&selector_parser,
input,
ParseErrorRecovery::DiscardList,
NestingRequirement::Implicit,
)
} else {
SelectorList::parse(
&selector_parser,
input,
ParseErrorRecovery::DiscardList,
NestingRequirement::None,
)
}
}
fn parse_block<'t>(
&mut self,
selectors: Self::Prelude,
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i, Self::Error>> {
let loc = self.loc(start);
let (declarations, rules) = self.parse_nested(input, true)?;
self.rules.0.push(CssRule::Style(StyleRule {
selectors,
vendor_prefix: VendorPrefix::empty(),
declarations,
rules,
loc,
}));
Ok(())
}
}
/// Parse a declaration within {} block: `color: blue`
impl<'a, 'o, 'i, T: crate::traits::AtRuleParser<'i>> cssparser::DeclarationParser<'i>
for NestedRuleParser<'a, 'o, 'i, T>
{
type Declaration = ();
type Error = ParserError<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut cssparser::Parser<'i, 't>,
) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
if self.rules.0.is_empty() {
parse_declaration(
name,
input,
&mut self.declarations,
&mut self.important_declarations,
&self.options,
)
} else if let Some(CssRule::NestedDeclarations(last)) = self.rules.0.last_mut() {
parse_declaration(
name,
input,
&mut last.declarations.declarations,
&mut last.declarations.important_declarations,
&self.options,
)
} else {
let loc = self.loc(&input.state());
let mut nested = NestedDeclarationsRule {
declarations: DeclarationBlock::new(),
loc,
};
parse_declaration(
name,
input,
&mut nested.declarations.declarations,
&mut nested.declarations.important_declarations,
&self.options,
)?;
self.rules.0.push(CssRule::NestedDeclarations(nested));
Ok(())
}
}
}
impl<'a, 'o, 'b, 'i, T: crate::traits::AtRuleParser<'i>> RuleBodyItemParser<'i, (), ParserError<'i>>
for NestedRuleParser<'a, 'o, 'i, T>
{
fn parse_qualified(&self) -> bool {
true
}
fn parse_declarations(&self) -> bool {
self.allow_declarations
}
}
fn parse_custom_at_rule_prelude<'i, 't, T: crate::traits::AtRuleParser<'i>>(
name: &CowRcStr<'i>,
input: &mut Parser<'i, 't>,
options: &ParserOptions<'_, 'i>,
at_rule_parser: &mut T,
) -> Result<AtRulePrelude<'i, T::Prelude>, ParseError<'i, ParserError<'i>>> {
match at_rule_parser.parse_prelude(name.clone(), input, options) {
Ok(prelude) => return Ok(AtRulePrelude::Custom(prelude)),
Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(..)),
..
}) => {}
Err(err) => {
return Err(match &err.kind {
ParseErrorKind::Basic(kind) => ParseError {
kind: ParseErrorKind::Basic(kind.clone()),
location: err.location,
},
_ => input.new_custom_error(ParserError::AtRulePreludeInvalid),
})
}
}
options.warn(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone())));
input.skip_whitespace();
let tokens = TokenList::parse(input, &options, 0)?;
Ok(AtRulePrelude::Unknown(name.into(), tokens))
}
fn parse_custom_at_rule_body<'i, 't, T: crate::traits::AtRuleParser<'i>>(
prelude: T::Prelude,
input: &mut Parser<'i, 't>,
start: &ParserState,
options: &ParserOptions<'_, 'i>,
at_rule_parser: &mut T,
is_nested: bool,
) -> Result<CssRule<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
at_rule_parser
.parse_block(prelude, start, input, options, is_nested)
.map(|prelude| CssRule::Custom(prelude))
.map_err(|err| match &err.kind {
ParseErrorKind::Basic(kind) => ParseError {
kind: ParseErrorKind::Basic(kind.clone()),
location: err.location,
},
_ => input.new_error(BasicParseErrorKind::AtRuleBodyInvalid),
})
}
fn parse_custom_at_rule_without_block<'i, 't, T: crate::traits::AtRuleParser<'i>>(
prelude: T::Prelude,
start: &ParserState,
options: &ParserOptions<'_, 'i>,
at_rule_parser: &mut T,
is_nested: bool,
) -> Result<CssRule<'i, T::AtRule>, ()> {
at_rule_parser
.rule_without_block(prelude, start, options, is_nested)
.map(|prelude| CssRule::Custom(prelude))
}
pub fn parse_rule_list<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(
input: &mut Parser<'i, 't>,
options: &'a ParserOptions<'o, 'i>,
at_rule_parser: &mut T,
) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
let mut parser = NestedRuleParser {
options,
at_rule_parser,
declarations: DeclarationList::new(),
important_declarations: DeclarationList::new(),
rules: &mut CssRuleList(Vec::new()),
is_in_style_rule: false,
allow_declarations: false,
};
let (_, rules) = parser.parse_nested(input, false)?;
Ok(rules)
}
pub fn parse_style_block<'a, 'o, 'i, 't, T: crate::traits::AtRuleParser<'i>>(
input: &mut Parser<'i, 't>,
options: &'a ParserOptions<'o, 'i>,
at_rule_parser: &mut T,
is_nested: bool,
) -> Result<CssRuleList<'i, T::AtRule>, ParseError<'i, ParserError<'i>>> {
let mut parser = NestedRuleParser {
options,
at_rule_parser,
declarations: DeclarationList::new(),
important_declarations: DeclarationList::new(),
rules: &mut CssRuleList(Vec::new()),
is_in_style_rule: is_nested,
allow_declarations: true,
};
parser.parse_style_block(input)
}
#[inline]
pub fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool {
string.len() >= prefix.len() && string.as_bytes()[0..prefix.len()].eq_ignore_ascii_case(prefix.as_bytes())
}