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
89
90#[derive(Clone, Debug)]
91pub struct Node {
92    pub kind: NodeKind,
93    pub name: String,
94    pub prefix: Option<String>,
95    pub ns_uri: Option<String>,
96    pub content: String,
97    pub parent: Option<NodeId>,
98    pub first_child: Option<NodeId>,
99    pub last_child: Option<NodeId>,
100    pub prev_sibling: Option<NodeId>,
101    pub next_sibling: Option<NodeId>,
102    pub first_attr: Option<NodeId>,
103    pub last_attr: Option<NodeId>,
104    /// Namespace declarations on this element (`xmlns` / `xmlns:prefix`), in source order.
105    pub ns_defs: Vec<(Option<String>, String)>,
106}
107
108impl Node {
109    fn new(kind: NodeKind, name: String) -> Self {
110        Self {
111            kind,
112            name,
113            prefix: None,
114            ns_uri: None,
115            content: String::new(),
116            parent: None,
117            first_child: None,
118            last_child: None,
119            prev_sibling: None,
120            next_sibling: None,
121            first_attr: None,
122            last_attr: None,
123            ns_defs: Vec::new(),
124        }
125    }
126}
127
128/// libxml2 `xmlDoc`.
129#[derive(Clone, Debug)]
130pub struct XmlDoc {
131    nodes: Vec<Node>,
132    /// XML version string; default `"1.0"`.
133    pub version: String,
134    /// Encoding name from the XML declaration, if any.
135    pub encoding: Option<String>,
136    /// `Some(true/false)` from `standalone`, `None` if omitted.
137    pub standalone: Option<bool>,
138    /// First element child of the document (cached; also discoverable by walk).
139    root: Option<NodeId>,
140    /// Internal / attached DTD, if any.
141    pub dtd: Option<XmlDtd>,
142}
143
144impl Default for XmlDoc {
145    fn default() -> Self {
146        Self::xml_new_doc(Some("1.0"))
147    }
148}
149
150impl XmlDoc {
151    /// `xmlNewDoc`.
152    #[doc(alias = "xmlNewDoc")]
153    pub fn xml_new_doc(version: Option<&str>) -> Self {
154        Self::with_node_capacity(version, 1)
155    }
156
157    /// Build a document whose node arena is sized up front. Starting a parse
158    /// used to allocate the arena, push into it, then reallocate on reserve --
159    /// three trips for what one sized allocation does. The document node's
160    /// name is implied by its kind, so it stores no String either.
161    pub fn with_node_capacity(version: Option<&str>, cap: usize) -> Self {
162        const MAX_ARENA_BYTES: usize = 32 << 20;
163        let ceiling = MAX_ARENA_BYTES / std::mem::size_of::<Node>();
164        // A floor of 4 keeps a small document from reallocating its way up
165        // from a single slot, which is what Vec::new()+push used to give it.
166        let mut nodes = Vec::with_capacity(cap.clamp(4, ceiling));
167        nodes.push(Node::new(NodeKind::Document, String::new()));
168        Self {
169            nodes,
170            version: version.unwrap_or("1.0").to_string(),
171            encoding: None,
172            standalone: None,
173            root: None,
174            dtd: None,
175        }
176    }
177
178    /// Pre-size the node arena. XML runs about one node per 10-15 input bytes,
179    /// so a parser that knows the document length can skip most of the arena's
180    /// doubling-and-copy. Capped so a huge document cannot reserve wildly.
181    pub fn reserve_nodes(&mut self, n: usize) {
182        // Measured node density is ~1 per 10-12 input bytes. The previous cap
183        // of 65_536 was below a 700 KB document's node count, so the arena
184        // still doubled-and-copied its way up -- about 22 MB of memcpy on a
185        // 627 KB file. Shrinking Node itself would need an API break for a
186        // sub-1% effect; reserving correctly removes the same traffic for free.
187        // Cap by memory, not node count: 1<<20 nodes is ~190 MB of arena, which
188        // a large document would commit up front before parsing a byte.
189        const MAX_ARENA_BYTES: usize = 32 << 20;
190        let cap = MAX_ARENA_BYTES / std::mem::size_of::<Node>();
191        self.nodes.reserve(n.min(cap));
192    }
193
194    pub fn node(&self, id: NodeId) -> &Node {
195        &self.nodes[id.index()]
196    }
197
198    pub fn node_mut(&mut self, id: NodeId) -> &mut Node {
199        &mut self.nodes[id.index()]
200    }
201
202    pub fn kind(&self, id: NodeId) -> NodeKind {
203        self.node(id).kind
204    }
205
206    pub fn name(&self, id: NodeId) -> &str {
207        let n = self.node(id);
208        if n.name.is_empty() {
209            // Nodes whose name is fixed by their kind store no String at all;
210            // allocating "#text" once per text node was a measurable share of
211            // every parse. The canonical name is derived here instead.
212            return match n.kind {
213                NodeKind::Text => "#text",
214                NodeKind::CData => "#cdata-section",
215                NodeKind::Comment => "#comment",
216                NodeKind::Document => "#document",
217                _ => "",
218            };
219        }
220        &n.name
221    }
222
223    pub fn prefix(&self, id: NodeId) -> Option<&str> {
224        self.node(id).prefix.as_deref()
225    }
226
227    pub fn ns_uri(&self, id: NodeId) -> Option<&str> {
228        self.node(id).ns_uri.as_deref()
229    }
230
231    pub fn content(&self, id: NodeId) -> &str {
232        &self.node(id).content
233    }
234
235    pub fn parent(&self, id: NodeId) -> Option<NodeId> {
236        self.node(id).parent
237    }
238
239    pub fn first_child(&self, id: NodeId) -> Option<NodeId> {
240        self.node(id).first_child
241    }
242
243    pub fn last_child(&self, id: NodeId) -> Option<NodeId> {
244        self.node(id).last_child
245    }
246
247    pub fn next_sibling(&self, id: NodeId) -> Option<NodeId> {
248        self.node(id).next_sibling
249    }
250
251    pub fn prev_sibling(&self, id: NodeId) -> Option<NodeId> {
252        self.node(id).prev_sibling
253    }
254
255    pub fn first_attr(&self, id: NodeId) -> Option<NodeId> {
256        self.node(id).first_attr
257    }
258
259    pub fn ns_defs(&self, id: NodeId) -> &[(Option<String>, String)] {
260        &self.node(id).ns_defs
261    }
262
263    /// Allocate a node whose name is implied by its kind, storing no String.
264    /// [`XmlDoc::name`] reports the canonical name for these.
265    pub fn alloc_unnamed(&mut self, kind: NodeKind) -> NodeId {
266        let id = NodeId(self.nodes.len() as u32);
267        self.nodes.push(Node::new(kind, String::new()));
268        id
269    }
270
271    pub fn alloc(&mut self, kind: NodeKind, name: impl Into<String>) -> NodeId {
272        let id = NodeId(self.nodes.len() as u32);
273        self.nodes.push(Node::new(kind, name.into()));
274        id
275    }
276
277    /// `xmlDocGetRootElement`.
278    #[doc(alias = "xmlDocGetRootElement")]
279    pub fn xml_doc_get_root_element(&self) -> Option<NodeId> {
280        if let Some(r) = self.root {
281            return Some(r);
282        }
283        let mut c = self.first_child(NodeId::DOCUMENT);
284        while let Some(id) = c {
285            if self.kind(id) == NodeKind::Element {
286                return Some(id);
287            }
288            c = self.next_sibling(id);
289        }
290        None
291    }
292
293    /// `xmlDocSetRootElement`. Returns the previous root, if any.
294    #[doc(alias = "xmlDocSetRootElement")]
295    pub fn xml_doc_set_root_element(&mut self, elem: NodeId) -> Option<NodeId> {
296        let prev = self.xml_doc_get_root_element();
297        if let Some(p) = prev {
298            self.xml_unlink_node(p);
299        }
300        self.xml_add_child(NodeId::DOCUMENT, elem);
301        self.root = Some(elem);
302        prev
303    }
304
305    /// `xmlNewNode`.
306    #[doc(alias = "xmlNewNode")]
307    pub fn xml_new_node(&mut self, ns_uri: Option<&str>, name: &str) -> NodeId {
308        let id = self.alloc(NodeKind::Element, name);
309        self.node_mut(id).ns_uri = ns_uri.map(str::to_string);
310        id
311    }
312
313    /// `xmlNewDocNode`.
314    #[doc(alias = "xmlNewDocNode")]
315    pub fn xml_new_doc_node(
316        &mut self,
317        ns_uri: Option<&str>,
318        name: &str,
319        content: Option<&str>,
320    ) -> NodeId {
321        let id = self.xml_new_node(ns_uri, name);
322        if let Some(c) = content {
323            if !c.is_empty() {
324                let t = self.alloc(NodeKind::Text, "#text");
325                self.node_mut(t).content = c.to_string();
326                self.xml_add_child(id, t);
327            }
328        }
329        id
330    }
331
332    /// `xmlNewChild`.
333    #[doc(alias = "xmlNewChild")]
334    pub fn xml_new_child(
335        &mut self,
336        parent: NodeId,
337        ns_uri: Option<&str>,
338        name: &str,
339        content: Option<&str>,
340    ) -> NodeId {
341        let id = self.xml_new_doc_node(ns_uri, name, content);
342        self.xml_add_child(parent, id);
343        id
344    }
345
346    /// `xmlAddChild`.
347    #[doc(alias = "xmlAddChild")]
348    pub fn xml_add_child(&mut self, parent: NodeId, child: NodeId) {
349        if child == parent {
350            return;
351        }
352        self.xml_unlink_node(child);
353        self.node_mut(child).parent = Some(parent);
354        let last = self.node(parent).last_child;
355        if let Some(l) = last {
356            self.node_mut(l).next_sibling = Some(child);
357            self.node_mut(child).prev_sibling = Some(l);
358        } else {
359            self.node_mut(parent).first_child = Some(child);
360        }
361        self.node_mut(parent).last_child = Some(child);
362        if parent == NodeId::DOCUMENT && self.kind(child) == NodeKind::Element {
363            self.root = Some(child);
364        }
365    }
366
367    /// `xmlAddNextSibling`.
368    #[doc(alias = "xmlAddNextSibling")]
369    pub fn xml_add_next_sibling(&mut self, cur: NodeId, elem: NodeId) {
370        self.xml_unlink_node(elem);
371        let parent = self.node(cur).parent;
372        let next = self.node(cur).next_sibling;
373        self.node_mut(elem).parent = parent;
374        self.node_mut(elem).prev_sibling = Some(cur);
375        self.node_mut(elem).next_sibling = next;
376        self.node_mut(cur).next_sibling = Some(elem);
377        if let Some(n) = next {
378            self.node_mut(n).prev_sibling = Some(elem);
379        } else if let Some(p) = parent {
380            self.node_mut(p).last_child = Some(elem);
381        }
382    }
383
384    /// `xmlAddPrevSibling`.
385    #[doc(alias = "xmlAddPrevSibling")]
386    pub fn xml_add_prev_sibling(&mut self, cur: NodeId, elem: NodeId) {
387        self.xml_unlink_node(elem);
388        let parent = self.node(cur).parent;
389        let prev = self.node(cur).prev_sibling;
390        self.node_mut(elem).parent = parent;
391        self.node_mut(elem).next_sibling = Some(cur);
392        self.node_mut(elem).prev_sibling = prev;
393        self.node_mut(cur).prev_sibling = Some(elem);
394        if let Some(p) = prev {
395            self.node_mut(p).next_sibling = Some(elem);
396        } else if let Some(par) = parent {
397            self.node_mut(par).first_child = Some(elem);
398        }
399    }
400
401    /// `xmlUnlinkNode`.
402    #[doc(alias = "xmlUnlinkNode")]
403    pub fn xml_unlink_node(&mut self, id: NodeId) {
404        if id == NodeId::DOCUMENT {
405            return;
406        }
407        let parent = self.node(id).parent;
408        let prev = self.node(id).prev_sibling;
409        let next = self.node(id).next_sibling;
410        if let Some(p) = prev {
411            self.node_mut(p).next_sibling = next;
412        }
413        if let Some(n) = next {
414            self.node_mut(n).prev_sibling = prev;
415        }
416        if let Some(par) = parent {
417            if self.node(par).first_child == Some(id) {
418                self.node_mut(par).first_child = next;
419            }
420            if self.node(par).last_child == Some(id) {
421                self.node_mut(par).last_child = prev;
422            }
423        }
424        if self.root == Some(id) {
425            self.root = None;
426        }
427        self.node_mut(id).parent = None;
428        self.node_mut(id).prev_sibling = None;
429        self.node_mut(id).next_sibling = None;
430    }
431
432    /// `xmlReplaceNode`.
433    #[doc(alias = "xmlReplaceNode")]
434    pub fn xml_replace_node(&mut self, old: NodeId, new: NodeId) -> NodeId {
435        self.xml_add_next_sibling(old, new);
436        self.xml_unlink_node(old);
437        new
438    }
439
440    /// As [`XmlDoc::add_attr`], but takes ownership. The borrowing form has to
441    /// allocate a fresh String for the name, the prefix and the value, all of
442    /// which the parser already owns.
443    pub fn add_attr_owned(
444        &mut self,
445        elem: NodeId,
446        name: String,
447        prefix: Option<String>,
448        value: String,
449    ) -> NodeId {
450        let id = self.alloc(NodeKind::Attribute, name);
451        self.node_mut(id).prefix = prefix;
452        self.node_mut(id).content = value;
453        self.node_mut(id).parent = Some(elem);
454        let last = self.node(elem).last_attr;
455        if let Some(l) = last {
456            self.node_mut(l).next_sibling = Some(id);
457            self.node_mut(id).prev_sibling = Some(l);
458        } else {
459            self.node_mut(elem).first_attr = Some(id);
460        }
461        self.node_mut(elem).last_attr = Some(id);
462        id
463    }
464
465    pub fn add_attr(&mut self, elem: NodeId, name: &str, prefix: Option<&str>, value: &str) -> NodeId {
466        let id = self.alloc(NodeKind::Attribute, name);
467        self.node_mut(id).prefix = prefix.map(str::to_string);
468        self.node_mut(id).content = value.to_string();
469        self.node_mut(id).parent = Some(elem);
470        let last = self.node(elem).last_attr;
471        if let Some(l) = last {
472            self.node_mut(l).next_sibling = Some(id);
473            self.node_mut(id).prev_sibling = Some(l);
474        } else {
475            self.node_mut(elem).first_attr = Some(id);
476        }
477        self.node_mut(elem).last_attr = Some(id);
478        id
479    }
480
481    pub fn push_ns_def(&mut self, elem: NodeId, prefix: Option<String>, uri: String) {
482        self.node_mut(elem).ns_defs.push((prefix, uri));
483    }
484
485    /// `xmlSetProp`.
486    #[doc(alias = "xmlSetProp")]
487    pub fn xml_set_prop(&mut self, node: NodeId, name: &str, value: &str) -> NodeId {
488        let mut a = self.first_attr(node);
489        while let Some(id) = a {
490            if self.node(id).prefix.is_none() && self.node(id).name == name {
491                self.node_mut(id).content = value.to_string();
492                return id;
493            }
494            a = self.next_sibling(id);
495        }
496        self.add_attr(node, name, None, value)
497    }
498
499    /// `xmlGetProp`.
500    #[doc(alias = "xmlGetProp")]
501    pub fn xml_get_prop(&self, node: NodeId, name: &str) -> Option<String> {
502        let mut a = self.first_attr(node);
503        while let Some(id) = a {
504            if self.node(id).prefix.is_none() && self.node(id).name == name {
505                return Some(self.node(id).content.clone());
506            }
507            a = self.next_sibling(id);
508        }
509        None
510    }
511
512    /// `xmlHasProp`.
513    #[doc(alias = "xmlHasProp")]
514    pub fn xml_has_prop(&self, node: NodeId, name: &str) -> bool {
515        self.xml_get_prop(node, name).is_some()
516    }
517
518    /// `xmlUnsetProp`.
519    #[doc(alias = "xmlUnsetProp")]
520    pub fn xml_unset_prop(&mut self, node: NodeId, name: &str) -> bool {
521        let mut a = self.first_attr(node);
522        let mut prev: Option<NodeId> = None;
523        while let Some(id) = a {
524            let next = self.next_sibling(id);
525            if self.node(id).prefix.is_none() && self.node(id).name == name {
526                if let Some(p) = prev {
527                    self.node_mut(p).next_sibling = next;
528                } else {
529                    self.node_mut(node).first_attr = next;
530                }
531                if next.is_none() {
532                    self.node_mut(node).last_attr = prev;
533                }
534                if let Some(n) = next {
535                    self.node_mut(n).prev_sibling = prev;
536                }
537                self.node_mut(id).parent = None;
538                self.node_mut(id).prev_sibling = None;
539                self.node_mut(id).next_sibling = None;
540                return true;
541            }
542            prev = Some(id);
543            a = next;
544        }
545        false
546    }
547
548    /// `xmlNodeGetContent` — concatenate descendant text/CDATA.
549    #[doc(alias = "xmlNodeGetContent")]
550    pub fn xml_node_get_content(&self, id: NodeId) -> String {
551        match self.kind(id) {
552            NodeKind::Text | NodeKind::CData | NodeKind::Comment | NodeKind::Pi | NodeKind::Attribute => {
553                self.content(id).to_string()
554            }
555            _ => {
556                let mut out = String::new();
557                self.collect_text(id, &mut out);
558                out
559            }
560        }
561    }
562
563    fn collect_text(&self, id: NodeId, out: &mut String) {
564        let mut c = self.first_child(id);
565        while let Some(ch) = c {
566            match self.kind(ch) {
567                NodeKind::Text | NodeKind::CData => out.push_str(self.content(ch)),
568                NodeKind::Element => self.collect_text(ch, out),
569                _ => {}
570            }
571            c = self.next_sibling(ch);
572        }
573    }
574
575    /// `xmlNodeSetContent` — replace children with a single text node.
576    #[doc(alias = "xmlNodeSetContent")]
577    pub fn xml_node_set_content(&mut self, id: NodeId, content: &str) {
578        match self.kind(id) {
579            NodeKind::Text | NodeKind::CData | NodeKind::Comment | NodeKind::Pi | NodeKind::Attribute => {
580                self.node_mut(id).content = content.to_string();
581            }
582            _ => {
583                let mut c = self.first_child(id);
584                while let Some(ch) = c {
585                    let next = self.next_sibling(ch);
586                    self.xml_unlink_node(ch);
587                    c = next;
588                }
589                if !content.is_empty() {
590                    let t = self.alloc(NodeKind::Text, "#text");
591                    self.node_mut(t).content = content.to_string();
592                    self.xml_add_child(id, t);
593                }
594            }
595        }
596    }
597
598    /// `xmlIsBlankNode`.
599    #[doc(alias = "xmlIsBlankNode")]
600    pub fn xml_is_blank_node(&self, id: NodeId) -> bool {
601        match self.kind(id) {
602            NodeKind::Text | NodeKind::CData => self.content(id).chars().all(|c| {
603                c == ' ' || c == '\t' || c == '\n' || c == '\r'
604            }),
605            _ => false,
606        }
607    }
608
609    /// `xmlSearchNs` — walk ancestors for a prefix binding.
610    #[doc(alias = "xmlSearchNs")]
611    pub fn xml_search_ns(&self, node: NodeId, prefix: Option<&str>) -> Option<String> {
612        if prefix == Some("xml") {
613            return Some("http://www.w3.org/XML/1998/namespace".into());
614        }
615        if prefix == Some("xmlns") {
616            return Some("http://www.w3.org/2000/xmlns/".into());
617        }
618        let mut cur = Some(node);
619        while let Some(id) = cur {
620            for (p, uri) in self.ns_defs(id) {
621                if p.as_deref() == prefix {
622                    return Some(uri.clone());
623                }
624            }
625            cur = self.parent(id);
626        }
627        None
628    }
629
630    /// `xmlNewNs` — add a namespace declaration on an element.
631    #[doc(alias = "xmlNewNs")]
632    pub fn xml_new_ns(&mut self, node: NodeId, href: &str, prefix: Option<&str>) {
633        self.push_ns_def(node, prefix.map(str::to_string), href.to_string());
634    }
635
636    /// `xmlSetNs`.
637    #[doc(alias = "xmlSetNs")]
638    pub fn xml_set_ns(&mut self, node: NodeId, href: Option<&str>, prefix: Option<&str>) {
639        self.node_mut(node).ns_uri = href.map(str::to_string);
640        self.node_mut(node).prefix = prefix.map(str::to_string);
641    }
642
643    /// `xmlCopyDoc` — deep copy.
644    #[doc(alias = "xmlCopyDoc")]
645    pub fn xml_copy_doc(&self) -> XmlDoc {
646        self.clone()
647    }
648
649    pub fn qname(&self, id: NodeId) -> String {
650        match self.prefix(id) {
651            Some(p) => format!("{}:{}", p, self.name(id)),
652            None => self.name(id).to_string(),
653        }
654    }
655
656    pub fn children(&self, id: NodeId) -> NodeIter<'_> {
657        NodeIter {
658            doc: self,
659            next: self.first_child(id),
660        }
661    }
662
663    pub fn attrs(&self, id: NodeId) -> NodeIter<'_> {
664        NodeIter {
665            doc: self,
666            next: self.first_attr(id),
667        }
668    }
669
670    pub fn len(&self) -> usize {
671        self.nodes.len()
672    }
673}
674
675/// Sibling iterator.
676pub struct NodeIter<'a> {
677    doc: &'a XmlDoc,
678    next: Option<NodeId>,
679}
680
681impl Iterator for NodeIter<'_> {
682    type Item = NodeId;
683
684    fn next(&mut self) -> Option<Self::Item> {
685        let n = self.next?;
686        self.next = self.doc.next_sibling(n);
687        Some(n)
688    }
689}
690
691/// `xmlFreeDoc` is `Drop`.
692#[doc(alias = "xmlFreeDoc")]
693pub fn xml_free_doc(_doc: XmlDoc) {}