1use super::{GenerationError, MarkupFormatter, Formatter};
3use crate::parse::parser::{ParseNode, ParseTree};
4
5use std::slice::Iter;
6
7#[derive(Debug, Clone)]
8pub struct CSSFormatter<'a> {
9 pub tree: ParseTree<'a>,
10}
11
12impl<'a> CSSFormatter<'a> {
13 pub fn new(tree: ParseTree<'a>) -> Self {
14 Self { tree }
15 }
16}
17
18pub const DEFAULT: &str = "\n";
19
20const CSS_FUNCTIONS: [&str; 58] = [
22 "attr", "blur", "brightness", "calc", "circle", "color", "contrast",
23 "counter", "counters", "cubic-bezier", "drop-shadow", "ellipse", "format",
24 "grayscale", "hsl", "hsla", "hue-rotate", "hwb", "image", "inset",
25 "invert", "lab", "lch", "linear-gradient", "matrix", "matrix3d",
26 "opacity", "perspective", "polygon", "radial-gradient",
27 "repeating-linear-gradient", "repeating-radial-gradient",
28 "rgb", "rgba", "rotate", "rotate3d", "rotateX",
29 "rotateY", "rotateZ", "saturate", "sepia", "scale", "scale3d",
30 "scaleX", "scaleY", "scaleZ", "skew", "skewX", "skewY", "symbols",
31 "translate", "translate3d", "translateX", "translateY", "translateZ",
32 "url", "var", "supports"
33];
34
35const CSS_COMMA_DELIM: [&str; 2] = ["rgba", "hsla"];
38
39const CSS_SPECIAL_SELECTORS: [&str; 12]
43 = [ "@charset" , "@counter-style" , "@document"
44 , "@font-face" , "@font-feature-values" , "@import"
45 , "@keyframes" , "@media" , "@namespace"
46 , "@page" , "@property" , "@supports"
47 ];
48
49const CSS_ONELINE_RULES: [&str; 3]
51 = [ CSS_SPECIAL_SELECTORS[0] , CSS_SPECIAL_SELECTORS[5] , CSS_SPECIAL_SELECTORS[8] ];
55
56const CSS_NON_NESTED_SELECTORS: [&str; 3]
58 = [ CSS_SPECIAL_SELECTORS[3] , CSS_SPECIAL_SELECTORS[9] , CSS_SPECIAL_SELECTORS[10] ];
62
63const BINARY_OPERATORS: [&str; 4] = ["+", "-", "*", "/"];
66
67fn convert_value<'a>(node: &'a ParseNode<'a>) -> Result<String, GenerationError<'a>> {
68 match node {
69 ParseNode::List { nodes: list, .. } => {
70 let result = match &**list {
71 [head, tail@..] => {
72 let head = convert_value(head)?;
73
74 let mut tail_tmp = vec![String::new(); tail.len()];
75 for (i, e) in tail.iter().enumerate() {
76 tail_tmp[i] = convert_value(e)?
77 }
78 let tail = tail_tmp.as_slice();
79
80 let delim = if CSS_COMMA_DELIM.contains(&head.as_str()) {
81 ", "
82 } else {
83 " "
84 };
85 let args = tail.join(delim);
86 let args = args.trim();
87 if CSS_FUNCTIONS.contains(&head.as_str()) {
88 format!("{}({})", head, args)
89 } else if BINARY_OPERATORS.contains(&head.as_str()) {
90 let args = tail
91 .join(&format!(" {} ", head));
92 format!("({})", args)
93 } else {
94 format!("{} {}", head, args)
95 }
96 },
97 [] => String::from("")
98 };
99 Ok(result)
100 },
101 ParseNode::Number(node)
102 | ParseNode::Symbol(node)
103 | ParseNode::String(node) =>
104 Ok(if node.value.chars().any(|c| c.is_whitespace()) {
105 format!("{:?}", node.value)
106 } else {
107 node.value.to_owned()
108 }),
109 ParseNode::Raw(node) => {
110 Ok(node.value.to_owned())
111 },
112 ParseNode::Attribute { .. } => Err(GenerationError::new("CSS-value",
113 "Incompatible structure (attribute) found in CSS \
114 property value.",
115 &node.site()))
116 }
117}
118
119pub fn css_value<'a>(_property: &str, node: &'a ParseNode<'a>)
123-> Result<String, GenerationError<'a>> {
124 convert_value(node)
127}
128
129fn generate_special_selector<'a>(f: Formatter,
136 selector: &str,
137 mut arguments: Iter<'a, ParseNode<'a>>)
138-> Result<(), GenerationError<'a>> {
139 let mut parsing_rules: bool = false;
140 let unexpected_node = |node: &'a ParseNode<'a>, rules: bool| {
141 if rules {
142 Err(GenerationError::new("CSS",
143 "Expected list (i.e. a CSS rule) here!",
144 &node.site()))
145 } else {
146 Ok(())
147 }
148 };
149 if CSS_ONELINE_RULES.contains(&selector) {
151 write!(f, "{} ", selector)?;
152 arguments.next(); for arg in arguments {
154 match arg {
155 ParseNode::Attribute { ref keyword, node, .. } => {
156 write!(f, "({}: {}) ", keyword, css_value(keyword, &*node)?)?;
157 },
158 _ => write!(f, "{} ", css_value(selector, arg)?)?
159 }
160 }
161 writeln!(f, ";")?;
162 return Ok(());
163 }
164
165 write!(f, "{} ", selector)?;
167 arguments.next(); for arg in arguments {
170 match arg {
171 ParseNode::Attribute { ref keyword, node, .. } => {
172 unexpected_node(&arg, parsing_rules)?;
173 write!(f, "({}: {}) ", keyword, css_value(keyword, &*node)?)?;
174 },
175 ParseNode::List { nodes: ref rule, leading_whitespace, .. } => {
176 if !parsing_rules {
178 writeln!(f, "{{")?;
179 }
180 parsing_rules = true;
181 write!(f, "{}", leading_whitespace)?;
182 generate_css_rule(f, rule.into_iter())?;
183 },
184 _ => {
185 unexpected_node(&arg, parsing_rules)?;
186 write!(f, "{} ", css_value(selector, arg)?)?;
187 }
188 }
189 }
190 write!(f, "}}")?;
191 Ok(())
192}
193
194fn generate_css_rule<'a>(f: Formatter, iter: Iter<'a, ParseNode<'a>>) -> Result<(), GenerationError<'a>> {
195 let mut prop_i = 0; let mut selectors = iter.clone()
199 .take_while(|n| { prop_i += 1; n.atomic().is_some() })
200 .map(|n| n.atomic().unwrap()) .peekable();
202
203 let head = if let Some(head) = selectors.next() {
206 head
207 } else {
208 return Err(GenerationError::new("CSS",
209 "CSS selector(s) missing. \
210 Expected a symbol/identifier node, none was found!",
211 &selectors.peek().unwrap().site));
212 };
213
214 if CSS_SPECIAL_SELECTORS.contains(&head.value.as_ref())
216 && !CSS_NON_NESTED_SELECTORS.contains(&head.value.as_ref()) {
217 return generate_special_selector(f, &head.value, iter);
219 }
220
221 write!(f, "{} ", head.value)?;
223 for selector in selectors {
224 write!(f, "{} ", selector.value)?;
225 }
226 writeln!(f, "{{")?;
227
228 let properties = iter.skip(prop_i - 1);
229
230 for property in properties {
231 let ParseNode::Attribute { ref node, ref keyword, .. } = property else {
232 return Err(GenerationError::new("CSS",
233 "CSS property-value pairs must be in the \
234 form of attributes, i.e. `:property value`.",
235 &property.site()));
236 };
237 writeln!(f, " {}: {};", keyword, css_value(keyword, node)?)?;
238 }
239 write!(f, "}}")?;
240 Ok(())
241}
242
243impl<'a> MarkupFormatter for CSSFormatter<'a> {
244 fn document(&self) -> Result<String, GenerationError> {
245 let mut doc = String::new();
246 if self.tree.is_empty() {
247 return Ok(String::from(DEFAULT));
248 }
249 doc += &self.display()?;
250 Ok(doc)
251 }
252
253 fn generate(&self, f: Formatter)
254 -> Result<(), GenerationError> {
255 let mut tree_iter = self.tree.iter().peekable();
256 while let Some(node) = tree_iter.next() {
257 match node {
258 ParseNode::List { nodes: list, leading_whitespace, .. } => {
259 write!(f, "{}", leading_whitespace)?;
260 generate_css_rule(f, list.into_iter())?;
261 },
262 ParseNode::Attribute { site, .. } => {
263 return Err(GenerationError::new("CSS",
264 "Attribute not expected here, CSS documents \
265 are supposed to be a series of selectors \
266 and property-value pairs, wrapped in parentheses.",
267 &site.to_owned()));
268 },
269 ParseNode::Symbol(node)
270 | ParseNode::Number(node)
271 | ParseNode::String(node)
272 | ParseNode::Raw(node) => {
273 let site = node.site.to_owned();
274 return Err(GenerationError::new("CSS",
275 "Symbolic node not expected here, CSS documents \
276 are supposed to be a series of selectors \
277 and property-value pairs, wrapped in parentheses.",
278 &site));
279 }
280 }
281 }
282 Ok(())
283 }
284}