1mod node;
2
3use std::{
4 io::{self, Write},
5 str,
6};
7
8use node::NodeKind;
9pub use node::NodeRef;
10
11const VOID_ELEMENTS: &[&str] = &[
13 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source",
14 "track", "wbr",
15];
16
17pub fn render_string(node: &NodeRef) -> String {
18 let mut buf = vec![];
19 render(&mut buf, node).unwrap();
20 unsafe { String::from_utf8_unchecked(buf) }
22}
23
24pub fn render<W: Write>(mut w: W, node: &NodeRef) -> io::Result<()> {
25 match &*node.kind() {
26 NodeKind::Text(text) => {
27 write!(w, "{}", escape(text))?;
28 }
29 NodeKind::Raw(raw) => {
30 write!(w, "{raw}")?;
31 }
32 NodeKind::Fragment(children) => {
33 write!(w, "{}", render_children(children, ""))?;
34 }
35 NodeKind::Element {
36 namespace,
37 name,
38 attrs,
39 children,
40 } => {
41 let tag = namespace
42 .as_ref()
43 .map(|ns| format!("{ns}:{name}"))
44 .unwrap_or_else(|| name.clone());
45
46 let attrs = attrs
47 .iter()
48 .map(|(name, value)| format!(" {name}=\"{}\"", escape(value)))
49 .collect::<Vec<_>>()
50 .join("");
51
52 if !children.is_empty() {
53 write!(
54 w,
55 "<{tag}{attrs}>\n{children}\n</{tag}>",
56 children = render_children(children, " ")
57 )?;
58 } else if VOID_ELEMENTS.contains(&tag.as_str()) {
59 write!(w, "<{tag}{attrs}>")?;
60 } else {
61 write!(w, "<{tag}{attrs}></{tag}>")?;
62 }
63 }
64 }
65
66 Ok(())
67}
68
69fn render_children(children: &[NodeRef], indent: &str) -> String {
70 let mut buf = vec![];
71 for child in children {
72 render(&mut buf, child).unwrap();
73
74 buf.push(b'\n');
75 }
76
77 let text = unsafe { String::from_utf8_unchecked(buf) };
79 text.lines()
80 .map(|line| format!("{indent}{line}"))
81 .collect::<Vec<_>>()
82 .join("\n")
83}
84
85pub fn escape(text: &str) -> String {
87 let mut output = String::new();
88 for c in text.chars() {
89 match c {
90 '&' => output.push_str("&"),
91 '<' => output.push_str("<"),
92 '>' => output.push_str(">"),
93 '"' => output.push_str("""),
94 '\'' => output.push_str("'"),
95 _ => output.push(c),
96 }
97 }
98 output
99}