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}