use cssparser::{
AtRuleParser, CowRcStr, DeclarationParser, ParseError as CssParseError, Parser, ParserInput,
ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, StyleSheetParser, Token,
match_ignore_ascii_case,
};
use crate::ast::{
Combinator, Declaration, PseudoClass, Rule, Selector, SimpleSelector, Stylesheet,
};
use crate::error::{ParseError, ParseErrorOwned};
use crate::value::{Color, Length, SideValue, Value};
pub fn parse(input: &str) -> Result<Stylesheet, ParseError> {
let mut parser_input = ParserInput::new(input);
let mut parser = Parser::new(&mut parser_input);
let mut rule_parser = StylesheetRuleParser;
let mut rules = Vec::new();
let iter = StyleSheetParser::new(&mut parser, &mut rule_parser);
for item in iter {
match item {
Ok(Some(rule)) => rules.push(rule),
Ok(None) => {}
Err(_) => {}
}
}
Ok(Stylesheet { rules })
}
struct StylesheetRuleParser;
impl<'i> QualifiedRuleParser<'i> for StylesheetRuleParser {
type Prelude = Vec<Selector>;
type QualifiedRule = Option<Rule>;
type Error = ParseErrorOwned;
fn parse_prelude<'t>(
&mut self,
parser: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, CssParseError<'i, Self::Error>> {
parse_selector_list(parser)
}
fn parse_block<'t>(
&mut self,
selectors: Self::Prelude,
_start: &ParserState,
parser: &mut Parser<'i, 't>,
) -> Result<Self::QualifiedRule, CssParseError<'i, Self::Error>> {
let mut declarations = Vec::new();
let mut decl_parser = DeclParser;
let body = RuleBodyParser::new(parser, &mut decl_parser);
for item in body {
match item {
Ok(decl) => declarations.push(decl),
Err(_) => continue,
}
}
Ok(Some(Rule {
selectors,
declarations,
}))
}
}
impl<'i> AtRuleParser<'i> for StylesheetRuleParser {
type Prelude = ();
type AtRule = Option<Rule>;
type Error = ParseErrorOwned;
fn parse_prelude<'t>(
&mut self,
_name: CowRcStr<'i>,
parser: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, CssParseError<'i, Self::Error>> {
while parser.next().is_ok() {}
Ok(())
}
fn rule_without_block(
&mut self,
_prelude: Self::Prelude,
_start: &ParserState,
) -> Result<Self::AtRule, ()> {
Ok(None)
}
fn parse_block<'t>(
&mut self,
_prelude: Self::Prelude,
_start: &ParserState,
parser: &mut Parser<'i, 't>,
) -> Result<Self::AtRule, CssParseError<'i, Self::Error>> {
while parser.next().is_ok() {}
Ok(None)
}
}
fn parse_selector_list<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<Vec<Selector>, CssParseError<'i, ParseErrorOwned>> {
let mut selectors = Vec::new();
loop {
selectors.push(parse_compound_selector(parser)?);
if parser.expect_comma().is_err() {
break;
}
}
Ok(selectors)
}
fn parse_simple_selector<'i, 't>(
parser: &mut Parser<'i, 't>,
allow_type: bool,
) -> Result<SimpleSelector, CssParseError<'i, ParseErrorOwned>> {
let mut sel = SimpleSelector::default();
let mut saw_anything = false;
loop {
let save = parser.state();
let next = parser.next_including_whitespace().cloned();
match next {
Ok(Token::Ident(name)) if allow_type && !saw_anything => {
sel.element = Some(name.to_string());
saw_anything = true;
}
Ok(Token::Delim('.')) => {
let name = parser.expect_ident()?.to_string();
sel.classes.push(name);
saw_anything = true;
}
Ok(Token::Colon) => {
let ident = parser.expect_ident_cloned()?;
let pseudo = match PseudoClass::from_ident(&ident) {
Some(p) => p,
None => {
return Err(parser.new_custom_error(ParseErrorOwned(format!(
"unknown pseudo-class :{ident}"
))));
}
};
if sel.pseudo.is_some() {
return Err(parser.new_custom_error(ParseErrorOwned(
"multiple pseudo-classes per selector are not supported".to_string(),
)));
}
sel.pseudo = Some(pseudo);
saw_anything = true;
}
_ => {
parser.reset(&save);
break;
}
}
}
if !saw_anything {
return Err(parser.new_custom_error(ParseErrorOwned("empty selector".to_string())));
}
Ok(sel)
}
fn parse_compound_selector<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<Selector, CssParseError<'i, ParseErrorOwned>> {
parser.skip_whitespace();
let first = parse_simple_selector(parser, true)?;
let mut parts = vec![first];
let mut combinators = vec![];
loop {
let save = parser.state();
let tok = parser.next_including_whitespace().cloned();
match tok {
Ok(Token::Delim('>')) => {
parser.skip_whitespace();
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::Child);
}
Ok(Token::Delim('+')) => {
parser.skip_whitespace();
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::AdjacentSibling);
}
Ok(Token::Delim('~')) => {
parser.skip_whitespace();
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::GeneralSibling);
}
Ok(Token::WhiteSpace(_)) => {
parser.skip_whitespace();
let after_ws = parser.state();
let next_tok = parser.next_including_whitespace().cloned();
match next_tok {
Ok(Token::Delim('>')) => {
parser.skip_whitespace();
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::Child);
}
Ok(Token::Delim('+')) => {
parser.skip_whitespace();
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::AdjacentSibling);
}
Ok(Token::Delim('~')) => {
parser.skip_whitespace();
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::GeneralSibling);
}
Ok(Token::Ident(_)) | Ok(Token::Delim('.')) | Ok(Token::Colon) => {
parser.reset(&after_ws);
let next_simple = parse_simple_selector(parser, true)?;
parts.push(next_simple);
combinators.push(Combinator::Descendant);
}
_ => {
parser.reset(&save);
break;
}
}
}
_ => {
parser.reset(&save);
break;
}
}
}
Ok(Selector { parts, combinators })
}
struct DeclParser;
impl<'i> DeclarationParser<'i> for DeclParser {
type Declaration = Declaration;
type Error = ParseErrorOwned;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
parser: &mut Parser<'i, 't>,
_start: &ParserState,
) -> Result<Self::Declaration, CssParseError<'i, Self::Error>> {
let prop = name.to_string();
let (value, important) = parse_value(&prop, parser)?;
Ok(Declaration {
property: prop,
value,
important,
})
}
}
impl<'i> AtRuleParser<'i> for DeclParser {
type Prelude = ();
type AtRule = Declaration;
type Error = ParseErrorOwned;
}
impl<'i> QualifiedRuleParser<'i> for DeclParser {
type Prelude = ();
type QualifiedRule = Declaration;
type Error = ParseErrorOwned;
}
impl<'i> RuleBodyItemParser<'i, Declaration, ParseErrorOwned> for DeclParser {
fn parse_declarations(&self) -> bool {
true
}
fn parse_qualified(&self) -> bool {
false
}
}
fn parse_value<'i, 't>(
prop: &str,
parser: &mut Parser<'i, 't>,
) -> Result<(Value, bool), CssParseError<'i, ParseErrorOwned>> {
let value = parse_value_inner(prop, parser)?;
let important = consume_important(parser);
parser.expect_exhausted()?;
Ok((value, important))
}
fn parse_value_inner<'i, 't>(
prop: &str,
parser: &mut Parser<'i, 't>,
) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
match property_kind(prop) {
PropertyKind::Color => parse_color(parser).map(Value::Color),
PropertyKind::Length => parse_length(parser).map(Value::Length),
PropertyKind::LengthOrAuto => {
if parser.try_parse(expect_auto).is_ok() {
Ok(Value::Auto)
} else {
parse_length(parser).map(Value::Length)
}
}
PropertyKind::SideLengths => {
let mut lengths = Vec::new();
while let Ok(len) = parser.try_parse(parse_length) {
lengths.push(len);
if lengths.len() == 4 {
break;
}
}
if lengths.is_empty() {
return Err(parser
.new_custom_error(ParseErrorOwned(format!("expected length for `{prop}`"))));
}
Ok(Value::LengthSet(lengths))
}
PropertyKind::SideLengthsOrAuto => parse_side_lengths_or_auto(prop, parser),
PropertyKind::Keyword(allowed) => {
let ident = parser.try_parse(|p| p.expect_ident_cloned()).map_err(|_| {
parser.new_custom_error(ParseErrorOwned(format!("expected keyword for `{prop}`")))
})?;
let kw = ident.to_ascii_lowercase();
if allowed.contains(&kw.as_str()) {
Ok(Value::Keyword(kw))
} else {
Err(parser.new_custom_error(ParseErrorOwned(format!(
"unknown keyword `{kw}` for `{prop}`"
))))
}
}
PropertyKind::Number => {
let n = parser.try_parse(|p| p.expect_number()).map_err(|_| {
parser.new_custom_error(ParseErrorOwned(format!("expected number for `{prop}`")))
})?;
if n < 0.0 {
return Err(parser.new_custom_error(ParseErrorOwned(format!(
"negative number not allowed for `{prop}`"
))));
}
Ok(Value::Number(f64::from(n)))
}
PropertyKind::NumberOrLength => {
if let Ok(n) = parser.try_parse(|p| {
let loc = p.current_source_location();
let tok = p.next()?.clone();
match tok {
Token::Number { value, .. } => Ok(value),
other => Err(loc.new_custom_error::<ParseErrorOwned, ParseErrorOwned>(
ParseErrorOwned(format!("not a plain number: {other:?}")),
)),
}
}) {
Ok(Value::Number(f64::from(n)))
} else {
parse_length(parser).map(Value::Length)
}
}
PropertyKind::FontWeight => {
if let Ok(n) = parser.try_parse(|p| p.expect_number()) {
let n = f64::from(n);
if n.fract() != 0.0 || !(1.0..=1000.0).contains(&n) {
return Err(parser.new_custom_error(ParseErrorOwned(format!(
"font-weight numeric value `{n}` is out of range (must be integer 1–1000)"
))));
}
Ok(Value::Number(n))
} else {
let ident = parser.try_parse(|p| p.expect_ident_cloned()).map_err(|_| {
parser.new_custom_error(ParseErrorOwned(
"expected number or keyword for `font-weight`".to_string(),
))
})?;
let kw = ident.to_ascii_lowercase();
if ["normal", "bold"].contains(&kw.as_str()) {
Ok(Value::Keyword(kw))
} else {
Err(parser.new_custom_error(ParseErrorOwned(format!(
"unknown keyword `{kw}` for `font-weight`"
))))
}
}
}
PropertyKind::FontFamily => parse_font_family(parser),
PropertyKind::Border => parse_border_shorthand(prop, parser),
PropertyKind::Unknown => {
if let Ok(c) = parser.try_parse(parse_color) {
return Ok(Value::Color(c));
}
if let Ok(len) = parser.try_parse(parse_length) {
return Ok(Value::Length(len));
}
if let Ok(ident) = parser.try_parse(|p| p.expect_ident_cloned()) {
return Ok(Value::Keyword(ident.to_ascii_lowercase()));
}
Err(parser.new_custom_error(ParseErrorOwned(format!(
"could not parse value for `{prop}`"
))))
}
}
}
fn parse_side_lengths_or_auto<'i, 't>(
prop: &str,
parser: &mut Parser<'i, 't>,
) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
let mut sides: Vec<SideValue> = Vec::new();
loop {
if sides.len() == 4 {
break;
}
if let Ok(()) = parser.try_parse(expect_auto) {
sides.push(SideValue::Auto);
} else if let Ok(len) = parser.try_parse(parse_length) {
sides.push(SideValue::Length(len));
} else {
break;
}
}
if sides.is_empty() {
return Err(parser.new_custom_error(ParseErrorOwned(format!(
"expected length or auto for `{prop}`"
))));
}
if sides == [SideValue::Auto] {
return Ok(Value::Auto);
}
let all_lengths: Option<Vec<Length>> = sides
.iter()
.map(|sv| match sv {
SideValue::Length(l) => Some(*l),
SideValue::Auto => None,
})
.collect();
if let Some(lengths) = all_lengths {
return Ok(Value::LengthSet(lengths));
}
Ok(Value::SideSet(sides))
}
fn parse_font_family<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
let mut families: Vec<String> = Vec::new();
let mut pending_comma = false;
loop {
let pushed = if let Ok(s) = parser.try_parse(|p| p.expect_string_cloned()) {
families.push(s.to_string());
true
} else if let Ok(ident) = parser.try_parse(|p| p.expect_ident_cloned()) {
let mut name = ident.to_string();
loop {
let state = parser.state();
match parser.next_including_whitespace() {
Ok(Token::WhiteSpace(_)) => {
let state2 = parser.state();
match parser.next_including_whitespace() {
Ok(Token::Ident(next_ident)) => {
name.push(' ');
name.push_str(next_ident.as_ref());
}
_ => {
parser.reset(&state2);
break;
}
}
}
_ => {
parser.reset(&state);
break;
}
}
}
families.push(name);
true
} else {
false
};
if !pushed {
if pending_comma {
return Err(parser
.new_custom_error(ParseErrorOwned("trailing comma in font-family".into())));
}
break;
}
if parser.try_parse(|p| p.expect_comma()).is_err() {
break;
}
pending_comma = true;
}
if families.is_empty() {
return Err(parser.new_custom_error(ParseErrorOwned("expected font-family value".into())));
}
Ok(Value::FontFamilyList(families))
}
fn parse_border_shorthand<'i, 't>(
prop: &str,
parser: &mut Parser<'i, 't>,
) -> Result<Value, CssParseError<'i, ParseErrorOwned>> {
let mut width: Option<Length> = None;
let mut color: Option<Color> = None;
let mut saw_none_style = false;
for _ in 0..3 {
if width.is_none()
&& let Ok(len) = parser.try_parse(parse_length)
{
width = Some(len);
continue;
}
if color.is_none()
&& let Ok(c) = parser.try_parse(parse_color)
{
color = Some(c);
continue;
}
if let Ok(ident) = parser.try_parse(|p| p.expect_ident_cloned()) {
let kw = ident.to_ascii_lowercase();
match kw.as_str() {
"none" => {
saw_none_style = true;
continue;
}
_ => continue,
}
}
break;
}
let width = if saw_none_style {
width.unwrap_or(Length::Px(0.0))
} else {
width.ok_or_else(|| {
parser.new_custom_error(ParseErrorOwned(format!("missing width in `{prop}`")))
})?
};
let color = match color {
Some(c) => c,
None if saw_none_style => Color::rgba(0, 0, 0, 0),
None => {
return Err(
parser.new_custom_error(ParseErrorOwned(format!("missing color in `{prop}`")))
);
}
};
Ok(Value::Border { width, color })
}
fn expect_auto<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<(), CssParseError<'i, ParseErrorOwned>> {
let loc = parser.current_source_location();
let tok = parser.next()?.clone();
match &tok {
Token::Ident(name) if name.eq_ignore_ascii_case("auto") => Ok(()),
other => {
Err(loc.new_custom_error(ParseErrorOwned(format!("expected `auto`, got {other:?}"))))
}
}
}
#[derive(Debug, Clone)]
enum PropertyKind {
Color,
Length,
LengthOrAuto,
SideLengths,
SideLengthsOrAuto,
Keyword(&'static [&'static str]),
Number,
NumberOrLength,
FontFamily,
FontWeight,
Border,
Unknown,
}
fn property_kind(name: &str) -> PropertyKind {
match name {
"color" | "background-color" => PropertyKind::Color,
"width" | "height" | "flex-basis" => PropertyKind::LengthOrAuto,
"padding" | "border-radius" => PropertyKind::SideLengths,
"margin" => PropertyKind::SideLengthsOrAuto,
"gap" | "row-gap" | "column-gap" => PropertyKind::Length,
"display" => PropertyKind::Keyword(&["flex", "block", "none"]),
"flex-direction" => {
PropertyKind::Keyword(&["row", "column", "row-reverse", "column-reverse"])
}
"align-items" => PropertyKind::Keyword(&["start", "end", "center", "stretch", "baseline"]),
"justify-content" => PropertyKind::Keyword(&[
"start",
"end",
"center",
"space-between",
"space-around",
"space-evenly",
]),
"flex-grow" | "flex-shrink" => PropertyKind::Number,
"border" | "border-top" | "border-right" | "border-bottom" | "border-left" | "outline" => {
PropertyKind::Border
}
"border-width" => PropertyKind::SideLengths,
"border-color" => PropertyKind::Color,
"border-top-color" | "border-right-color" | "border-bottom-color" | "border-left-color" => {
PropertyKind::Color
}
"font-family" => PropertyKind::FontFamily,
"font-size" => PropertyKind::Length,
"font-weight" => PropertyKind::FontWeight,
"font-style" => PropertyKind::Keyword(&["normal", "italic", "oblique"]),
"text-align" => PropertyKind::Keyword(&["left", "center", "right", "justify"]),
"line-height" => PropertyKind::NumberOrLength,
_ => PropertyKind::Unknown,
}
}
fn consume_important(parser: &mut Parser<'_, '_>) -> bool {
parser
.try_parse(|p| -> Result<(), CssParseError<'_, ParseErrorOwned>> {
p.expect_delim('!')?;
let ident = p.expect_ident_cloned()?;
if ident.eq_ignore_ascii_case("important") {
Ok(())
} else {
Err(p.new_custom_error(ParseErrorOwned("not !important".to_string())))
}
})
.is_ok()
}
fn parse_length<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<Length, CssParseError<'i, ParseErrorOwned>> {
let location = parser.current_source_location();
let token = parser.next()?.clone();
match token {
Token::Dimension { value, unit, .. } => match unit.as_ref() {
"px" => Ok(Length::Px(f64::from(value))),
other => Err(location.new_custom_error(ParseErrorOwned(format!(
"unsupported length unit `{other}`"
)))),
},
Token::Percentage { unit_value, .. } => Ok(Length::Percent(f64::from(unit_value) * 100.0)),
Token::Number { value, .. } => Ok(Length::Px(f64::from(value))),
other => {
Err(location
.new_custom_error(ParseErrorOwned(format!("expected length, got {other:?}"))))
}
}
}
fn parse_color<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<Color, CssParseError<'i, ParseErrorOwned>> {
let location = parser.current_source_location();
let token = parser.next()?.clone();
match token {
Token::IDHash(h) | Token::Hash(h) => parse_hex(h.as_ref()).ok_or_else(|| {
location.new_custom_error(ParseErrorOwned(format!("bad hex color `#{h}`")))
}),
Token::Ident(name) => named_color(name.as_ref()).ok_or_else(|| {
location.new_custom_error(ParseErrorOwned(format!("unknown color name `{name}`")))
}),
Token::Function(name) => {
let name_lc = name.to_ascii_lowercase();
parser.parse_nested_block(|p| match name_lc.as_str() {
"rgb" => parse_rgb_args(p, false),
"rgba" => parse_rgb_args(p, true),
other => Err(p.new_custom_error(ParseErrorOwned(format!(
"unsupported color function `{other}`"
)))),
})
}
other => {
Err(location
.new_custom_error(ParseErrorOwned(format!("expected color, got {other:?}"))))
}
}
}
fn parse_rgb_args<'i, 't>(
parser: &mut Parser<'i, 't>,
expect_alpha: bool,
) -> Result<Color, CssParseError<'i, ParseErrorOwned>> {
let r = parse_u8_channel(parser)?;
parser.expect_comma()?;
let g = parse_u8_channel(parser)?;
parser.expect_comma()?;
let b = parse_u8_channel(parser)?;
let a = if expect_alpha {
parser.expect_comma()?;
let f = parser.expect_number()?;
(f.clamp(0.0, 1.0) * 255.0).round() as u8
} else {
0xff
};
parser.expect_exhausted()?;
Ok(Color::rgba(r, g, b, a))
}
fn parse_u8_channel<'i, 't>(
parser: &mut Parser<'i, 't>,
) -> Result<u8, CssParseError<'i, ParseErrorOwned>> {
let location = parser.current_source_location();
let token = parser.next()?.clone();
let n = match token {
Token::Number { value, .. } => value,
Token::Percentage { unit_value, .. } => unit_value * 255.0,
other => {
return Err(location
.new_custom_error(ParseErrorOwned(format!("expected channel, got {other:?}"))));
}
};
Ok(n.clamp(0.0, 255.0).round() as u8)
}
fn parse_hex(s: &str) -> Option<Color> {
let hex = |c: char| c.to_digit(16).map(|d| d as u8);
let chars: Vec<u8> = s.chars().filter_map(hex).collect();
if chars.len() != s.chars().count() {
return None;
}
let dup = |n: u8| (n << 4) | n;
Some(match chars.len() {
3 => Color::rgb(dup(chars[0]), dup(chars[1]), dup(chars[2])),
4 => Color::rgba(dup(chars[0]), dup(chars[1]), dup(chars[2]), dup(chars[3])),
6 => Color::rgb(
(chars[0] << 4) | chars[1],
(chars[2] << 4) | chars[3],
(chars[4] << 4) | chars[5],
),
8 => Color::rgba(
(chars[0] << 4) | chars[1],
(chars[2] << 4) | chars[3],
(chars[4] << 4) | chars[5],
(chars[6] << 4) | chars[7],
),
_ => return None,
})
}
fn named_color(name: &str) -> Option<Color> {
Some(match_ignore_ascii_case! { name,
"transparent" => Color::rgba(0, 0, 0, 0),
"black" => Color::rgb(0x00, 0x00, 0x00),
"silver" => Color::rgb(0xc0, 0xc0, 0xc0),
"gray" => Color::rgb(0x80, 0x80, 0x80),
"grey" => Color::rgb(0x80, 0x80, 0x80),
"white" => Color::rgb(0xff, 0xff, 0xff),
"maroon" => Color::rgb(0x80, 0x00, 0x00),
"red" => Color::rgb(0xff, 0x00, 0x00),
"purple" => Color::rgb(0x80, 0x00, 0x80),
"fuchsia" => Color::rgb(0xff, 0x00, 0xff),
"green" => Color::rgb(0x00, 0x80, 0x00),
"lime" => Color::rgb(0x00, 0xff, 0x00),
"olive" => Color::rgb(0x80, 0x80, 0x00),
"yellow" => Color::rgb(0xff, 0xff, 0x00),
"navy" => Color::rgb(0x00, 0x00, 0x80),
"blue" => Color::rgb(0x00, 0x00, 0xff),
"teal" => Color::rgb(0x00, 0x80, 0x80),
"aqua" => Color::rgb(0x00, 0xff, 0xff),
"cyan" => Color::rgb(0x00, 0xff, 0xff),
"magenta" => Color::rgb(0xff, 0x00, 0xff),
"orange" => Color::rgb(0xff, 0xa5, 0x00),
"brown" => Color::rgb(0xa5, 0x2a, 0x2a),
"pink" => Color::rgb(0xff, 0xc0, 0xcb),
_ => return None,
})
}