1use cssparser::{
4 AtRuleParser, ParseError, Parser, ParserInput, QualifiedRuleParser, StyleSheetParser,
5};
6
7use crate::css::property::{parse_declaration_block, PropertyParseError};
8use crate::css::selector::{Selector, SelectorParseError, SelectorParser};
9use crate::css::types::Declaration;
10
11#[derive(Debug, Clone)]
13pub struct Rule {
14 pub selectors: Vec<Selector>,
16 pub declarations: Vec<Declaration>,
18}
19
20#[derive(Debug, Clone)]
22pub enum TcssParseError {
23 InvalidSelector(String),
25 InvalidProperty(String),
27 InvalidValue(String),
29}
30
31impl From<SelectorParseError> for TcssParseError {
32 fn from(e: SelectorParseError) -> Self {
33 TcssParseError::InvalidSelector(e.0)
34 }
35}
36
37impl From<PropertyParseError> for TcssParseError {
38 fn from(e: PropertyParseError) -> Self {
39 match e {
40 PropertyParseError::UnknownProperty(s) => TcssParseError::InvalidProperty(s),
41 PropertyParseError::InvalidValue(s) => TcssParseError::InvalidValue(s),
42 }
43 }
44}
45
46pub struct TcssRuleParser;
48
49impl<'i> AtRuleParser<'i> for TcssRuleParser {
50 type Prelude = ();
51 type AtRule = Rule;
52 type Error = TcssParseError;
53}
54
55impl<'i> QualifiedRuleParser<'i> for TcssRuleParser {
56 type Prelude = Vec<Selector>;
57 type QualifiedRule = Rule;
58 type Error = TcssParseError;
59
60 fn parse_prelude<'t>(
61 &mut self,
62 input: &mut Parser<'i, 't>,
63 ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
64 SelectorParser::parse_selector_list(input).map_err(|e| e.into::<TcssParseError>())
65 }
66
67 fn parse_block<'t>(
68 &mut self,
69 prelude: Self::Prelude,
70 _start: &cssparser::ParserState,
71 input: &mut Parser<'i, 't>,
72 ) -> Result<Self::QualifiedRule, ParseError<'i, Self::Error>> {
73 let declarations =
74 parse_declaration_block(input).map_err(|e| e.into::<TcssParseError>())?;
75 Ok(Rule {
76 selectors: prelude,
77 declarations,
78 })
79 }
80}
81
82pub fn parse_stylesheet(css: &str) -> (Vec<Rule>, Vec<String>) {
86 let mut input = ParserInput::new(css);
87 let mut parser = Parser::new(&mut input);
88 let mut rule_parser = TcssRuleParser;
89
90 let mut rules = Vec::new();
91 let mut errors = Vec::new();
92
93 let sheet_parser = StyleSheetParser::new(&mut parser, &mut rule_parser);
94 for result in sheet_parser {
95 match result {
96 Ok(rule) => rules.push(rule),
97 Err((parse_error, slice)) => {
98 let loc = parse_error.location;
99 let msg = format!(
101 "CSS parse error at line {}, column {}: {:?} (near {:?})",
102 loc.line + 1,
103 loc.column,
104 parse_error.kind,
105 slice
106 );
107 errors.push(msg);
108 }
109 }
110 }
111
112 (rules, errors)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use crate::css::selector::Selector;
119 use crate::css::types::{TcssColor, TcssValue};
120
121 #[test]
122 fn parse_stylesheet_single_rule() {
123 let (rules, errors) = parse_stylesheet("Button { color: red; }");
124 assert!(errors.is_empty(), "unexpected errors: {:?}", errors);
125 assert_eq!(rules.len(), 1);
126 assert_eq!(
127 rules[0].selectors,
128 vec![Selector::Type("Button".to_string())]
129 );
130 assert_eq!(rules[0].declarations.len(), 1);
131 assert_eq!(rules[0].declarations[0].property, "color");
132 assert!(matches!(
133 rules[0].declarations[0].value,
134 TcssValue::Color(TcssColor::Rgb(255, 0, 0))
135 ));
136 }
137
138 #[test]
139 fn parse_stylesheet_three_rules() {
140 let css = r#"
141 Button { color: red; }
142 .active { display: block; }
143 #sidebar { width: 20; }
144 "#;
145 let (rules, errors) = parse_stylesheet(css);
146 assert!(errors.is_empty(), "unexpected errors: {:?}", errors);
147 assert_eq!(rules.len(), 3);
148 }
149
150 #[test]
151 fn parse_stylesheet_syntax_error_collects_line_number() {
152 let css = r#"Button { color: red; }
154$invalid { color: blue; }
155Label { display: flex; }"#;
156 let (rules, errors) = parse_stylesheet(css);
157 assert!(!errors.is_empty(), "expected at least one error");
159 assert!(
161 errors[0].contains("line 2") || errors[0].contains("2"),
162 "error should mention line number: {}",
163 errors[0]
164 );
165 assert!(
167 rules.len() >= 1,
168 "should have parsed at least one valid rule"
169 );
170 }
171
172 #[test]
173 fn parse_stylesheet_empty_returns_empty() {
174 let (rules, errors) = parse_stylesheet("");
175 assert!(rules.is_empty());
176 assert!(errors.is_empty());
177 }
178
179 #[test]
180 fn parse_stylesheet_multiple_declarations() {
181 let css = "Button { color: red; display: block; opacity: 0.5; }";
182 let (rules, errors) = parse_stylesheet(css);
183 assert!(errors.is_empty(), "unexpected errors: {:?}", errors);
184 assert_eq!(rules.len(), 1);
185 assert_eq!(rules[0].declarations.len(), 3);
186 }
187}