Skip to main content

domtree/
events.rs

1//! Streaming parse events: the tree builder's recovery without the tree.
2//!
3//! [`events`](crate::events) runs the same tokenizer and the same tree
4//! builder as [`parse`](crate::parse), but the builder drives an event queue
5//! instead of the [`Dom`](crate::Dom) arena (see `src/builder.rs`), so the
6//! two can never disagree: replaying the events into a tree gives exactly the
7//! tree `parse` builds. Memory stays proportional to the nesting depth (plus
8//! the events of one token and the collected diagnostics), not to the
9//! document.
10
11use alloc::borrow::Cow;
12use alloc::collections::VecDeque;
13use alloc::vec::Vec;
14use core::iter::FusedIterator;
15use core::ops::Range;
16
17use crate::builder::{Sink, TreeBuilder, lower};
18use crate::entities;
19use crate::error::ParseError;
20use crate::tokenizer::Tokenizer;
21
22/// One step of a document as the tree builder sees it, after recovery. See
23/// [`events`](crate::events).
24///
25/// Every `Start` is matched by exactly one later `End` with the same name,
26/// properly nested — including elements whose end tag was omitted or
27/// misplaced, and void and self-closing elements, whose `End` follows
28/// immediately.
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub enum Event<'a> {
31    /// An element starts.
32    Start {
33        /// The lower-cased tag name.
34        name: Cow<'a, str>,
35        /// The attributes in source order, duplicates kept: names
36        /// lower-cased, values entity-decoded (`""` for a valueless one).
37        attrs: Vec<(Cow<'a, str>, Cow<'a, str>)>,
38    },
39    /// An element ends.
40    End {
41        /// The lower-cased tag name (that of the matching `Start`).
42        name: Cow<'a, str>,
43        /// `true` when the element has no end tag of its own: recovery closed
44        /// it (an implicitly closed `<p>` or `<li>`, an element left open
45        /// inside a mismatched end tag, anything open at the end of the
46        /// input), or it is a void or self-closing element.
47        implied: bool,
48    },
49    /// Character data: entity-decoded, except inside `<script>`/`<style>`.
50    /// CDATA sections are text too. Adjacent text can arrive in several
51    /// pieces (a stray `<` is its own piece), exactly like the tree's text
52    /// nodes.
53    Text(Cow<'a, str>),
54    /// The body of a comment (or of a bogus comment like `<?xml …>`).
55    Comment(&'a str),
56    /// The body of a `<!doctype …>`, trimmed.
57    Doctype(&'a str),
58}
59
60/// The iterator returned by [`events`](crate::events). Yields each
61/// [`Event`] with its byte range in the source.
62///
63/// The range is the event's markup: a start tag, an end tag, a text run (raw,
64/// before decoding), a comment or doctype. An implied [`Event::End`] has an
65/// empty range at the position where recovery closed the element — the start
66/// of the token that closed it, the end of a void element's start tag, or the
67/// end of the input. So an element spans from its `Start`'s range start to
68/// its `End`'s range end, the same as
69/// [`NodeRef::source_range`](crate::NodeRef::source_range).
70pub struct Events<'a> {
71    tokenizer: Tokenizer<'a>,
72    builder: TreeBuilder<'a, Queue<'a>>,
73    len: usize,
74    done: bool,
75}
76
77impl<'a> Events<'a> {
78    pub(crate) fn new(src: &'a str) -> Self {
79        Events {
80            tokenizer: Tokenizer::new(src),
81            builder: TreeBuilder::new(
82                Queue {
83                    events: VecDeque::new(),
84                    attrs: Vec::new(),
85                },
86                src,
87            ),
88            len: src.len(),
89            done: false,
90        }
91    }
92
93    /// The recovery diagnostics, the same as
94    /// [`Dom::errors`](crate::Dom::errors) for the same input. Complete and
95    /// sorted by position once the iterator has returned `None`; before that,
96    /// those found so far.
97    ///
98    /// ```
99    /// let mut events = domtree::events("<p>a</b>");
100    /// let n = events.by_ref().count();
101    /// assert_eq!(n, 3); // Start p, Text, End p (implied, at the end of input)
102    /// assert_eq!(events.errors(), domtree::parse("<p>a</b>").errors());
103    /// ```
104    pub fn errors(&self) -> &[ParseError] {
105        &self.builder.errors
106    }
107}
108
109impl<'a> Iterator for Events<'a> {
110    type Item = (Event<'a>, Range<usize>);
111
112    fn next(&mut self) -> Option<Self::Item> {
113        loop {
114            if let Some(event) = self.builder.sink.events.pop_front() {
115                return Some(event);
116            }
117            if self.done {
118                return None;
119            }
120            match self.tokenizer.next_token() {
121                Some((tok, pos)) => {
122                    let end = self.tokenizer.offset();
123                    self.builder.token(tok, self.tokenizer.attrs(), pos, end);
124                }
125                None => {
126                    self.builder.finish(self.len, &mut self.tokenizer.errors);
127                    self.done = true;
128                }
129            }
130        }
131    }
132}
133
134impl FusedIterator for Events<'_> {}
135
136/// The [`Sink`] behind [`Events`]: queues what the builder reports for one
137/// token (an implicit close, then a start tag, say).
138struct Queue<'a> {
139    events: VecDeque<(Event<'a>, Range<usize>)>,
140    /// The attributes of the start tag being reported.
141    attrs: Vec<(Cow<'a, str>, Cow<'a, str>)>,
142}
143
144impl<'a> Sink<'a> for Queue<'a> {
145    /// The open element's lower-cased name.
146    type Open = Cow<'a, str>;
147
148    fn name<'s>(&'s self, open: &'s Cow<'a, str>) -> &'s str {
149        open
150    }
151
152    fn attr(&mut self, name: &'a str, value: &'a str) -> Option<usize> {
153        let (value, unknown) = entities::decode(value, true);
154        self.attrs.push((lower(name), value));
155        unknown
156    }
157
158    fn start(
159        &mut self,
160        _parent: Option<&Cow<'a, str>>,
161        name: Cow<'a, str>,
162        _attrs: usize,
163        start: usize,
164        end: usize,
165    ) -> Cow<'a, str> {
166        let attrs = core::mem::take(&mut self.attrs);
167        let event = Event::Start {
168            name: name.clone(),
169            attrs,
170        };
171        self.events.push_back((event, start..end));
172        name
173    }
174
175    fn end(&mut self, name: Cow<'a, str>, start: usize, end: usize, implied: bool) {
176        self.events
177            .push_back((Event::End { name, implied }, start..end));
178    }
179
180    fn text(
181        &mut self,
182        _parent: Option<&Cow<'a, str>>,
183        text: &'a str,
184        decode: bool,
185        start: usize,
186        end: usize,
187    ) -> Option<usize> {
188        let (text, unknown) = if decode {
189            entities::decode(text, false)
190        } else {
191            (Cow::Borrowed(text), None)
192        };
193        self.events.push_back((Event::Text(text), start..end));
194        unknown
195    }
196
197    fn comment(&mut self, _parent: Option<&Cow<'a, str>>, text: &'a str, start: usize, end: usize) {
198        self.events.push_back((Event::Comment(text), start..end));
199    }
200
201    fn doctype(&mut self, _parent: Option<&Cow<'a, str>>, text: &'a str, start: usize, end: usize) {
202        self.events.push_back((Event::Doctype(text), start..end));
203    }
204}