1use std::sync::{Arc, Mutex};
6use crate::css::{parse_stylesheet, CSSRule as InternalCSSRule};
7use crate::cssrule::{CSSStyleRule};
8use crate::cssrulelist::CSSRuleList;
9
10#[derive(Debug, Clone)]
26pub struct CSSStyleSheet {
27 disabled: bool,
29 href: Option<String>,
31 owner_node: Option<String>,
33 css_rules: Arc<Mutex<CSSRuleList>>,
35 internal_stylesheet: Arc<Mutex<InternalStylesheetWrapper>>,
37}
38
39#[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 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 pub fn from_css(css_text: &str) -> Self {
94 let sheet = Self::new();
95
96 let internal_stylesheet = parse_stylesheet(css_text);
98
99 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 sheet.internal_stylesheet.lock().unwrap().rules = internal_stylesheet.rules;
109
110 sheet
111 }
112
113 pub fn disabled(&self) -> bool {
115 self.disabled
116 }
117
118 pub fn set_disabled(&mut self, disabled: bool) {
120 self.disabled = disabled;
121 }
122
123 pub fn href(&self) -> Option<&str> {
125 self.href.as_deref()
126 }
127
128 pub fn set_href(&mut self, href: &str) {
130 self.href = Some(href.to_string());
131 }
132
133 pub fn owner_node(&self) -> Option<&str> {
135 self.owner_node.as_deref()
136 }
137
138 pub fn set_owner_node(&mut self, node_id: &str) {
140 self.owner_node = Some(node_id.to_string());
141 }
142
143 pub fn css_rules(&self) -> Arc<Mutex<CSSRuleList>> {
157 Arc::clone(&self.css_rules)
158 }
159
160 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 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 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 self.internal_stylesheet.lock().unwrap().rules.insert(index, internal_rule);
211
212 Ok(index as u32)
213 }
214
215 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 self.internal_stylesheet.lock().unwrap().rules.remove(index);
250
251 Ok(())
252 }
253
254 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 let index = texts.iter().position(|t| t == old_rule);
274 drop(rules);
275
276 if let Some(index) = index {
277 self.delete_rule(index)?;
279 self.insert_rule(new_rule, index)
281 } else {
282 Err("Old rule not found".to_string())
283 }
284 }
285
286 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 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 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 pub fn clear(&mut self) {
328 self.css_rules.lock().unwrap().clear();
329 self.internal_stylesheet.lock().unwrap().rules.clear();
330 }
331
332 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}