xml_builder/
xmlelement.rs

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