use quarb::{AstAdapter, NodeId, Value};
use quick_xml::escape::resolve_predefined_entity;
use quick_xml::events::{BytesStart, Event};
use quick_xml::{Decoder, Reader};
use std::collections::HashMap;
struct Node {
tag: Option<String>,
attrs: Vec<(String, String)>,
text: String,
parent: Option<NodeId>,
children: Vec<NodeId>,
}
impl Node {
fn attr(&self, name: &str) -> Option<&str> {
self.attrs
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.as_str())
}
}
#[derive(Debug, thiserror::Error)]
pub enum XmlError {
#[error(transparent)]
Syntax(#[from] quick_xml::Error),
#[error("unknown entity reference '&{0};'")]
UnknownEntity(String),
#[error("unclosed element '<{0}>'")]
Unclosed(String),
#[error("no root element")]
NoRoot,
#[error("content after the root element")]
TrailingContent,
}
pub struct XmlAdapter {
nodes: Vec<Node>,
ids: HashMap<String, NodeId>,
root: NodeId,
}
impl XmlAdapter {
pub fn parse(xml: &str) -> Result<Self, XmlError> {
let mut nodes = vec![Node {
tag: None,
attrs: Vec::new(),
text: String::new(),
parent: None,
children: Vec::new(),
}];
let mut ids = HashMap::new();
let root = NodeId(0);
let mut reader = Reader::from_str(xml);
let decoder = reader.decoder();
let mut stack: Vec<usize> = vec![0];
loop {
match reader.read_event()? {
Event::Start(e) => {
let idx = intern(&mut nodes, &mut ids, &stack, decoder, &e)?;
stack.push(idx);
}
Event::Empty(e) => {
intern(&mut nodes, &mut ids, &stack, decoder, &e)?;
}
Event::End(_) => {
let closed = stack.pop().expect("end event matches an open element");
let text = std::mem::take(&mut nodes[closed].text);
let parent = *stack.last().expect("stack holds the root");
nodes[parent].text.push_str(&text);
nodes[closed].text = text;
}
Event::Text(e) => {
let open = *stack.last().expect("stack holds the root");
let text = e.xml_content().map_err(quick_xml::Error::from)?;
nodes[open].text.push_str(&text);
}
Event::CData(e) => {
let open = *stack.last().expect("stack holds the root");
let text = e.decode().map_err(quick_xml::Error::from)?;
nodes[open].text.push_str(&text);
}
Event::GeneralRef(e) => {
let open = *stack.last().expect("stack holds the root");
if let Some(ch) = e.resolve_char_ref()? {
nodes[open].text.push(ch);
} else {
let name = e.decode().map_err(quick_xml::Error::from)?;
let Some(resolved) = resolve_predefined_entity(&name) else {
return Err(XmlError::UnknownEntity(name.into_owned()));
};
nodes[open].text.push_str(resolved);
}
}
Event::Decl(_) | Event::PI(_) | Event::Comment(_) | Event::DocType(_) => {}
Event::Eof => break,
}
}
if stack.len() > 1 {
let open = stack.pop().expect("checked non-root");
let tag = nodes[open].tag.clone().expect("open elements are tagged");
return Err(XmlError::Unclosed(tag));
}
match nodes[0].children.len() {
0 => return Err(XmlError::NoRoot),
1 => {}
_ => return Err(XmlError::TrailingContent),
}
nodes[0].text = nodes[nodes[0].children[0].0 as usize].text.clone();
Ok(XmlAdapter { nodes, ids, root })
}
pub fn locator(&self, node: NodeId) -> String {
let mut segments = Vec::new();
let mut cur = Some(node);
while let Some(id) = cur {
let n = &self.nodes[id.0 as usize];
if let Some(tag) = &n.tag {
segments.push(self.segment(id, tag));
}
cur = n.parent;
}
segments.reverse();
format!("/{}", segments.join("/"))
}
fn segment(&self, node: NodeId, tag: &str) -> String {
let Some(parent) = self.nodes[node.0 as usize].parent else {
return tag.to_string();
};
let siblings = &self.nodes[parent.0 as usize].children;
let same_tag: Vec<NodeId> = siblings
.iter()
.copied()
.filter(|&s| self.nodes[s.0 as usize].tag.as_deref() == Some(tag))
.collect();
if same_tag.len() > 1 {
let n = same_tag.iter().position(|&s| s == node).unwrap() + 1;
format!("{tag}[{n}]")
} else {
tag.to_string()
}
}
}
fn intern(
nodes: &mut Vec<Node>,
ids: &mut HashMap<String, NodeId>,
stack: &[usize],
decoder: Decoder,
e: &BytesStart,
) -> Result<usize, XmlError> {
let idx = nodes.len();
let this = NodeId(idx as u64);
let tag = decoder
.decode(e.name().as_ref())
.map_err(quick_xml::Error::from)?
.into_owned();
let mut attrs = Vec::new();
for attr in e.attributes() {
let attr = attr.map_err(quick_xml::Error::from)?;
let key = decoder
.decode(attr.key.as_ref())
.map_err(quick_xml::Error::from)?
.into_owned();
let value = attr.decode_and_unescape_value(decoder)?.into_owned();
if key == "id" || key == "xml:id" {
ids.entry(value.clone()).or_insert(this);
}
attrs.push((key, value));
}
let parent = *stack.last().expect("stack holds the root");
nodes.push(Node {
tag: Some(tag),
attrs,
text: String::new(),
parent: Some(NodeId(parent as u64)),
children: Vec::new(),
});
nodes[parent].children.push(this);
Ok(idx)
}
fn split_qualified(tag: &str) -> (Option<&str>, &str) {
match tag.split_once(':') {
Some((prefix, local)) => (Some(prefix), local),
None => (None, tag),
}
}
impl AstAdapter for XmlAdapter {
fn root(&self) -> NodeId {
self.root
}
fn children(&self, node: NodeId) -> Vec<NodeId> {
self.nodes[node.0 as usize].children.clone()
}
fn name(&self, node: NodeId) -> Option<String> {
self.nodes[node.0 as usize].tag.clone()
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
self.nodes[node.0 as usize].parent
}
fn property(&self, node: NodeId, name: &str) -> Option<Value> {
self.nodes[node.0 as usize]
.attr(name)
.map(|v| Value::Str(v.to_string()))
}
fn default_value(&self, node: NodeId) -> Option<Value> {
Some(Value::Str(self.nodes[node.0 as usize].text.clone()))
}
fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
let n = &self.nodes[node.0 as usize];
match key {
"tag" => n.tag.clone().map(Value::Str),
"local-name" => n
.tag
.as_deref()
.map(|t| Value::Str(split_qualified(t).1.to_string())),
"ns-prefix" => n
.tag
.as_deref()
.and_then(|t| split_qualified(t).0)
.map(|p| Value::Str(p.to_string())),
"n-attrs" => Some(Value::Int(n.attrs.len() as i64)),
_ => None,
}
}
fn resolve(&self, node: NodeId, property: &str, _hint: Option<&str>) -> Option<NodeId> {
let value = self.nodes[node.0 as usize].attr(property)?;
let target = value.strip_prefix('#').unwrap_or(value);
self.ids.get(target).copied()
}
}