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}