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}