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}