Skip to main content

iris_cssom/
stylesheet.rs

1//! CSSStyleSheet - CSSOM 样式表对象
2//!
3//! 实现 Web 标准的 CSSStyleSheet 接口,提供样式表的完整操作 API。
4
5use std::sync::{Arc, Mutex};
6use crate::css::{parse_stylesheet, CSSRule as InternalCSSRule};
7use crate::cssrule::{CSSStyleRule};
8use crate::cssrulelist::CSSRuleList;
9
10/// CSSStyleSheet 对象
11///
12/// 表示一个 CSS 样式表,提供对样式表规则的完整操作能力。
13/// 对标 Web API: `CSSStyleSheet`
14///
15/// # 示例
16///
17/// ```rust
18/// use iris_cssom::stylesheet::CSSStyleSheet;
19///
20/// let mut sheet = CSSStyleSheet::new();
21/// sheet.insert_rule(".container { color: red; }", 0).unwrap();
22///
23/// assert_eq!(sheet.css_rules().lock().unwrap().length(), 1);
24/// ```
25#[derive(Debug, Clone)]
26pub struct CSSStyleSheet {
27    /// 样式表是否禁用
28    disabled: bool,
29    /// 样式表来源 URL
30    href: Option<String>,
31    /// 拥有此样式表的 DOM 元素(标识)
32    owner_node: Option<String>,
33    /// 规则列表
34    css_rules: Arc<Mutex<CSSRuleList>>,
35    /// 内部样式表(用于与 iris-layout 集成)
36    internal_stylesheet: Arc<Mutex<InternalStylesheetWrapper>>,
37}
38
39/// 内部样式表包装器
40#[derive(Debug, Clone)]
41struct InternalStylesheetWrapper {
42    pub rules: Vec<InternalCSSRule>,
43}
44
45impl InternalStylesheetWrapper {
46    fn new() -> Self {
47        Self {
48            rules: Vec::new(),
49        }
50    }
51}
52
53impl CSSStyleSheet {
54    /// 创建空的样式表
55    ///
56    /// # 示例
57    ///
58    /// ```rust
59    /// use iris_cssom::stylesheet::CSSStyleSheet;
60    ///
61    /// let sheet = CSSStyleSheet::new();
62    /// assert!(!sheet.disabled());
63    /// assert_eq!(sheet.css_rules().lock().unwrap().length(), 0);
64/// ```
65    pub fn new() -> Self {
66        Self {
67            disabled: false,
68            href: None,
69            owner_node: None,
70            css_rules: Arc::new(Mutex::new(CSSRuleList::new())),
71            internal_stylesheet: Arc::new(Mutex::new(InternalStylesheetWrapper::new())),
72        }
73    }
74
75    /// 从 CSS 文本创建样式表
76    ///
77    /// # 参数
78    ///
79    /// * `css_text` - CSS 文本内容
80    ///
81    /// # 示例
82    ///
83    /// ```rust
84    /// use iris_cssom::stylesheet::CSSStyleSheet;
85    ///
86    /// let css = r#"
87    ///     .container { padding: 20px; }
88    ///     #header { background: blue; }
89    /// "#;
90    /// let sheet = CSSStyleSheet::from_css(css);
91    /// assert!(sheet.css_rules().lock().unwrap().length() > 0);
92    /// ```
93    pub fn from_css(css_text: &str) -> Self {
94        let sheet = Self::new();
95        
96        // 解析 CSS
97        let internal_stylesheet = parse_stylesheet(css_text);
98        
99        // 转换为 CSSOM 规则
100        for rule in &internal_stylesheet.rules {
101            let style_rule = CSSStyleRule::from_internal(rule);
102            sheet.css_rules.lock().unwrap().append_rule(
103                Arc::new(Mutex::new(style_rule))
104            );
105        }
106        
107        // 保存内部样式表
108        sheet.internal_stylesheet.lock().unwrap().rules = internal_stylesheet.rules;
109        
110        sheet
111    }
112
113    /// 获取样式表是否禁用
114    pub fn disabled(&self) -> bool {
115        self.disabled
116    }
117
118    /// 设置样式表禁用状态
119    pub fn set_disabled(&mut self, disabled: bool) {
120        self.disabled = disabled;
121    }
122
123    /// 获取样式表 URL
124    pub fn href(&self) -> Option<&str> {
125        self.href.as_deref()
126    }
127
128    /// 设置样式表 URL
129    pub fn set_href(&mut self, href: &str) {
130        self.href = Some(href.to_string());
131    }
132
133    /// 获取拥有此样式表的 DOM 元素标识
134    pub fn owner_node(&self) -> Option<&str> {
135        self.owner_node.as_deref()
136    }
137
138    /// 设置拥有此样式表的 DOM 元素标识
139    pub fn set_owner_node(&mut self, node_id: &str) {
140        self.owner_node = Some(node_id.to_string());
141    }
142
143    /// 获取规则列表(实时更新的 live list)
144    ///
145    /// # 示例
146    ///
147    /// ```rust
148    /// use iris_cssom::stylesheet::CSSStyleSheet;
149    ///
150    /// let mut sheet = CSSStyleSheet::new();
151    /// sheet.insert_rule(".class { color: red; }", 0).unwrap();
152    ///
153    /// let rules = sheet.css_rules();
154    /// assert_eq!(rules.lock().unwrap().length(), 1);
155    /// ```
156    pub fn css_rules(&self) -> Arc<Mutex<CSSRuleList>> {
157        Arc::clone(&self.css_rules)
158    }
159
160    /// 插入一条新规则
161    ///
162    /// # 参数
163    ///
164    /// * `rule` - CSS 规则文本(如 ".class { color: red; }")
165    /// * `index` - 插入位置(从 0 开始)
166    ///
167    /// # 返回值
168    ///
169    /// 成功返回插入位置的索引,失败返回错误
170    ///
171    /// # 错误
172    ///
173    /// * 如果索引超出范围,返回 `Err`
174    /// * 如果 CSS 规则语法错误,返回 `Err`
175    ///
176    /// # 示例
177    ///
178    /// ```rust
179    /// use iris_cssom::stylesheet::CSSStyleSheet;
180    ///
181    /// let mut sheet = CSSStyleSheet::new();
182    /// let index = sheet.insert_rule(".container { color: red; }", 0);
183    /// assert!(index.is_ok());
184    /// assert_eq!(index.unwrap(), 0);
185    /// ```
186    pub fn insert_rule(&mut self, rule: &str, index: usize) -> Result<u32, String> {
187        if self.disabled {
188            return Err("Cannot insert rule into a disabled stylesheet".to_string());
189        }
190
191        // 解析单条规则
192        let temp_stylesheet = parse_stylesheet(rule);
193        if temp_stylesheet.rules.is_empty() {
194            return Err("Failed to parse CSS rule".to_string());
195        }
196
197        let internal_rule = temp_stylesheet.rules.into_iter().next().unwrap();
198        let style_rule = CSSStyleRule::from_internal(&internal_rule);
199        let rule_arc = Arc::new(Mutex::new(style_rule));
200
201        // 插入到规则列表
202        let mut rules = self.css_rules.lock().unwrap();
203        if index > rules.length() as usize {
204            return Err("Index out of bounds".to_string());
205        }
206        
207        rules.insert_rule(rule_arc, index);
208        
209        // 更新内部样式表
210        self.internal_stylesheet.lock().unwrap().rules.insert(index, internal_rule);
211
212        Ok(index as u32)
213    }
214
215    /// 删除指定索引的规则
216    ///
217    /// # 参数
218    ///
219    /// * `index` - 要删除的规则索引
220    ///
221    /// # 错误
222    ///
223    /// 如果索引超出范围,返回 `Err`
224    ///
225    /// # 示例
226    ///
227    /// ```rust
228    /// use iris_cssom::stylesheet::CSSStyleSheet;
229    ///
230    /// let mut sheet = CSSStyleSheet::new();
231    /// sheet.insert_rule(".class { color: red; }", 0).unwrap();
232    /// sheet.delete_rule(0).unwrap();
233    ///
234    /// assert_eq!(sheet.css_rules().lock().unwrap().length(), 0);
235    /// ```
236    pub fn delete_rule(&mut self, index: usize) -> Result<(), String> {
237        if self.disabled {
238            return Err("Cannot delete rule from a disabled stylesheet".to_string());
239        }
240
241        let mut rules = self.css_rules.lock().unwrap();
242        if index >= rules.length() as usize {
243            return Err("Index out of bounds".to_string());
244        }
245        
246        rules.remove_rule(index);
247        
248        // 更新内部样式表
249        self.internal_stylesheet.lock().unwrap().rules.remove(index);
250
251        Ok(())
252    }
253
254    /// 替换指定索引的规则
255    ///
256    /// # 参数
257    ///
258    /// * `old_rule` - 旧的 CSS 规则文本(用于匹配)
259    /// * `new_rule` - 新的 CSS 规则文本
260    ///
261    /// # 返回值
262    ///
263    /// 成功返回 `Ok(())`,失败返回错误
264    ///
265    /// # 注意
266    ///
267    /// 这是较新的 API(replaceRule),部分浏览器支持
268    pub fn replace_rule(&mut self, old_rule: &str, new_rule: &str) -> Result<u32, String> {
269        let rules = self.css_rules.lock().unwrap();
270        let texts = rules.get_all_css_texts();
271        
272        // 查找旧规则
273        let index = texts.iter().position(|t| t == old_rule);
274        drop(rules);
275        
276        if let Some(index) = index {
277            // 删除旧规则
278            self.delete_rule(index)?;
279            // 插入新规则
280            self.insert_rule(new_rule, index)
281        } else {
282            Err("Old rule not found".to_string())
283        }
284    }
285
286    /// 添加一条规则到末尾(较新的 API)
287    ///
288    /// # 参数
289    ///
290    /// * `rule` - CSS 规则文本
291    ///
292    /// # 返回值
293    ///
294    /// 成功返回新规则的索引
295    pub fn add_rule(&mut self, rule: &str) -> Result<u32, String> {
296        let index = self.css_rules.lock().unwrap().length() as usize;
297        self.insert_rule(rule, index)
298    }
299
300    /// 获取内部样式表(用于与 iris-layout 集成)
301    pub fn internal_stylesheet(&self) -> crate::css::Stylesheet {
302        let wrapper = self.internal_stylesheet.lock().unwrap();
303        crate::css::Stylesheet {
304            rules: wrapper.rules.clone(),
305        }
306    }
307
308    /// 获取 CSS 文本(整个样式表的文本表示)
309    ///
310    /// # 示例
311    ///
312    /// ```rust
313    /// use iris_cssom::stylesheet::CSSStyleSheet;
314    ///
315    /// let mut sheet = CSSStyleSheet::new();
316    /// sheet.insert_rule(".class { color: red; }", 0).unwrap();
317    ///
318    /// let css_text = sheet.get_css_text();
319    /// assert!(css_text.contains(".class"));
320    /// ```
321    pub fn get_css_text(&self) -> String {
322        let rules = self.css_rules.lock().unwrap();
323        rules.get_all_css_texts().join("\n")
324    }
325
326    /// 清空所有规则
327    pub fn clear(&mut self) {
328        self.css_rules.lock().unwrap().clear();
329        self.internal_stylesheet.lock().unwrap().rules.clear();
330    }
331
332    /// 获取规则数量
333    pub fn rule_count(&self) -> u32 {
334        self.css_rules.lock().unwrap().length()
335    }
336}
337
338impl Default for CSSStyleSheet {
339    fn default() -> Self {
340        Self::new()
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[test]
349    fn test_new_stylesheet() {
350        let sheet = CSSStyleSheet::new();
351        assert!(!sheet.disabled());
352        assert_eq!(sheet.css_rules().lock().unwrap().length(), 0);
353    }
354
355    #[test]
356    fn test_from_css() {
357        let css = ".container { padding: 20px; }";
358        let sheet = CSSStyleSheet::from_css(css);
359        assert_eq!(sheet.css_rules().lock().unwrap().length(), 1);
360    }
361
362    #[test]
363    fn test_insert_rule() {
364        let mut sheet = CSSStyleSheet::new();
365        let result = sheet.insert_rule(".class { color: red; }", 0);
366        assert!(result.is_ok());
367        assert_eq!(result.unwrap(), 0);
368        assert_eq!(sheet.rule_count(), 1);
369    }
370
371    #[test]
372    fn test_delete_rule() {
373        let mut sheet = CSSStyleSheet::new();
374        sheet.insert_rule(".class { color: red; }", 0).unwrap();
375        sheet.delete_rule(0).unwrap();
376        assert_eq!(sheet.rule_count(), 0);
377    }
378
379    #[test]
380    fn test_insert_rule_out_of_bounds() {
381        let mut sheet = CSSStyleSheet::new();
382        let result = sheet.insert_rule(".class { color: red; }", 5);
383        assert!(result.is_err());
384    }
385
386    #[test]
387    fn test_delete_rule_out_of_bounds() {
388        let mut sheet = CSSStyleSheet::new();
389        let result = sheet.delete_rule(0);
390        assert!(result.is_err());
391    }
392
393    #[test]
394    fn test_disabled_stylesheet() {
395        let mut sheet = CSSStyleSheet::new();
396        sheet.set_disabled(true);
397        let result = sheet.insert_rule(".class { color: red; }", 0);
398        assert!(result.is_err());
399    }
400
401    #[test]
402    fn test_add_rule() {
403        let mut sheet = CSSStyleSheet::new();
404        sheet.add_rule(".class1 { color: red; }").unwrap();
405        sheet.add_rule(".class2 { color: blue; }").unwrap();
406        assert_eq!(sheet.rule_count(), 2);
407    }
408
409    #[test]
410    fn test_get_css_text() {
411        let mut sheet = CSSStyleSheet::new();
412        sheet.insert_rule(".class { color: red; }", 0).unwrap();
413        let css_text = sheet.get_css_text();
414        assert!(css_text.contains(".class"));
415        assert!(css_text.contains("color: red"));
416    }
417
418    #[test]
419    fn test_clear() {
420        let mut sheet = CSSStyleSheet::new();
421        sheet.insert_rule(".class1 { color: red; }", 0).unwrap();
422        sheet.insert_rule(".class2 { color: blue; }", 1).unwrap();
423        sheet.clear();
424        assert_eq!(sheet.rule_count(), 0);
425    }
426
427    #[test]
428    fn test_internal_stylesheet() {
429        let mut sheet = CSSStyleSheet::new();
430        sheet.insert_rule(".class { color: red; }", 0).unwrap();
431        
432        let internal = sheet.internal_stylesheet();
433        assert_eq!(internal.rules.len(), 1);
434        assert_eq!(internal.rules[0].selector.text, ".class");
435    }
436}