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 =
156 self.selector.matches(':').count() - self.selector.matches("::").count();
157 specificity += ((class_count + attribute_count + pseudo_class_count) as u32) * 10;
158
159 let element_count = self
161 .selector
162 .split_whitespace()
163 .filter(|s| {
164 !s.starts_with('.')
165 && !s.starts_with('#')
166 && !s.starts_with('[')
167 && !s.starts_with(':')
168 })
169 .count();
170 specificity += element_count as u32;
171
172 specificity
173 }
174
175 pub fn matches_selector(&self, target_selector: &str) -> bool {
177 self.selector == target_selector
178 }
179
180 pub fn add_declaration(&mut self, property: String, value: String, important: bool) {
182 let declaration = CSSDeclaration {
183 property,
184 value,
185 important,
186 position: None,
187 };
188 self.declarations.push(declaration);
189 }
190
191 pub fn remove_declaration(&mut self, property: &str) {
193 self.declarations.retain(|decl| decl.property != property);
194 }
195
196 pub fn get_declaration(&self, property: &str) -> Option<&CSSDeclaration> {
198 self.declarations
199 .iter()
200 .find(|decl| decl.property == property)
201 }
202
203 pub fn has_property(&self, property: &str) -> bool {
205 self.declarations
206 .iter()
207 .any(|decl| decl.property == property)
208 }
209}
210
211impl CSSDeclaration {
212 pub fn new(property: String, value: String) -> Self {
214 Self {
215 property,
216 value,
217 important: false,
218 position: None,
219 }
220 }
221
222 pub fn new_important(property: String, value: String) -> Self {
224 Self {
225 property,
226 value,
227 important: true,
228 position: None,
229 }
230 }
231
232 pub fn set_important(&mut self) {
234 self.important = true;
235 }
236
237 pub fn is_important(&self) -> bool {
239 self.important
240 }
241}
242
243impl CSSAtRule {
244 pub fn new(name: String, params: String) -> Self {
246 Self {
247 name,
248 params,
249 body: Vec::new(),
250 position: None,
251 }
252 }
253
254 pub fn add_rule(&mut self, rule: CSSRule) {
256 self.body.push(CSSNode::Rule(rule));
257 }
258
259 pub fn add_declaration(&mut self, declaration: CSSDeclaration) {
261 self.body.push(CSSNode::Declaration(declaration));
262 }
263}
264
265impl CSSNode {
267 pub fn get_rules(&self) -> Vec<&CSSRule> {
269 match self {
270 CSSNode::Stylesheet(rules) => rules.iter().collect(),
271 CSSNode::Rule(rule) => vec![rule],
272 _ => Vec::new(),
273 }
274 }
275
276 pub fn get_declarations(&self) -> Vec<&CSSDeclaration> {
278 match self {
279 CSSNode::Rule(rule) => rule.declarations.iter().collect(),
280 CSSNode::Declaration(decl) => vec![decl],
281 _ => Vec::new(),
282 }
283 }
284
285 pub fn find_rules_by_selector(&self, selector: &str) -> Vec<&CSSRule> {
287 self.get_rules()
288 .into_iter()
289 .filter(|rule| rule.matches_selector(selector))
290 .collect()
291 }
292
293 pub fn find_rules_by_property(&self, property: &str) -> Vec<&CSSRule> {
295 self.get_rules()
296 .into_iter()
297 .filter(|rule| rule.has_property(property))
298 .collect()
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn test_css_rule_creation() {
308 let rule = CSSRule {
309 selector: ".test".to_string(),
310 declarations: vec![
311 CSSDeclaration::new("color".to_string(), "red".to_string()),
312 CSSDeclaration::new("font-size".to_string(), "16px".to_string()),
313 ],
314 nested_rules: Vec::new(),
315 media_query: None,
316 specificity: 0,
317 position: None,
318 };
319
320 assert_eq!(rule.selector, ".test");
321 assert_eq!(rule.declarations.len(), 2);
322 assert!(rule.has_property("color"));
323 assert!(!rule.has_property("background"));
324 }
325
326 #[test]
327 fn test_specificity_calculation() {
328 let rule = CSSRule {
329 selector: "#id .class div".to_string(),
330 declarations: Vec::new(),
331 nested_rules: Vec::new(),
332 media_query: None,
333 specificity: 0,
334 position: None,
335 };
336
337 let specificity = rule.calculate_specificity();
338 assert_eq!(specificity, 111);
340 }
341
342 #[test]
343 fn test_declaration_creation() {
344 let decl = CSSDeclaration::new_important("color".to_string(), "red".to_string());
345 assert_eq!(decl.property, "color");
346 assert_eq!(decl.value, "red");
347 assert!(decl.is_important());
348 }
349
350 #[test]
351 fn test_at_rule_creation() {
352 let mut at_rule = CSSAtRule::new("media".to_string(), "(max-width: 768px)".to_string());
353 at_rule.add_rule(CSSRule {
354 selector: ".mobile".to_string(),
355 declarations: vec![CSSDeclaration::new(
356 "display".to_string(),
357 "block".to_string(),
358 )],
359 nested_rules: Vec::new(),
360 media_query: None,
361 specificity: 0,
362 position: None,
363 });
364
365 assert_eq!(at_rule.name, "media");
366 assert_eq!(at_rule.params, "(max-width: 768px)");
367 assert_eq!(at_rule.body.len(), 1);
368 }
369}