pochoir_parser/
render.rs

1use pochoir_template_engine::TemplateBlock;
2
3use crate::{Node, Tree, TreeRefId, EMPTY_HTML_ELEMENTS};
4use std::fmt::Write;
5
6/// Render a [`Tree`] to an HTML string.
7///
8/// This functions ignore comments and render template blocks (expressions are statements) as raw
9/// strings with no spaces inside their delimiters (will render `{{expr}}` even if it was `{{ expr }}`,
10/// `{!expr!}` even if it was `{! expr !}` and `{%stmt%}` even if it was `{% stmt %}`).
11///
12/// ```
13/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
14/// let html = r#"<main><!-- A comment which will be ignored -->
15///     <p>A paragraph</p>
16///     <img src="/link/to/image">
17///     {{ expr }}
18/// </main>"#;
19/// let tree = pochoir_parser::parse("index.html", html)?;
20/// let rendered_html = pochoir_parser::render(&tree);
21///
22/// assert_eq!(rendered_html, r#"<main>
23///     <p>A paragraph</p>
24///     <img src="/link/to/image">
25///     {{expr}}
26/// </main>"#);
27/// # Ok(())
28/// # }
29/// ```
30pub fn render(tree: &Tree) -> String {
31    fn recursive(result: &mut String, tree: &Tree, node_id: TreeRefId) {
32        let node = tree.get(node_id);
33        match node.data() {
34            Node::Element(name, attrs) => {
35                write!(result, "<{name}").expect("writing to a String can't fail");
36
37                for (key, val) in attrs {
38                    if val.is_empty() {
39                        write!(result, " {}", **key).expect("writing to a String can't fail");
40                    } else {
41                        write!(
42                            result,
43                            " {}=\"{}\"",
44                            **key,
45                            val.iter()
46                                .map(|b| {
47                                    match &**b {
48                                        TemplateBlock::RawText(t) => t.to_string(),
49                                        TemplateBlock::Expr(t, is_escaped) => format!(
50                                            "{{{}{t}{}}}",
51                                            if *is_escaped { "{" } else { "!" },
52                                            if *is_escaped { "}" } else { "!" }
53                                        ),
54                                        TemplateBlock::Stmt(t) => format!("{{%{t}%}}"),
55                                    }
56                                })
57                                .collect::<String>()
58                        )
59                        .expect("writing to a String can't fail");
60                    }
61                }
62
63                write!(result, ">").expect("writing to a String can't fail");
64
65                for node_id in node.children_id() {
66                    recursive(result, tree, node_id);
67                }
68
69                if !EMPTY_HTML_ELEMENTS.contains(&&**name) {
70                    write!(result, "</{name}>").expect("writing to a String can't fail");
71                }
72            }
73            Node::Doctype(doctype) => {
74                write!(result, "<!DOCTYPE {doctype}>").expect("writing to a String can't fail");
75            }
76            Node::TemplateBlock(TemplateBlock::RawText(text)) => {
77                write!(result, "{text}").expect("writing to a String can't fail");
78            }
79            Node::TemplateBlock(TemplateBlock::Expr(text, is_escaped)) => write!(
80                result,
81                "{{{}{text}{}}}",
82                if *is_escaped { "{" } else { "!" },
83                if *is_escaped { "}" } else { "!" }
84            )
85            .expect("writing to a String can't fail"),
86            Node::TemplateBlock(TemplateBlock::Stmt(text)) => {
87                write!(result, "{{%{text}%}}",).expect("writing to a String can't fail");
88            }
89            _ => (),
90        }
91    }
92
93    let mut result = String::new();
94    for node_id in tree.root_nodes() {
95        recursive(&mut result, tree, node_id);
96    }
97
98    result
99}