use super::ast::*;
pub struct Renderer {
pub output: String,
pub preformatted: bool,
}
impl Renderer {
pub fn new() -> Renderer {
Renderer {
output: String::new(),
preformatted: false,
}
}
pub fn escape_attribute(&self, attribute: String) -> String {
attribute
.clone()
.replace("&", "&")
.replace('"', """)
.replace('<', "<")
.replace('>', ">")
}
pub fn escape_text(&self, text: String) -> String {
text.clone()
.replace("&", "&")
.replace('<', "<")
.replace('>', ">")
}
pub fn root(mut self, children: Vec<Data>) -> String {
self.element_children(&children);
self.output
}
pub fn text(&mut self, text: &String) {
let text = self.escape_text(text.to_owned());
self.output.push_str(&text);
}
pub fn escape(&mut self, text: &String) {
let text = self.escape_text(text.to_owned());
self.output.push_str(&text);
}
pub fn line_break(&mut self, size: &usize) {
if self.preformatted {
self.output
.push_str(&(0..size.to_owned()).map(|_| "\n").collect::<String>());
} else {
self.output.push_str("<br>");
}
}
pub fn attributes(&mut self, attributes: &Vec<Data>) {
let mut class_attributes: Vec<&ClassAttribute> = Vec::new();
for attribute in attributes {
match attribute {
Data::ClassAttribute(attribute) => {
class_attributes.push(attribute);
}
Data::IdAttribute(attribute) => {
self.id_attribute(&attribute);
}
Data::DataAttribute(attribute) => {
self.data_attribute(&attribute);
}
Data::NamedAttribute(attribute) => {
self.named_attribute(&attribute);
}
_ => {}
}
}
if class_attributes.len() > 0 {
self.class_attribute(class_attributes);
}
}
pub fn class_attribute(&mut self, attributes: Vec<&ClassAttribute>) {
let mut classes: Vec<String> = Vec::new();
for attribute in &attributes {
let value = &attribute.name;
let value = self.escape_attribute(value.to_owned());
classes.push(value);
}
if classes.len() > 0 {
self.output.push_str(" class=\"");
self.output.push_str(&classes.join(" "));
self.output.push_str("\"")
}
}
pub fn data_attribute(&mut self, attribute: &DataAttribute) {
self.output.push_str(" data-");
self.output.push_str(&attribute.name);
if let Some(children) = &attribute.children {
self.output.push_str("=\"");
self.attribute_children(&children);
self.output.push_str("\"")
}
}
pub fn id_attribute(&mut self, attribute: &IdAttribute) {
let value = &attribute.name;
let value = self.escape_attribute(value.to_owned());
self.output.push_str(" id=\"");
self.output.push_str(&value);
self.output.push_str("\"");
}
pub fn named_attribute(&mut self, attribute: &NamedAttribute) {
self.output.push_str(" ");
self.output.push_str(&attribute.name);
if let Some(children) = &attribute.children {
self.output.push_str("=\"");
self.attribute_children(&children);
self.output.push_str("\"")
}
}
pub fn attribute_children(&mut self, children: &Vec<Data>) {
let mut value = String::new();
for child in children {
match child {
Data::Escape(text) => {
value.push_str(&text);
}
Data::Text(text) => {
value.push_str(&text);
}
Data::Url(text) => {
value.push_str(&text);
}
_ => {}
}
}
value = self.escape_attribute(value);
self.output.push_str(&value);
}
pub fn element(&mut self, element: &Element) {
self.output.push_str("<");
self.output.push_str(&element.name);
if element.is_preformatted() {
self.preformatted = true;
}
if let Some(attributes) = &element.attributes {
self.attributes(attributes);
}
self.output.push_str(">");
if let Some(children) = &element.children {
self.element_children(children);
}
if !element.is_self_closing() {
self.output.push_str("</");
self.output.push_str(&element.name);
self.output.push_str(">");
}
if element.is_preformatted() {
self.preformatted = false;
}
}
pub fn element_children(&mut self, children: &Vec<Data>) {
for child in children {
match child {
Data::InlineElement(element) | Data::BlockElement(element) => {
self.element(&element);
}
Data::Escape(text) => {
self.escape(&text);
}
Data::Text(text) => {
self.text(&text);
}
Data::LineBreak(size) => {
self.line_break(&size);
}
_ => {}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_element() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: None,
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, "<p></p>");
}
#[test]
fn test_element_with_children() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: None,
children: Some(vec![
Data::Text("boofar".to_string()),
Data::Escape(":".to_string()),
]),
};
renderer.element(&element);
assert_eq!(renderer.output, "<p>boofar:</p>");
}
#[test]
fn test_element_with_classname() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: Some(vec![
Data::ClassAttribute(ClassAttribute {
name: "class".to_string(),
}),
Data::ClassAttribute(ClassAttribute {
name: "name".to_string(),
}),
]),
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, r#"<p class="class name"></p>"#);
}
#[test]
fn test_element_data_attribute() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: Some(vec![Data::DataAttribute(DataAttribute {
name: "foobar".to_string(),
children: None,
})]),
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, r#"<p data-foobar></p>"#);
}
#[test]
fn test_element_data_attribute_and_value() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: Some(vec![Data::DataAttribute(DataAttribute {
name: "foobar".to_string(),
children: Some(vec![
Data::Text("boofar".to_string()),
Data::Escape(":".to_string()),
]),
})]),
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, r#"<p data-foobar="boofar:"></p>"#);
}
#[test]
fn test_element_with_id() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: Some(vec![Data::IdAttribute(IdAttribute {
name: "foobar".to_string(),
})]),
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, r#"<p id="foobar"></p>"#);
}
#[test]
fn test_element_named_attribute() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: Some(vec![Data::NamedAttribute(NamedAttribute {
name: "foobar".to_string(),
children: None,
})]),
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, r#"<p foobar></p>"#);
}
#[test]
fn test_element_named_attribute_and_value() {
let mut renderer = Renderer::new();
let element = Element {
name: "p".to_string(),
attributes: Some(vec![Data::NamedAttribute(NamedAttribute {
name: "foobar".to_string(),
children: Some(vec![
Data::Text("boofar".to_string()),
Data::Escape(":".to_string()),
]),
})]),
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, r#"<p foobar="boofar:"></p>"#);
}
#[test]
fn test_childless_element() {
let mut renderer = Renderer::new();
let element = Element {
name: "br".to_string(),
attributes: None,
children: None,
};
renderer.element(&element);
assert_eq!(renderer.output, "<br>");
}
#[test]
fn test_text() {
let mut renderer = Renderer::new();
renderer.text(&"".to_string());
assert_eq!(renderer.output, "");
let mut renderer = Renderer::new();
renderer.text(&"foobar".to_string());
assert_eq!(renderer.output, "foobar");
}
#[test]
fn test_escape() {
let mut renderer = Renderer::new();
renderer.text(&"<&>".to_string());
assert_eq!(renderer.output, "<&>");
}
#[test]
fn test_formatted_line_breaks() {
let mut renderer = Renderer::new();
let children: Vec<Data> = vec![Data::LineBreak(1)];
renderer.element_children(&children);
assert_eq!(renderer.output, "<br>");
let mut renderer = Renderer::new();
let children: Vec<Data> = vec![Data::LineBreak(10)];
renderer.element_children(&children);
assert_eq!(renderer.output, "<br>");
}
#[test]
fn test_preformatted_line_breaks() {
let mut renderer = Renderer::new();
let element = Element {
name: "pre".to_string(),
attributes: None,
children: Some(vec![Data::LineBreak(1)]),
};
renderer.element(&element);
assert_eq!(renderer.output, "<pre>\n</pre>");
let mut renderer = Renderer::new();
let element = Element {
name: "pre".to_string(),
attributes: None,
children: Some(vec![Data::LineBreak(10)]),
};
renderer.element(&element);
assert_eq!(renderer.output, "<pre>\n\n\n\n\n\n\n\n\n\n</pre>");
}
}