use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum XmlElement<'s> {
StartTag {
name: &'s str,
attrs: &'s str,
},
EndTag {
name: &'s str,
},
EmptyTag {
name: &'s str,
attrs: &'s str,
},
Text(&'s str),
Comment(&'s str),
}
impl<'s> XmlElement<'s> {
#[must_use]
#[track_caller]
pub fn unwrap_start_tag(&self) -> (&'s str, &'s str) {
match self {
Self::StartTag { name, attrs } => (name, attrs),
_ => panic!("unwrap_start_tag on non-StartTag: {:?}", self),
}
}
#[must_use]
#[track_caller]
pub fn unwrap_end_tag(&self) -> &'s str {
match self {
Self::EndTag { name } => name,
_ => panic!("unwrap_end_tag on non-EndTag: {:?}", self),
}
}
#[must_use]
#[track_caller]
pub fn unwrap_text(&self) -> &'s str {
match self {
Self::Text(t) => t,
_ => panic!("unwrap_text on non-Text: {:?}", self),
}
}
#[must_use]
#[track_caller]
pub fn unwrap_comment(&self) -> &'s str {
match self {
Self::Comment(t) => t,
_ => panic!("unwrap_comment on non-Comment: {:?}", self),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ElementIterator<'s> {
text: &'s str,
}
impl<'s> ElementIterator<'s> {
#[inline]
#[must_use]
pub fn new(text: &'s str) -> Self {
let text = trim_xml_declaration(text).unwrap_or_default();
Self { text }
}
}
impl<'s> Iterator for ElementIterator<'s> {
type Item = XmlElement<'s>;
#[inline]
#[must_use]
fn next(&mut self) -> Option<Self::Item> {
#[allow(clippy::never_loop)]
'clear_and_return_none: loop {
if self.text.is_empty() {
return None;
} else if self.text.starts_with("<!CDATA[") {
let (cdata, rest) = match break_on_first_str(self.text, "]]>") {
Some((cdata, rest)) => (&cdata[8..], rest),
None => break 'clear_and_return_none,
};
self.text = rest;
return Some(XmlElement::Text(cdata));
} else if self.text.starts_with("<!--") {
let (comment, rest) = match break_on_first_str(self.text, "-->") {
Some((comment, rest)) => (&comment[4..], rest),
None => break 'clear_and_return_none,
};
self.text = rest;
return Some(XmlElement::Comment(comment));
} else if self.text.starts_with('<') {
let (tag_text, rest) = match break_on_first_char(self.text, '>') {
Some((tag_text, rest)) => (&tag_text[1..], rest),
None => break 'clear_and_return_none,
};
self.text = rest;
if let Some(stripped) = tag_text.strip_suffix('/') {
let (name, attrs) =
break_on_first_char(tag_text, ' ').unwrap_or((stripped, "/"));
let attrs = &attrs[..attrs.len() - 1];
return Some(XmlElement::EmptyTag { name, attrs });
} else if let Some(name) = tag_text.strip_prefix('/') {
return Some(XmlElement::EndTag { name });
} else {
let (name, attrs) =
break_on_first_char(tag_text, ' ').unwrap_or((tag_text, ""));
return Some(XmlElement::StartTag { name, attrs });
}
} else {
let text_end_byte = self.text.find('<').unwrap_or(self.text.len());
let (here, rest) = self.text.split_at(text_end_byte);
self.text = rest;
return Some(XmlElement::Text(here));
}
}
self.text = "";
None
}
}
impl<'s> core::iter::FusedIterator for ElementIterator<'s> {}
#[inline]
#[must_use]
pub fn skip_empty_text_elements(el: XmlElement<'_>) -> Option<XmlElement<'_>> {
match el {
XmlElement::Text(t) => {
if t.trim().is_empty() {
None
} else {
Some(XmlElement::Text(t))
}
}
other => Some(other),
}
}
#[inline]
#[must_use]
pub fn skip_comments(el: XmlElement<'_>) -> Option<XmlElement<'_>> {
match el {
XmlElement::Comment(_) => None,
other => Some(other),
}
}
#[inline]
#[must_use]
pub fn trim_text(el: XmlElement<'_>) -> XmlElement<'_> {
match el {
XmlElement::Text(t) => XmlElement::Text(t.trim()),
other => other,
}
}
fn trim_xml_declaration(mut text: &str) -> Option<&str> {
text = text.trim();
if text.starts_with("<?xml") {
break_on_first_str(text.trim_start(), "?>")
.map(|(_decl, rest)| rest.trim_start())
} else {
Some(text)
}
}
#[test]
fn test_trim_xml_declaration() {
assert_eq!(trim_xml_declaration(""), Some(""));
assert_eq!(trim_xml_declaration(" "), Some(""));
assert_eq!(trim_xml_declaration("<?xml"), None);
let a = r#"<?xml ?>"#;
assert_eq!(trim_xml_declaration(a), Some(""));
let b = r#"<?xml
version="version_number" ?>"#;
assert_eq!(trim_xml_declaration(b), Some(""));
let c = r#"<?xml
version="version_number"
encoding="encoding_declaration" ?>"#;
assert_eq!(trim_xml_declaration(c), Some(""));
let d = r#"<?xml
version="version_number"
encoding="encoding_declaration"
standalone="standalone_status" ?>"#;
assert_eq!(trim_xml_declaration(d), Some(""));
let graphics = r#"<?xml version="1.0" encoding="UTF-8"?>
<registry>"#;
assert_eq!(trim_xml_declaration(graphics), Some("<registry>"));
}