use super::{GenerationError, MarkupFormatter, Formatter};
use crate::parse::parser::{ParseNode, ParseTree};
use std::slice::Iter;
#[derive(Debug, Clone)]
pub struct CSSFormatter<'a> {
pub tree: ParseTree<'a>,
}
impl<'a> CSSFormatter<'a> {
pub fn new(tree: ParseTree<'a>) -> Self {
Self { tree }
}
}
pub const DEFAULT: &str = "\n";
const CSS_FUNCTIONS: [&str; 58] = [
"attr", "blur", "brightness", "calc", "circle", "color", "contrast",
"counter", "counters", "cubic-bezier", "drop-shadow", "ellipse", "format",
"grayscale", "hsl", "hsla", "hue-rotate", "hwb", "image", "inset",
"invert", "lab", "lch", "linear-gradient", "matrix", "matrix3d",
"opacity", "perspective", "polygon", "radial-gradient",
"repeating-linear-gradient", "repeating-radial-gradient",
"rgb", "rgba", "rotate", "rotate3d", "rotateX",
"rotateY", "rotateZ", "saturate", "sepia", "scale", "scale3d",
"scaleX", "scaleY", "scaleZ", "skew", "skewX", "skewY", "symbols",
"translate", "translate3d", "translateX", "translateY", "translateZ",
"url", "var", "supports"
];
const CSS_COMMA_DELIM: [&str; 2] = ["rgba", "hsla"];
const CSS_SPECIAL_SELECTORS: [&str; 12]
= [ "@charset" , "@counter-style" , "@document"
, "@font-face" , "@font-feature-values" , "@import"
, "@keyframes" , "@media" , "@namespace"
, "@page" , "@property" , "@supports"
];
const CSS_ONELINE_RULES: [&str; 3]
= [ CSS_SPECIAL_SELECTORS[0] , CSS_SPECIAL_SELECTORS[5] , CSS_SPECIAL_SELECTORS[8] ];
const CSS_NON_NESTED_SELECTORS: [&str; 3]
= [ CSS_SPECIAL_SELECTORS[3] , CSS_SPECIAL_SELECTORS[9] , CSS_SPECIAL_SELECTORS[10] ];
const BINARY_OPERATORS: [&str; 4] = ["+", "-", "*", "/"];
fn convert_value<'a>(node: &'a ParseNode<'a>) -> Result<String, GenerationError<'a>> {
match node {
ParseNode::List { nodes: list, .. } => {
let result = match &**list {
[head, tail@..] => {
let head = convert_value(head)?;
let mut tail_tmp = vec![String::new(); tail.len()];
for (i, e) in tail.iter().enumerate() {
tail_tmp[i] = convert_value(e)?
}
let tail = tail_tmp.as_slice();
let delim = if CSS_COMMA_DELIM.contains(&head.as_str()) {
", "
} else {
" "
};
let args = tail.join(delim);
let args = args.trim();
if CSS_FUNCTIONS.contains(&head.as_str()) {
format!("{}({})", head, args)
} else if BINARY_OPERATORS.contains(&head.as_str()) {
let args = tail
.join(&format!(" {} ", head));
format!("({})", args)
} else {
format!("{} {}", head, args)
}
},
[] => String::from("")
};
Ok(result)
},
ParseNode::Number(node)
| ParseNode::Symbol(node)
| ParseNode::String(node) =>
Ok(if node.value.chars().any(|c| c.is_whitespace()) {
format!("\"{}\"", node.value)
} else {
node.value.to_owned()
}),
ParseNode::Attribute { .. } => Err(GenerationError::new("CSS-value",
"Incompatible structure (attribute) found in CSS \
property value.",
&node.site()))
}
}
pub fn css_value<'a>(_property: &str, node: &'a ParseNode<'a>)
-> Result<String, GenerationError<'a>> {
convert_value(node)
}
fn generate_special_selector<'a>(f: Formatter,
selector: &str,
mut arguments: Iter<'a, ParseNode<'a>>)
-> Result<(), GenerationError<'a>> {
let mut parsing_rules: bool = false;
let unexpected_node = |node: &'a ParseNode<'a>, rules: bool| {
if rules {
Err(GenerationError::new("CSS",
"Expected list (i.e. a CSS rule) here!",
&node.site()))
} else {
Ok(())
}
};
if CSS_ONELINE_RULES.contains(&selector) {
write!(f, "{} ", selector)?;
arguments.next(); for arg in arguments {
match arg {
ParseNode::Attribute { ref keyword, node, .. } => {
write!(f, "({}: {}) ", keyword, css_value(keyword, &*node)?)?;
},
_ => write!(f, "{} ", css_value(selector, arg)?)?
}
}
writeln!(f, ";")?;
return Ok(());
}
write!(f, "{} ", selector)?;
arguments.next();
for arg in arguments {
match arg {
ParseNode::Attribute { ref keyword, node, .. } => {
unexpected_node(&arg, parsing_rules)?;
write!(f, "({}: {}) ", keyword, css_value(keyword, &*node)?)?;
},
ParseNode::List { nodes: ref rule, leading_whitespace, .. } => {
if !parsing_rules {
writeln!(f, "{{")?;
}
parsing_rules = true;
write!(f, "{}", leading_whitespace)?;
generate_css_rule(f, rule.into_iter())?;
},
_ => {
unexpected_node(&arg, parsing_rules)?;
write!(f, "{} ", css_value(selector, arg)?)?;
}
}
}
write!(f, "}}")?;
Ok(())
}
fn generate_css_rule<'a>(f: Formatter, iter: Iter<'a, ParseNode<'a>>) -> Result<(), GenerationError<'a>> {
let mut prop_i = 0; let mut selectors = iter.clone()
.take_while(|n| { prop_i += 1; n.atomic().is_some() })
.map(|n| n.atomic().unwrap()) .peekable();
let head = if let Some(head) = selectors.next() {
head
} else {
return Err(GenerationError::new("CSS",
"CSS selector(s) missing. \
Expected a symbol/identifier node, none was found!",
&selectors.peek().unwrap().site));
};
if CSS_SPECIAL_SELECTORS.contains(&head.value.as_ref())
&& !CSS_NON_NESTED_SELECTORS.contains(&head.value.as_ref()) {
return generate_special_selector(f, &head.value, iter);
}
write!(f, "{} ", head.value)?;
for selector in selectors {
write!(f, "{} ", selector.value)?;
}
writeln!(f, "{{")?;
let properties = iter.skip(prop_i - 1);
for property in properties {
let ParseNode::Attribute { ref node, ref keyword, .. } = property else {
return Err(GenerationError::new("CSS",
"CSS property-value pairs must be in the \
form of attributes, i.e. `:property value`.",
&property.site()));
};
writeln!(f, " {}: {};", keyword, css_value(keyword, node)?)?;
}
write!(f, "}}")?;
Ok(())
}
impl<'a> MarkupFormatter for CSSFormatter<'a> {
fn document(&self) -> Result<String, GenerationError> {
let mut doc = String::new();
if self.tree.is_empty() {
return Ok(String::from(DEFAULT));
}
doc += &self.display()?;
Ok(doc)
}
fn generate(&self, f: Formatter)
-> Result<(), GenerationError> {
let mut tree_iter = self.tree.iter().peekable();
while let Some(node) = tree_iter.next() {
match node {
ParseNode::List { nodes: list, leading_whitespace, .. } => {
write!(f, "{}", leading_whitespace)?;
generate_css_rule(f, list.into_iter())?;
},
ParseNode::Attribute { site, .. } => {
return Err(GenerationError::new("CSS",
"Attribute not expected here, CSS documents \
are supposed to be a series of selectors \
and property-value pairs, wrapped in parentheses.",
&site.to_owned()));
},
ParseNode::Symbol(node)
| ParseNode::Number(node)
| ParseNode::String(node) => {
let site = node.site.to_owned();
return Err(GenerationError::new("CSS",
"Symbolic node not expected here, CSS documents \
are supposed to be a series of selectors \
and property-value pairs, wrapped in parentheses.",
&site));
}
}
}
Ok(())
}
}