use crate::{Error, Result};
use quick_xml::events::{BytesStart, Event};
use quick_xml::{Reader, XmlVersion};
#[derive(Debug, Clone, Default)]
pub struct Element {
pub name: String,
pub prefix: String,
pub attrs: Vec<(String, String)>,
pub children: Vec<Element>,
pub text: String,
}
impl Element {
pub fn attr(&self, name: &str) -> Option<&str> {
self.attrs.iter().find(|(k, _)| k == name).map(|(_, v)| v.as_str())
}
pub fn child(&self, name: &str) -> Option<&Element> {
self.children.iter().find(|c| c.name == name)
}
pub fn children_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Element> + 'a {
self.children.iter().filter(move |c| c.name == name)
}
pub fn child_text(&self, name: &str) -> Option<&str> {
self.child(name).map(|c| c.text.trim()).filter(|t| !t.is_empty())
}
pub fn find(&self, name: &str) -> Option<&Element> {
if self.name == name {
return Some(self);
}
self.children.iter().find_map(|c| c.find(name))
}
pub fn find_all<'a>(&'a self, name: &str, out: &mut Vec<&'a Element>) {
for c in &self.children {
if c.name == name {
out.push(c);
} else {
c.find_all(name, out);
}
}
}
}
fn local(name: &str) -> String {
name.rsplit_once(':').map_or(name, |(_, l)| l).to_string()
}
fn element(e: &BytesStart) -> Element {
let attrs = e
.attributes()
.flatten()
.map(|a| (local(a.key.as_ref()), a.normalized_value(XmlVersion::Implicit1_0).map(|v| v.into_owned()).unwrap_or_default()))
.collect();
let name = e.name();
let qname: &str = name.as_ref();
let prefix = qname.rsplit_once(':').map(|(p, _)| p.to_string()).unwrap_or_default();
Element { name: local(qname), prefix, attrs, ..Default::default() }
}
pub fn decode(bytes: &[u8]) -> String {
let bytes = bytes.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(bytes);
match std::str::from_utf8(bytes) {
Ok(s) => s.to_string(),
Err(_) => bytes.iter().map(|&b| b as char).collect(),
}
}
pub fn parse(text: &str) -> Result<Element> {
let err = |e: &dyn std::fmt::Display| Error::InvalidData(format!("XML: {e}"));
let mut reader = Reader::from_str(text);
reader.config_mut().check_end_names = false;
let mut stack: Vec<Element> = vec![Element::default()];
loop {
match reader.read_event().map_err(|e| err(&e))? {
Event::Start(e) => stack.push(element(&e)),
Event::Empty(e) => {
let el = element(&e);
if let Some(parent) = stack.last_mut() {
parent.children.push(el);
}
}
Event::End(_) => {
if stack.len() > 1 {
let el = stack.pop().unwrap_or_default();
if let Some(parent) = stack.last_mut() {
parent.children.push(el);
}
}
}
Event::Text(t) => {
if let Some(top) = stack.last_mut() {
top.text.push_str(&t.xml10_content());
}
}
Event::CData(t) => {
if let Some(top) = stack.last_mut() {
top.text.push_str(&t.xml10_content());
}
}
Event::GeneralRef(r) => {
let c = match r.resolve_char_ref().ok().flatten() {
Some(c) => Some(c),
None => match &*r {
"amp" => Some('&'),
"lt" => Some('<'),
"gt" => Some('>'),
"quot" => Some('"'),
"apos" => Some('\''),
_ => None,
},
};
if let (Some(c), Some(top)) = (c, stack.last_mut()) {
top.text.push(c);
}
}
Event::Eof => break,
_ => {}
}
}
while stack.len() > 1 {
let el = stack.pop().unwrap_or_default();
if let Some(parent) = stack.last_mut() {
parent.children.push(el);
}
}
let doc = stack.pop().unwrap_or_default();
doc.children.into_iter().next().ok_or_else(|| Error::InvalidData("XML: no root element".into()))
}
pub fn escape(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
c => out.push(c),
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_elements_text_and_entities() {
let root = parse(r#"<?xml version="1.0"?><a:root xmlns:a="x" a:id="1"><b>T & é<![CDATA[<c>]]></b><b/></a:root>"#).unwrap();
assert_eq!((root.name.as_str(), root.prefix.as_str()), ("root", "a"));
assert_eq!(root.attr("id"), Some("1"));
assert_eq!(root.children_named("b").count(), 2);
assert_eq!(root.child_text("b"), Some("T & é<c>"));
assert_eq!(escape("a<b & \"c\""), "a<b & "c"");
}
}