Skip to main content

iris_cssom/
css.rs

1//! CSS 解析器
2//!
3//! 基于 cssparser 实现 CSS 样式表解析。
4
5use cssparser::{Parser, ParserInput};
6
7/// CSS 选择器类型
8#[allow(missing_docs)]
9#[derive(Debug, Clone, PartialEq)]
10pub enum SelectorType {
11    /// 标签选择器: div, p, span
12    Tag(String),
13    /// ID 选择器: #id
14    Id(String),
15    /// Class 选择器: .class
16    Class(String),
17    /// 属性选择器: [attr=value]
18    Attribute { name: String, value: Option<String> },
19    /// 通配符: *
20    Universal,
21    /// 复合选择器: div.class#id
22    Compound(Vec<SelectorType>),
23    /// 后代选择器: div p
24    Descendant(Box<SelectorType>, Box<SelectorType>),
25    /// 子元素选择器: div > p
26    Child(Box<SelectorType>, Box<SelectorType>),
27}
28
29/// CSS 选择器
30#[derive(Debug, Clone, PartialEq)]
31pub struct Selector {
32    /// 选择器文本 (如 ".class", "#id", "div")
33    pub text: String,
34    /// 选择器类型(增强版)
35    pub selector_type: SelectorType,
36}
37
38impl Selector {
39    /// 创建新的选择器(自动解析类型)
40    pub fn new(text: &str) -> Self {
41        let selector_type = parse_selector_type(text);
42        Self {
43            text: text.to_string(),
44            selector_type,
45        }
46    }
47
48    /// 创建指定类型的选择器
49    pub fn with_type(text: &str, selector_type: SelectorType) -> Self {
50        Self {
51            text: text.to_string(),
52            selector_type,
53        }
54    }
55
56    /// 判断是否为 ID 选择器
57    pub fn is_id(&self) -> bool {
58        matches!(self.selector_type, SelectorType::Id(_))
59    }
60
61    /// 判断是否为 Class 选择器
62    pub fn is_class(&self) -> bool {
63        matches!(self.selector_type, SelectorType::Class(_))
64    }
65
66    /// 判断是否为标签选择器
67    pub fn is_tag(&self) -> bool {
68        matches!(self.selector_type, SelectorType::Tag(_))
69    }
70
71    /// 判断是否为复合选择器
72    pub fn is_compound(&self) -> bool {
73        matches!(self.selector_type, SelectorType::Compound(_))
74    }
75}
76
77/// 解析选择器类型(增强版)
78fn parse_selector_type(text: &str) -> SelectorType {
79    let text = text.trim();
80    
81    // 通配符
82    if text == "*" {
83        return SelectorType::Universal;
84    }
85    
86    // 属性选择器 [attr] 或 [attr=value]
87    if text.starts_with('[') && text.ends_with(']') {
88        let content = &text[1..text.len() - 1];
89        if let Some(eq_pos) = content.find('=') {
90            let name = content[..eq_pos].trim().to_string();
91            let value = Some(content[eq_pos + 1..].trim().trim_matches(|c| c == '"' || c == '\'').to_string());
92            return SelectorType::Attribute { name, value };
93        } else {
94            return SelectorType::Attribute {
95                name: content.trim().to_string(),
96                value: None,
97            };
98        }
99    }
100    
101    // 复合选择器(包含 . 或 # 的组合)
102    if (text.contains('.') || text.contains('#')) && !text.starts_with('.') && !text.starts_with('#') {
103        // 例如: div.class, div#id, div.class1.class2
104        let mut parts = Vec::new();
105        let mut current = String::new();
106        let mut chars = text.chars().peekable();
107        
108        while let Some(ch) = chars.next() {
109            if ch == '.' || ch == '#' {
110                if !current.is_empty() {
111                    // 保存之前的部分
112                    if parts.is_empty() && !current.starts_with('.') && !current.starts_with('#') {
113                        parts.push(SelectorType::Tag(current.clone()));
114                    } else if current.starts_with('.') {
115                        parts.push(SelectorType::Class(current[1..].to_string()));
116                    } else if current.starts_with('#') {
117                        parts.push(SelectorType::Id(current[1..].to_string()));
118                    }
119                    current.clear();
120                }
121                current.push(ch);
122            } else {
123                current.push(ch);
124            }
125        }
126        
127        // 处理最后一部分
128        if !current.is_empty() {
129            if current.starts_with('.') {
130                parts.push(SelectorType::Class(current[1..].to_string()));
131            } else if current.starts_with('#') {
132                parts.push(SelectorType::Id(current[1..].to_string()));
133            } else {
134                parts.push(SelectorType::Class(current)); // 默认当作 class
135            }
136        }
137        
138        if parts.len() > 1 {
139            return SelectorType::Compound(parts);
140        }
141    }
142    
143    // 简单选择器
144    if text.starts_with('#') {
145        SelectorType::Id(text[1..].to_string())
146    } else if text.starts_with('.') {
147        SelectorType::Class(text[1..].to_string())
148    } else {
149        SelectorType::Tag(text.to_string())
150    }
151}
152
153/// CSS 声明 (属性: 值)
154#[derive(Debug, Clone)]
155pub struct Declaration {
156    /// 属性名 (如 "color", "font-size")
157    pub property: String,
158    /// 属性值 (如 "red", "16px")
159    pub value: String,
160}
161
162/// CSS 规则 (选择器 { 声明块 })
163#[derive(Debug, Clone)]
164pub struct CSSRule {
165    /// 选择器
166    pub selector: Selector,
167    /// 声明列表
168    pub declarations: Vec<Declaration>,
169}
170
171impl CSSRule {
172    /// 创建新的 CSS 规则
173    pub fn new(selector: Selector, declarations: Vec<Declaration>) -> Self {
174        Self {
175            selector,
176            declarations,
177        }
178    }
179}
180
181/// CSS 样式表
182#[derive(Debug, Clone)]
183pub struct Stylesheet {
184    /// CSS 规则列表
185    pub rules: Vec<CSSRule>,
186}
187
188impl Stylesheet {
189    /// 创建空的样式表
190    pub fn new() -> Self {
191        Self {
192            rules: Vec::new(),
193        }
194    }
195
196    /// 添加规则
197    pub fn add_rule(&mut self, rule: CSSRule) {
198        self.rules.push(rule);
199    }
200}
201
202/// 解析 CSS 字符串,生成样式表
203///
204/// # 示例
205///
206/// ```rust
207/// use iris_cssom::css::parse_stylesheet;
208///
209/// let css = r#"
210///     .container {
211///         padding: 20px;
212///         background-color: white;
213///     }
214///     
215///     #title {
216///         font-size: 24px;
217///         color: blue;
218///     }
219/// "#;
220///
221/// let stylesheet = parse_stylesheet(css);
222/// assert!(!stylesheet.rules.is_empty());
223/// ```
224pub fn parse_stylesheet(css: &str) -> Stylesheet {
225    let mut input = ParserInput::new(css);
226    let _parser = Parser::new(&mut input);
227    
228    let mut stylesheet = Stylesheet::new();
229    
230    // 简化实现:手动解析 CSS
231    // 实际应该使用 cssparser 的完整解析能力
232    parse_css_manual(css, &mut stylesheet);
233    
234    stylesheet
235}
236
237/// 手动解析 CSS (简化实现)
238fn parse_css_manual(css: &str, stylesheet: &mut Stylesheet) {
239    // 移除注释
240    let css = remove_comments(css);
241    
242    // 分割规则
243    let rules = split_rules(&css);
244    
245    for rule_text in rules {
246        if let Some(rule) = parse_single_rule(rule_text.trim()) {
247            stylesheet.add_rule(rule);
248        }
249    }
250}
251
252/// 移除 CSS 注释
253fn remove_comments(css: &str) -> String {
254    let mut result = String::new();
255    let mut chars = css.chars().peekable();
256    
257    while let Some(ch) = chars.next() {
258        if ch == '/' {
259            if let Some(&'*') = chars.peek() {
260                chars.next(); // consume '*'
261                // Skip until */
262                while let Some(ch) = chars.next() {
263                    if ch == '*' {
264                        if let Some(&'/') = chars.peek() {
265                            chars.next(); // consume '/'
266                            break;
267                        }
268                    }
269                }
270            } else {
271                result.push(ch);
272            }
273        } else {
274            result.push(ch);
275        }
276    }
277    
278    result
279}
280
281/// 分割 CSS 规则
282fn split_rules(css: &str) -> Vec<&str> {
283    let mut rules = Vec::new();
284    let mut start = 0;
285    let mut brace_count = 0;
286    
287    for (i, ch) in css.char_indices() {
288        match ch {
289            '{' => brace_count += 1,
290            '}' => {
291                brace_count -= 1;
292                if brace_count == 0 {
293                    rules.push(&css[start..=i]);
294                    start = i + 1;
295                }
296            }
297            _ => {}
298        }
299    }
300    
301    rules
302}
303
304/// 解析单个 CSS 规则
305fn parse_single_rule(rule_text: &str) -> Option<CSSRule> {
306    // 查找 { 的位置
307    let brace_pos = rule_text.find('{')?;
308    
309    let selector_text = rule_text[..brace_pos].trim();
310    let declarations_text = &rule_text[brace_pos + 1..rule_text.len() - 1]; // remove { }
311    
312    if selector_text.is_empty() {
313        return None;
314    }
315    
316    let selector = Selector::new(selector_text);
317    let declarations = parse_declarations(declarations_text);
318    
319    Some(CSSRule::new(selector, declarations))
320}
321
322/// 解析声明块
323fn parse_declarations(text: &str) -> Vec<Declaration> {
324    let mut declarations = Vec::new();
325    
326    for decl_text in text.split(';') {
327        let decl_text = decl_text.trim();
328        if decl_text.is_empty() {
329            continue;
330        }
331        
332        if let Some(colon_pos) = decl_text.find(':') {
333            let property = decl_text[..colon_pos].trim().to_string();
334            let value = decl_text[colon_pos + 1..].trim().to_string();
335            
336            declarations.push(Declaration { property, value });
337        }
338    }
339    
340    declarations
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_parse_simple_css() {
349        let css = r#"
350            .container {
351                padding: 20px;
352                background-color: white;
353            }
354        "#;
355        
356        let stylesheet = parse_stylesheet(css);
357        assert_eq!(stylesheet.rules.len(), 1);
358        assert_eq!(stylesheet.rules[0].selector.text, ".container");
359        assert_eq!(stylesheet.rules[0].declarations.len(), 2);
360    }
361
362    #[test]
363    fn test_parse_multiple_rules() {
364        let css = r#"
365            .class1 { color: red; }
366            #id1 { font-size: 16px; }
367            div { margin: 0; }
368        "#;
369        
370        let stylesheet = parse_stylesheet(css);
371        assert_eq!(stylesheet.rules.len(), 3);
372    }
373
374    #[test]
375    fn test_selector_types() {
376        let class_sel = Selector::new(".container");
377        let id_sel = Selector::new("#main");
378        let tag_sel = Selector::new("div");
379        
380        assert!(class_sel.is_class());
381        assert!(id_sel.is_id());
382        assert!(tag_sel.is_tag());
383    }
384
385    #[test]
386    fn test_parse_with_comments() {
387        let css = r#"
388            /* This is a comment */
389            .container {
390                padding: 20px; /* inline comment */
391            }
392        "#;
393        
394        let stylesheet = parse_stylesheet(css);
395        assert_eq!(stylesheet.rules.len(), 1);
396    }
397
398    #[test]
399    fn test_attribute_selector() {
400        let sel1 = Selector::new("[data-type]");
401        let sel2 = Selector::new("[data-type=button]");
402        let sel3 = Selector::new("[href=\"https://example.com\"]");
403        
404        assert!(sel1.is_compound() || matches!(sel1.selector_type, crate::css::SelectorType::Attribute { .. }));
405        assert!(sel2.is_compound() || matches!(sel2.selector_type, crate::css::SelectorType::Attribute { .. }));
406        assert!(sel3.is_compound() || matches!(sel3.selector_type, crate::css::SelectorType::Attribute { .. }));
407    }
408
409    #[test]
410    fn test_compound_selector() {
411        let sel = Selector::new("div.container");
412        assert!(sel.is_compound());
413        
414        let sel2 = Selector::new("div#main.container");
415        assert!(sel2.is_compound());
416    }
417
418    #[test]
419    fn test_universal_selector() {
420        let sel = Selector::new("*");
421        assert!(matches!(sel.selector_type, crate::css::SelectorType::Universal));
422    }
423
424    #[test]
425    fn test_selector_type_parsing() {
426        use crate::css::SelectorType;
427        
428        let id_sel = Selector::new("#main");
429        assert!(matches!(id_sel.selector_type, SelectorType::Id(s) if s == "main"));
430        
431        let class_sel = Selector::new(".container");
432        assert!(matches!(class_sel.selector_type, SelectorType::Class(s) if s == "container"));
433        
434        let tag_sel = Selector::new("div");
435        assert!(matches!(tag_sel.selector_type, SelectorType::Tag(s) if s == "div"));
436    }
437}