//! CSS parsing logic. Mostly relies from the rust-cssparser crate, //! slightly modified to fit the `Styles` structure we want internally. use cssparser::{ AtRuleParser, BasicParseError, CowRcStr, DeclarationListParser, DeclarationParser, Parser, ParseError, QualifiedRuleParser, SourceLocation, Token }; use crate::styles::*; /// Represents a style rule, a `key: [values...];` pair. #[derive(Debug)] pub struct Rule { pub key: String, pub styles: Vec } /// The parser itself. #[derive(Debug)] pub struct RuleParser; /// Some type information for our parser. impl<'i> AtRuleParser<'i> for RuleParser { type PreludeBlock = (); type PreludeNoBlock = (); type AtRule = Rule; type Error = BasicParseError<'i>; } /// The actual work our parser does. Walks style rules and attempts to /// extract the key/value pairings from a given stylesheet string. impl<'i> QualifiedRuleParser<'i> for RuleParser { type Prelude = String; type QualifiedRule = Rule; type Error = BasicParseError<'i>; /// Parses out the selector. fn parse_prelude<'t>( &mut self, input: &mut Parser<'i, 't>, ) -> Result> { let location = input.current_source_location(); let selector = match input.next()? { Token::Ident(ref element_name) => element_name.to_string(), t => { return Err(location.new_unexpected_token_error(t.clone())); } }; // If there's a next, someone is writing their code assuming cascading. Let's... warn them. /*match input.next()? { Ok(_) => {}, Err(e) => {} };*/ Ok(selector) } /// Parses the block (`{...}`) into a Rule struct. fn parse_block<'t>( &mut self, key: Self::Prelude, _location: SourceLocation, input: &mut Parser<'i, 't>, ) -> Result> { let styles = DeclarationListParser::new(input, StyleParser {}).collect::>(); Ok(Rule { key: key, styles: styles.into_iter().filter_map(|decl| { if !decl.is_ok() { eprintln!("{:?}", decl); } decl.ok() }).collect() }) } } /// Contains logic for matching CSS attributes to their `Styles` counterpart. #[derive(Debug)] pub struct StyleParser; /// Types, etc. impl<'i> AtRuleParser<'i> for StyleParser { type PreludeBlock = (); type PreludeNoBlock = (); type AtRule = Styles; type Error = BasicParseError<'i>; } /// A utility method for dereferencing a value, to make some code later on a bit more clean. fn ident<'a>(token: &'a Token) -> &'a str { match token { Token::Ident(ref value) => &*value, _ => "" } } impl<'i> DeclarationParser<'i> for StyleParser { type Declaration = Styles; type Error = BasicParseError<'i>; /// Parses a value (e.g, `background-color: #307ace;`) into a `Styles` value. fn parse_value<'t>( &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result> { let style = match &*name { "align-content" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "flex-start" => Styles::AlignContent(AlignContent::FlexStart), "flex-end" => Styles::AlignContent(AlignContent::FlexEnd), "center" => Styles::AlignContent(AlignContent::Center), "stretch" => Styles::AlignContent(AlignContent::Stretch), "space-between" => Styles::AlignContent(AlignContent::SpaceBetween), "space-around" => Styles::AlignContent(AlignContent::SpaceAround), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "align-items" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "flex-start" => Styles::AlignItems(AlignItems::FlexStart), "flex-end" => Styles::AlignItems(AlignItems::FlexEnd), "center" => Styles::AlignItems(AlignItems::Center), "baseline" => Styles::AlignItems(AlignItems::Baseline), "stretch" => Styles::AlignItems(AlignItems::Stretch), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "align_self" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "auto" => Styles::AlignSelf(AlignSelf::Auto), "flex-start" => Styles::AlignSelf(AlignSelf::FlexStart), "flex-end" => Styles::AlignSelf(AlignSelf::FlexEnd), "center" => Styles::AlignSelf(AlignSelf::Center), "baseline" => Styles::AlignSelf(AlignSelf::Baseline), "stretch" => Styles::AlignSelf(AlignSelf::Stretch), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, // @TODO: Aspect Ratio... could be string, no? Should this be handled better? "aspect-ratio" => Styles::AspectRatio(Number::Defined(parse_floaty_mcfloatface_value(input)?)), "backface-visibility" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "visible" => Styles::BackfaceVisibility(BackfaceVisibility::Visible), "hidden" => Styles::BackfaceVisibility(BackfaceVisibility::Hidden), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "background-color" => Styles::BackgroundColor(Color::parse(input)?), // Border values~ "border-color" => Styles::BorderColor(Color::parse(input)?), "border-top-color" => Styles::BorderTopColor(Color::parse(input)?), "border-bottom-color" => Styles::BorderBottomColor(Color::parse(input)?), "border-left-color" => Styles::BorderLeftColor(Color::parse(input)?), "border-right-color" => Styles::BorderRightColor(Color::parse(input)?), "bottom" => Styles::Bottom(parse_floaty_mcfloatface_value(input)?), "color" => Styles::TextColor(Color::parse(input)?), "direction" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "inherit" => Styles::Direction(Direction::Inherit), "ltr" => Styles::Direction(Direction::LTR), "rtl" => Styles::Direction(Direction::RTL), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "display" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "flex" => Styles::Display(Display::Flex), "none" => Styles::Display(Display::None), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "end" => Styles::End(parse_floaty_mcfloatface_value(input)?), "flex-basis" => Styles::FlexBasis(parse_floaty_mcfloatface_value(input)?), "flex-direction" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "row" => Styles::FlexDirection(FlexDirection::Row), "row-reverse" => Styles::FlexDirection(FlexDirection::RowReverse), "column" => Styles::FlexDirection(FlexDirection::Column), "column-reverse" => Styles::FlexDirection(FlexDirection::ColumnReverse), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "flex-grow" => Styles::FlexGrow(parse_floaty_mcfloatface_value(input)?), "flex-shrink" => Styles::FlexShrink(parse_floaty_mcfloatface_value(input)?), "flex-wrap" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "no-wrap" => Styles::FlexWrap(FlexWrap::NoWrap), "wrap" => Styles::FlexWrap(FlexWrap::Wrap), "wrap-reverse" => Styles::FlexWrap(FlexWrap::WrapReverse), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, //FontFamily(FontFamily), "font-size" => Styles::FontSize(parse_floaty_mcfloatface_value(input)?), "font-style" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "normal" => Styles::FontStyle(FontStyle::Normal), "italic" => Styles::FontStyle(FontStyle::Italic), "oblique" => Styles::FontStyle(FontStyle::Oblique), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "font-weight" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "normal" => Styles::FontWeight(FontWeight::Normal), "bold" => Styles::FontWeight(FontWeight::Bold), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "height" => Styles::Height(parse_floaty_mcfloatface_value(input)?), "justify-content" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "flex-start" => Styles::JustifyContent(JustifyContent::FlexStart), "flex-end" => Styles::JustifyContent(JustifyContent::FlexEnd), "center" => Styles::JustifyContent(JustifyContent::Center), "space-between" => Styles::JustifyContent(JustifyContent::SpaceBetween), "space-around" => Styles::JustifyContent(JustifyContent::SpaceAround), "space-evenly" => Styles::JustifyContent(JustifyContent::SpaceEvenly), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "left" => Styles::Left(parse_floaty_mcfloatface_value(input)?), "line-height" => Styles::FontLineHeight(parse_floaty_mcfloatface_value(input)?), "margin-bottom" => Styles::MarginBottom(parse_floaty_mcfloatface_value(input)?), "margin-end" => Styles::MarginEnd(parse_floaty_mcfloatface_value(input)?), "margin-left" => Styles::MarginLeft(parse_floaty_mcfloatface_value(input)?), "margin-right" => Styles::MarginRight(parse_floaty_mcfloatface_value(input)?), "margin-start" => Styles::MarginStart(parse_floaty_mcfloatface_value(input)?), "margin-top" => Styles::MarginTop(parse_floaty_mcfloatface_value(input)?), "max-height" => Styles::MaxHeight(parse_floaty_mcfloatface_value(input)?), "max-width" => Styles::MaxWidth(parse_floaty_mcfloatface_value(input)?), "min-height" => Styles::MinHeight(parse_floaty_mcfloatface_value(input)?), "min-width" => Styles::MinWidth(parse_floaty_mcfloatface_value(input)?), "opacity" => Styles::Opacity(parse_floaty_mcfloatface_value(input)?), "overflow" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "visible" => Styles::Overflow(Overflow::Visible), "hidden" => Styles::Overflow(Overflow::Hidden), "scroll" => Styles::Overflow(Overflow::Scroll), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "padding-bottom" => Styles::PaddingBottom(parse_floaty_mcfloatface_value(input)?), "padding-end" => Styles::PaddingEnd(parse_floaty_mcfloatface_value(input)?), "padding-left" => Styles::PaddingLeft(parse_floaty_mcfloatface_value(input)?), "padding-right" => Styles::PaddingRight(parse_floaty_mcfloatface_value(input)?), "padding-start" => Styles::PaddingStart(parse_floaty_mcfloatface_value(input)?), "padding-top" => Styles::PaddingTop(parse_floaty_mcfloatface_value(input)?), "position" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "absolute" => Styles::PositionType(PositionType::Absolute), "relative" => Styles::PositionType(PositionType::Relative), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "right" => Styles::Right(parse_floaty_mcfloatface_value(input)?), "start" => Styles::Start(parse_floaty_mcfloatface_value(input)?), "text-align" => { let s = input.current_source_location(); let t = input.next()?; match ident(&t) { "auto" => Styles::TextAlignment(TextAlignment::Auto), "left" => Styles::TextAlignment(TextAlignment::Left), "right" => Styles::TextAlignment(TextAlignment::Right), "center" => Styles::TextAlignment(TextAlignment::Center), "justify" => Styles::TextAlignment(TextAlignment::Justify), _ => { return Err(s.new_unexpected_token_error(t.clone())); } }}, "text-decoration-color" => Styles::TextDecorationColor(Color::parse(input)?), "text-shadow-color" => Styles::TextShadowColor(Color::parse(input)?), "tint-color" => Styles::TintColor(Color::parse(input)?), "top" => Styles::Top(parse_floaty_mcfloatface_value(input)?), "width" => Styles::Width(parse_floaty_mcfloatface_value(input)?), t => { let location = input.current_source_location(); return Err(location.new_unexpected_token_error(Token::Ident(t.to_string().into()))); } }; Ok(style) } } /// A utility method for handling some float values. /// Mostly used to reduce code verbosity in the massive switch table for `Styles` parsing. fn parse_floaty_mcfloatface_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { let location = input.current_source_location(); let token = input.next()?; match token { Token::Number { value, .. } => Ok(*value), _ => Err(location.new_basic_unexpected_token_error(token.clone())) } }