tailwind_rs_postcss/css_optimizer/
rule_merger.rs1use regex::Regex;
4use std::collections::HashMap;
5use super::types::*;
6
7pub struct RuleMerger {
9 compatibility_matrix: HashMap<String, Vec<String>>,
10}
11
12impl RuleMerger {
13 pub fn new() -> Self {
15 Self {
16 compatibility_matrix: Self::build_compatibility_matrix(),
17 }
18 }
19
20 pub fn merge_rules(&self, css: &str) -> Result<RuleMergeResult, OptimizationError> {
22 let start_time = std::time::Instant::now();
23 let mut rules_merged = 0;
24 let mut optimized_css = css.to_string();
25
26 let duplicate_selectors = self.find_duplicate_selectors(css)?;
28
29 for (selector, rules) in duplicate_selectors {
30 if rules.len() > 1 {
31 let merged_rule = self.merge_rule_properties(&rules)?;
32 optimized_css = self.replace_duplicate_rules(&optimized_css, &selector, &merged_rule)?;
33 rules_merged += 1;
34 }
35 }
36
37 Ok(RuleMergeResult {
38 optimized_css,
39 rules_merged,
40 merge_time: start_time.elapsed(),
41 })
42 }
43
44 fn find_duplicate_selectors(&self, css: &str) -> Result<HashMap<String, Vec<CSSRule>>, OptimizationError> {
46 let mut selector_map: HashMap<String, Vec<CSSRule>> = HashMap::new();
47 let rule_pattern = Regex::new(r"([^{]+)\s*\{([^}]+)\}").unwrap();
48
49 for cap in rule_pattern.captures_iter(css) {
50 let selector = cap[1].trim().to_string();
51 let properties = cap[2].trim();
52
53 let rule = CSSRule {
54 selector: selector.clone(),
55 properties: self.parse_properties(properties)?,
56 };
57
58 selector_map.entry(selector).or_insert_with(Vec::new).push(rule);
59 }
60
61 Ok(selector_map)
62 }
63
64 fn merge_rule_properties(&self, rules: &[CSSRule]) -> Result<CSSRule, OptimizationError> {
66 let mut merged_properties = Vec::new();
67 let mut seen_properties: HashMap<String, String> = HashMap::new();
68
69 for rule in rules {
70 for property in &rule.properties {
71 seen_properties.insert(property.name.clone(), property.value.clone());
72 }
73 }
74
75 for (name, value) in seen_properties {
76 merged_properties.push(CSSProperty { name, value });
77 }
78
79 Ok(CSSRule {
80 selector: rules[0].selector.clone(),
81 properties: merged_properties,
82 })
83 }
84
85 fn replace_duplicate_rules(&self, css: &str, selector: &str, merged_rule: &CSSRule) -> Result<String, OptimizationError> {
87 let mut result = css.to_string();
88 let rule_pattern = Regex::new(&format!(r"{}[^{{]*\{{[^}}]*\}}", regex::escape(selector))).unwrap();
89
90 result = rule_pattern.replace_all(&result, "").to_string();
92
93 let merged_css = format!("{} {{\n{}\n}}", merged_rule.selector,
95 merged_rule.properties.iter()
96 .map(|p| format!(" {}: {};", p.name, p.value))
97 .collect::<Vec<_>>()
98 .join("\n"));
99
100 result.push_str(&merged_css);
101 Ok(result)
102 }
103
104 fn parse_properties(&self, properties_str: &str) -> Result<Vec<CSSProperty>, OptimizationError> {
106 let mut properties = Vec::new();
107 let property_pattern = Regex::new(r"([^:]+):\s*([^;]+);").unwrap();
108
109 for cap in property_pattern.captures_iter(properties_str) {
110 properties.push(CSSProperty {
111 name: cap[1].trim().to_string(),
112 value: cap[2].trim().to_string(),
113 });
114 }
115
116 Ok(properties)
117 }
118
119 fn build_compatibility_matrix() -> HashMap<String, Vec<String>> {
121 let mut matrix = HashMap::new();
122
123 matrix.insert("class".to_string(), vec![
125 "class".to_string(),
126 "element".to_string(),
127 ]);
128
129 matrix.insert("element".to_string(), vec![
131 "element".to_string(),
132 "class".to_string(),
133 ]);
134
135 matrix
136 }
137}