use quick_xml::events::{BytesStart, Event};
use quick_xml::name::{QName, ResolveResult};
use quick_xml::{NsReader, XmlVersion};
use super::{Attribute, Element, Name, Node, Ns};
use crate::Error;
pub fn parse(bytes: &[u8], part: &str) -> Result<Element, Error> {
let text = std::str::from_utf8(bytes).map_err(|e| Error::Xml {
part: part.to_owned(),
detail: format!("not UTF-8: {e}"),
})?;
let text = text.strip_prefix('\u{feff}').unwrap_or(text);
let mut reader = NsReader::from_str(text);
let config = reader.config_mut();
config.expand_empty_elements = false;
config.trim_text_start = false;
config.trim_text_end = false;
config.check_end_names = true;
let mut stack: Vec<Element> = Vec::new();
let mut root: Option<Element> = None;
loop {
let event = reader.read_event().map_err(|e| Error::Xml {
part: part.to_owned(),
detail: e.to_string(),
})?;
match event {
Event::Start(ref start) => {
let element = element_of(start, &reader, false);
stack.push(element);
}
Event::Empty(ref start) => {
let element = element_of(start, &reader, true);
push_node(&mut stack, &mut root, Node::Element(element), part)?;
}
Event::End(_) => {
let done = stack.pop().ok_or_else(|| Error::Xml {
part: part.to_owned(),
detail: "an end tag with nothing open".to_owned(),
})?;
push_node(&mut stack, &mut root, Node::Element(done), part)?;
}
Event::Text(ref t) => {
push_text(&mut stack, &t.xml10_content());
}
Event::GeneralRef(ref r) => {
let text = reference(r, part)?;
push_text(&mut stack, &text);
}
Event::CData(ref c) => {
let node = Node::CData(c.xml10_content().into_owned());
push_node(&mut stack, &mut root, node, part)?;
}
Event::Comment(ref c) => {
let node = Node::Comment(c.xml10_content().into_owned());
push_node(&mut stack, &mut root, node, part)?;
}
Event::PI(p) => {
let node = Node::ProcessingInstruction(p.into_inner().into_owned());
push_node(&mut stack, &mut root, node, part)?;
}
Event::Decl(_) | Event::DocType(_) => {}
Event::Eof => break,
}
}
root.ok_or(Error::Xml {
part: part.to_owned(),
detail: "no root element".to_owned(),
})
}
fn reference(r: &quick_xml::events::BytesRef<'_>, part: &str) -> Result<String, Error> {
let name = r.xml10_content();
let resolved = if r.is_char_ref() {
r.resolve_char_ref().map_err(|e| Error::Xml {
part: part.to_owned(),
detail: e.to_string(),
})?
} else {
match &*name {
"amp" => Some('&'),
"lt" => Some('<'),
"gt" => Some('>'),
"quot" => Some('"'),
"apos" => Some('\''),
_ => None,
}
};
Ok(match resolved {
Some(c) => c.to_string(),
None => format!("&{name};"),
})
}
fn push_text(stack: &mut [Element], text: &str) {
if let Some(open) = stack.last_mut() {
if let Some(Node::Text(existing)) = open.children.last_mut() {
existing.push_str(text);
} else {
open.children.push(Node::Text(text.to_owned()));
}
}
}
fn push_node(
stack: &mut [Element],
root: &mut Option<Element>,
node: Node,
part: &str,
) -> Result<(), Error> {
if let Some(open) = stack.last_mut() {
open.children.push(node);
return Ok(());
}
match node {
Node::Element(e) if root.is_none() => {
*root = Some(e);
Ok(())
}
Node::Element(_) => Err(Error::Xml {
part: part.to_owned(),
detail: "a second root element".to_owned(),
}),
_ => Ok(()),
}
}
fn element_of(start: &BytesStart<'_>, reader: &NsReader<&[u8]>, self_closing: bool) -> Element {
let name = resolved_name(reader, start.name(), false);
let mut attrs = Vec::new();
for attr in start.attributes() {
let Ok(attr) = attr else { continue };
let value = attr
.normalized_value(XmlVersion::Implicit1_0)
.unwrap_or_default()
.into_owned();
attrs.push(Attribute {
name: resolved_name(reader, attr.key, true),
value,
});
}
Element {
name,
attrs,
children: Vec::new(),
self_closing,
}
}
fn resolved_name(reader: &NsReader<&[u8]>, qname: QName<'_>, is_attribute: bool) -> Name {
let prefix = qname.prefix().map(|p| p.as_ref().into());
let local: Box<str> = qname.local_name().as_ref().into();
if prefix.as_deref() == Some("xmlns") {
return Name {
prefix,
local,
ns: Ns::Xmlns,
};
}
if prefix.is_none() && &*local == "xmlns" {
return Name {
prefix: None,
local,
ns: Ns::Xmlns,
};
}
let (resolved, _) = if is_attribute {
reader.resolver().resolve_attribute(qname)
} else {
reader.resolver().resolve_element(qname)
};
let ns = match resolved {
ResolveResult::Bound(uri) => Ns::from_uri(uri.as_ref()),
ResolveResult::Unbound | ResolveResult::Unknown(_) => Ns::None,
};
Name { prefix, local, ns }
}