1use quarb::{AstAdapter, NodeId, Value};
34use quick_xml::escape::resolve_predefined_entity;
35use quick_xml::events::{BytesStart, Event};
36use quick_xml::{Decoder, Reader};
37use std::collections::HashMap;
38
39struct Node {
40 tag: Option<String>,
42 attrs: Vec<(String, String)>,
43 text: String,
46 parent: Option<NodeId>,
47 children: Vec<NodeId>,
48}
49
50impl Node {
51 fn attr(&self, name: &str) -> Option<&str> {
52 self.attrs
53 .iter()
54 .find(|(k, _)| k == name)
55 .map(|(_, v)| v.as_str())
56 }
57}
58
59#[derive(Debug, thiserror::Error)]
61pub enum XmlError {
62 #[error(transparent)]
63 Syntax(#[from] quick_xml::Error),
64 #[error("unknown entity reference '&{0};'")]
65 UnknownEntity(String),
66 #[error("unclosed element '<{0}>'")]
67 Unclosed(String),
68 #[error("no root element")]
69 NoRoot,
70 #[error("content after the root element")]
71 TrailingContent,
72}
73
74pub struct XmlAdapter {
76 nodes: Vec<Node>,
77 ids: HashMap<String, NodeId>,
79 root: NodeId,
80}
81
82impl XmlAdapter {
83 pub fn parse(xml: &str) -> Result<Self, XmlError> {
87 let mut nodes = vec![Node {
88 tag: None,
89 attrs: Vec::new(),
90 text: String::new(),
91 parent: None,
92 children: Vec::new(),
93 }];
94 let mut ids = HashMap::new();
95 let root = NodeId(0);
96
97 let mut reader = Reader::from_str(xml);
98 let decoder = reader.decoder();
99 let mut stack: Vec<usize> = vec![0];
102
103 loop {
104 match reader.read_event()? {
105 Event::Start(e) => {
106 let idx = intern(&mut nodes, &mut ids, &stack, decoder, &e)?;
107 stack.push(idx);
108 }
109 Event::Empty(e) => {
110 intern(&mut nodes, &mut ids, &stack, decoder, &e)?;
111 }
112 Event::End(_) => {
113 let closed = stack.pop().expect("end event matches an open element");
114 let text = std::mem::take(&mut nodes[closed].text);
115 let parent = *stack.last().expect("stack holds the root");
116 nodes[parent].text.push_str(&text);
117 nodes[closed].text = text;
118 }
119 Event::Text(e) => {
120 let open = *stack.last().expect("stack holds the root");
121 let text = e.xml_content().map_err(quick_xml::Error::from)?;
122 nodes[open].text.push_str(&text);
123 }
124 Event::CData(e) => {
125 let open = *stack.last().expect("stack holds the root");
126 let text = e.decode().map_err(quick_xml::Error::from)?;
127 nodes[open].text.push_str(&text);
128 }
129 Event::GeneralRef(e) => {
130 let open = *stack.last().expect("stack holds the root");
131 if let Some(ch) = e.resolve_char_ref()? {
132 nodes[open].text.push(ch);
133 } else {
134 let name = e.decode().map_err(quick_xml::Error::from)?;
135 let Some(resolved) = resolve_predefined_entity(&name) else {
136 return Err(XmlError::UnknownEntity(name.into_owned()));
137 };
138 nodes[open].text.push_str(resolved);
139 }
140 }
141 Event::Decl(_) | Event::PI(_) | Event::Comment(_) | Event::DocType(_) => {}
142 Event::Eof => break,
143 }
144 }
145
146 if stack.len() > 1 {
147 let open = stack.pop().expect("checked non-root");
148 let tag = nodes[open].tag.clone().expect("open elements are tagged");
149 return Err(XmlError::Unclosed(tag));
150 }
151 match nodes[0].children.len() {
152 0 => return Err(XmlError::NoRoot),
153 1 => {}
154 _ => return Err(XmlError::TrailingContent),
155 }
156 nodes[0].text = nodes[nodes[0].children[0].0 as usize].text.clone();
159
160 Ok(XmlAdapter { nodes, ids, root })
161 }
162
163 pub fn locator(&self, node: NodeId) -> String {
167 let mut segments = Vec::new();
168 let mut cur = Some(node);
169 while let Some(id) = cur {
170 let n = &self.nodes[id.0 as usize];
171 if let Some(tag) = &n.tag {
172 segments.push(self.segment(id, tag));
173 }
174 cur = n.parent;
175 }
176 segments.reverse();
177 format!("/{}", segments.join("/"))
178 }
179
180 fn segment(&self, node: NodeId, tag: &str) -> String {
181 let Some(parent) = self.nodes[node.0 as usize].parent else {
182 return tag.to_string();
183 };
184 let siblings = &self.nodes[parent.0 as usize].children;
185 let same_tag: Vec<NodeId> = siblings
186 .iter()
187 .copied()
188 .filter(|&s| self.nodes[s.0 as usize].tag.as_deref() == Some(tag))
189 .collect();
190 if same_tag.len() > 1 {
191 let n = same_tag.iter().position(|&s| s == node).unwrap() + 1;
192 format!("{tag}[{n}]")
193 } else {
194 tag.to_string()
195 }
196 }
197}
198
199fn intern(
202 nodes: &mut Vec<Node>,
203 ids: &mut HashMap<String, NodeId>,
204 stack: &[usize],
205 decoder: Decoder,
206 e: &BytesStart,
207) -> Result<usize, XmlError> {
208 let idx = nodes.len();
209 let this = NodeId(idx as u64);
210 let tag = decoder
211 .decode(e.name().as_ref())
212 .map_err(quick_xml::Error::from)?
213 .into_owned();
214 let mut attrs = Vec::new();
215 for attr in e.attributes() {
216 let attr = attr.map_err(quick_xml::Error::from)?;
217 let key = decoder
218 .decode(attr.key.as_ref())
219 .map_err(quick_xml::Error::from)?
220 .into_owned();
221 let value = attr.decode_and_unescape_value(decoder)?.into_owned();
222 if key == "id" || key == "xml:id" {
223 ids.entry(value.clone()).or_insert(this);
224 }
225 attrs.push((key, value));
226 }
227 let parent = *stack.last().expect("stack holds the root");
228 nodes.push(Node {
229 tag: Some(tag),
230 attrs,
231 text: String::new(),
232 parent: Some(NodeId(parent as u64)),
233 children: Vec::new(),
234 });
235 nodes[parent].children.push(this);
236 Ok(idx)
237}
238
239fn split_qualified(tag: &str) -> (Option<&str>, &str) {
241 match tag.split_once(':') {
242 Some((prefix, local)) => (Some(prefix), local),
243 None => (None, tag),
244 }
245}
246
247impl AstAdapter for XmlAdapter {
248 fn root(&self) -> NodeId {
249 self.root
250 }
251
252 fn children(&self, node: NodeId) -> Vec<NodeId> {
253 self.nodes[node.0 as usize].children.clone()
254 }
255
256 fn name(&self, node: NodeId) -> Option<String> {
257 self.nodes[node.0 as usize].tag.clone()
258 }
259
260 fn parent(&self, node: NodeId) -> Option<NodeId> {
261 self.nodes[node.0 as usize].parent
262 }
263
264 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
269 self.nodes[node.0 as usize]
270 .attr(name)
271 .map(|v| Value::Str(v.to_string()))
272 }
273
274 fn default_value(&self, node: NodeId) -> Option<Value> {
276 Some(Value::Str(self.nodes[node.0 as usize].text.clone()))
277 }
278
279 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
283 let n = &self.nodes[node.0 as usize];
284 match key {
285 "tag" => n.tag.clone().map(Value::Str),
286 "local-name" => n
287 .tag
288 .as_deref()
289 .map(|t| Value::Str(split_qualified(t).1.to_string())),
290 "ns-prefix" => n
291 .tag
292 .as_deref()
293 .and_then(|t| split_qualified(t).0)
294 .map(|p| Value::Str(p.to_string())),
295 "n-attrs" => Some(Value::Int(n.attrs.len() as i64)),
296 _ => None,
297 }
298 }
299
300 fn resolve(&self, node: NodeId, property: &str, _hint: Option<&str>) -> Option<NodeId> {
304 let value = self.nodes[node.0 as usize].attr(property)?;
305 let target = value.strip_prefix('#').unwrap_or(value);
306 self.ids.get(target).copied()
307 }
308}