pochoir-parser 0.12.2

HTML parser for the pochoir template engine
Documentation
use pochoir_template_engine::TemplateBlock;

use crate::{Node, Tree, TreeRefId, EMPTY_HTML_ELEMENTS};
use std::fmt::Write;

/// Render a [`Tree`] to an HTML string.
///
/// This functions ignore comments and render template blocks (expressions are statements) as raw
/// strings with no spaces inside their delimiters (will render `{{expr}}` even if it was `{{ expr }}`,
/// `{!expr!}` even if it was `{! expr !}` and `{%stmt%}` even if it was `{% stmt %}`).
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let html = r#"<main><!-- A comment which will be ignored -->
///     <p>A paragraph</p>
///     <img src="/link/to/image">
///     {{ expr }}
/// </main>"#;
/// let tree = pochoir_parser::parse("index.html", html)?;
/// let rendered_html = pochoir_parser::render(&tree);
///
/// assert_eq!(rendered_html, r#"<main>
///     <p>A paragraph</p>
///     <img src="/link/to/image">
///     {{expr}}
/// </main>"#);
/// # Ok(())
/// # }
/// ```
pub fn render(tree: &Tree) -> String {
    fn recursive(result: &mut String, tree: &Tree, node_id: TreeRefId) {
        let node = tree.get(node_id);
        match node.data() {
            Node::Element(name, attrs) => {
                write!(result, "<{name}").expect("writing to a String can't fail");

                for (key, val) in attrs {
                    if val.is_empty() {
                        write!(result, " {}", **key).expect("writing to a String can't fail");
                    } else {
                        write!(
                            result,
                            " {}=\"{}\"",
                            **key,
                            val.iter()
                                .map(|b| {
                                    match &**b {
                                        TemplateBlock::RawText(t) => t.to_string(),
                                        TemplateBlock::Expr(t, is_escaped) => format!(
                                            "{{{}{t}{}}}",
                                            if *is_escaped { "{" } else { "!" },
                                            if *is_escaped { "}" } else { "!" }
                                        ),
                                        TemplateBlock::Stmt(t) => format!("{{%{t}%}}"),
                                    }
                                })
                                .collect::<String>()
                        )
                        .expect("writing to a String can't fail");
                    }
                }

                write!(result, ">").expect("writing to a String can't fail");

                for node_id in node.children_id() {
                    recursive(result, tree, node_id);
                }

                if !EMPTY_HTML_ELEMENTS.contains(&&**name) {
                    write!(result, "</{name}>").expect("writing to a String can't fail");
                }
            }
            Node::Doctype(doctype) => {
                write!(result, "<!DOCTYPE {doctype}>").expect("writing to a String can't fail");
            }
            Node::TemplateBlock(TemplateBlock::RawText(text)) => {
                write!(result, "{text}").expect("writing to a String can't fail");
            }
            Node::TemplateBlock(TemplateBlock::Expr(text, is_escaped)) => write!(
                result,
                "{{{}{text}{}}}",
                if *is_escaped { "{" } else { "!" },
                if *is_escaped { "}" } else { "!" }
            )
            .expect("writing to a String can't fail"),
            Node::TemplateBlock(TemplateBlock::Stmt(text)) => {
                write!(result, "{{%{text}%}}",).expect("writing to a String can't fail");
            }
            _ => (),
        }
    }

    let mut result = String::new();
    for node_id in tree.root_nodes() {
        recursive(&mut result, tree, node_id);
    }

    result
}