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