use std::collections::HashMap;
#[derive(Debug, Clone)]
struct CSSPropertyValue {
value: String,
important: bool,
}
#[derive(Debug, Clone)]
pub struct CSSStyleDeclaration {
properties: HashMap<String, CSSPropertyValue>,
#[allow(dead_code)]
parent_stylesheet: Option<String>,
}
impl CSSStyleDeclaration {
pub fn new() -> Self {
Self {
properties: HashMap::new(),
parent_stylesheet: None,
}
}
pub fn from_declarations(declarations: &[crate::css::Declaration]) -> Self {
let mut properties = HashMap::new();
for decl in declarations {
properties.insert(
decl.property.clone(),
CSSPropertyValue {
value: decl.value.clone(),
important: false, },
);
}
Self {
properties,
parent_stylesheet: None,
}
}
pub fn get_property_value(&self, property: &str) -> String {
self.properties
.get(property)
.map(|p| p.value.clone())
.unwrap_or_default()
}
pub fn get_property_priority(&self, property: &str) -> String {
self.properties
.get(property)
.map(|p| if p.important { "important" } else { "" })
.unwrap_or_default()
.to_string()
}
pub fn set_property(&mut self, property: &str, value: &str, priority: &str) {
let important = priority.to_lowercase() == "important";
self.properties.insert(
property.to_lowercase(),
CSSPropertyValue {
value: value.to_string(),
important,
},
);
}
pub fn remove_property(&mut self, property: &str) -> String {
self.properties
.remove(&property.to_lowercase())
.map(|p| p.value)
.unwrap_or_default()
}
pub fn length(&self) -> usize {
self.properties.len()
}
pub fn item(&self, index: usize) -> Option<String> {
self.properties.keys().nth(index).cloned()
}
pub fn get_css_text(&self) -> String {
self.properties
.iter()
.map(|(k, v)| {
let important = if v.important { " !important" } else { "" };
format!("{}: {}{}", k, v.value, important)
})
.collect::<Vec<_>>()
.join("; ")
}
pub fn set_css_text(&mut self, text: &str) {
self.properties.clear();
for declaration in text.split(';') {
let declaration = declaration.trim();
if declaration.is_empty() {
continue;
}
if let Some(colon_pos) = declaration.find(':') {
let property = declaration[..colon_pos].trim().to_lowercase();
let mut value_part = declaration[colon_pos + 1..].trim();
let important = value_part.ends_with("!important");
if important {
value_part = value_part[..value_part.len() - 10].trim();
}
if !property.is_empty() && !value_part.is_empty() {
self.properties.insert(
property,
CSSPropertyValue {
value: value_part.to_string(),
important,
},
);
}
}
}
}
pub fn get_property_names(&self) -> Vec<String> {
self.properties.keys().cloned().collect()
}
pub fn has_property(&self, property: &str) -> bool {
self.properties.contains_key(&property.to_lowercase())
}
pub fn clear(&mut self) {
self.properties.clear();
}
pub fn merge(&mut self, other: &CSSStyleDeclaration) {
for (key, value) in &other.properties {
if !self.properties.contains_key(key) {
self.properties.insert(key.clone(), value.clone());
}
}
}
pub fn to_declarations(&self) -> Vec<crate::css::Declaration> {
self.properties
.iter()
.map(|(k, v)| crate::css::Declaration {
property: k.clone(),
value: v.value.clone(),
})
.collect()
}
}
impl Default for CSSStyleDeclaration {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_get_property() {
let mut style = CSSStyleDeclaration::new();
style.set_property("color", "red", "");
assert_eq!(style.get_property_value("color"), "red");
}
#[test]
fn test_important_priority() {
let mut style = CSSStyleDeclaration::new();
style.set_property("color", "red", "important");
assert_eq!(style.get_property_priority("color"), "important");
}
#[test]
fn test_remove_property() {
let mut style = CSSStyleDeclaration::new();
style.set_property("color", "red", "");
let removed = style.remove_property("color");
assert_eq!(removed, "red");
assert!(!style.has_property("color"));
}
#[test]
fn test_css_text() {
let mut style = CSSStyleDeclaration::new();
style.set_property("color", "red", "");
style.set_property("font-weight", "bold", "important");
let css_text = style.get_css_text();
assert!(css_text.contains("color: red"));
assert!(css_text.contains("font-weight: bold !important"));
}
#[test]
fn test_set_css_text() {
let mut style = CSSStyleDeclaration::new();
style.set_css_text("color: red; font-size: 16px !important");
assert_eq!(style.get_property_value("color"), "red");
assert_eq!(style.get_property_value("font-size"), "16px");
assert_eq!(style.get_property_priority("font-size"), "important");
}
#[test]
fn test_length_and_item() {
let mut style = CSSStyleDeclaration::new();
style.set_property("color", "red", "");
style.set_property("font-size", "16px", "");
assert_eq!(style.length(), 2);
assert!(style.item(0).is_some());
}
#[test]
fn test_merge() {
let mut style1 = CSSStyleDeclaration::new();
style1.set_property("color", "red", "");
let mut style2 = CSSStyleDeclaration::new();
style2.set_property("font-size", "16px", "");
style1.merge(&style2);
assert_eq!(style1.get_property_value("color"), "red");
assert_eq!(style1.get_property_value("font-size"), "16px");
}
#[test]
fn test_clear() {
let mut style = CSSStyleDeclaration::new();
style.set_property("color", "red", "");
style.clear();
assert_eq!(style.length(), 0);
}
}