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 fn declared_encoding(bytes: &[u8]) -> Option<String> {
83 quarb::source::xml_declared_encoding(bytes)
84}
85
86pub struct XmlAdapter {
88 nodes: Vec<Node>,
89 ids: HashMap<String, NodeId>,
91 root: NodeId,
92}
93
94impl XmlAdapter {
95 pub fn parse(xml: &str) -> Result<Self, XmlError> {
99 let mut nodes = vec![Node {
100 tag: None,
101 attrs: Vec::new(),
102 text: String::new(),
103 parent: None,
104 children: Vec::new(),
105 }];
106 let mut ids = HashMap::new();
107 let root = NodeId(0);
108
109 let mut reader = Reader::from_str(xml);
110 let decoder = reader.decoder();
111 let mut stack: Vec<usize> = vec![0];
114
115 loop {
116 match reader.read_event()? {
117 Event::Start(e) => {
118 let idx = intern(&mut nodes, &mut ids, &stack, decoder, &e)?;
119 stack.push(idx);
120 }
121 Event::Empty(e) => {
122 intern(&mut nodes, &mut ids, &stack, decoder, &e)?;
123 }
124 Event::End(_) => {
125 let closed = stack.pop().expect("end event matches an open element");
126 let text = std::mem::take(&mut nodes[closed].text);
127 let parent = *stack.last().expect("stack holds the root");
128 nodes[parent].text.push_str(&text);
129 nodes[closed].text = text;
130 }
131 Event::Text(e) => {
132 let open = *stack.last().expect("stack holds the root");
133 let text = e.xml_content().map_err(quick_xml::Error::from)?;
134 nodes[open].text.push_str(&text);
135 }
136 Event::CData(e) => {
137 let open = *stack.last().expect("stack holds the root");
138 let text = e.decode().map_err(quick_xml::Error::from)?;
139 nodes[open].text.push_str(&text);
140 }
141 Event::GeneralRef(e) => {
142 let open = *stack.last().expect("stack holds the root");
143 if let Some(ch) = e.resolve_char_ref()? {
144 nodes[open].text.push(ch);
145 } else {
146 let name = e.decode().map_err(quick_xml::Error::from)?;
147 let Some(resolved) = resolve_predefined_entity(&name) else {
148 return Err(XmlError::UnknownEntity(name.into_owned()));
149 };
150 nodes[open].text.push_str(resolved);
151 }
152 }
153 Event::Decl(_) | Event::PI(_) | Event::Comment(_) | Event::DocType(_) => {}
154 Event::Eof => break,
155 }
156 }
157
158 if stack.len() > 1 {
159 let open = stack.pop().expect("checked non-root");
160 let tag = nodes[open].tag.clone().expect("open elements are tagged");
161 return Err(XmlError::Unclosed(tag));
162 }
163 match nodes[0].children.len() {
164 0 => return Err(XmlError::NoRoot),
165 1 => {}
166 _ => return Err(XmlError::TrailingContent),
167 }
168 nodes[0].text = nodes[nodes[0].children[0].0 as usize].text.clone();
171
172 Ok(XmlAdapter { nodes, ids, root })
173 }
174
175 pub fn locator(&self, node: NodeId) -> String {
179 let mut segments = Vec::new();
180 let mut cur = Some(node);
181 while let Some(id) = cur {
182 let n = &self.nodes[id.0 as usize];
183 if let Some(tag) = &n.tag {
184 segments.push(self.segment(id, tag));
185 }
186 cur = n.parent;
187 }
188 segments.reverse();
189 format!("/{}", segments.join("/"))
190 }
191
192 fn segment(&self, node: NodeId, tag: &str) -> String {
193 let Some(parent) = self.nodes[node.0 as usize].parent else {
194 return tag.to_string();
195 };
196 let siblings = &self.nodes[parent.0 as usize].children;
197 let same_tag: Vec<NodeId> = siblings
198 .iter()
199 .copied()
200 .filter(|&s| self.nodes[s.0 as usize].tag.as_deref() == Some(tag))
201 .collect();
202 if same_tag.len() > 1 {
203 let n = same_tag.iter().position(|&s| s == node).unwrap() + 1;
204 format!("{tag}[{n}]")
205 } else {
206 tag.to_string()
207 }
208 }
209}
210
211fn intern(
214 nodes: &mut Vec<Node>,
215 ids: &mut HashMap<String, NodeId>,
216 stack: &[usize],
217 decoder: Decoder,
218 e: &BytesStart,
219) -> Result<usize, XmlError> {
220 let idx = nodes.len();
221 let this = NodeId(idx as u64);
222 let tag = decoder
223 .decode(e.name().as_ref())
224 .map_err(quick_xml::Error::from)?
225 .into_owned();
226 let mut attrs = Vec::new();
227 for attr in e.attributes() {
228 let attr = attr.map_err(quick_xml::Error::from)?;
229 let key = decoder
230 .decode(attr.key.as_ref())
231 .map_err(quick_xml::Error::from)?
232 .into_owned();
233 let value = attr.decode_and_unescape_value(decoder)?.into_owned();
234 if key == "id" || key == "xml:id" {
235 ids.entry(value.clone()).or_insert(this);
236 }
237 attrs.push((key, value));
238 }
239 let parent = *stack.last().expect("stack holds the root");
240 nodes.push(Node {
241 tag: Some(tag),
242 attrs,
243 text: String::new(),
244 parent: Some(NodeId(parent as u64)),
245 children: Vec::new(),
246 });
247 nodes[parent].children.push(this);
248 Ok(idx)
249}
250
251fn split_qualified(tag: &str) -> (Option<&str>, &str) {
253 match tag.split_once(':') {
254 Some((prefix, local)) => (Some(prefix), local),
255 None => (None, tag),
256 }
257}
258
259impl AstAdapter for XmlAdapter {
260 fn root(&self) -> NodeId {
261 self.root
262 }
263
264 fn walks_for_references(&self) -> bool {
267 true
268 }
269
270 fn children(&self, node: NodeId) -> Vec<NodeId> {
271 self.nodes[node.0 as usize].children.clone()
272 }
273
274 fn name(&self, node: NodeId) -> Option<String> {
275 self.nodes[node.0 as usize].tag.clone()
276 }
277
278 fn parent(&self, node: NodeId) -> Option<NodeId> {
279 self.nodes[node.0 as usize].parent
280 }
281
282 fn ref_property(&self, node: NodeId) -> Option<String> {
289 let n = &self.nodes[node.0 as usize];
290 ["xlink:href", "href"]
291 .into_iter()
292 .find(|a| n.attr(a).is_some())
293 .map(str::to_string)
294 }
295
296 fn property_names(&self, node: NodeId) -> Vec<String> {
298 self.nodes[node.0 as usize]
299 .attrs
300 .iter()
301 .map(|(k, _)| k.clone())
302 .collect()
303 }
304
305 fn can_refer_to(&self, _source: NodeId, _target: NodeId) -> Option<bool> {
308 Some(true)
309 }
310
311 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
312 self.nodes[node.0 as usize]
313 .attr(name)
314 .map(|v| Value::Str(v.to_string()))
315 }
316
317 fn default_value(&self, node: NodeId) -> Option<Value> {
319 Some(Value::Str(self.nodes[node.0 as usize].text.clone()))
320 }
321
322 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
326 let n = &self.nodes[node.0 as usize];
327 match key {
328 "tag" => n.tag.clone().map(Value::Str),
329 "local-name" => n
330 .tag
331 .as_deref()
332 .map(|t| Value::Str(split_qualified(t).1.to_string())),
333 "ns-prefix" => n
334 .tag
335 .as_deref()
336 .and_then(|t| split_qualified(t).0)
337 .map(|p| Value::Str(p.to_string())),
338 "n-attrs" => Some(Value::Int(n.attrs.len() as i64)),
339 _ => None,
340 }
341 }
342
343 fn resolve(&self, node: NodeId, property: &str, _hint: Option<&str>) -> Option<NodeId> {
347 let value = self.nodes[node.0 as usize].attr(property)?;
348 let target = value.strip_prefix('#').unwrap_or(value);
349 self.ids.get(target).copied()
350 }
351}
352
353#[cfg(test)]
354mod declared_tests {
355 #[test]
356 fn prologue_declaration() {
357 assert_eq!(
358 super::declared_encoding(b"<?xml version='1.0' encoding = 'Windows-1251' ?><a/>"),
359 Some("Windows-1251".into())
360 );
361 assert_eq!(
362 super::declared_encoding(b"<?xml version=\"1.0\"?><a/>"),
363 None
364 );
365 assert_eq!(super::declared_encoding(b"<a encoding=\"x\"/>"), None);
366 assert_eq!(
367 super::declared_encoding(b"<?xml-stylesheet href=\"a.xsl\" encoding=\"koi8-r\"?><r/>"),
368 None
369 );
370 assert_eq!(
371 super::declared_encoding(b"\xef\xbb\xbf<?xml encoding=\"utf-8\"?>"),
372 None
373 );
374 }
375}