1use std::fmt::Display;
2
3pub(crate) enum Element {
4 Text(String),
5 Raw(String),
6 Node(Node),
7}
8
9pub(crate) struct Node {
10 name: String,
11 attributes: Option<Vec<(String, String)>>,
12 content: Option<Vec<Element>>,
13}
14
15impl Node {
16 pub(crate) fn with_name(name: &str) -> Node {
17 Node {
18 name: name.into(),
19 attributes: None,
20 content: None,
21 }
22 }
23
24 pub(crate) fn with_attributes(name: &str, attributes: &[(&str, &str)]) -> Node {
25 let mut node = Node::with_name(name);
26 for (k, v) in attributes {
27 node.add_attr(*k, *v);
28 }
29
30 node
31 }
32
33 pub(crate) fn with_name_and<T>(name: &str, and: T) -> Node where T: FnOnce(&mut Node) {
34 let mut node = Node::with_name(name);
35 and(&mut node);
36 node
37 }
38
39 pub(crate) fn add_attr<V: Display + ?Sized>(&mut self, name: &str, value: &V) {
40 self.attributes.get_or_insert_with(Vec::new).push((name.into(), format!("{}", value)));
41 }
42
43 pub(crate) fn add_attrs<V: Display + ?Sized>(&mut self, attrs: &[(&str, &V)]) {
44 for (k, v) in attrs {
45 self.add_attr(*k, *v);
46 }
47 }
48}
49
50pub(crate) struct Document {
51 elements: Vec<Element>,
52}
53
54impl Document {
55 pub(crate) fn new() -> Document {
56 Document {
57 elements: Vec::new(),
58 }
59 }
60}
61
62pub(crate) trait Pusher {
63 fn push_element(&mut self, elem: Element);
64
65 fn push_text(&mut self, text: &str) {
66 self.push_element(Element::Text(text.into()))
67 }
68
69 fn push_raw(&mut self, content: &str) {
70 self.push_element(Element::Raw(content.into()))
71 }
72
73 fn push_node(&mut self, node: Node) {
74 self.push_element(Element::Node(node))
75 }
76
77 fn push_nodes(&mut self, nodes: Vec<Node>) {
78 for n in nodes {
79 self.push_node(n);
80 }
81 }
82
83 fn push_node_and<T>(&mut self, node: Node, and: T) where T: FnOnce(&mut Node) {
84 let mut node = node;
85 and(&mut node);
86 self.push_node(node);
87 }
88
89 fn push_node_named<T>(&mut self, name: &str, and: T) where T: FnOnce(&mut Node) {
90 self.push_node_and(Node::with_name(name), and);
91 }
92}
93
94impl Pusher for Node {
95 fn push_element(&mut self, elem: Element) {
96 self.content.get_or_insert_with(Vec::new).push(elem);
97 }
98}
99
100impl Pusher for Document {
101 fn push_element(&mut self, elem: Element) {
102 self.elements.push(elem);
103 }
104}
105
106pub(crate) struct Renderer {
107 inner: Vec<u8>,
108}
109
110impl Renderer {
111 fn write_escaped(&mut self, data: &str) {
112 self.write_raw(&self.escape_xml(data));
113 }
114
115 fn write_raw(&mut self, data: &str) {
116 self.inner.extend_from_slice(data.as_bytes());
117 }
118
119 fn write_attr(&mut self, name: &str, value: &str) {
120 let escaped = self.escape_xml(value);
121 self.inner.reserve(1 + name.len() + 2 + escaped.len() + 1);
122 self.write_raw(" ");
123 self.write_raw(name);
124 self.write_raw("=\"");
125 self.write_raw(&escaped);
126 self.write_raw("\"");
127 }
128
129 fn escape_xml(&self, text: &str) -> String {
130 text
131 .replace('&', "&")
132 .replace('<', "<")
133 .replace('>', ">")
134 .replace('"', """)
135 .replace('\'', "'")
136 }
137
138 fn write_node(&mut self, node: &Node) {
139 self.write_raw("<");
140 self.write_raw(&node.name);
141 if let Some(attrs) = node.attributes.as_ref() {
142 for (name, value) in attrs {
143 self.write_attr(name, value);
144 }
145 }
146
147 if let Some(elms) = node.content.as_ref() {
148 self.write_raw(">");
149 for elem in elms {
150 self.trampoline(elem);
151 }
152 self.write_raw(&format!("</{}>", node.name))
153 } else {
154 self.write_raw("/>");
155 }
156 }
157
158 fn trampoline(&mut self, el: &Element) {
159 match el {
160 Element::Text(txt) => self.write_escaped(txt),
161 Element::Raw(raw) => self.write_raw(raw),
162 Element::Node(node) => self.write_node(node),
163 }
164 }
165
166 pub(crate) fn render(doc: &Document) -> String {
167 let mut writer = Renderer { inner: vec![] };
168 for elm in doc.elements.iter() {
169 writer.trampoline(elm);
170 };
171 String::from_utf8(writer.inner).unwrap()
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use crate::xml::*;
178
179 #[test]
180 fn writes_simple_element() {
181 let person = Node::with_attributes("person", &[
182 ("name", "Paul Appleseed"),
183 ("email", "paul@example.org"),
184 ]);
185 let mut doc = Document::new();
186 doc.push_node(person);
187
188 let str = Renderer::render(&doc);
189 assert_eq!(&str, "<person name=\"Paul Appleseed\" email=\"paul@example.org\"/>");
190 }
191
192 #[test]
193 fn writes_nested_elements() {
194 let mut person = Node::with_attributes("person", &[
195 ("name", "Paul Appleseed"),
196 ("email", "paul@example.org"),
197 ]);
198
199 person.push_node_and(Node::with_name("Todo"), |node| {
200 node.push_node_and(Node::with_name("Task"),
201 |n| n.push_text("Water plants"));
202 node.push_node_and(Node::with_name("Task"),
203 |n| n.push_text("Pet dog"));
204 node.push_node_and(Node::with_name("Task"),
205 |n| n.push_text("Use Rust"));
206 });
207
208 person.push_node_and(Node::with_name("danger"),
209 |n| n.push_raw("some raw content!"));
210
211 let mut doc = Document::new();
212 doc.push_node(person);
213
214 let str = Renderer::render(&doc);
215 assert_eq!(&str, "<person name=\"Paul Appleseed\" email=\"paul@example.org\"><Todo><Task>Water plants</Task><Task>Pet dog</Task><Task>Use Rust</Task></Todo><danger>some raw content!</danger></person>");
216 }
217}