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;
6use core::ops::Range;
7
8use crate::error::ParseError;
9use crate::node::{AttrRange, Node, NodeId, NodeKind, RawKind, Span, SrcRange};
10
11/// A parsed HTML document: an arena of nodes, a single string buffer they
12/// reference, the top-level (root) nodes, and any recovery diagnostics.
13///
14/// Build one with [`crate::parse`] and traverse it with [`NodeRef`] handles
15/// from [`Dom::root`], [`Dom::roots`], [`Dom::get`], or the `find_by_*`
16/// helpers. The editing methods ([`Dom::remove`], [`Dom::set_text`],
17/// [`Dom::append_html`], [`Dom::wrap`], …) rewrite it in place.
18#[derive(Clone)]
19pub struct Dom {
20    pub(crate) nodes: Vec<Node>,
21    pub(crate) strings: String,
22    /// Every element's attributes as `(name, value)` spans, each element
23    /// owning one contiguous run ([`AttrRange`]). The parser appends runs in
24    /// document order.
25    pub(crate) attrs: Vec<(Span, Span)>,
26    /// Slots of `attrs` that no element owns any more (left behind when an
27    /// edit moved or shrank a run); see `Dom::compact_attrs`.
28    pub(crate) attr_waste: usize,
29    /// The first and last top-level node. Top-level nodes are linked as
30    /// siblings, like the children of an implicit document node.
31    pub(crate) first_root: Option<NodeId>,
32    pub(crate) last_root: Option<NodeId>,
33    /// Set by any edit that can put a node out of arena order. While it is
34    /// clear, arena order *is* document order (the parser only ever appends)
35    /// and [`Dom::nodes`] is a plain scan of the arena.
36    pub(crate) reordered: bool,
37    pub(crate) errors: Vec<ParseError>,
38}
39
40impl Dom {
41    pub(crate) fn with_capacity(src_len: usize) -> Self {
42        Dom {
43            // About one node per 13 bytes of dense markup, so the arena rarely
44            // reallocates (copying it) mid-parse: ~4 bytes reserved per input
45            // byte with 52-byte nodes.
46            nodes: Vec::with_capacity(src_len / 13 + 8),
47            strings: String::with_capacity(src_len),
48            // About one attribute per 48 bytes of dense markup.
49            attrs: Vec::with_capacity(src_len / 48 + 4),
50            attr_waste: 0,
51            first_root: None,
52            last_root: None,
53            reordered: false,
54            errors: Vec::new(),
55        }
56    }
57
58    /// Give back arena capacity that `with_capacity` reserved for dense markup
59    /// but the document did not use (text-heavy pages have far fewer nodes and
60    /// attributes per byte), so a parsed `Dom` does not hold on to several
61    /// times its size. Only when more than half is unused: shrinking costs at
62    /// most a copy of what is kept.
63    pub(crate) fn release_spare_capacity(&mut self) {
64        if self.nodes.capacity() / 2 > self.nodes.len() + 64 {
65            self.nodes.shrink_to_fit();
66        }
67        if self.attrs.capacity() / 2 > self.attrs.len() + 64 {
68            self.attrs.shrink_to_fit();
69        }
70        // The string arena is reserved at the input's length, which nearly
71        // every document fills to well over half (text is copied into it), so
72        // it is left alone — saving the code for a third shrink in wasm.
73    }
74
75    /// Append a string to the arena and return its [`Span`].
76    pub(crate) fn intern(&mut self, s: &str) -> Span {
77        if s.is_empty() {
78            return Span::EMPTY;
79        }
80        let start = self.strings.len() as u32;
81        self.strings.push_str(s);
82        Span {
83            start,
84            len: s.len() as u32,
85        }
86    }
87
88    /// The [`Span`] of everything appended to the arena since `start`.
89    #[inline]
90    fn span_since(&self, start: usize) -> Span {
91        Span {
92            start: start as u32,
93            len: (self.strings.len() - start) as u32,
94        }
95    }
96
97    /// Append `s` ASCII-lower-cased (a tag or attribute name), without a
98    /// temporary string.
99    pub(crate) fn intern_lower(&mut self, s: &str) -> Span {
100        let start = self.strings.len();
101        self.strings.push_str(s);
102        if let Some(new) = self.strings.get_mut(start..) {
103            new.make_ascii_lowercase();
104        }
105        self.span_since(start)
106    }
107
108    /// Append `s` with its character references decoded straight into the
109    /// arena. Also returns where in `s` the first reference-shaped `&…` left
110    /// as is starts, if any (see [`crate::entities::decode_into`]).
111    pub(crate) fn intern_decoded(&mut self, s: &str, in_attr: bool) -> (Span, Option<usize>) {
112        let start = self.strings.len();
113        let unknown = crate::entities::decode_into(s, &mut self.strings, in_attr);
114        (self.span_since(start), unknown)
115    }
116
117    #[inline]
118    pub(crate) fn span_str(&self, span: Span) -> &str {
119        self.strings.get(span.range()).unwrap_or("")
120    }
121
122    /// An element's `(name, value)` attribute spans.
123    #[inline]
124    pub(crate) fn attr_slice(&self, attrs: AttrRange) -> &[(Span, Span)] {
125        self.attrs.get(attrs.range()).unwrap_or(&[])
126    }
127
128    pub(crate) fn alloc(&mut self, raw: RawKind, src: SrcRange) -> NodeId {
129        let id = NodeId::from_index(self.nodes.len());
130        self.nodes.push(Node::new(raw, src));
131        id
132    }
133
134    #[inline]
135    pub(crate) fn node(&self, id: NodeId) -> Option<&Node> {
136        self.nodes.get(id.index())
137    }
138
139    #[inline]
140    pub(crate) fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
141        self.nodes.get_mut(id.index())
142    }
143
144    /// Append `child` under `parent` (or as a root when `parent` is `None`),
145    /// maintaining all sibling/child links.
146    #[inline]
147    pub(crate) fn append(&mut self, parent: Option<NodeId>, child: NodeId) {
148        self.link(parent, None, child);
149    }
150
151    /// Link the unlinked node `id` into `parent` (`None`: the top level),
152    /// before its child `before` (`None`: as the last child). Touches links
153    /// only; callers keep `detached` and `reordered` right.
154    pub(crate) fn link(&mut self, parent: Option<NodeId>, before: Option<NodeId>, id: NodeId) {
155        let prev = match (before, parent) {
156            (Some(b), _) => self.node(b).and_then(|n| n.prev_sibling),
157            (None, Some(p)) => self.node(p).and_then(|n| n.last_child),
158            (None, None) => self.last_root,
159        };
160        if let Some(n) = self.node_mut(id) {
161            n.parent = parent;
162            n.prev_sibling = prev;
163            n.next_sibling = before;
164        }
165        match (prev, parent) {
166            (Some(p), _) => {
167                if let Some(p) = self.node_mut(p) {
168                    p.next_sibling = Some(id);
169                }
170            }
171            (None, Some(p)) => {
172                if let Some(p) = self.node_mut(p) {
173                    p.first_child = Some(id);
174                }
175            }
176            (None, None) => self.first_root = Some(id),
177        }
178        match (before, parent) {
179            (Some(b), _) => {
180                if let Some(b) = self.node_mut(b) {
181                    b.prev_sibling = Some(id);
182                }
183            }
184            (None, Some(p)) => {
185                if let Some(p) = self.node_mut(p) {
186                    p.last_child = Some(id);
187                }
188            }
189            (None, None) => self.last_root = Some(id),
190        }
191    }
192
193    /// Unlink `id` from its parent and siblings (or from the top level). Its
194    /// subtree stays intact; `detached` flags are left to the caller.
195    pub(crate) fn unlink(&mut self, id: NodeId) {
196        let Some(node) = self.node(id) else { return };
197        let (parent, prev, next) = (node.parent, node.prev_sibling, node.next_sibling);
198        // A parentless node is top-level only while attached; a detached one
199        // (removed or created) is an orphan with no siblings.
200        let top = parent.is_none() && !node.detached;
201        match (prev, parent) {
202            (Some(p), _) => {
203                if let Some(p) = self.node_mut(p) {
204                    p.next_sibling = next;
205                }
206            }
207            (None, Some(p)) => {
208                if let Some(p) = self.node_mut(p) {
209                    p.first_child = next;
210                }
211            }
212            (None, None) if top => self.first_root = next,
213            (None, None) => {}
214        }
215        match (next, parent) {
216            (Some(n), _) => {
217                if let Some(n) = self.node_mut(n) {
218                    n.prev_sibling = prev;
219                }
220            }
221            (None, Some(p)) => {
222                if let Some(p) = self.node_mut(p) {
223                    p.last_child = prev;
224                }
225            }
226            (None, None) if top => self.last_root = prev,
227            (None, None) => {}
228        }
229        if let Some(n) = self.node_mut(id) {
230            n.parent = None;
231            n.prev_sibling = None;
232            n.next_sibling = None;
233        }
234    }
235
236    /// The node after `cur` in document order, following links only.
237    fn next_in_document(&self, cur: NodeId) -> Option<NodeId> {
238        if let Some(child) = self.node(cur).and_then(|n| n.first_child) {
239            return Some(child);
240        }
241        let mut n = cur;
242        loop {
243            let node = self.node(n)?;
244            if let Some(sib) = node.next_sibling {
245                return Some(sib);
246            }
247            n = node.parent?;
248        }
249    }
250
251    // --- public read API -------------------------------------------------
252
253    /// Number of nodes in the arena, including removed and detached ones.
254    pub fn len(&self) -> usize {
255        self.nodes.len()
256    }
257
258    /// Whether the document contains no nodes.
259    pub fn is_empty(&self) -> bool {
260        self.nodes.is_empty()
261    }
262
263    /// Recovery diagnostics collected during parsing (empty for clean input).
264    pub fn errors(&self) -> &[ParseError] {
265        &self.errors
266    }
267
268    /// A handle to a node by id, or `None` if the id is out of range.
269    pub fn get(&self, id: NodeId) -> Option<NodeRef<'_>> {
270        if id.index() < self.nodes.len() {
271            Some(NodeRef { dom: self, id })
272        } else {
273            None
274        }
275    }
276
277    /// The first top-level node (the document element for a typical page).
278    pub fn root(&self) -> Option<NodeRef<'_>> {
279        self.first_root.map(|id| NodeRef { dom: self, id })
280    }
281
282    /// All top-level nodes, in document order.
283    pub fn roots(&self) -> impl Iterator<Item = NodeRef<'_>> {
284        core::iter::successors(self.first_root, move |&id| {
285            self.node(id).and_then(|n| n.next_sibling)
286        })
287        .map(move |id| NodeRef { dom: self, id })
288    }
289
290    /// Every node in the document, in document (pre-order) order. Nodes
291    /// removed with [`Dom::remove`] (and created nodes not yet attached) are
292    /// skipped.
293    ///
294    /// ```
295    /// let mut dom = domtree::parse("<p>a</p><p>c</p>");
296    /// let c = dom.find_by_tag("p").nth(1).unwrap().id();
297    /// dom.insert_html_before(c, "<p>b</p>");
298    /// let text: Vec<_> = dom.nodes().filter_map(|n| n.text()).collect();
299    /// assert_eq!(text, ["a", "b", "c"]);
300    /// ```
301    pub fn nodes(&self) -> impl Iterator<Item = NodeRef<'_>> {
302        Nodes {
303            dom: self,
304            order: if self.reordered {
305                Order::Links(self.first_root)
306            } else {
307                Order::Arena(0)
308            },
309        }
310    }
311
312    /// All elements whose tag name equals `name` (ASCII case-insensitive).
313    pub fn find_by_tag<'a>(&'a self, name: &'a str) -> impl Iterator<Item = NodeRef<'a>> + 'a {
314        self.nodes()
315            .filter(move |n| n.tag_name().is_some_and(|t| t.eq_ignore_ascii_case(name)))
316    }
317
318    /// The first element with `id="…"` equal to `id`, if any.
319    pub fn find_by_id<'a>(&'a self, id: &'a str) -> Option<NodeRef<'a>> {
320        self.nodes().find(|n| n.attr("id") == Some(id))
321    }
322
323    /// All elements whose `class` attribute contains `class`.
324    pub fn find_by_class<'a>(&'a self, class: &'a str) -> impl Iterator<Item = NodeRef<'a>> + 'a {
325        self.nodes().filter(move |n| n.has_class(class))
326    }
327}
328
329/// The iterator behind [`Dom::nodes`].
330struct Nodes<'a> {
331    dom: &'a Dom,
332    order: Order,
333}
334
335enum Order {
336    /// Arena order is document order: scan it, skipping detached nodes.
337    Arena(usize),
338    /// An edit moved nodes: follow the links from the first root (allocation-
339    /// free, and only reaches attached nodes).
340    Links(Option<NodeId>),
341}
342
343impl<'a> Iterator for Nodes<'a> {
344    type Item = NodeRef<'a>;
345
346    fn next(&mut self) -> Option<NodeRef<'a>> {
347        let dom = self.dom;
348        let id = match &mut self.order {
349            Order::Arena(i) => loop {
350                let node = dom.nodes.get(*i)?;
351                let id = NodeId::from_index(*i);
352                *i += 1;
353                if !node.detached {
354                    break id;
355                }
356            },
357            Order::Links(next) => {
358                let id = (*next)?;
359                *next = dom.next_in_document(id);
360                id
361            }
362        };
363        Some(NodeRef { dom, id })
364    }
365}
366
367impl fmt::Debug for Dom {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        let mut list = f.debug_list();
370        for r in self.roots() {
371            list.entry(&r);
372        }
373        list.finish()
374    }
375}
376
377/// A lightweight, `Copy` read handle to a single node, bound to its [`Dom`].
378///
379/// All traversal (`parent`, `children`, `descendants`, `ancestors`, siblings)
380/// is O(1) per step thanks to the arena's links.
381#[derive(Clone, Copy)]
382pub struct NodeRef<'a> {
383    dom: &'a Dom,
384    id: NodeId,
385}
386
387impl<'a> NodeRef<'a> {
388    #[inline]
389    fn raw(&self) -> &'a Node {
390        // Invariant: a `NodeRef` is only ever constructed from an id valid in
391        // `dom` (via the `Dom` accessors or by following node links). The arena
392        // is append-only and never shrinks, so this index is always in bounds.
393        debug_assert!(self.id.index() < self.dom.nodes.len());
394        &self.dom.nodes[self.id.index()]
395    }
396
397    /// This node's arena id.
398    #[inline]
399    pub fn id(&self) -> NodeId {
400        self.id
401    }
402
403    /// The byte range this node was parsed from, in the original input
404    /// string (pair it with [`crate::line_col`] for line/column positions).
405    ///
406    /// - **Text, comments, doctypes, CDATA:** exactly their markup, raw —
407    ///   `&amp;` in the source spans 5 bytes even though the text is `&`.
408    /// - **Elements:** from the `<` of the start tag to the `>` of the end tag.
409    ///   Void and self-closing elements span just their start tag. An element
410    ///   with no end tag of its own ends where recovery closed it: at the
411    ///   start of the tag that closed it (a start tag like the next `<li>`, or
412    ///   a mismatched end tag like `</ul>`), or at the end of the input. So a
413    ///   parent's range always contains its children's.
414    /// - **`None`** for nodes created by editing ([`Dom::create_element`],
415    ///   [`Dom::set_text`] on an element, …), including those inserted with
416    ///   [`Dom::append_html`] and friends, whose source is a different string.
417    ///
418    /// Edits never move a range: a renamed element or rewritten text node
419    /// still points at where it came from.
420    ///
421    /// ```
422    /// let src = "<ul>\n  <li>Tom &amp; Jerry\n  <li>Spike</ul>";
423    /// let dom = domtree::parse(src);
424    /// let first = dom.find_by_tag("li").next().unwrap();
425    /// // Closed implicitly by the second `<li>`:
426    /// assert_eq!(&src[first.source_range().unwrap()], "<li>Tom &amp; Jerry\n  ");
427    /// let text = first.first_child().unwrap();
428    /// assert_eq!(&src[text.source_range().unwrap()], "Tom &amp; Jerry\n  ");
429    /// assert_eq!(&src[dom.root().unwrap().source_range().unwrap()], src);
430    /// ```
431    pub fn source_range(&self) -> Option<Range<usize>> {
432        self.raw().src.get()
433    }
434
435    /// Whether this node is *not* part of the document: it was removed (or
436    /// sits inside a removed subtree), or was created by editing and not yet
437    /// attached. Detached nodes are skipped by [`Dom::nodes`], selectors and
438    /// serializers of the whole document. The inverse of the DOM's
439    /// `isConnected`.
440    ///
441    /// ```
442    /// let mut dom = domtree::parse("<p>a <b>b</b></p>");
443    /// let p = dom.root().unwrap().id();
444    /// let b = dom.find_by_tag("b").next().unwrap().id();
445    /// dom.remove(b);
446    /// assert!(dom.get(b).unwrap().is_detached());
447    /// dom.append_child(p, b); // re-attached
448    /// assert!(!dom.get(b).unwrap().is_detached());
449    /// ```
450    pub fn is_detached(&self) -> bool {
451        self.raw().detached
452    }
453
454    /// The node's kind (element / text / comment / doctype) as a borrowed view.
455    pub fn kind(&self) -> NodeKind<'a> {
456        match &self.raw().raw {
457            RawKind::Element { name, .. } => NodeKind::Element {
458                tag: self.dom.span_str(*name),
459            },
460            RawKind::Text(s) => NodeKind::Text(self.dom.span_str(*s)),
461            RawKind::Comment(s) => NodeKind::Comment(self.dom.span_str(*s)),
462            RawKind::Doctype(s) => NodeKind::Doctype(self.dom.span_str(*s)),
463        }
464    }
465
466    /// Whether this node is an element.
467    pub fn is_element(&self) -> bool {
468        matches!(self.raw().raw, RawKind::Element { .. })
469    }
470
471    /// Whether this node is a text node.
472    pub fn is_text(&self) -> bool {
473        matches!(self.raw().raw, RawKind::Text(_))
474    }
475
476    /// The tag name (lower-cased), if this node is an element.
477    pub fn tag_name(&self) -> Option<&'a str> {
478        match &self.raw().raw {
479            RawKind::Element { name, .. } => Some(self.dom.span_str(*name)),
480            _ => None,
481        }
482    }
483
484    /// This element's attribute spans (empty for non-elements).
485    #[inline]
486    fn attr_spans(&self) -> &'a [(Span, Span)] {
487        match self.raw().raw {
488            RawKind::Element { attrs, .. } => self.dom.attr_slice(attrs),
489            _ => &[],
490        }
491    }
492
493    /// The value of attribute `name`, if this is an element with that
494    /// attribute. With duplicate attributes, the first one wins.
495    pub fn attr(&self, name: &str) -> Option<&'a str> {
496        let dom = self.dom;
497        self.attr_spans()
498            .iter()
499            .find(|&&(k, _)| dom.span_str(k) == name)
500            .map(|&(_, v)| dom.span_str(v))
501    }
502
503    /// Whether attribute `name` is present (including valueless boolean attrs).
504    pub fn has_attr(&self, name: &str) -> bool {
505        let dom = self.dom;
506        self.attr_spans()
507            .iter()
508            .any(|&(k, _)| dom.span_str(k) == name)
509    }
510
511    /// All `(name, value)` attribute pairs in source order, duplicates
512    /// included (empty iterator for non-elements).
513    pub fn attributes(&self) -> impl Iterator<Item = (&'a str, &'a str)> {
514        let dom = self.dom;
515        self.attr_spans()
516            .iter()
517            .map(move |&(k, v)| (dom.span_str(k), dom.span_str(v)))
518    }
519
520    /// The class names on this element. Like HTML, splits on ASCII whitespace
521    /// only, so a non-breaking space stays part of a class name.
522    pub fn classes(&self) -> impl Iterator<Item = &'a str> {
523        self.attr("class")
524            .into_iter()
525            .flat_map(str::split_ascii_whitespace)
526    }
527
528    /// Whether this element has the given class.
529    pub fn has_class(&self, class: &str) -> bool {
530        self.classes().any(|c| c == class)
531    }
532
533    /// This node's own text, if it is a text node (already entity-decoded).
534    pub fn text(&self) -> Option<&'a str> {
535        match &self.raw().raw {
536            RawKind::Text(s) => Some(self.dom.span_str(*s)),
537            _ => None,
538        }
539    }
540
541    /// The body text of this comment node, if it is one.
542    pub fn comment(&self) -> Option<&'a str> {
543        match &self.raw().raw {
544            RawKind::Comment(s) => Some(self.dom.span_str(*s)),
545            _ => None,
546        }
547    }
548
549    /// Concatenated text of this node and all its descendants, in document
550    /// order (entity-decoded). Equivalent to the DOM `textContent`.
551    pub fn text_content(&self) -> String {
552        let mut out = String::new();
553        if let RawKind::Text(s) = &self.raw().raw {
554            out.push_str(self.dom.span_str(*s));
555        }
556        for d in self.descendants() {
557            if let RawKind::Text(s) = &d.raw().raw {
558                out.push_str(self.dom.span_str(*s));
559            }
560        }
561        out
562    }
563
564    // --- traversal -------------------------------------------------------
565
566    /// This node's parent, if any.
567    pub fn parent(&self) -> Option<NodeRef<'a>> {
568        self.raw().parent.map(|id| NodeRef { dom: self.dom, id })
569    }
570
571    /// The first child, if any.
572    pub fn first_child(&self) -> Option<NodeRef<'a>> {
573        self.raw()
574            .first_child
575            .map(|id| NodeRef { dom: self.dom, id })
576    }
577
578    /// The last child, if any.
579    pub fn last_child(&self) -> Option<NodeRef<'a>> {
580        self.raw()
581            .last_child
582            .map(|id| NodeRef { dom: self.dom, id })
583    }
584
585    /// The next sibling, if any.
586    pub fn next_sibling(&self) -> Option<NodeRef<'a>> {
587        self.raw()
588            .next_sibling
589            .map(|id| NodeRef { dom: self.dom, id })
590    }
591
592    /// The previous sibling, if any.
593    pub fn prev_sibling(&self) -> Option<NodeRef<'a>> {
594        self.raw()
595            .prev_sibling
596            .map(|id| NodeRef { dom: self.dom, id })
597    }
598
599    /// The next sibling that is an element (skips text, comments, doctypes).
600    ///
601    /// ```
602    /// let dom = domtree::parse("<p><a>1</a> text <!-- c --><b>2</b></p>");
603    /// let a = dom.find_by_tag("a").next().unwrap();
604    /// assert_eq!(a.next_element_sibling().unwrap().tag_name(), Some("b"));
605    /// assert!(a.prev_element_sibling().is_none());
606    /// ```
607    pub fn next_element_sibling(&self) -> Option<NodeRef<'a>> {
608        core::iter::successors(self.next_sibling(), NodeRef::next_sibling).find(NodeRef::is_element)
609    }
610
611    /// The previous sibling that is an element (skips text, comments, doctypes).
612    ///
613    /// ```
614    /// let dom = domtree::parse("<ul><li>a</li> <li>b</li></ul>");
615    /// let b = dom.find_by_tag("li").nth(1).unwrap();
616    /// assert_eq!(b.prev_element_sibling().unwrap().text_content(), "a");
617    /// ```
618    pub fn prev_element_sibling(&self) -> Option<NodeRef<'a>> {
619        core::iter::successors(self.prev_sibling(), NodeRef::prev_sibling).find(NodeRef::is_element)
620    }
621
622    /// This node's direct children, in order.
623    pub fn children(&self) -> impl Iterator<Item = NodeRef<'a>> {
624        let dom = self.dom;
625        core::iter::successors(self.raw().first_child, move |id| {
626            dom.node(*id).and_then(|n| n.next_sibling)
627        })
628        .map(move |id| NodeRef { dom, id })
629    }
630
631    /// Child *elements* only (skips text/comment/doctype nodes).
632    pub fn child_elements(&self) -> impl Iterator<Item = NodeRef<'a>> {
633        self.children().filter(NodeRef::is_element)
634    }
635
636    /// This node's ancestors, from parent up to the root.
637    pub fn ancestors(&self) -> impl Iterator<Item = NodeRef<'a>> {
638        let dom = self.dom;
639        core::iter::successors(self.raw().parent, move |id| {
640            dom.node(*id).and_then(|n| n.parent)
641        })
642        .map(move |id| NodeRef { dom, id })
643    }
644
645    /// All descendants of this node, in document (pre-order) order. Does not
646    /// include the node itself. Allocation-free.
647    pub fn descendants(&self) -> Descendants<'a> {
648        Descendants {
649            dom: self.dom,
650            root: self.id,
651            next: self.raw().first_child,
652        }
653    }
654}
655
656impl<'a> fmt::Debug for NodeRef<'a> {
657    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
658        Dbg(*self, 0).fmt(f)
659    }
660}
661
662/// `Debug` nests one formatter frame per tree level, so deeper levels are
663/// elided to keep `{:?}` on pathological input from overflowing the stack.
664const DEBUG_MAX_DEPTH: usize = 64;
665
666struct Dbg<'a>(NodeRef<'a>, usize);
667impl<'a> fmt::Debug for Dbg<'a> {
668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669        let (node, depth) = (self.0, self.1);
670        match node.kind() {
671            NodeKind::Element { .. } if depth >= DEBUG_MAX_DEPTH => f.write_str("…"),
672            NodeKind::Element { tag } => f
673                .debug_struct("Element")
674                .field("tag", &tag)
675                .field("attrs", &Attrs(node))
676                .field("children", &Kids(node, depth + 1))
677                .finish(),
678            NodeKind::Text(t) => f.debug_tuple("Text").field(&t).finish(),
679            NodeKind::Comment(c) => f.debug_tuple("Comment").field(&c).finish(),
680            NodeKind::Doctype(d) => f.debug_tuple("Doctype").field(&d).finish(),
681        }
682    }
683}
684
685struct Attrs<'a>(NodeRef<'a>);
686impl<'a> fmt::Debug for Attrs<'a> {
687    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
688        let mut m = f.debug_map();
689        for (k, v) in self.0.attributes() {
690            m.entry(&k, &v);
691        }
692        m.finish()
693    }
694}
695
696struct Kids<'a>(NodeRef<'a>, usize);
697impl<'a> fmt::Debug for Kids<'a> {
698    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
699        let depth = self.1;
700        f.debug_list()
701            .entries(self.0.children().map(|c| Dbg(c, depth)))
702            .finish()
703    }
704}
705
706/// Pre-order descendant iterator (see [`NodeRef::descendants`]).
707pub struct Descendants<'a> {
708    dom: &'a Dom,
709    root: NodeId,
710    next: Option<NodeId>,
711}
712
713impl<'a> Iterator for Descendants<'a> {
714    type Item = NodeRef<'a>;
715
716    fn next(&mut self) -> Option<NodeRef<'a>> {
717        let cur = self.next?;
718        self.next = self.advance(cur);
719        Some(NodeRef {
720            dom: self.dom,
721            id: cur,
722        })
723    }
724}
725
726impl<'a> Descendants<'a> {
727    fn advance(&self, cur: NodeId) -> Option<NodeId> {
728        if let Some(child) = self.dom.node(cur).and_then(|n| n.first_child) {
729            return Some(child);
730        }
731        let mut n = cur;
732        loop {
733            if n == self.root {
734                return None;
735            }
736            if let Some(sib) = self.dom.node(n).and_then(|nd| nd.next_sibling) {
737                return Some(sib);
738            }
739            n = self.dom.node(n).and_then(|nd| nd.parent)?;
740        }
741    }
742}