edit_xml/element/builder.rs
1use crate::{utils::HashMap, Document, Node};
2
3use super::Element;
4#[derive(Debug, Clone, PartialEq, Eq)]
5enum NewNodes {
6 Element(ElementBuilder),
7 Text(String),
8 Comment(String),
9 CData(String),
10 PI(String),
11}
12impl NewNodes {
13 /// Converts te NewNode into a Node and pushes it to the parent.
14 ///
15 /// # Panics
16 /// If the parent is not an element.
17 fn push_to(self, doc: &mut Document, parent: Element) {
18 let result = match self {
19 NewNodes::Element(elem) => {
20 let elem = elem.finish(doc);
21 parent.push_child(doc, Node::Element(elem))
22 }
23 NewNodes::Text(text) => parent.push_child(doc, Node::Text(text)),
24 NewNodes::Comment(text) => parent.push_child(doc, Node::Comment(text)),
25 NewNodes::CData(text) => parent.push_child(doc, Node::CData(text)),
26 NewNodes::PI(text) => parent.push_child(doc, Node::PI(text)),
27 };
28
29 if let Err(e) = result {
30 panic!("Illegal Parameter put in ElementBuilder: {:?}", e);
31 }
32 }
33}
34/// An easy way to build a new element
35/// by chaining methods to add properties.
36///
37/// Call [`Element::build()`] to start building.
38/// To finish building, either call `.finish()` or `.push_to(parent)`
39/// which returns [`Element`].
40///
41/// # Examples
42///
43/// ```
44/// use edit_xml::{Document, Element, Node};
45///
46/// let mut doc = Document::new();
47///
48/// let root = Element::build("root")
49/// .attribute("id", "main")
50/// .attribute("class", "main")
51/// .finish(&mut doc);
52/// doc.push_root_node(root.as_node());
53///
54/// let name = Element::build("name")
55/// .add_text("No Name")
56/// .push_to(&mut doc, root);
57///
58/// /* Equivalent xml:
59/// <root id="main" class="main">
60/// <name>No Name</name>
61/// </root>
62/// */
63/// ```
64///
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct ElementBuilder {
67 full_name: String,
68 attributes: HashMap<String, String>,
69 namespace_decls: HashMap<String, String>,
70 content: Vec<NewNodes>,
71}
72macro_rules! push_node {
73 (
74 $(
75 $(#[$docs:meta])*
76 $fn_name:ident => $node:ident
77 ),*
78 ) => {
79 $(
80 $(#[$docs])*
81 pub fn $fn_name<S: Into<String>>(self, text: S) -> Self {
82 self.push_node(NewNodes::$node(text.into()))
83 }
84 )*
85 };
86}
87impl ElementBuilder {
88 /// Creates a new ElementBuilder with the full name of the element.
89 pub fn new(full_name: impl Into<String>) -> ElementBuilder {
90 ElementBuilder::new_with_capacities(full_name, 0, 0, 0)
91 }
92 /// Creates a new ElementBuilder with the full name of the element and the capacities of the attributes, namespace declarations, and content.
93 pub fn new_with_capacities(
94 full_name: impl Into<String>,
95 attribute_capacity: usize,
96 namespace_capacity: usize,
97 content_capacity: usize,
98 ) -> ElementBuilder {
99 ElementBuilder {
100 full_name: full_name.into(),
101 attributes: HashMap::with_capacity(attribute_capacity),
102 namespace_decls: HashMap::with_capacity(namespace_capacity),
103 content: Vec::with_capacity(content_capacity),
104 }
105 }
106 /// Removes previous prefix if it exists, and attach new prefix.
107 pub fn prefix(mut self, prefix: &str) -> Self {
108 let (_, name) = Element::separate_prefix_name(&self.full_name);
109 if prefix.is_empty() {
110 self.full_name = name.to_string();
111 } else {
112 self.full_name = format!("{}{}", prefix, name);
113 }
114 self
115 }
116 /// Add an attribute to the element.
117 pub fn attribute<S, T>(mut self, name: S, value: T) -> Self
118 where
119 S: Into<String>,
120 T: Into<String>,
121 {
122 self.attributes.insert(name.into(), value.into());
123 self
124 }
125 /// Add a namespace declaration to the element.
126 pub fn namespace_decl<S, T>(mut self, prefix: S, namespace: T) -> Self
127 where
128 S: Into<String>,
129 T: Into<String>,
130 {
131 self.namespace_decls.insert(prefix.into(), namespace.into());
132 self
133 }
134
135 fn push_node(mut self, node: NewNodes) -> Self {
136 self.content.push(node);
137 self
138 }
139
140 push_node![
141 /// Add text content to the element.
142 ///
143 /// ```
144 /// use edit_xml::{Document, Element};
145 /// let mut doc = Document::new();
146 /// let root = Element::build("root")
147 /// .add_text("Hello")
148 /// .finish(&mut doc);
149 /// let content = root.children(&doc);
150 /// assert_eq!(content.len(), 1);
151 /// assert!(content[0].is_text());
152 /// ```
153 add_text => Text,
154 /// Add a comment node to the element.
155 ///
156 /// ```
157 /// use edit_xml::{Document, Element};
158 /// let mut doc = Document::new();
159 /// let root = Element::build("root")
160 /// .add_comment("This is a comment")
161 /// .finish(&mut doc);
162 /// let content = root.children(&doc);
163 /// assert_eq!(content.len(), 1);
164 /// assert!(content[0].is_comment());
165 /// ```
166 add_comment => Comment,
167 /// Add a CDATA node to the element.
168 /// ```
169 /// use edit_xml::{Document, Element};
170 /// let mut doc = Document::new();
171 /// let root = Element::build("root")
172 /// .add_cdata("This is a CDATA")
173 /// .finish(&mut doc);
174 /// let content = root.children(&doc);
175 /// assert_eq!(content.len(), 1);
176 /// assert!(content[0].is_cdata());
177 /// ```
178 add_cdata => CData,
179 /// Add a Processing Instruction node to the element.
180 ///
181 /// ```
182 /// use edit_xml::{Document, Element};
183 /// let mut doc = Document::new();
184 /// let root = Element::build("root")
185 /// .add_pi("target")
186 /// .finish(&mut doc);
187 /// let content = root.children(&doc);
188 /// assert_eq!(content.len(), 1);
189 /// assert!(content[0].is_pi());
190 /// ```
191 add_pi => PI
192 ];
193 /// Add an element to the element.
194 pub fn add_element(mut self, elem: ElementBuilder) -> Self {
195 self.content.push(NewNodes::Element(elem));
196 self
197 }
198 /// Adds an element to the element.
199 ///
200 /// ```
201 /// use edit_xml::{Document, Element};
202 /// let mut doc = Document::new();
203 ///
204 /// let root = Element::build("root")
205 /// .create_element("child", |builder| {
206 /// builder
207 /// .add_text("Hello")
208 /// }).finish(&mut doc);
209 ///
210 /// let content = root.children(&doc);
211 /// assert_eq!(content.len(), 1);
212 /// assert_eq!(content[0].text_content(&doc), "Hello");
213 /// ```
214 pub fn create_element<F>(self, name: impl Into<String>, f: F) -> Self
215 where
216 F: FnOnce(ElementBuilder) -> ElementBuilder,
217 {
218 let builder = f(ElementBuilder::new(name));
219 self.add_element(builder)
220 }
221 /// Finish building the element and return it.
222 /// The result must be pushed to a parent element or the root node.
223 ///
224 /// ```
225 /// use edit_xml::{Document, Element, ElementBuilder};
226 /// let mut doc = Document::new();
227 /// let root = ElementBuilder::new("root")
228 /// .add_text("Hello")
229 /// .finish(&mut doc);
230 /// doc.push_root_node(root.as_node());
231 /// ```
232 #[must_use]
233 pub fn finish(self, doc: &mut Document) -> Element {
234 let Self {
235 full_name,
236 attributes,
237 namespace_decls,
238 content,
239 } = self;
240 let elem = Element::with_data(doc, full_name, attributes, namespace_decls);
241
242 for node in content {
243 node.push_to(doc, elem);
244 }
245 elem
246 }
247
248 /// Push this element to the parent's children.
249 ///
250 /// # Panics
251 ///
252 /// If the parent is not an element.
253 pub fn push_to(self, doc: &mut Document, parent: Element) -> Element {
254 let elem = self.finish(doc);
255 parent
256 .push_child_element(doc, elem)
257 .expect("Illegal Parameter put in ElementBuilder");
258 elem
259 }
260 /// Push this element to the root node.
261 /// ```
262 /// use edit_xml::{Document, Element, ElementBuilder};
263 /// let mut doc = Document::new();
264 /// let root = ElementBuilder::new("root")
265 /// .add_text("Hello")
266 /// .push_to_root_node(&mut doc);
267 /// assert_eq!(doc.root_element().unwrap(), root);
268 pub fn push_to_root_node(self, doc: &mut Document) -> Element {
269 let elem = self.finish(doc);
270 doc.push_root_node(Node::Element(elem))
271 .expect("Illegal Parameter put in ElementBuilder");
272 elem
273 }
274}
275
276#[cfg(test)]
277mod tests {
278
279 use super::*;
280 use crate::Document;
281 fn start() -> Document {
282 crate::utils::tests::setup_logger();
283
284 Document::new()
285 }
286 #[test]
287 fn test_element_builder() -> anyhow::Result<()> {
288 let mut doc = start();
289
290 let element = ElementBuilder::new("root")
291 .attribute("id", "main")
292 .attribute("class", "main")
293 .create_element("tests", |new| {
294 new.create_element("child", |new| new.add_text("Hello"))
295 })
296 .add_comment("This is a comment")
297 .push_to_root_node(&mut doc);
298
299 let new_element = ElementBuilder::new("hello")
300 .add_text("world")
301 .finish(&mut doc);
302 element.push_child(&mut doc, new_element)?;
303 let to_string = doc.write_str()?;
304
305 println!("{}", to_string);
306
307 Ok(())
308 }
309}