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}