use std::sync::{Arc, Mutex};
use crate::css::{parse_stylesheet, CSSRule as InternalCSSRule};
use crate::cssrule::{CSSStyleRule};
use crate::cssrulelist::CSSRuleList;
#[derive(Debug, Clone)]
pub struct CSSStyleSheet {
disabled: bool,
href: Option<String>,
owner_node: Option<String>,
css_rules: Arc<Mutex<CSSRuleList>>,
internal_stylesheet: Arc<Mutex<InternalStylesheetWrapper>>,
}
#[derive(Debug, Clone)]
struct InternalStylesheetWrapper {
pub rules: Vec<InternalCSSRule>,
}
impl InternalStylesheetWrapper {
fn new() -> Self {
Self {
rules: Vec::new(),
}
}
}
impl CSSStyleSheet {
pub fn new() -> Self {
Self {
disabled: false,
href: None,
owner_node: None,
css_rules: Arc::new(Mutex::new(CSSRuleList::new())),
internal_stylesheet: Arc::new(Mutex::new(InternalStylesheetWrapper::new())),
}
}
pub fn from_css(css_text: &str) -> Self {
let sheet = Self::new();
let internal_stylesheet = parse_stylesheet(css_text);
for rule in &internal_stylesheet.rules {
let style_rule = CSSStyleRule::from_internal(rule);
sheet.css_rules.lock().unwrap().append_rule(
Arc::new(Mutex::new(style_rule))
);
}
sheet.internal_stylesheet.lock().unwrap().rules = internal_stylesheet.rules;
sheet
}
pub fn disabled(&self) -> bool {
self.disabled
}
pub fn set_disabled(&mut self, disabled: bool) {
self.disabled = disabled;
}
pub fn href(&self) -> Option<&str> {
self.href.as_deref()
}
pub fn set_href(&mut self, href: &str) {
self.href = Some(href.to_string());
}
pub fn owner_node(&self) -> Option<&str> {
self.owner_node.as_deref()
}
pub fn set_owner_node(&mut self, node_id: &str) {
self.owner_node = Some(node_id.to_string());
}
pub fn css_rules(&self) -> Arc<Mutex<CSSRuleList>> {
Arc::clone(&self.css_rules)
}
pub fn insert_rule(&mut self, rule: &str, index: usize) -> Result<u32, String> {
if self.disabled {
return Err("Cannot insert rule into a disabled stylesheet".to_string());
}
let temp_stylesheet = parse_stylesheet(rule);
if temp_stylesheet.rules.is_empty() {
return Err("Failed to parse CSS rule".to_string());
}
let internal_rule = temp_stylesheet.rules.into_iter().next().unwrap();
let style_rule = CSSStyleRule::from_internal(&internal_rule);
let rule_arc = Arc::new(Mutex::new(style_rule));
let mut rules = self.css_rules.lock().unwrap();
if index > rules.length() as usize {
return Err("Index out of bounds".to_string());
}
rules.insert_rule(rule_arc, index);
self.internal_stylesheet.lock().unwrap().rules.insert(index, internal_rule);
Ok(index as u32)
}
pub fn delete_rule(&mut self, index: usize) -> Result<(), String> {
if self.disabled {
return Err("Cannot delete rule from a disabled stylesheet".to_string());
}
let mut rules = self.css_rules.lock().unwrap();
if index >= rules.length() as usize {
return Err("Index out of bounds".to_string());
}
rules.remove_rule(index);
self.internal_stylesheet.lock().unwrap().rules.remove(index);
Ok(())
}
pub fn replace_rule(&mut self, old_rule: &str, new_rule: &str) -> Result<u32, String> {
let rules = self.css_rules.lock().unwrap();
let texts = rules.get_all_css_texts();
let index = texts.iter().position(|t| t == old_rule);
drop(rules);
if let Some(index) = index {
self.delete_rule(index)?;
self.insert_rule(new_rule, index)
} else {
Err("Old rule not found".to_string())
}
}
pub fn add_rule(&mut self, rule: &str) -> Result<u32, String> {
let index = self.css_rules.lock().unwrap().length() as usize;
self.insert_rule(rule, index)
}
pub fn internal_stylesheet(&self) -> crate::css::Stylesheet {
let wrapper = self.internal_stylesheet.lock().unwrap();
crate::css::Stylesheet {
rules: wrapper.rules.clone(),
}
}
pub fn get_css_text(&self) -> String {
let rules = self.css_rules.lock().unwrap();
rules.get_all_css_texts().join("\n")
}
pub fn clear(&mut self) {
self.css_rules.lock().unwrap().clear();
self.internal_stylesheet.lock().unwrap().rules.clear();
}
pub fn rule_count(&self) -> u32 {
self.css_rules.lock().unwrap().length()
}
}
impl Default for CSSStyleSheet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_stylesheet() {
let sheet = CSSStyleSheet::new();
assert!(!sheet.disabled());
assert_eq!(sheet.css_rules().lock().unwrap().length(), 0);
}
#[test]
fn test_from_css() {
let css = ".container { padding: 20px; }";
let sheet = CSSStyleSheet::from_css(css);
assert_eq!(sheet.css_rules().lock().unwrap().length(), 1);
}
#[test]
fn test_insert_rule() {
let mut sheet = CSSStyleSheet::new();
let result = sheet.insert_rule(".class { color: red; }", 0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 0);
assert_eq!(sheet.rule_count(), 1);
}
#[test]
fn test_delete_rule() {
let mut sheet = CSSStyleSheet::new();
sheet.insert_rule(".class { color: red; }", 0).unwrap();
sheet.delete_rule(0).unwrap();
assert_eq!(sheet.rule_count(), 0);
}
#[test]
fn test_insert_rule_out_of_bounds() {
let mut sheet = CSSStyleSheet::new();
let result = sheet.insert_rule(".class { color: red; }", 5);
assert!(result.is_err());
}
#[test]
fn test_delete_rule_out_of_bounds() {
let mut sheet = CSSStyleSheet::new();
let result = sheet.delete_rule(0);
assert!(result.is_err());
}
#[test]
fn test_disabled_stylesheet() {
let mut sheet = CSSStyleSheet::new();
sheet.set_disabled(true);
let result = sheet.insert_rule(".class { color: red; }", 0);
assert!(result.is_err());
}
#[test]
fn test_add_rule() {
let mut sheet = CSSStyleSheet::new();
sheet.add_rule(".class1 { color: red; }").unwrap();
sheet.add_rule(".class2 { color: blue; }").unwrap();
assert_eq!(sheet.rule_count(), 2);
}
#[test]
fn test_get_css_text() {
let mut sheet = CSSStyleSheet::new();
sheet.insert_rule(".class { color: red; }", 0).unwrap();
let css_text = sheet.get_css_text();
assert!(css_text.contains(".class"));
assert!(css_text.contains("color: red"));
}
#[test]
fn test_clear() {
let mut sheet = CSSStyleSheet::new();
sheet.insert_rule(".class1 { color: red; }", 0).unwrap();
sheet.insert_rule(".class2 { color: blue; }", 1).unwrap();
sheet.clear();
assert_eq!(sheet.rule_count(), 0);
}
#[test]
fn test_internal_stylesheet() {
let mut sheet = CSSStyleSheet::new();
sheet.insert_rule(".class { color: red; }", 0).unwrap();
let internal = sheet.internal_stylesheet();
assert_eq!(internal.rules.len(), 1);
assert_eq!(internal.rules[0].selector.text, ".class");
}
}