seam

Symbolic-Expressions As Markup.
git clone git://git.knutsen.co/seam
Log | Files | Refs | README | LICENSE

html.rs (6010B)


//! Assembles an expanded tree into valid HTML.
use super::{GenerationError, MarkupDisplay, Formatter};
use super::css::CSSFormatter;

use crate::parse::parser::{ParseNode, ParseTree, SearchTree, SearchType};

#[derive(Debug, Clone)]
pub struct HTMLFormatter {
    pub tree : ParseTree,
}

impl HTMLFormatter {
    pub fn new(tree : ParseTree) -> Self {
        Self { tree }
    }
}

pub const DEFAULT : &str =
    "<!DOCTYPE html>\n\
    <html>\n\
        <head></head>\n\
        <body>\n\
            <!-- Generated by SEAM (empty file) -->\n\
        </body>\n\
    </html>\n";

impl MarkupDisplay for HTMLFormatter {
    fn document(&self) -> Result<String, GenerationError> {
        let mut doc = String::new();
        if self.tree.is_empty() {
            return Ok(String::from(DEFAULT));
        }

        // Check if top-level <!DOCTYPE html> exists.
        let doctype_tag
            = self.tree.search_node(SearchType::ListHead, "!doctype", true, 1);
        // Check if top-level <html></html> root object exists.
        let html_tag
            = self.tree.search_node(SearchType::ListHead, "html", true, 1);
        // Check if <head></head> tag object exists.
        let head_tag
            = self.tree.search_node(SearchType::ListHead, "head", true, 2);
        // Check if <body></body> tag object exists.
        let body_tag
            = self.tree.search_node(SearchType::ListHead, "body", true, 2);

        if doctype_tag.is_none() {
            eprintln!("no doctype found");
            doc += "<!DOCTYPE html>\n";
            if html_tag.is_none() {
                doc += "<html>\n";
                if head_tag.is_none() {
                    doc += "<head></head>\n"
                }
                if body_tag.is_none() {
                    doc += "<body>\n"
                }
            }
        }
        // Populate.
        doc += &self.display()?;
        doc += "\n<!-- Generated by SEAM. -->\n";

        if doctype_tag.is_none() {
            if html_tag.is_none() {
                if body_tag.is_none() {
                    doc += "</body>\n"
                }
                doc += "</html>\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::Symbol(node)
                | ParseNode::Number(node) => {
                    // If symbol ahead is so-called "symbolic", we can
                    // infere there was a space between them.
                    write!(f, "{}", node.value)?;
                    if let Some(peek) = tree_iter.peek() {
                        if peek.symbolic().is_some() {
                            write!(f, " ")?
                        }
                    }
                },
                ParseNode::String(node) => write!(f, "{}", node.value)?,
                ParseNode::List(list) => {
                    let head = list.first();
                    let mut tag = "";
                    if let Some(head_node) = head {
                        if let ParseNode::Symbol(head_symbol) = head_node {
                            tag = &head_symbol.value;
                            write!(f, "<{}", tag)?;
                        } else {
                            // TODO: Error, tags can only have symbol values.
                        }
                    } else {
                        // TODO: Error, empty tags not supported.
                    }

                    let mut rest = &list[1..];

                    // Declarations behave differently.
                    if tag.as_bytes()[0] == '!' as u8 {
                        while !rest.is_empty() {
                            if let Some(node) = rest[0].symbolic() {
                                write!(f, " {}", node.value)?;
                            } else {
                                return Err(GenerationError::new("HTML",
                                    "Non-symbolic item in declaration",
                                    &rest[0].site()));
                            }
                            rest = &rest[1..];
                        }
                        write!(f, ">")?;
                        continue;
                    }

                    while let Some(ParseNode::Attribute(attr)) = rest.first() {
                        if let Some(atom) = (*attr.node).atomic() {
                            write!(f, " {}=\"{}\"", attr.keyword, atom.value)?;
                            rest = &rest[1..];
                        } else {
                            // Error! Cannot be non atomic.
                            return Err(GenerationError::new("HTML",
                                "Attribute cannot contain non-atomic data.",
                                &(*attr.node).site()));
                        }
                    }
                    write!(f, ">")?;

                    // <style /> tag needs to generate CSS.
                    if tag == "style" {  // TODO: If just a string, don't convert.
                        writeln!(f, "")?;
                        let css_fmt = CSSFormatter::new(rest.to_owned());
                        css_fmt.generate(f)?;
                    } else {
                        let html_fmt = HTMLFormatter::new(rest.to_owned());
                        html_fmt.generate(f)?;
                    }
                    write!(f, "</{}>", tag)?;
                },
                ParseNode::Attribute(_attr) =>
                    return Err(GenerationError::new("HTML",
                        "Unexpected attribute encountered.",
                        &node.site()))
            }
        }
        Ok(())
    }
}


// TODO: Convert special characters to HTML compatible ones.
// e.g.
//      <  =>  &lt;
//      >  =>  &gt;
//      &  =>  &amp;
//      "  =>  &quot;
//      !  =>  &excl;
//      etc.