minidom_ext/
only_child.rs

1use crate::Error;
2use minidom::Element;
3
4/// Get the one and only child of an element.
5///
6/// If no children or more than two children are found, it is considered an error.
7pub trait OnlyChildElementExt {
8    /// Try to get the unique child of an element.
9    ///
10    /// To select this element, a predicate is specified taking the element as
11    /// an input and returning a boolean.
12    ///
13    /// The function returns a [`Result`] with an error if there is none
14    /// [`NoChildrenFound`] or more than one [`MultipleChildrenFound`] selected elements by the
15    /// predicate above.
16    ///
17    /// [`NoChildrenFound`]: enum.Error.html#variant.NoChildrenFound
18    /// [`MultipleChildrenFound`]: enum.Error.html#variant.MultipleChildrenFound
19    /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
20    fn try_find_only_child<'a, P>(&'a self, predicate: P) -> Result<&'a Self, Error>
21    where
22        P: Fn(&'a Self) -> bool;
23
24    /// Get the unique child of an element.
25    ///
26    /// To select this element, a predicate is specified taking the element as
27    /// an input and returning a boolean.
28    ///
29    /// The function returns an [`Option`] with the child if there is one and
30    /// only one child corresponding to the predicate.
31    ///
32    /// [`Option`]:  https://doc.rust-lang.org/std/option/enum.Option.html
33    fn find_only_child<'a, P>(&'a self, predicate: P) -> Option<&'a Self>
34    where
35        P: Fn(&'a Self) -> bool,
36    {
37        self.try_find_only_child(predicate).ok()
38    }
39
40    /// Try to get an unique child from its name and return a [`Result`].
41    ///
42    /// Returns an [`Error`] if the child can't be found ([`NoChildren`])
43    /// or if the child is not unique ([`MultipleChildren`])
44    ///
45    /// [`Error`]: enum.Error.html
46    /// [`NoChildren`]: enum.Error.html#variant.NoChildren
47    /// [`MultipleChildren`]: enum.Error.html#variant.MultipleChildren
48    /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
49    fn try_only_child<'a>(&'a self, child_name: &str) -> Result<&'a Self, Error>;
50
51    /// Get a unique child from its name and return an [`Option`].
52    ///
53    /// Returns [`None`] if the child can't be found or if the child is not
54    /// unique.
55    ///
56    /// [`None`]:  https://doc.rust-lang.org/std/option/enum.Option.html#variant.None
57    /// [`Option`]:  https://doc.rust-lang.org/std/option/enum.Option.html
58    fn only_child<'a>(&'a self, child_name: &str) -> Option<&'a Self> {
59        self.try_only_child(child_name).ok()
60    }
61}
62
63impl OnlyChildElementExt for Element {
64    /// Implementation of [`OnlyChildElementExt`] for [`Element`] gives you the ability to
65    /// select one and only one child of an XML tag depending on a predicate. If
66    /// none or more than two children are found with the predicate, an error is
67    /// returned.
68    ///
69    /// ```
70    /// use minidom::Element;
71    /// use minidom_ext::OnlyChildElementExt;
72    ///
73    /// let xml: &'static str = r#"<root>
74    ///         <child type="ugly" />
75    ///         <child />
76    ///     </root>"#;
77    /// let root: Element = xml.parse().unwrap();
78    /// let child = root
79    ///     .try_find_only_child(|e| {
80    ///         e.name() == "child" && e.attr("type").map(|id| id == "ugly").unwrap_or(false)
81    ///     })
82    ///     .unwrap();
83    /// assert_eq!("child", child.name());
84    /// ```
85    ///
86    /// [`OnlyChildElementExt`]: trait.OnlyChildElementExt.html
87    /// [`Element`]: ../minidom/element/struct.Element.html
88    fn try_find_only_child<'a, P>(&'a self, predicate: P) -> Result<&'a Self, Error>
89    where
90        P: Fn(&'a Self) -> bool,
91    {
92        let mut child_iterator = self.children().filter(|child| predicate(*child));
93        if let Some(child) = child_iterator.next() {
94            if child_iterator.next().is_none() {
95                Ok(child)
96            } else {
97                Err(Error::MultipleChildrenFound(
98                    self.name().to_owned(),
99                    2 + child_iterator.count(),
100                ))
101            }
102        } else {
103            Err(Error::NoChildrenFound(self.name().to_owned()))
104        }
105    }
106
107    /// Implementation of [`OnlyChildElementExt`] for [`Element`] gives you the ability to
108    /// select one and only one child of an XML tag depending on its name. If
109    /// none or more than two are found with the name, an error is returned.
110    ///
111    /// ```
112    /// use minidom::Element;
113    /// use minidom_ext::OnlyChildElementExt;
114    ///
115    /// let xml: &'static str = r#"<root>
116    ///         <child />
117    ///     </root>"#;
118    /// let root: Element = xml.parse().unwrap();
119    /// let child = root
120    ///     .try_only_child("child")
121    ///     .unwrap();
122    /// assert_eq!("child", child.name());
123    /// ```
124    ///
125    /// [`OnlyChildElementExt`]: trait.OnlyChildElementExt.html
126    /// [`Element`]: ../minidom/element/struct.Element.html
127    fn try_only_child<'a>(&'a self, child_name: &str) -> Result<&'a Self, Error> {
128        self.try_find_only_child(|element| element.name() == child_name)
129            .map_err(|e| match e {
130                Error::MultipleChildrenFound(element_name, count) => {
131                    Error::MultipleChildren(element_name, child_name.to_owned(), count)
132                }
133                Error::NoChildrenFound(element_name) => {
134                    Error::NoChildren(element_name, child_name.to_owned())
135                }
136                e => e,
137            })
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use pretty_assertions::assert_eq;
145
146    #[test]
147    fn only_one_child() {
148        let xml: &'static str = r#"<root>
149                <child type="ugly" />
150                <child />
151            </root>"#;
152        let root: Element = xml.parse().unwrap();
153        let child = root
154            .try_find_only_child(|e| {
155                e.name() == "child" && e.attr("type").map(|id| id == "ugly").unwrap_or(false)
156            })
157            .unwrap();
158        assert_eq!("child", child.name());
159    }
160
161    #[test]
162    fn no_children() {
163        let xml: &'static str = r#"<root />"#;
164        let root: Element = xml.parse().unwrap();
165        let error = root
166            .try_find_only_child(|e| e.name() == "child")
167            .unwrap_err();
168        assert_eq!(
169            "No children matching predicate found in Element \'root\'",
170            format!("{}", error)
171        );
172    }
173
174    #[test]
175    fn multiple_child() {
176        let xml: &'static str = r#"<root>
177                <child />
178                <child />
179            </root>"#;
180        let root: Element = xml.parse().unwrap();
181        let error = root
182            .try_find_only_child(|e| e.name() == "child")
183            .unwrap_err();
184        assert_eq!(
185            "Multiple children matching predicate found in Element \'root\' (found 2 elements)",
186            format!("{}", error)
187        );
188    }
189}