xml_creator/
lib.rs

1//! `xml_creator`
2//! building a simple xml (with childs, attributes, CDATA...)
3//!
4//! # Usage
5//!
6//! Use [XMLElement](struct.XMLElement.html) to create elements with everything you need.
7//!
8//! You can write an XML document by calling
9//! [write](struct.XMLElement.html#method.write) on your root element.
10//!
11//! # Example
12//!
13//! ```rust
14//! # use std::io;
15//! # fn main() -> io::Result<()> {
16//! use std::fs::File;
17//! use xml_creator::XMLElement;
18//!
19//! # /*
20//! let mut file = File::create("example.xml").unwrap();
21//! # */
22//! # let mut file: Vec<u8> = Vec::new();
23//!
24//! let mut company = XMLElement::new("company");
25//! company.add_attribute("name", "val");
26//!
27//! let mut employee = XMLElement::new("employee");
28//! // no escaping and CDATA needed
29//! employee.add_text("Max Mustermann".to_string(), false, false);
30//!
31//! let mut cdata = XMLElement::new("cdata");
32//! // will get wrapped into CDATA
33//! cdata.add_text("<p>Some Html</p>".to_string(), false, true);
34//! company.add_child(cdata);
35//!
36//! let mut escape = XMLElement::new("escape");
37//! // < will get escaped
38//! escape.add_text("<".to_string(), true, false);
39//! company.add_child(escape);
40//!
41//! // add employee to company
42//! company.add_child(employee);
43//!
44//! company.write(file).unwrap();
45//! # Ok(())
46//! # }
47//! ```
48//! `sample.xml` will contain:
49//! ```xml
50//! <?xml version = "1.0" encoding = "UTF-8"?>
51//! <company name="val">
52//!     <employee>Max Mustermann</employee>
53//!     <cdata><![CDATA[<p>Some Html</p>]]></cdata>
54//!     <escape>&lt;</escape>
55//! </company>
56//! ```
57
58#![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('&', "&amp;")
199        .replace('"', "&quot;")
200        .replace('\'', "&apos;")
201        .replace('<', "&lt;")
202        .replace('>', "&gt;")
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 &amp;" at2="test &lt;" at3="test &quot;">
247		<inner test="example" />
248	</child2>
249	<child3>&amp;&lt; &amp;</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}