use crate::common::is_void_element;
use crate::parser::{
HsmlNode, RootNode, attribute::node::AttributeNode, comment::node::CommentNode,
doctype::node::DoctypeNode, tag::node::TagNode,
};
pub struct HsmlCompileOptions {
pub pretty: bool,
pub indent_size: usize,
}
impl Default for HsmlCompileOptions {
fn default() -> Self {
Self {
pretty: false,
indent_size: 2,
}
}
}
fn compile_tag_node(
tag_node: &TagNode,
options: &HsmlCompileOptions,
depth: usize,
) -> Result<String, String> {
let mut html_content = String::new();
let indent = if options.pretty {
" ".repeat(depth * options.indent_size)
} else {
String::new()
};
html_content.push_str(&indent);
html_content.push('<');
html_content.push_str(&tag_node.tag);
if let Some(id_node) = tag_node.ids.first() {
html_content.push_str(r#" id=""#);
html_content.push_str(&id_node.id);
html_content.push('\"');
}
let mut all_classes: Vec<&str> = Vec::new();
if let Some(class_nodes) = &tag_node.classes {
all_classes.extend(class_nodes.iter().map(|c| c.name.as_str()));
}
if let Some(attributes) = &tag_node.attributes {
for node in attributes {
if let HsmlNode::Attribute(AttributeNode { key, value, .. }) = node
&& key == "class"
&& let Some(value) = value
{
all_classes.extend(value.split_whitespace());
}
}
}
if !all_classes.is_empty() {
html_content.push_str(r#" class=""#);
html_content.push_str(&all_classes.join(" "));
html_content.push('\"');
}
if let Some(attributes) = &tag_node.attributes {
for node in attributes {
match node {
HsmlNode::Attribute(AttributeNode { key, value, .. }) if key != "class" => {
html_content.push(' ');
html_content.push_str(key);
if let Some(value) = value {
html_content.push_str(r#"=""#);
html_content.push_str(value);
html_content.push('\"');
}
}
HsmlNode::Attribute(AttributeNode { key, .. }) if key == "class" => {
}
HsmlNode::Comment(node) if node.is_dev => {
}
other => {
return Err(format!(
"Unsupported node type in attributes of <{}>: {other:?}",
tag_node.tag
));
}
}
}
}
if is_void_element(&tag_node.tag) {
html_content.push_str(" />");
if options.pretty {
html_content.push('\n');
}
return Ok(html_content);
}
html_content.push('>');
if let Some(text) = &tag_node.text {
html_content.push_str(&text.text);
}
if let Some(child_nodes) = &tag_node.children {
if options.pretty {
html_content.push('\n');
}
for child_node in child_nodes {
match child_node {
HsmlNode::Tag(tag_node) => {
html_content.push_str(&compile_tag_node(tag_node, options, depth + 1)?)
}
HsmlNode::Comment(comment_node) => {
if !comment_node.is_dev {
html_content.push_str(&compile_comment_node(
comment_node,
options,
depth + 1,
))
}
}
other => {
return Err(format!(
"Unsupported child node type in <{}>: {other:?}",
tag_node.tag
));
}
}
}
if options.pretty {
html_content.push_str(&indent);
}
}
html_content.push_str("</");
html_content.push_str(&tag_node.tag);
html_content.push('>');
if options.pretty {
html_content.push('\n');
}
Ok(html_content)
}
fn compile_comment_node(
comment_node: &CommentNode,
options: &HsmlCompileOptions,
depth: usize,
) -> String {
let mut html_content = String::new();
if options.pretty {
html_content.push_str(&" ".repeat(depth * options.indent_size));
}
html_content.push_str("<!--");
html_content.push_str(&comment_node.text);
html_content.push_str(" -->");
if options.pretty {
html_content.push('\n');
}
html_content
}
fn compile_doctype_node(doctype_node: &DoctypeNode) -> String {
format!("<!DOCTYPE {}>", doctype_node.doctype)
}
fn compile_node(
node: &HsmlNode,
options: &HsmlCompileOptions,
depth: usize,
) -> Result<String, String> {
match node {
HsmlNode::Doctype(doctype_node) => {
let mut s = compile_doctype_node(doctype_node);
if options.pretty {
s.push('\n');
}
Ok(s)
}
HsmlNode::Tag(tag_node) => compile_tag_node(tag_node, options, depth),
HsmlNode::Comment(comment_node) if !comment_node.is_dev => {
Ok(compile_comment_node(comment_node, options, depth))
}
HsmlNode::Comment(_) => Ok(String::from("")),
other => Err(format!("Unsupported root node type: {other:?}")),
}
}
pub fn compile(hsml_ast: &RootNode, options: &HsmlCompileOptions) -> Result<String, String> {
let mut html_content = String::new();
for node in &hsml_ast.nodes {
html_content.push_str(&compile_node(node, options, 0)?);
}
Ok(html_content)
}