Skip to main content

xml_builder/
xmlelement.rs

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