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}