Skip to main content

domtree/
tree.rs

1//! The parsed document ([`Dom`]) and the read handle into it ([`NodeRef`]).
2
3use alloc::string::String;
4use alloc::vec::Vec;
5use core::fmt;
6
7use crate::error::ParseError;
8use crate::node::{Node, NodeId, NodeKind, RawKind, Span};
9
10/// A parsed HTML document: an arena of nodes, a single string buffer they
11/// reference, the top-level (root) nodes, and any recovery diagnostics.
12///
13/// Build one with [`crate::parse`]. It is immutable once built; traverse it with
14/// [`NodeRef`] handles from [`Dom::root`], [`Dom::roots`], [`Dom::get`], or the
15/// `find_by_*` helpers.
16#[derive(Clone)]
17pub struct Dom {
18    pub(crate) nodes: Vec<Node>,
19    pub(crate) strings: String,
20    pub(crate) roots: Vec<NodeId>,
21    pub(crate) errors: Vec<ParseError>,
22}
23
24impl Dom {
25    pub(crate) fn with_capacity(src_len: usize) -> Self {
26        Dom {
27            nodes: Vec::with_capacity(src_len / 16 + 8),
28            strings: String::with_capacity(src_len),
29            roots: Vec::new(),
30            errors: Vec::new(),
31        }
32    }
33
34    /// Append a string to the arena and return its [`Span`].
35    pub(crate) fn intern(&mut self, s: &str) -> Span {
36        if s.is_empty() {
37            return Span::EMPTY;
38        }
39        let start = self.strings.len() as u32;
40        self.strings.push_str(s);
41        Span {
42            start,
43            len: s.len() as u32,
44        }
45    }
46
47    #[inline]
48    pub(crate) fn span_str(&self, span: Span) -> &str {
49        self.strings.get(span.range()).unwrap_or("")
50    }
51
52    pub(crate) fn alloc(&mut self, raw: RawKind) -> NodeId {
53        let id = NodeId(self.nodes.len() as u32);
54        self.nodes.push(Node::new(raw));
55        id
56    }
57
58    #[inline]
59    pub(crate) fn node(&self, id: NodeId) -> Option<&Node> {
60        self.nodes.get(id.index())
61    }
62
63    #[inline]
64    pub(crate) fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
65        self.nodes.get_mut(id.index())
66    }
67
68    /// Append `child` under `parent` (or as a root when `parent` is `None`),
69    /// maintaining all sibling/child links.
70    pub(crate) fn append(&mut self, parent: Option<NodeId>, child: NodeId) {
71        let parent = match parent {
72            None => {
73                self.roots.push(child);
74                return;
75            }
76            Some(p) => p,
77        };
78        if let Some(c) = self.node_mut(child) {
79            c.parent = Some(parent);
80        }
81        let last = self.node(parent).and_then(|n| n.last_child);
82        match last {
83            None => {
84                if let Some(p) = self.node_mut(parent) {
85                    p.first_child = Some(child);
86                    p.last_child = Some(child);
87                }
88            }
89            Some(last) => {
90                if let Some(l) = self.node_mut(last) {
91                    l.next_sibling = Some(child);
92                }
93                if let Some(c) = self.node_mut(child) {
94                    c.prev_sibling = Some(last);
95                }
96                if let Some(p) = self.node_mut(parent) {
97                    p.last_child = Some(child);
98                }
99            }
100        }
101    }
102
103    // --- public read API -------------------------------------------------
104
105    /// Number of nodes in the arena.
106    pub fn len(&self) -> usize {
107        self.nodes.len()
108    }
109
110    /// Whether the document contains no nodes.
111    pub fn is_empty(&self) -> bool {
112        self.nodes.is_empty()
113    }
114
115    /// Recovery diagnostics collected during parsing (empty for clean input).
116    pub fn errors(&self) -> &[ParseError] {
117        &self.errors
118    }
119
120    /// A handle to a node by id, or `None` if the id is out of range.
121    pub fn get(&self, id: NodeId) -> Option<NodeRef<'_>> {
122        if id.index() < self.nodes.len() {
123            Some(NodeRef { dom: self, id })
124        } else {
125            None
126        }
127    }
128
129    /// The first top-level node (the document element for a typical page).
130    pub fn root(&self) -> Option<NodeRef<'_>> {
131        self.roots.first().map(|&id| NodeRef { dom: self, id })
132    }
133
134    /// All top-level nodes, in document order.
135    pub fn roots(&self) -> impl Iterator<Item = NodeRef<'_>> {
136        self.roots.iter().map(move |&id| NodeRef { dom: self, id })
137    }
138
139    /// Every node in the arena, in document (pre-order) order.
140    pub fn nodes(&self) -> impl Iterator<Item = NodeRef<'_>> {
141        (0..self.nodes.len()).map(move |i| NodeRef {
142            dom: self,
143            id: NodeId(i as u32),
144        })
145    }
146
147    /// All elements whose tag name equals `name` (ASCII case-insensitive).
148    pub fn find_by_tag<'a>(&'a self, name: &'a str) -> impl Iterator<Item = NodeRef<'a>> + 'a {
149        self.nodes()
150            .filter(move |n| n.tag_name().is_some_and(|t| t.eq_ignore_ascii_case(name)))
151    }
152
153    /// The first element with `id="…"` equal to `id`, if any.
154    pub fn find_by_id<'a>(&'a self, id: &'a str) -> Option<NodeRef<'a>> {
155        self.nodes().find(|n| n.attr("id") == Some(id))
156    }
157
158    /// All elements whose `class` attribute contains `class`.
159    pub fn find_by_class<'a>(&'a self, class: &'a str) -> impl Iterator<Item = NodeRef<'a>> + 'a {
160        self.nodes().filter(move |n| n.has_class(class))
161    }
162}
163
164impl fmt::Debug for Dom {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        let mut list = f.debug_list();
167        for r in self.roots() {
168            list.entry(&r);
169        }
170        list.finish()
171    }
172}
173
174/// A lightweight, `Copy` read handle to a single node, bound to its [`Dom`].
175///
176/// All traversal (`parent`, `children`, `descendants`, `ancestors`, siblings)
177/// is O(1) per step thanks to the arena's links.
178#[derive(Clone, Copy)]
179pub struct NodeRef<'a> {
180    dom: &'a Dom,
181    id: NodeId,
182}
183
184impl<'a> NodeRef<'a> {
185    #[inline]
186    fn raw(&self) -> &'a Node {
187        // Invariant: a `NodeRef` is only ever constructed from an id valid in
188        // `dom` (via the `Dom` accessors or by following node links). The arena
189        // is append-only and never shrinks, so this index is always in bounds.
190        debug_assert!(self.id.index() < self.dom.nodes.len());
191        &self.dom.nodes[self.id.index()]
192    }
193
194    /// This node's arena id.
195    #[inline]
196    pub fn id(&self) -> NodeId {
197        self.id
198    }
199
200    /// The node's kind (element / text / comment / doctype) as a borrowed view.
201    pub fn kind(&self) -> NodeKind<'a> {
202        match &self.raw().raw {
203            RawKind::Element { name, .. } => NodeKind::Element {
204                tag: self.dom.span_str(*name),
205            },
206            RawKind::Text(s) => NodeKind::Text(self.dom.span_str(*s)),
207            RawKind::Comment(s) => NodeKind::Comment(self.dom.span_str(*s)),
208            RawKind::Doctype(s) => NodeKind::Doctype(self.dom.span_str(*s)),
209        }
210    }
211
212    /// Whether this node is an element.
213    pub fn is_element(&self) -> bool {
214        matches!(self.raw().raw, RawKind::Element { .. })
215    }
216
217    /// Whether this node is a text node.
218    pub fn is_text(&self) -> bool {
219        matches!(self.raw().raw, RawKind::Text(_))
220    }
221
222    /// The tag name (lower-cased), if this node is an element.
223    pub fn tag_name(&self) -> Option<&'a str> {
224        match &self.raw().raw {
225            RawKind::Element { name, .. } => Some(self.dom.span_str(*name)),
226            _ => None,
227        }
228    }
229
230    /// The value of attribute `name`, if this is an element with that attribute.
231    pub fn attr(&self, name: &str) -> Option<&'a str> {
232        let dom = self.dom;
233        match &self.raw().raw {
234            RawKind::Element { attrs, .. } => attrs
235                .iter()
236                .find(|&&(k, _)| dom.span_str(k) == name)
237                .map(|&(_, v)| dom.span_str(v)),
238            _ => None,
239        }
240    }
241
242    /// Whether attribute `name` is present (including valueless boolean attrs).
243    pub fn has_attr(&self, name: &str) -> bool {
244        let dom = self.dom;
245        match &self.raw().raw {
246            RawKind::Element { attrs, .. } => attrs.iter().any(|&(k, _)| dom.span_str(k) == name),
247            _ => false,
248        }
249    }
250
251    /// All `(name, value)` attribute pairs (empty iterator for non-elements).
252    pub fn attributes(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
253        let dom = self.dom;
254        let attrs: &'a [(Span, Span)] = match &self.raw().raw {
255            RawKind::Element { attrs, .. } => attrs.as_slice(),
256            _ => &[],
257        };
258        attrs
259            .iter()
260            .map(move |&(k, v)| (dom.span_str(k), dom.span_str(v)))
261    }
262
263    /// The class names on this element.
264    pub fn classes(&self) -> impl Iterator<Item = &'a str> {
265        self.attr("class")
266            .into_iter()
267            .flat_map(str::split_whitespace)
268    }
269
270    /// Whether this element has the given class.
271    pub fn has_class(&self, class: &str) -> bool {
272        self.classes().any(|c| c == class)
273    }
274
275    /// This node's own text, if it is a text node (already entity-decoded).
276    pub fn text(&self) -> Option<&'a str> {
277        match &self.raw().raw {
278            RawKind::Text(s) => Some(self.dom.span_str(*s)),
279            _ => None,
280        }
281    }
282
283    /// The body text of this comment node, if it is one.
284    pub fn comment(&self) -> Option<&'a str> {
285        match &self.raw().raw {
286            RawKind::Comment(s) => Some(self.dom.span_str(*s)),
287            _ => None,
288        }
289    }
290
291    /// Concatenated text of this node and all its descendants, in document
292    /// order (entity-decoded). Equivalent to the DOM `textContent`.
293    pub fn text_content(&self) -> String {
294        let mut out = String::new();
295        if let RawKind::Text(s) = &self.raw().raw {
296            out.push_str(self.dom.span_str(*s));
297        }
298        for d in self.descendants() {
299            if let RawKind::Text(s) = &d.raw().raw {
300                out.push_str(self.dom.span_str(*s));
301            }
302        }
303        out
304    }
305
306    // --- traversal -------------------------------------------------------
307
308    /// This node's parent, if any.
309    pub fn parent(&self) -> Option<NodeRef<'a>> {
310        self.raw().parent.map(|id| NodeRef { dom: self.dom, id })
311    }
312
313    /// The first child, if any.
314    pub fn first_child(&self) -> Option<NodeRef<'a>> {
315        self.raw()
316            .first_child
317            .map(|id| NodeRef { dom: self.dom, id })
318    }
319
320    /// The last child, if any.
321    pub fn last_child(&self) -> Option<NodeRef<'a>> {
322        self.raw()
323            .last_child
324            .map(|id| NodeRef { dom: self.dom, id })
325    }
326
327    /// The next sibling, if any.
328    pub fn next_sibling(&self) -> Option<NodeRef<'a>> {
329        self.raw()
330            .next_sibling
331            .map(|id| NodeRef { dom: self.dom, id })
332    }
333
334    /// The previous sibling, if any.
335    pub fn prev_sibling(&self) -> Option<NodeRef<'a>> {
336        self.raw()
337            .prev_sibling
338            .map(|id| NodeRef { dom: self.dom, id })
339    }
340
341    /// This node's direct children, in order.
342    pub fn children(&self) -> impl Iterator<Item = NodeRef<'a>> {
343        let dom = self.dom;
344        core::iter::successors(self.raw().first_child, move |id| {
345            dom.node(*id).and_then(|n| n.next_sibling)
346        })
347        .map(move |id| NodeRef { dom, id })
348    }
349
350    /// Child *elements* only (skips text/comment/doctype nodes).
351    pub fn child_elements(&self) -> impl Iterator<Item = NodeRef<'a>> {
352        self.children().filter(NodeRef::is_element)
353    }
354
355    /// This node's ancestors, from parent up to the root.
356    pub fn ancestors(&self) -> impl Iterator<Item = NodeRef<'a>> {
357        let dom = self.dom;
358        core::iter::successors(self.raw().parent, move |id| {
359            dom.node(*id).and_then(|n| n.parent)
360        })
361        .map(move |id| NodeRef { dom, id })
362    }
363
364    /// All descendants of this node, in document (pre-order) order. Does not
365    /// include the node itself. Allocation-free.
366    pub fn descendants(&self) -> Descendants<'a> {
367        Descendants {
368            dom: self.dom,
369            root: self.id,
370            next: self.raw().first_child,
371        }
372    }
373}
374
375impl<'a> fmt::Debug for NodeRef<'a> {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        match self.kind() {
378            NodeKind::Element { tag } => f
379                .debug_struct("Element")
380                .field("tag", &tag)
381                .field("attrs", &Attrs(*self))
382                .field("children", &Kids(*self))
383                .finish(),
384            NodeKind::Text(t) => f.debug_tuple("Text").field(&t).finish(),
385            NodeKind::Comment(c) => f.debug_tuple("Comment").field(&c).finish(),
386            NodeKind::Doctype(d) => f.debug_tuple("Doctype").field(&d).finish(),
387        }
388    }
389}
390
391struct Attrs<'a>(NodeRef<'a>);
392impl<'a> fmt::Debug for Attrs<'a> {
393    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394        let mut m = f.debug_map();
395        for (k, v) in self.0.attributes() {
396            m.entry(&k, &v);
397        }
398        m.finish()
399    }
400}
401
402struct Kids<'a>(NodeRef<'a>);
403impl<'a> fmt::Debug for Kids<'a> {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        f.debug_list().entries(self.0.children()).finish()
406    }
407}
408
409/// Pre-order descendant iterator (see [`NodeRef::descendants`]).
410pub struct Descendants<'a> {
411    dom: &'a Dom,
412    root: NodeId,
413    next: Option<NodeId>,
414}
415
416impl<'a> Iterator for Descendants<'a> {
417    type Item = NodeRef<'a>;
418
419    fn next(&mut self) -> Option<NodeRef<'a>> {
420        let cur = self.next?;
421        self.next = self.advance(cur);
422        Some(NodeRef {
423            dom: self.dom,
424            id: cur,
425        })
426    }
427}
428
429impl<'a> Descendants<'a> {
430    fn advance(&self, cur: NodeId) -> Option<NodeId> {
431        if let Some(child) = self.dom.node(cur).and_then(|n| n.first_child) {
432            return Some(child);
433        }
434        let mut n = cur;
435        loop {
436            if n == self.root {
437                return None;
438            }
439            if let Some(sib) = self.dom.node(n).and_then(|nd| nd.next_sibling) {
440                return Some(sib);
441            }
442            match self.dom.node(n).and_then(|nd| nd.parent) {
443                Some(p) => n = p,
444                None => return None,
445            }
446        }
447    }
448}