simple_xml_builder/
lib.rs1#![doc(html_root_url = "https://docs.rs/simple-xml-builder/1.1.0")]
52
53extern crate indexmap;
54use indexmap::IndexMap;
55use std::fmt;
56use std::io::{self, Write};
57
58#[derive(Debug, Clone, Eq, PartialEq)]
60pub struct XMLElement {
61 name: String,
62 attributes: IndexMap<String, String>,
63 content: XMLElementContent,
64}
65
66#[derive(Debug, Clone, Eq, PartialEq)]
67enum XMLElementContent {
68 Empty,
69 Elements(Vec<XMLElement>),
70 Text(String),
71}
72
73impl fmt::Display for XMLElement {
74 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75 let mut s: Vec<u8> = Vec::new();
76 self.write(&mut s)
77 .expect("Failure writing output to Vec<u8>");
78 write!(f, "{}", unsafe { String::from_utf8_unchecked(s) })
79 }
80}
81
82impl XMLElement {
83 pub fn new(name: impl ToString) -> Self {
85 XMLElement {
86 name: name.to_string(),
87 attributes: IndexMap::new(),
88 content: XMLElementContent::Empty,
89 }
90 }
91
92 pub fn add_attribute(&mut self, name: impl ToString, value: impl ToString) {
95 self.attributes
96 .insert(name.to_string(), escape_str(&value.to_string()));
97 }
98
99 pub fn add_child(&mut self, child: XMLElement) {
109 use XMLElementContent::*;
110 match self.content {
111 Empty => {
112 self.content = Elements(vec![child]);
113 }
114 Elements(ref mut list) => {
115 list.push(child);
116 }
117 Text(_) => {
118 panic!("Attempted adding child element to element with text.");
119 }
120 }
121 }
122
123 pub fn add_text(&mut self, text: impl ToString) {
131 use XMLElementContent::*;
132 match self.content {
133 Empty => {
134 self.content = Text(escape_str(&text.to_string()));
135 }
136 _ => {
137 panic!("Attempted adding text to non-empty element.");
138 }
139 }
140 }
141
142 pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
150 writeln!(writer, r#"<?xml version = "1.0" encoding = "UTF-8"?>"#)?;
151 self.write_level(&mut writer, 0)
152 }
153
154 fn write_level<W: Write>(&self, writer: &mut W, level: usize) -> io::Result<()> {
155 use XMLElementContent::*;
156 let prefix = "\t".repeat(level);
157 match &self.content {
158 Empty => {
159 writeln!(
160 writer,
161 "{}<{}{} />",
162 prefix,
163 self.name,
164 self.attribute_string()
165 )?;
166 }
167 Elements(list) => {
168 writeln!(
169 writer,
170 "{}<{}{}>",
171 prefix,
172 self.name,
173 self.attribute_string()
174 )?;
175 for elem in list {
176 elem.write_level(writer, level + 1)?;
177 }
178 writeln!(writer, "{}</{}>", prefix, self.name)?;
179 }
180 Text(text) => {
181 writeln!(
182 writer,
183 "{}<{}{}>{}</{1}>",
184 prefix,
185 self.name,
186 self.attribute_string(),
187 text
188 )?;
189 }
190 }
191 Ok(())
192 }
193
194 fn attribute_string(&self) -> String {
195 if self.attributes.is_empty() {
196 "".to_owned()
197 } else {
198 let mut result = "".to_owned();
199 for (k, v) in &self.attributes {
200 result = result + &format!(r#" {}="{}""#, k, v);
201 }
202 result
203 }
204 }
205}
206
207fn escape_str(input: &str) -> String {
208 input
209 .replace('&', "&")
210 .replace('"', """)
211 .replace('\'', "'")
212 .replace('<', "<")
213 .replace('>', ">")
214}
215
216#[cfg(test)]
217mod tests {
218 use XMLElement;
219
220 #[test]
221 fn write_xml() {
222 let mut root = XMLElement::new("root");
223 let mut child1 = XMLElement::new("child1");
224 let inner1 = XMLElement::new("inner");
225 child1.add_child(inner1);
226 let mut inner2 = XMLElement::new("inner");
227 inner2.add_text("Example Text\nNew line");
228 child1.add_child(inner2);
229 root.add_child(child1);
230 let mut child2 = XMLElement::new("child2");
231 child2.add_attribute("at1", "test &");
232 child2.add_attribute("at2", "test <");
233 child2.add_attribute("at3", "test \"");
234 let mut inner3 = XMLElement::new("inner");
235 inner3.add_attribute("test", "example");
236 child2.add_child(inner3);
237 root.add_child(child2);
238 let mut child3 = XMLElement::new("child3");
239 child3.add_text("&< &");
240 root.add_child(child3);
241 let mut child4 = XMLElement::new("child4");
242 child4.add_attribute("non-str-attribute", 5);
243 child4.add_text(6);
244 root.add_child(child4);
245
246 let expected = r#"<?xml version = "1.0" encoding = "UTF-8"?>
247<root>
248 <child1>
249 <inner />
250 <inner>Example Text
251New line</inner>
252 </child1>
253 <child2 at1="test &" at2="test <" at3="test "">
254 <inner test="example" />
255 </child2>
256 <child3>&< &</child3>
257 <child4 non-str-attribute="5">6</child4>
258</root>
259"#;
260 assert_eq!(
261 format!("{}", root),
262 expected,
263 "Attempt to write XML did not give expected results."
264 );
265 }
266
267 #[test]
268 #[should_panic]
269 fn add_text_to_parent_element() {
270 let mut e = XMLElement::new("test");
271 e.add_child(XMLElement::new("test"));
272 e.add_text("example text");
273 }
274
275 #[test]
276 #[should_panic]
277 fn add_child_to_text_element() {
278 let mut e = XMLElement::new("test");
279 e.add_text("example text");
280 e.add_child(XMLElement::new("test"));
281 }
282}