Skip to main content

rusty_xml_tree/
lib.rs

1//! Arena DOM matching libxml2 `tree.h` ownership: the document owns every node.
2//! Handles are indices, not parent+child `&mut` pairs.
3
4#![forbid(unsafe_code)]
5
6use std::collections::HashMap;
7
8/// libxml2 `xmlElementType` discriminants.
9#[derive(Clone, Copy, PartialEq, Eq, Debug)]
10#[repr(u32)]
11pub enum NodeKind {
12    Element = 1,
13    Attribute = 2,
14    Text = 3,
15    CData = 4,
16    EntityRef = 5,
17    Entity = 6,
18    Pi = 7,
19    Comment = 8,
20    Document = 9,
21    DocumentType = 10,
22    DocumentFrag = 11,
23    Notation = 12,
24    HtmlDocument = 13,
25    Dtd = 14,
26    ElementDecl = 15,
27    AttributeDecl = 16,
28    EntityDecl = 17,
29    Namespace = 18,
30    XIncludeStart = 19,
31    XIncludeEnd = 20,
32}
33
34/// Parsed DTD attached to a document (`xmlDtd`).
35#[derive(Clone, Debug, Default)]
36pub struct XmlDtd {
37    pub name: Option<String>,
38    pub public_id: Option<String>,
39    pub system_id: Option<String>,
40    pub int_subset: Option<String>,
41    /// General entity name → replacement text.
42    pub entities: HashMap<String, String>,
43    /// Parameter entity name → replacement.
44    pub parameter_entities: HashMap<String, String>,
45    /// Element name → content model.
46    pub elements: HashMap<String, ElementDecl>,
47    /// (element, attribute) → declaration.
48    pub attributes: HashMap<(String, String), AttrDecl>,
49}
50
51#[derive(Clone, Debug)]
52pub enum ElementDecl {
53    Empty,
54    Any,
55    Mixed(Vec<String>),
56    Children(String),
57}
58
59#[derive(Clone, Debug)]
60pub struct AttrDecl {
61    pub att_type: String,
62    pub default: AttrDefault,
63    pub default_value: Option<String>,
64    pub enumerated: Vec<String>,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum AttrDefault {
69    Required,
70    Implied,
71    Fixed,
72    Value,
73}
74
75/// Stable handle into an [`XmlDoc`] arena. Valid for the lifetime of the doc.
76#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
77pub struct NodeId(pub u32);
78
79impl NodeId {
80    /// Document node is always slot 0.
81    pub const DOCUMENT: NodeId = NodeId(0);
82
83    pub fn index(self) -> usize {
84        self.0 as usize
85    }
86}
87
88#[derive(Clone, Debug)]
89pub struct Node {
90    pub kind: NodeKind,
91    pub name: String,
92    pub prefix: Option<String>,
93    pub ns_uri: Option<String>,
94    pub content: String,
95    pub parent: Option<NodeId>,
96    pub first_child: Option<NodeId>,
97    pub last_child: Option<NodeId>,
98    pub prev_sibling: Option<NodeId>,
99    pub next_sibling: Option<NodeId>,
100    pub first_attr: Option<NodeId>,
101    pub last_attr: Option<NodeId>,
102    /// Namespace declarations on this element (`xmlns` / `xmlns:prefix`), in source order.
103    pub ns_defs: Vec<(Option<String>, String)>,
104}
105
106impl Node {
107    fn new(kind: NodeKind, name: String) -> Self {
108        Self {
109            kind,
110            name,
111            prefix: None,
112            ns_uri: None,
113            content: String::new(),
114            parent: None,
115            first_child: None,
116            last_child: None,
117            prev_sibling: None,
118            next_sibling: None,
119            first_attr: None,
120            last_attr: None,
121            ns_defs: Vec::new(),
122        }
123    }
124}
125
126/// libxml2 `xmlDoc`.
127#[derive(Clone, Debug)]
128pub struct XmlDoc {
129    nodes: Vec<Node>,
130    /// XML version string; default `"1.0"`.
131    pub version: String,
132    /// Encoding name from the XML declaration, if any.
133    pub encoding: Option<String>,
134    /// `Some(true/false)` from `standalone`, `None` if omitted.
135    pub standalone: Option<bool>,
136    /// First element child of the document (cached; also discoverable by walk).
137    root: Option<NodeId>,
138    /// Internal / attached DTD, if any.
139    pub dtd: Option<XmlDtd>,
140}
141
142impl Default for XmlDoc {
143    fn default() -> Self {
144        Self::xml_new_doc(Some("1.0"))
145    }
146}
147
148impl XmlDoc {
149    /// `xmlNewDoc`.
150    #[doc(alias = "xmlNewDoc")]
151    pub fn xml_new_doc(version: Option<&str>) -> Self {
152        let mut nodes = Vec::new();
153        nodes.push(Node::new(NodeKind::Document, "#document".into()));
154        Self {
155            nodes,
156            version: version.unwrap_or("1.0").to_string(),
157            encoding: None,
158            standalone: None,
159            root: None,
160            dtd: None,
161        }
162    }
163
164    pub fn node(&self, id: NodeId) -> &Node {
165        &self.nodes[id.index()]
166    }
167
168    pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
169        &mut self.nodes[id.index()]
170    }
171
172    pub fn kind(&self, id: NodeId) -> NodeKind {
173        self.node(id).kind
174    }
175
176    pub fn name(&self, id: NodeId) -> &str {
177        &self.node(id).name
178    }
179
180    pub fn prefix(&self, id: NodeId) -> Option<&str> {
181        self.node(id).prefix.as_deref()
182    }
183
184    pub fn ns_uri(&self, id: NodeId) -> Option<&str> {
185        self.node(id).ns_uri.as_deref()
186    }
187
188    pub fn content(&self, id: NodeId) -> &str {
189        &self.node(id).content
190    }
191
192    pub fn parent(&self, id: NodeId) -> Option<NodeId> {
193        self.node(id).parent
194    }
195
196    pub fn first_child(&self, id: NodeId) -> Option<NodeId> {
197        self.node(id).first_child
198    }
199
200    pub fn last_child(&self, id: NodeId) -> Option<NodeId> {
201        self.node(id).last_child
202    }
203
204    pub fn next_sibling(&self, id: NodeId) -> Option<NodeId> {
205        self.node(id).next_sibling
206    }
207
208    pub fn prev_sibling(&self, id: NodeId) -> Option<NodeId> {
209        self.node(id).prev_sibling
210    }
211
212    pub fn first_attr(&self, id: NodeId) -> Option<NodeId> {
213        self.node(id).first_attr
214    }
215
216    pub fn ns_defs(&self, id: NodeId) -> &[(Option<String>, String)] {
217        &self.node(id).ns_defs
218    }
219
220    pub fn alloc(&mut self, kind: NodeKind, name: impl Into<String>) -> NodeId {
221        let id = NodeId(self.nodes.len() as u32);
222        self.nodes.push(Node::new(kind, name.into()));
223        id
224    }
225
226    /// `xmlDocGetRootElement`.
227    #[doc(alias = "xmlDocGetRootElement")]
228    pub fn xml_doc_get_root_element(&self) -> Option<NodeId> {
229        if let Some(r) = self.root {
230            return Some(r);
231        }
232        let mut c = self.first_child(NodeId::DOCUMENT);
233        while let Some(id) = c {
234            if self.kind(id) == NodeKind::Element {
235                return Some(id);
236            }
237            c = self.next_sibling(id);
238        }
239        None
240    }
241
242    /// `xmlDocSetRootElement`. Returns the previous root, if any.
243    #[doc(alias = "xmlDocSetRootElement")]
244    pub fn xml_doc_set_root_element(&mut self, elem: NodeId) -> Option<NodeId> {
245        let prev = self.xml_doc_get_root_element();
246        if let Some(p) = prev {
247            self.xml_unlink_node(p);
248        }
249        self.xml_add_child(NodeId::DOCUMENT, elem);
250        self.root = Some(elem);
251        prev
252    }
253
254    /// `xmlNewNode`.
255    #[doc(alias = "xmlNewNode")]
256    pub fn xml_new_node(&mut self, ns_uri: Option<&str>, name: &str) -> NodeId {
257        let id = self.alloc(NodeKind::Element, name);
258        self.node_mut(id).ns_uri = ns_uri.map(str::to_string);
259        id
260    }
261
262    /// `xmlNewDocNode`.
263    #[doc(alias = "xmlNewDocNode")]
264    pub fn xml_new_doc_node(
265        &mut self,
266        ns_uri: Option<&str>,
267        name: &str,
268        content: Option<&str>,
269    ) -> NodeId {
270        let id = self.xml_new_node(ns_uri, name);
271        if let Some(c) = content {
272            if !c.is_empty() {
273                let t = self.alloc(NodeKind::Text, "#text");
274                self.node_mut(t).content = c.to_string();
275                self.xml_add_child(id, t);
276            }
277        }
278        id
279    }
280
281    /// `xmlNewChild`.
282    #[doc(alias = "xmlNewChild")]
283    pub fn xml_new_child(
284        &mut self,
285        parent: NodeId,
286        ns_uri: Option<&str>,
287        name: &str,
288        content: Option<&str>,
289    ) -> NodeId {
290        let id = self.xml_new_doc_node(ns_uri, name, content);
291        self.xml_add_child(parent, id);
292        id
293    }
294
295    /// `xmlAddChild`.
296    #[doc(alias = "xmlAddChild")]
297    pub fn xml_add_child(&mut self, parent: NodeId, child: NodeId) {
298        if child == parent {
299            return;
300        }
301        self.xml_unlink_node(child);
302        self.node_mut(child).parent = Some(parent);
303        let last = self.node(parent).last_child;
304        if let Some(l) = last {
305            self.node_mut(l).next_sibling = Some(child);
306            self.node_mut(child).prev_sibling = Some(l);
307        } else {
308            self.node_mut(parent).first_child = Some(child);
309        }
310        self.node_mut(parent).last_child = Some(child);
311        if parent == NodeId::DOCUMENT && self.kind(child) == NodeKind::Element {
312            self.root = Some(child);
313        }
314    }
315
316    /// `xmlAddNextSibling`.
317    #[doc(alias = "xmlAddNextSibling")]
318    pub fn xml_add_next_sibling(&mut self, cur: NodeId, elem: NodeId) {
319        self.xml_unlink_node(elem);
320        let parent = self.node(cur).parent;
321        let next = self.node(cur).next_sibling;
322        self.node_mut(elem).parent = parent;
323        self.node_mut(elem).prev_sibling = Some(cur);
324        self.node_mut(elem).next_sibling = next;
325        self.node_mut(cur).next_sibling = Some(elem);
326        if let Some(n) = next {
327            self.node_mut(n).prev_sibling = Some(elem);
328        } else if let Some(p) = parent {
329            self.node_mut(p).last_child = Some(elem);
330        }
331    }
332
333    /// `xmlAddPrevSibling`.
334    #[doc(alias = "xmlAddPrevSibling")]
335    pub fn xml_add_prev_sibling(&mut self, cur: NodeId, elem: NodeId) {
336        self.xml_unlink_node(elem);
337        let parent = self.node(cur).parent;
338        let prev = self.node(cur).prev_sibling;
339        self.node_mut(elem).parent = parent;
340        self.node_mut(elem).next_sibling = Some(cur);
341        self.node_mut(elem).prev_sibling = prev;
342        self.node_mut(cur).prev_sibling = Some(elem);
343        if let Some(p) = prev {
344            self.node_mut(p).next_sibling = Some(elem);
345        } else if let Some(par) = parent {
346            self.node_mut(par).first_child = Some(elem);
347        }
348    }
349
350    /// `xmlUnlinkNode`.
351    #[doc(alias = "xmlUnlinkNode")]
352    pub fn xml_unlink_node(&mut self, id: NodeId) {
353        if id == NodeId::DOCUMENT {
354            return;
355        }
356        let parent = self.node(id).parent;
357        let prev = self.node(id).prev_sibling;
358        let next = self.node(id).next_sibling;
359        if let Some(p) = prev {
360            self.node_mut(p).next_sibling = next;
361        }
362        if let Some(n) = next {
363            self.node_mut(n).prev_sibling = prev;
364        }
365        if let Some(par) = parent {
366            if self.node(par).first_child == Some(id) {
367                self.node_mut(par).first_child = next;
368            }
369            if self.node(par).last_child == Some(id) {
370                self.node_mut(par).last_child = prev;
371            }
372        }
373        if self.root == Some(id) {
374            self.root = None;
375        }
376        self.node_mut(id).parent = None;
377        self.node_mut(id).prev_sibling = None;
378        self.node_mut(id).next_sibling = None;
379    }
380
381    /// `xmlReplaceNode`.
382    #[doc(alias = "xmlReplaceNode")]
383    pub fn xml_replace_node(&mut self, old: NodeId, new: NodeId) -> NodeId {
384        self.xml_add_next_sibling(old, new);
385        self.xml_unlink_node(old);
386        new
387    }
388
389    pub fn add_attr(&mut self, elem: NodeId, name: &str, prefix: Option<&str>, value: &str) -> NodeId {
390        let id = self.alloc(NodeKind::Attribute, name);
391        self.node_mut(id).prefix = prefix.map(str::to_string);
392        self.node_mut(id).content = value.to_string();
393        self.node_mut(id).parent = Some(elem);
394        let last = self.node(elem).last_attr;
395        if let Some(l) = last {
396            self.node_mut(l).next_sibling = Some(id);
397            self.node_mut(id).prev_sibling = Some(l);
398        } else {
399            self.node_mut(elem).first_attr = Some(id);
400        }
401        self.node_mut(elem).last_attr = Some(id);
402        id
403    }
404
405    pub fn push_ns_def(&mut self, elem: NodeId, prefix: Option<String>, uri: String) {
406        self.node_mut(elem).ns_defs.push((prefix, uri));
407    }
408
409    /// `xmlSetProp`.
410    #[doc(alias = "xmlSetProp")]
411    pub fn xml_set_prop(&mut self, node: NodeId, name: &str, value: &str) -> NodeId {
412        let mut a = self.first_attr(node);
413        while let Some(id) = a {
414            if self.node(id).prefix.is_none() && self.node(id).name == name {
415                self.node_mut(id).content = value.to_string();
416                return id;
417            }
418            a = self.next_sibling(id);
419        }
420        self.add_attr(node, name, None, value)
421    }
422
423    /// `xmlGetProp`.
424    #[doc(alias = "xmlGetProp")]
425    pub fn xml_get_prop(&self, node: NodeId, name: &str) -> Option<String> {
426        let mut a = self.first_attr(node);
427        while let Some(id) = a {
428            if self.node(id).prefix.is_none() && self.node(id).name == name {
429                return Some(self.node(id).content.clone());
430            }
431            a = self.next_sibling(id);
432        }
433        None
434    }
435
436    /// `xmlHasProp`.
437    #[doc(alias = "xmlHasProp")]
438    pub fn xml_has_prop(&self, node: NodeId, name: &str) -> bool {
439        self.xml_get_prop(node, name).is_some()
440    }
441
442    /// `xmlUnsetProp`.
443    #[doc(alias = "xmlUnsetProp")]
444    pub fn xml_unset_prop(&mut self, node: NodeId, name: &str) -> bool {
445        let mut a = self.first_attr(node);
446        let mut prev: Option<NodeId> = None;
447        while let Some(id) = a {
448            let next = self.next_sibling(id);
449            if self.node(id).prefix.is_none() && self.node(id).name == name {
450                if let Some(p) = prev {
451                    self.node_mut(p).next_sibling = next;
452                } else {
453                    self.node_mut(node).first_attr = next;
454                }
455                if next.is_none() {
456                    self.node_mut(node).last_attr = prev;
457                }
458                if let Some(n) = next {
459                    self.node_mut(n).prev_sibling = prev;
460                }
461                self.node_mut(id).parent = None;
462                self.node_mut(id).prev_sibling = None;
463                self.node_mut(id).next_sibling = None;
464                return true;
465            }
466            prev = Some(id);
467            a = next;
468        }
469        false
470    }
471
472    /// `xmlNodeGetContent` — concatenate descendant text/CDATA.
473    #[doc(alias = "xmlNodeGetContent")]
474    pub fn xml_node_get_content(&self, id: NodeId) -> String {
475        match self.kind(id) {
476            NodeKind::Text | NodeKind::CData | NodeKind::Comment | NodeKind::Pi | NodeKind::Attribute => {
477                self.content(id).to_string()
478            }
479            _ => {
480                let mut out = String::new();
481                self.collect_text(id, &mut out);
482                out
483            }
484        }
485    }
486
487    fn collect_text(&self, id: NodeId, out: &mut String) {
488        let mut c = self.first_child(id);
489        while let Some(ch) = c {
490            match self.kind(ch) {
491                NodeKind::Text | NodeKind::CData => out.push_str(self.content(ch)),
492                NodeKind::Element => self.collect_text(ch, out),
493                _ => {}
494            }
495            c = self.next_sibling(ch);
496        }
497    }
498
499    /// `xmlNodeSetContent` — replace children with a single text node.
500    #[doc(alias = "xmlNodeSetContent")]
501    pub fn xml_node_set_content(&mut self, id: NodeId, content: &str) {
502        match self.kind(id) {
503            NodeKind::Text | NodeKind::CData | NodeKind::Comment | NodeKind::Pi | NodeKind::Attribute => {
504                self.node_mut(id).content = content.to_string();
505            }
506            _ => {
507                let mut c = self.first_child(id);
508                while let Some(ch) = c {
509                    let next = self.next_sibling(ch);
510                    self.xml_unlink_node(ch);
511                    c = next;
512                }
513                if !content.is_empty() {
514                    let t = self.alloc(NodeKind::Text, "#text");
515                    self.node_mut(t).content = content.to_string();
516                    self.xml_add_child(id, t);
517                }
518            }
519        }
520    }
521
522    /// `xmlIsBlankNode`.
523    #[doc(alias = "xmlIsBlankNode")]
524    pub fn xml_is_blank_node(&self, id: NodeId) -> bool {
525        match self.kind(id) {
526            NodeKind::Text | NodeKind::CData => self.content(id).chars().all(|c| {
527                c == ' ' || c == '\t' || c == '\n' || c == '\r'
528            }),
529            _ => false,
530        }
531    }
532
533    /// `xmlSearchNs` — walk ancestors for a prefix binding.
534    #[doc(alias = "xmlSearchNs")]
535    pub fn xml_search_ns(&self, node: NodeId, prefix: Option<&str>) -> Option<String> {
536        if prefix == Some("xml") {
537            return Some("http://www.w3.org/XML/1998/namespace".into());
538        }
539        if prefix == Some("xmlns") {
540            return Some("http://www.w3.org/2000/xmlns/".into());
541        }
542        let mut cur = Some(node);
543        while let Some(id) = cur {
544            for (p, uri) in self.ns_defs(id) {
545                if p.as_deref() == prefix {
546                    return Some(uri.clone());
547                }
548            }
549            cur = self.parent(id);
550        }
551        None
552    }
553
554    /// `xmlNewNs` — add a namespace declaration on an element.
555    #[doc(alias = "xmlNewNs")]
556    pub fn xml_new_ns(&mut self, node: NodeId, href: &str, prefix: Option<&str>) {
557        self.push_ns_def(node, prefix.map(str::to_string), href.to_string());
558    }
559
560    /// `xmlSetNs`.
561    #[doc(alias = "xmlSetNs")]
562    pub fn xml_set_ns(&mut self, node: NodeId, href: Option<&str>, prefix: Option<&str>) {
563        self.node_mut(node).ns_uri = href.map(str::to_string);
564        self.node_mut(node).prefix = prefix.map(str::to_string);
565    }
566
567    /// `xmlCopyDoc` — deep copy.
568    #[doc(alias = "xmlCopyDoc")]
569    pub fn xml_copy_doc(&self) -> XmlDoc {
570        self.clone()
571    }
572
573    pub fn qname(&self, id: NodeId) -> String {
574        match self.prefix(id) {
575            Some(p) => format!("{}:{}", p, self.name(id)),
576            None => self.name(id).to_string(),
577        }
578    }
579
580    pub fn children(&self, id: NodeId) -> NodeIter<'_> {
581        NodeIter {
582            doc: self,
583            next: self.first_child(id),
584        }
585    }
586
587    pub fn attrs(&self, id: NodeId) -> NodeIter<'_> {
588        NodeIter {
589            doc: self,
590            next: self.first_attr(id),
591        }
592    }
593
594    pub fn len(&self) -> usize {
595        self.nodes.len()
596    }
597}
598
599/// Sibling iterator.
600pub struct NodeIter<'a> {
601    doc: &'a XmlDoc,
602    next: Option<NodeId>,
603}
604
605impl Iterator for NodeIter<'_> {
606    type Item = NodeId;
607
608    fn next(&mut self) -> Option<Self::Item> {
609        let n = self.next?;
610        self.next = self.doc.next_sibling(n);
611        Some(n)
612    }
613}
614
615/// `xmlFreeDoc` is `Drop`.
616#[doc(alias = "xmlFreeDoc")]
617pub fn xml_free_doc(_doc: XmlDoc) {}