use std::collections::HashMap;
#[derive(Debug, Clone)]
pub(crate) struct XmlElement {
pub(crate) name: String,
pub(crate) attrs: HashMap<String, String>,
pub(crate) children: Vec<XmlElement>,
pub(crate) text_content: String,
}
impl XmlElement {
pub(crate) fn tag_name(&self) -> &str {
&self.name
}
pub(crate) fn attribute(&self, name: &str) -> Option<&str> {
self.attrs.get(name).map(String::as_str)
}
pub(crate) fn children(&self) -> impl Iterator<Item = &XmlElement> {
self.children.iter()
}
pub(crate) fn text_content(&self) -> &str {
&self.text_content
}
}
pub(crate) fn parse_dom(text: &str) -> Result<Option<XmlElement>, String> {
use quick_xml::Reader;
use quick_xml::events::Event;
let mut reader = Reader::from_str(text);
let mut buf: Vec<u8> = Vec::new();
let mut stack: Vec<XmlElement> = Vec::new();
let mut root: Option<XmlElement> = None;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let elem = element_from_start(&e)?;
stack.push(elem);
}
Ok(Event::Empty(e)) => {
let elem = element_from_start(&e)?;
attach_or_root(&mut stack, &mut root, elem);
}
Ok(Event::End(_)) => {
let elem = stack.pop().ok_or_else(|| "unmatched end tag".to_string())?;
attach_or_root(&mut stack, &mut root, elem);
}
Ok(Event::Text(e)) => {
if let Some(parent) = stack.last_mut()
&& let Ok(text) = e.xml_content(quick_xml::XmlVersion::Implicit1_0)
{
parent.text_content.push_str(&text);
}
}
Ok(Event::CData(e)) => {
if let Some(parent) = stack.last_mut()
&& let Ok(text) = e.decode()
{
parent.text_content.push_str(&text);
}
}
Ok(Event::Eof) => break,
Ok(_) => {
}
Err(e) => return Err(e.to_string()),
}
buf.clear();
}
if !stack.is_empty() {
return Err("unclosed elements at EOF".to_string());
}
Ok(root)
}
fn attach_or_root(stack: &mut [XmlElement], root: &mut Option<XmlElement>, elem: XmlElement) {
if let Some(parent) = stack.last_mut() {
parent.children.push(elem);
} else {
if root.is_none() {
*root = Some(elem);
}
}
}
fn element_from_start(e: &quick_xml::events::BytesStart<'_>) -> Result<XmlElement, String> {
let name_bytes = e.local_name();
let name = std::str::from_utf8(name_bytes.as_ref())
.map_err(|err| format!("non-UTF-8 element name: {err}"))?
.to_string();
let mut attrs = HashMap::new();
for attr in e.attributes() {
let attr = attr.map_err(|err| err.to_string())?;
let key_bytes = attr.key.local_name();
let key = std::str::from_utf8(key_bytes.as_ref())
.map_err(|err| format!("non-UTF-8 attribute name: {err}"))?
.to_string();
let value = attr
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.map_err(|err| err.to_string())?
.into_owned();
attrs.insert(key, value);
}
Ok(XmlElement {
name,
attrs,
children: Vec::new(),
text_content: String::new(),
})
}