use super::{GenerationError, MarkupDisplay, Formatter};
use crate::parse::parser::{self, ParseNode, ParseTree};
use std::slice::Iter;
#[derive(Debug, Clone)]
pub struct CSSFormatter {
pub tree : ParseTree,
}
impl CSSFormatter {
pub fn new(tree : ParseTree) -> Self {
Self { tree }
}
}
pub const DEFAULT : &str = "\n";
const CSS_FUNCTIONS : [&str; 57] = [
"attr", "blur", "brightness", "calc", "circle", "color", "contrast",
"counter", "counters", "cubic-bezier", "drop-shadow", "ellipse",
"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 BINARY_OPERATORS : [&str; 4] = ["+", "-", "*", "/"];
fn convert_value(node : &ParseNode) -> Result<String, GenerationError> {
match node {
ParseNode::List(list) => {
let list = parser::strip(list, false);
let result = match list.as_slice() {
[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(_property : &str, node : &ParseNode)
-> Result<String, GenerationError> {
convert_value(node)
}
fn generate_special_selector
(f: Formatter,
selector: &str,
arguments: Iter<ParseNode>) -> Result<(), GenerationError> {
if CSS_ONELINE_RULES.contains(&selector) {
write!(f, "{} ", selector)?;
for arg in arguments {
match arg {
ParseNode::Attribute(attr) => {
let kw = &attr.keyword;
write!(f, "({}: {}) ", kw, css_value(kw, &*attr.node)?)?;
},
_ => write!(f, "{} ", css_value(selector, arg)?)?
}
}
writeln!(f, ";")?;
return Ok(());
}
write!(f, "{} ", selector)?;
let mut parsing_rules = false;
let unexpected_node = |node: &ParseNode, rules: bool| {
if rules {
Err(GenerationError::new("CSS",
"Expected list (i.e. a CSS rule) here!", &node.site()))
} else {
Ok(())
}
};
for arg in arguments {
match arg {
ParseNode::Attribute(attr) => {
unexpected_node(&arg, parsing_rules)?;
let kw = &attr.keyword;
write!(f, "({}: {}) ", kw, css_value(kw, &*attr.node)?)?;
},
ParseNode::List(rule) => {
if !parsing_rules {
writeln!(f, "{{")?;
}
parsing_rules = true;
generate_css_rule(f, &rule)?;
},
_ => {
unexpected_node(&arg, parsing_rules)?;
write!(f, "{} ", css_value(selector, arg)?)?;
}
}
}
writeln!(f, "}}")?;
Ok(())
}
fn generate_css_rule(f: Formatter, list: &[ParseNode]) -> Result<(), GenerationError> {
let stripped = parser::strip(list, false);
let mut iter = stripped.iter();
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!",
&list[0].site()));
};
if CSS_SPECIAL_SELECTORS.contains(&head.value.as_ref()) {
iter.next();
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 {
if let ParseNode::Attribute(property) = property {
let value = &property.node;
writeln!(f, " {}: {};",
&property.keyword,
css_value(&property.keyword, value)?)?;
} 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, "}}")?;
Ok(())
}
impl MarkupDisplay for CSSFormatter {
fn document(&self) -> Result<String, GenerationError> {
let mut doc = String::new();
if self.tree.is_empty() {
return Ok(String::from(DEFAULT));
}
doc += &self.display()?;
doc += "\n/* Generated from symbolic-expressions, with SEAM */\n";
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(list) => {
generate_css_rule(f, list)?;
},
ParseNode::Attribute(attr) => {
let site = attr.site.to_owned();
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));
},
ParseNode::Symbol(node)
| ParseNode::Number(node)
| ParseNode::String(node) => {
let site = node.site.to_owned();
if node.value.trim().is_empty() {
continue;
}
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(())
}
}