shield_maker/
xml.rs

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('&', "&amp;")
132            .replace('<', "&lt;")
133            .replace('>', "&gt;")
134            .replace('"', "&quot;")
135            .replace('\'', "&apos;")
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}