vertigo/css/transform_css/
mod.rs

1use super::{get_selector::get_selector, next_id::NextId};
2
3mod splits;
4use splits::{css_row_split_to_pair, css_split_rows, find_brackets};
5
6#[cfg(test)]
7mod tests;
8
9pub fn transform_css_animation_value(
10    css: &str,
11    next_id: &NextId,
12) -> (String, Option<(String, String)>) {
13    let brackets = find_brackets(css);
14
15    if let Some((start_word, central_word, end_word)) = brackets {
16        let id = next_id.get_next_id();
17        let selector = get_selector(&id);
18
19        let keyframe_name = ["@keyframes ", &selector].concat();
20        let keyframe_content = central_word;
21
22        let new_css = [start_word, &selector, end_word].join(" ");
23
24        return (new_css, Some((keyframe_name, keyframe_content.into())));
25    }
26
27    (css.into(), None)
28}
29
30pub fn transform_css_selector_value(row: &str, parent_selector: &str) -> Option<(String, String)> {
31    let brackets = find_brackets(row);
32
33    if let Some((pseudo_selector, rules, trash)) = brackets {
34        if !trash.trim().is_empty() {
35            log::error!("Unexpected input after pseudo-selector rule set, missing semi-colon?");
36        }
37        let new_selector = [parent_selector, pseudo_selector].concat();
38        return Some((new_selector, rules.into()));
39    }
40
41    None
42}
43
44pub fn transform_css_media_query(
45    row: &str,
46    parent_selector: &str,
47    css_documents: &mut Vec<(String, String)>,
48) {
49    let brackets = find_brackets(row);
50
51    if let Some((query, rules_input, trash)) = brackets {
52        if !trash.trim().is_empty() {
53            log::error!("Unexpected input after media query, missing semicolon?");
54        }
55
56        // Collected rules
57        let mut regular_rules = vec![];
58        // Collected sets of rules inside pseudo-selectors
59        let mut rules_in_pseudo = vec![];
60
61        for row in css_split_rows(rules_input) {
62            if row.starts_with(':') {
63                // Pseudo selectors inside media query, collect it into separate vector
64                let extra_rule = transform_css_selector_value(row, parent_selector);
65
66                if let Some(extra_rule) = extra_rule {
67                    rules_in_pseudo.push([&extra_rule.0, " { ", &extra_rule.1, " }"].concat());
68                }
69            } else {
70                // Regular rule in media query
71                // TODO: Handle animation here
72                regular_rules.push(row);
73            }
74        }
75
76        if !regular_rules.is_empty() || !rules_in_pseudo.is_empty() {
77            let mut media_content = "".to_string();
78            // Insert regular rules first
79            if !regular_rules.is_empty() {
80                // selector { rules; }
81                media_content.push_str(parent_selector);
82                media_content.push_str(" { ");
83                media_content.push_str(&regular_rules.join(";"));
84                media_content.push_str(" }");
85            }
86            // Add sets of rules in pseudo selectors
87            for set in &rules_in_pseudo {
88                if !media_content.is_empty() {
89                    media_content.push('\n');
90                }
91                media_content.push_str(set);
92            }
93            css_documents.push((query.into(), media_content));
94        }
95    }
96}
97
98pub fn transform_css(css: &str, next_id: &NextId) -> (u64, Vec<(String, String)>) {
99    let class_id = next_id.get_next_id();
100    let selector = [".", &get_selector(&class_id)].concat();
101
102    let mut css_out: Vec<String> = Vec::new();
103    let mut css_documents: Vec<(String, String)> = Vec::new();
104
105    for row in css_split_rows(css) {
106        if row.starts_with([':', '[']) {
107            // It's a pseudo-selector or references Css
108            let extra_rule = transform_css_selector_value(row, &selector);
109
110            if let Some(extra_rule) = extra_rule {
111                css_documents.push(extra_rule);
112            }
113        } else if row.starts_with("@media") {
114            // It's a set of rules inside media query.
115            // Flush regular ones first as order matters here (media queries don't modify specificity)
116            if !css_out.is_empty() {
117                css_documents.push((selector.clone(), css_out.join("; ")));
118                css_out.clear();
119            }
120            transform_css_media_query(row, &selector, &mut css_documents);
121        } else {
122            // Single rule
123            match css_row_split_to_pair(row) {
124                Some((name, value)) => {
125                    let value_parsed = if name.trim() == "animation" {
126                        // Animation rule
127                        let (value_parsed, extra_animation) =
128                            transform_css_animation_value(&value, next_id);
129
130                        if let Some(extra_animation) = extra_animation {
131                            css_documents.push(extra_animation);
132                        }
133
134                        value_parsed
135                    } else {
136                        // Regular rule
137                        value
138                    };
139
140                    css_out.push([name, ": ", &value_parsed].concat());
141                }
142                None => {
143                    css_out.push(row.into());
144                }
145            }
146        }
147    }
148
149    // Flush all remaining regular rules
150    if !css_out.is_empty() {
151        let css_out: String = css_out.join("; ");
152        css_documents.push((selector, css_out));
153    }
154
155    (class_id, css_documents)
156}