Skip to main content

iris_cssom/
cssom.rs

1//! CSSStyleDeclaration - CSSOM 样式声明对象
2//!
3//! 实现 Web 标准的 CSSStyleDeclaration API,用于操作 CSS 属性。
4//!
5//! # 示例
6//!
7//! ```rust
8//! use iris_cssom::cssom::CSSStyleDeclaration;
9//!
10//! let mut style = CSSStyleDeclaration::new();
11//! style.set_property("color", "red", "");
12//! style.set_property("font-size", "16px", "important");
13//!
14//! assert_eq!(style.get_property_value("color"), "red");
15//! assert_eq!(style.get_property_priority("font-size"), "important");
16//! ```
17
18use std::collections::HashMap;
19
20/// CSS 属性值(包含优先级信息)
21#[derive(Debug, Clone)]
22struct CSSPropertyValue {
23    value: String,
24    important: bool,
25}
26
27/// CSSStyleDeclaration 对象
28///
29/// 表示一个 CSS 声明块,提供对样式属性的读写操作。
30/// 对标 Web API: `CSSStyleDeclaration`
31#[derive(Debug, Clone)]
32pub struct CSSStyleDeclaration {
33    /// 属性映射
34    properties: HashMap<String, CSSPropertyValue>,
35    /// 父样式表引用(可选)
36    #[allow(dead_code)]
37    parent_stylesheet: Option<String>,
38}
39
40impl CSSStyleDeclaration {
41    /// 创建空的样式声明
42    pub fn new() -> Self {
43        Self {
44            properties: HashMap::new(),
45            parent_stylesheet: None,
46        }
47    }
48
49    /// 从声明列表创建
50    pub fn from_declarations(declarations: &[crate::css::Declaration]) -> Self {
51        let mut properties = HashMap::new();
52        for decl in declarations {
53            properties.insert(
54                decl.property.clone(),
55                CSSPropertyValue {
56                    value: decl.value.clone(),
57                    important: false, // 默认不重要
58                },
59            );
60        }
61        Self {
62            properties,
63            parent_stylesheet: None,
64        }
65    }
66
67    /// 获取属性值
68    ///
69    /// # 示例
70    ///
71    /// ```rust
72    /// use iris_cssom::cssom::CSSStyleDeclaration;
73    ///
74    /// let mut style = CSSStyleDeclaration::new();
75    /// style.set_property("color", "red", "");
76    /// assert_eq!(style.get_property_value("color"), "red");
77    /// assert_eq!(style.get_property_value("background"), ""); // 不存在的属性
78    /// ```
79    pub fn get_property_value(&self, property: &str) -> String {
80        self.properties
81            .get(property)
82            .map(|p| p.value.clone())
83            .unwrap_or_default()
84    }
85
86    /// 获取属性优先级
87    ///
88    /// 返回 "important" 或空字符串
89    ///
90    /// # 示例
91    ///
92    /// ```rust
93    /// use iris_cssom::cssom::CSSStyleDeclaration;
94    ///
95    /// let mut style = CSSStyleDeclaration::new();
96    /// style.set_property("color", "red", "important");
97    /// assert_eq!(style.get_property_priority("color"), "important");
98    /// ```
99    pub fn get_property_priority(&self, property: &str) -> String {
100        self.properties
101            .get(property)
102            .map(|p| if p.important { "important" } else { "" })
103            .unwrap_or_default()
104            .to_string()
105    }
106
107    /// 设置属性值
108    ///
109    /// # 参数
110    ///
111    /// * `property` - 属性名(如 "color", "font-size")
112    /// * `value` - 属性值(如 "red", "16px")
113    /// * `priority` - 优先级("" 或 "important")
114    ///
115    /// # 示例
116    ///
117    /// ```rust
118    /// use iris_cssom::cssom::CSSStyleDeclaration;
119    ///
120    /// let mut style = CSSStyleDeclaration::new();
121    /// style.set_property("color", "red", "");
122    /// style.set_property("font-weight", "bold", "important");
123    /// ```
124    pub fn set_property(&mut self, property: &str, value: &str, priority: &str) {
125        let important = priority.to_lowercase() == "important";
126        self.properties.insert(
127            property.to_lowercase(),
128            CSSPropertyValue {
129                value: value.to_string(),
130                important,
131            },
132        );
133    }
134
135    /// 移除属性
136    ///
137    /// 返回被移除的属性值,如果属性不存在则返回空字符串
138    ///
139    /// # 示例
140    ///
141    /// ```rust
142    /// use iris_cssom::cssom::CSSStyleDeclaration;
143    ///
144    /// let mut style = CSSStyleDeclaration::new();
145    /// style.set_property("color", "red", "");
146    /// let removed = style.remove_property("color");
147    /// assert_eq!(removed, "red");
148    /// assert_eq!(style.get_property_value("color"), "");
149    /// ```
150    pub fn remove_property(&mut self, property: &str) -> String {
151        self.properties
152            .remove(&property.to_lowercase())
153            .map(|p| p.value)
154            .unwrap_or_default()
155    }
156
157    /// 获取属性数量
158    ///
159    /// # 示例
160    ///
161    /// ```rust
162    /// use iris_cssom::cssom::CSSStyleDeclaration;
163    ///
164    /// let mut style = CSSStyleDeclaration::new();
165    /// style.set_property("color", "red", "");
166    /// style.set_property("font-size", "16px", "");
167    /// assert_eq!(style.length(), 2);
168    /// ```
169    pub fn length(&self) -> usize {
170        self.properties.len()
171    }
172
173    /// 根据索引获取属性名
174    ///
175    /// # 注意
176    ///
177    /// 由于 HashMap 是无序的,这个方法返回的属性名顺序不保证稳定
178    ///
179    /// # 示例
180    ///
181    /// ```rust
182    /// use iris_cssom::cssom::CSSStyleDeclaration;
183    ///
184    /// let mut style = CSSStyleDeclaration::new();
185    /// style.set_property("color", "red", "");
186    /// if let Some(prop) = style.item(0) {
187    ///     assert_eq!(prop, "color");
188    /// }
189    /// ```
190    pub fn item(&self, index: usize) -> Option<String> {
191        self.properties.keys().nth(index).cloned()
192    }
193
194    /// 获取 CSS 文本表示
195    ///
196    /// 返回格式:`property1: value1; property2: value2 !important;`
197    ///
198    /// # 示例
199    ///
200    /// ```rust
201    /// use iris_cssom::cssom::CSSStyleDeclaration;
202    ///
203    /// let mut style = CSSStyleDeclaration::new();
204    /// style.set_property("color", "red", "");
205    /// style.set_property("font-weight", "bold", "important");
206    ///
207    /// let css_text = style.get_css_text();
208    /// assert!(css_text.contains("color: red"));
209    /// assert!(css_text.contains("font-weight: bold !important"));
210    /// ```
211    pub fn get_css_text(&self) -> String {
212        self.properties
213            .iter()
214            .map(|(k, v)| {
215                let important = if v.important { " !important" } else { "" };
216                format!("{}: {}{}", k, v.value, important)
217            })
218            .collect::<Vec<_>>()
219            .join("; ")
220    }
221
222    /// 设置 CSS 文本
223    ///
224    /// 解析 CSS 文本并替换所有属性
225    ///
226    /// # 示例
227    ///
228    /// ```rust
229    /// use iris_cssom::cssom::CSSStyleDeclaration;
230    ///
231    /// let mut style = CSSStyleDeclaration::new();
232    /// style.set_css_text("color: red; font-size: 16px");
233    /// assert_eq!(style.get_property_value("color"), "red");
234    /// assert_eq!(style.get_property_value("font-size"), "16px");
235    /// ```
236    pub fn set_css_text(&mut self, text: &str) {
237        self.properties.clear();
238        
239        // 简单的 CSS 解析器
240        for declaration in text.split(';') {
241            let declaration = declaration.trim();
242            if declaration.is_empty() {
243                continue;
244            }
245            
246            if let Some(colon_pos) = declaration.find(':') {
247                let property = declaration[..colon_pos].trim().to_lowercase();
248                let mut value_part = declaration[colon_pos + 1..].trim();
249                
250                // 检查 !important
251                let important = value_part.ends_with("!important");
252                if important {
253                    value_part = value_part[..value_part.len() - 10].trim();
254                }
255                
256                if !property.is_empty() && !value_part.is_empty() {
257                    self.properties.insert(
258                        property,
259                        CSSPropertyValue {
260                            value: value_part.to_string(),
261                            important,
262                        },
263                    );
264                }
265            }
266        }
267    }
268
269    /// 获取所有属性名列表
270    ///
271    /// # 示例
272    ///
273    /// ```rust
274    /// use iris_cssom::cssom::CSSStyleDeclaration;
275    ///
276    /// let mut style = CSSStyleDeclaration::new();
277    /// style.set_property("color", "red", "");
278    /// style.set_property("font-size", "16px", "");
279    ///
280    /// let props = style.get_property_names();
281    /// assert_eq!(props.len(), 2);
282    /// assert!(props.contains(&"color".to_string()));
283    /// ```
284    pub fn get_property_names(&self) -> Vec<String> {
285        self.properties.keys().cloned().collect()
286    }
287
288    /// 检查是否包含某个属性
289    ///
290    /// # 示例
291    ///
292    /// ```rust
293    /// use iris_cssom::cssom::CSSStyleDeclaration;
294    ///
295    /// let mut style = CSSStyleDeclaration::new();
296    /// style.set_property("color", "red", "");
297    /// assert!(style.has_property("color"));
298    /// assert!(!style.has_property("background"));
299    /// ```
300    pub fn has_property(&self, property: &str) -> bool {
301        self.properties.contains_key(&property.to_lowercase())
302    }
303
304    /// 清空所有属性
305    ///
306    /// # 示例
307    ///
308    /// ```rust
309    /// use iris_cssom::cssom::CSSStyleDeclaration;
310    ///
311    /// let mut style = CSSStyleDeclaration::new();
312    /// style.set_property("color", "red", "");
313    /// style.clear();
314    /// assert_eq!(style.length(), 0);
315    /// ```
316    pub fn clear(&mut self) {
317        self.properties.clear();
318    }
319
320    /// 合并另一个样式声明
321    ///
322    /// 只在当前没有该属性时才覆盖(低优先级)
323    ///
324    /// # 示例
325    ///
326    /// ```rust
327    /// use iris_cssom::cssom::CSSStyleDeclaration;
328    ///
329    /// let mut style1 = CSSStyleDeclaration::new();
330    /// style1.set_property("color", "red", "");
331    ///
332    /// let mut style2 = CSSStyleDeclaration::new();
333    /// style2.set_property("font-size", "16px", "");
334    /// style2.set_property("color", "blue", ""); // 不会覆盖 style1
335    ///
336    /// style1.merge(&style2);
337    /// assert_eq!(style1.get_property_value("color"), "red"); // 保留原值
338    /// assert_eq!(style1.get_property_value("font-size"), "16px"); // 新增
339    /// ```
340    pub fn merge(&mut self, other: &CSSStyleDeclaration) {
341        for (key, value) in &other.properties {
342            if !self.properties.contains_key(key) {
343                self.properties.insert(key.clone(), value.clone());
344            }
345        }
346    }
347
348    /// 转换为内部声明列表(用于与 iris-layout 集成)
349    pub fn to_declarations(&self) -> Vec<crate::css::Declaration> {
350        self.properties
351            .iter()
352            .map(|(k, v)| crate::css::Declaration {
353                property: k.clone(),
354                value: v.value.clone(),
355            })
356            .collect()
357    }
358}
359
360impl Default for CSSStyleDeclaration {
361    fn default() -> Self {
362        Self::new()
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369
370    #[test]
371    fn test_set_and_get_property() {
372        let mut style = CSSStyleDeclaration::new();
373        style.set_property("color", "red", "");
374        assert_eq!(style.get_property_value("color"), "red");
375    }
376
377    #[test]
378    fn test_important_priority() {
379        let mut style = CSSStyleDeclaration::new();
380        style.set_property("color", "red", "important");
381        assert_eq!(style.get_property_priority("color"), "important");
382    }
383
384    #[test]
385    fn test_remove_property() {
386        let mut style = CSSStyleDeclaration::new();
387        style.set_property("color", "red", "");
388        let removed = style.remove_property("color");
389        assert_eq!(removed, "red");
390        assert!(!style.has_property("color"));
391    }
392
393    #[test]
394    fn test_css_text() {
395        let mut style = CSSStyleDeclaration::new();
396        style.set_property("color", "red", "");
397        style.set_property("font-weight", "bold", "important");
398        
399        let css_text = style.get_css_text();
400        assert!(css_text.contains("color: red"));
401        assert!(css_text.contains("font-weight: bold !important"));
402    }
403
404    #[test]
405    fn test_set_css_text() {
406        let mut style = CSSStyleDeclaration::new();
407        style.set_css_text("color: red; font-size: 16px !important");
408        
409        assert_eq!(style.get_property_value("color"), "red");
410        assert_eq!(style.get_property_value("font-size"), "16px");
411        assert_eq!(style.get_property_priority("font-size"), "important");
412    }
413
414    #[test]
415    fn test_length_and_item() {
416        let mut style = CSSStyleDeclaration::new();
417        style.set_property("color", "red", "");
418        style.set_property("font-size", "16px", "");
419        
420        assert_eq!(style.length(), 2);
421        assert!(style.item(0).is_some());
422    }
423
424    #[test]
425    fn test_merge() {
426        let mut style1 = CSSStyleDeclaration::new();
427        style1.set_property("color", "red", "");
428        
429        let mut style2 = CSSStyleDeclaration::new();
430        style2.set_property("font-size", "16px", "");
431        
432        style1.merge(&style2);
433        assert_eq!(style1.get_property_value("color"), "red");
434        assert_eq!(style1.get_property_value("font-size"), "16px");
435    }
436
437    #[test]
438    fn test_clear() {
439        let mut style = CSSStyleDeclaration::new();
440        style.set_property("color", "red", "");
441        style.clear();
442        assert_eq!(style.length(), 0);
443    }
444}