#![doc(html_root_url = "https://docs.rs/xml-creator/0.0.1")]
extern crate indexmap;
use indexmap::IndexMap;
use std::fmt;
use std::io::{self, Write};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct XMLElement {
name: String,
attributes: IndexMap<String, String>,
content: XMLElementContent,
}
#[derive(Debug, Clone, Eq, PartialEq)]
enum XMLElementContent {
Empty,
Elements(Vec<XMLElement>),
Text(String),
}
impl fmt::Display for XMLElement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s: Vec<u8> = Vec::new();
self.write(&mut s)
.expect("Failure writing output to Vec<u8>");
write!(f, "{}", { String::from_utf8(s) }.unwrap())
}
}
impl XMLElement {
pub fn new(name: impl ToString) -> Self {
XMLElement {
name: name.to_string(),
attributes: IndexMap::new(),
content: XMLElementContent::Empty,
}
}
pub fn add_attribute(&mut self, name: impl ToString, value: impl ToString) {
self.attributes
.insert(name.to_string(), escape_str(&value.to_string()));
}
pub fn add_child(&mut self, child: XMLElement) {
use XMLElementContent::*;
match self.content {
Empty => {
self.content = Elements(vec![child]);
}
Elements(ref mut list) => {
list.push(child);
}
Text(_) => {
panic!("Attempted adding child element to element with text.");
}
}
}
pub fn add_text(&mut self, mut text: String, escape: bool, cdata: bool) {
use XMLElementContent::*;
if cdata {
text = wrap_in_cdata(text);
}
match self.content {
Empty => {
if escape {
self.content = Text(escape_str(&text));
} else {
self.content = Text(text);
}
}
_ => {
panic!("Attempted adding text to non-empty element.");
}
}
}
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
writeln!(writer, r#"<?xml version = "1.0" encoding = "UTF-8"?>"#)?;
self.write_level(&mut writer, 0)
}
fn write_level<W: Write>(&self, writer: &mut W, level: usize) -> io::Result<()> {
use XMLElementContent::*;
let prefix = "\t".repeat(level);
match &self.content {
Empty => {
writeln!(
writer,
"{}<{}{} />",
prefix,
self.name,
self.attribute_string()
)?;
}
Elements(list) => {
writeln!(
writer,
"{}<{}{}>",
prefix,
self.name,
self.attribute_string()
)?;
for elem in list {
elem.write_level(writer, level + 1)?;
}
writeln!(writer, "{}</{}>", prefix, self.name)?;
}
Text(text) => {
writeln!(
writer,
"{}<{}{}>{}</{1}>",
prefix,
self.name,
self.attribute_string(),
text
)?;
}
}
Ok(())
}
fn attribute_string(&self) -> String {
if self.attributes.is_empty() {
"".to_owned()
} else {
let mut result = "".to_owned();
for (k, v) in &self.attributes {
result = result + &format!(r#" {}="{}""#, k, v);
}
result
}
}
}
fn escape_str(input: &str) -> String {
input
.replace('&', "&")
.replace('"', """)
.replace('\'', "'")
.replace('<', "<")
.replace('>', ">")
}
fn wrap_in_cdata(string: String) -> String {
format!("{}{}{}", r#"<![CDATA["#, string, "]]>")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn write_xml() {
let mut root = XMLElement::new("root");
let mut child1 = XMLElement::new("child1");
let inner1 = XMLElement::new("inner");
child1.add_child(inner1);
let mut inner2 = XMLElement::new("inner");
inner2.add_text("Example Text\nNew line".to_string(), true, false);
child1.add_child(inner2);
root.add_child(child1);
let mut child2 = XMLElement::new("child2");
child2.add_attribute("at1", "test &");
child2.add_attribute("at2", "test <");
child2.add_attribute("at3", "test \"");
let mut inner3 = XMLElement::new("inner");
inner3.add_attribute("test", "example");
child2.add_child(inner3);
root.add_child(child2);
let mut child3 = XMLElement::new("child3");
child3.add_text("&< &".to_string(), true, false);
root.add_child(child3);
let mut child4 = XMLElement::new("child4");
child4.add_attribute("non-str-attribute", 5);
child4.add_text("6".to_string(), true, false);
root.add_child(child4);
let expected = r#"<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<child1>
<inner />
<inner>Example Text
New line</inner>
</child1>
<child2 at1="test &" at2="test <" at3="test "">
<inner test="example" />
</child2>
<child3>&< &</child3>
<child4 non-str-attribute="5">6</child4>
</root>
"#;
assert_eq!(
format!("{}", root),
expected,
"Attempt to write XML did not give expected results."
);
}
#[test]
#[should_panic]
fn add_text_to_parent_element() {
let mut e = XMLElement::new("test");
e.add_child(XMLElement::new("test"));
e.add_text("example text".to_string(), false, false);
}
#[test]
#[should_panic]
fn add_child_to_text_element() {
let mut e = XMLElement::new("test");
e.add_text("example text".to_string(), false, false);
e.add_child(XMLElement::new("test"));
}
}