1use serde::{Deserialize, Serialize};
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum CSSNode {
12 Stylesheet(Vec<CSSRule>),
14 Rule(CSSRule),
16 Declaration(CSSDeclaration),
18 AtRule(CSSAtRule),
20 Comment(String),
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct CSSRule {
27 pub selector: String,
29 pub declarations: Vec<CSSDeclaration>,
31 pub nested_rules: Vec<CSSRule>,
33 pub media_query: Option<String>,
35 pub specificity: u32,
37 pub position: Option<SourcePosition>,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43pub struct CSSDeclaration {
44 pub property: String,
46 pub value: String,
48 pub important: bool,
50 pub position: Option<SourcePosition>,
52}
53
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub struct CSSAtRule {
57 pub name: String,
59 pub params: String,
61 pub body: Vec<CSSNode>,
63 pub position: Option<SourcePosition>,
65}
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69pub struct SourcePosition {
70 pub line: usize,
72 pub column: usize,
74 pub source: Option<String>,
76}
77
78#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80pub enum SelectorComponent {
81 Class(String),
83 Id(String),
85 Element(String),
87 Attribute(AttributeSelector),
89 PseudoClass(String),
91 PseudoElement(String),
93 Universal,
95 Combinator(CombinatorType),
97 Group(Vec<SelectorComponent>),
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct AttributeSelector {
104 pub name: String,
105 pub operator: AttributeOperator,
106 pub value: Option<String>,
107 pub case_sensitive: bool,
108}
109
110#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
112pub enum AttributeOperator {
113 Exists,
115 Equals,
117 ContainsWord,
119 StartsWith,
121 StartsWithPrefix,
123 EndsWith,
125 Contains,
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub enum CombinatorType {
132 Descendant,
134 Child,
136 AdjacentSibling,
138 GeneralSibling,
140}
141
142impl CSSRule {
144 pub fn calculate_specificity(&self) -> u32 {
146 let mut specificity = 0u32;
147
148 let id_count = self.selector.matches('#').count();
150 specificity += (id_count as u32) * 100;
151
152 let class_count = self.selector.matches('.').count();
154 let attribute_count = self.selector.matches('[').count();
155 let pseudo_class_count = self.selector.matches(':').count() - self.selector.matches("::").count();
156 specificity += ((class_count + attribute_count + pseudo_class_count) as u32) * 10;
157
158 let element_count = self.selector.split_whitespace()
160 .filter(|s| !s.starts_with('.') && !s.starts_with('#') && !s.starts_with('[') && !s.starts_with(':'))
161 .count();
162 specificity += element_count as u32;
163
164 specificity
165 }
166
167 pub fn matches_selector(&self, target_selector: &str) -> bool {
169 self.selector == target_selector
170 }
171
172 pub fn add_declaration(&mut self, property: String, value: String, important: bool) {
174 let declaration = CSSDeclaration {
175 property,
176 value,
177 important,
178 position: None,
179 };
180 self.declarations.push(declaration);
181 }
182
183 pub fn remove_declaration(&mut self, property: &str) {
185 self.declarations.retain(|decl| decl.property != property);
186 }
187
188 pub fn get_declaration(&self, property: &str) -> Option<&CSSDeclaration> {
190 self.declarations.iter().find(|decl| decl.property == property)
191 }
192
193 pub fn has_property(&self, property: &str) -> bool {
195 self.declarations.iter().any(|decl| decl.property == property)
196 }
197}
198
199impl CSSDeclaration {
200 pub fn new(property: String, value: String) -> Self {
202 Self {
203 property,
204 value,
205 important: false,
206 position: None,
207 }
208 }
209
210 pub fn new_important(property: String, value: String) -> Self {
212 Self {
213 property,
214 value,
215 important: true,
216 position: None,
217 }
218 }
219
220 pub fn set_important(&mut self) {
222 self.important = true;
223 }
224
225 pub fn is_important(&self) -> bool {
227 self.important
228 }
229}
230
231impl CSSAtRule {
232 pub fn new(name: String, params: String) -> Self {
234 Self {
235 name,
236 params,
237 body: Vec::new(),
238 position: None,
239 }
240 }
241
242 pub fn add_rule(&mut self, rule: CSSRule) {
244 self.body.push(CSSNode::Rule(rule));
245 }
246
247 pub fn add_declaration(&mut self, declaration: CSSDeclaration) {
249 self.body.push(CSSNode::Declaration(declaration));
250 }
251}
252
253impl CSSNode {
255 pub fn get_rules(&self) -> Vec<&CSSRule> {
257 match self {
258 CSSNode::Stylesheet(rules) => rules.iter().collect(),
259 CSSNode::Rule(rule) => vec![rule],
260 _ => Vec::new(),
261 }
262 }
263
264 pub fn get_declarations(&self) -> Vec<&CSSDeclaration> {
266 match self {
267 CSSNode::Rule(rule) => rule.declarations.iter().collect(),
268 CSSNode::Declaration(decl) => vec![decl],
269 _ => Vec::new(),
270 }
271 }
272
273 pub fn find_rules_by_selector(&self, selector: &str) -> Vec<&CSSRule> {
275 self.get_rules()
276 .into_iter()
277 .filter(|rule| rule.matches_selector(selector))
278 .collect()
279 }
280
281 pub fn find_rules_by_property(&self, property: &str) -> Vec<&CSSRule> {
283 self.get_rules()
284 .into_iter()
285 .filter(|rule| rule.has_property(property))
286 .collect()
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
295 fn test_css_rule_creation() {
296 let rule = CSSRule {
297 selector: ".test".to_string(),
298 declarations: vec![
299 CSSDeclaration::new("color".to_string(), "red".to_string()),
300 CSSDeclaration::new("font-size".to_string(), "16px".to_string()),
301 ],
302 nested_rules: Vec::new(),
303 media_query: None,
304 specificity: 0,
305 position: None,
306 };
307
308 assert_eq!(rule.selector, ".test");
309 assert_eq!(rule.declarations.len(), 2);
310 assert!(rule.has_property("color"));
311 assert!(!rule.has_property("background"));
312 }
313
314 #[test]
315 fn test_specificity_calculation() {
316 let rule = CSSRule {
317 selector: "#id .class div".to_string(),
318 declarations: Vec::new(),
319 nested_rules: Vec::new(),
320 media_query: None,
321 specificity: 0,
322 position: None,
323 };
324
325 let specificity = rule.calculate_specificity();
326 assert_eq!(specificity, 111);
328 }
329
330 #[test]
331 fn test_declaration_creation() {
332 let decl = CSSDeclaration::new_important("color".to_string(), "red".to_string());
333 assert_eq!(decl.property, "color");
334 assert_eq!(decl.value, "red");
335 assert!(decl.is_important());
336 }
337
338 #[test]
339 fn test_at_rule_creation() {
340 let mut at_rule = CSSAtRule::new("media".to_string(), "(max-width: 768px)".to_string());
341 at_rule.add_rule(CSSRule {
342 selector: ".mobile".to_string(),
343 declarations: vec![CSSDeclaration::new("display".to_string(), "block".to_string())],
344 nested_rules: Vec::new(),
345 media_query: None,
346 specificity: 0,
347 position: None,
348 });
349
350 assert_eq!(at_rule.name, "media");
351 assert_eq!(at_rule.params, "(max-width: 768px)");
352 assert_eq!(at_rule.body.len(), 1);
353 }
354}