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}