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