1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
use std::io::Write;
use crate::{escape_str, Result, XMLElementContent, XMLError};
/// Structure representing an XML element field.
pub struct XMLElement {
/// The name of the XML element.
name: String,
/// A list of tuple representing (key, value) attributes.
attributes: Vec<(String, String)>,
/// A boolean representing whether we want attributes to be sorted.
///
/// If not set, defaults to the root's `XMLELement`.
sort_attributes: Option<bool>,
/// The content of this XML element.
content: XMLElementContent,
}
impl XMLElement {
/// Instantiates a new XMLElement object.
///
/// # Arguments
///
/// * `name` - A string slice that holds the name of the XML element.
pub fn new(name: &str) -> Self {
XMLElement {
name: name.into(),
attributes: Vec::new(),
sort_attributes: None,
content: XMLElementContent::Empty,
}
}
/// Enables attributes sorting.
pub fn enable_attributes_sorting(&mut self) {
self.sort_attributes = Some(true);
}
/// Disables attributes sorting.
pub fn disable_attributes_sorting(&mut self) {
self.sort_attributes = Some(false);
}
/// Adds the given name/value attribute to the XMLElement.
///
/// # Arguments
///
/// * `name` - A string slice that holds the name of the attribute
/// * `value` - A string slice that holds the value of the attribute
pub fn add_attribute(&mut self, name: &str, value: &str) {
self.attributes.push((name.into(), escape_str(value)));
}
/// Adds a new XMLElement child object to the references XMLElement.
///
/// Raises `XMLError` if trying to add a child to a text XMLElement.
///
/// # Arguments
///
/// * `element` - A XMLElement object to add as child
pub fn add_child(&mut self, element: XMLElement) -> Result<()> {
match self.content {
XMLElementContent::Empty => {
self.content = XMLElementContent::Elements(vec![element]);
}
XMLElementContent::Elements(ref mut e) => {
e.push(element);
}
XMLElementContent::Text(_) => {
return Err(XMLError::InsertError(
"Cannot insert child inside an element with text".into(),
))
}
};
Ok(())
}
/// Adds text content to a XMLElement object.
///
/// Raises `XMLError` if trying to add text to a non-empty object.
///
/// # Arguments
///
/// * `text` - A string containing the text to add to the object
pub fn add_text(&mut self, text: String) -> Result<()> {
match self.content {
XMLElementContent::Empty => {
self.content = XMLElementContent::Text(text);
}
_ => {
return Err(XMLError::InsertError(
"Cannot insert text in a non-empty element".into(),
))
}
};
Ok(())
}
/// Internal method rendering attribute list to a String.
///
/// # Arguments
///
/// * `should_sort` - A boolean indicating whether we should sort these atttibutes.
fn attributes_as_string(&self, should_sort: bool) -> String {
if self.attributes.is_empty() {
String::default()
} else {
let mut attributes = self.attributes.clone();
// Giving priority to the element boolean, and taking the global xml if not set
let should_sort_attributes = self.sort_attributes.unwrap_or(should_sort);
if should_sort_attributes {
attributes.sort();
}
let mut result = String::new();
for (k, v) in &attributes {
result = format!(r#"{} {}="{}""#, result, k, v);
}
result
}
}
/// Renders an XMLElement object into the specified writer implementing Write trait.
///
/// Does not take ownership of the object.
///
/// # Arguments
///
/// * `writer` - An object to render the referenced XMLElement to
pub fn render<W: Write>(
&self,
writer: &mut W,
should_sort: bool,
should_indent: bool,
) -> Result<()> {
self.render_level(writer, 0, should_sort, should_indent)
}
/// Internal method rendering and indenting a XMLELement object
///
/// # Arguments
///
/// * `writer` - An object to render the referenced XMLElement to
/// * `level` - An usize representing the depth of the XML tree. Used to indent the object.
fn render_level<W: Write>(
&self,
writer: &mut W,
level: usize,
should_sort: bool,
should_indent: bool,
) -> Result<()> {
let indent = match should_indent {
true => "\t".repeat(level),
false => "".into(),
};
let attributes = self.attributes_as_string(should_sort);
match &self.content {
XMLElementContent::Empty => {
writeln!(writer, "{}<{}{} />", indent, self.name, attributes)?;
}
XMLElementContent::Elements(elements) => {
writeln!(writer, "{}<{}{}>", indent, self.name, attributes)?;
for elem in elements {
elem.render_level(writer, level + 1, should_sort, should_indent)?;
}
writeln!(writer, "{}</{}>", indent, self.name)?;
}
XMLElementContent::Text(text) => {
writeln!(
writer,
"{}<{}{}>{}</{}>",
indent, self.name, attributes, text, self.name
)?;
}
};
Ok(())
}
}