Skip to main content

astral_html/
reader.rs

1//! HTML text-state selection for source-order readers.
2
3use crate::{Attribute, State, Token, Tokenizer};
4
5/// A source-order HTML event reader that selects text modes from start tags.
6///
7/// HTML text elements such as `script`, `style`, and `textarea` select the
8/// corresponding tokenizer state. Scripting is disabled (`noscript` is ordinary
9/// markup). Tree construction, SVG/MathML namespace transitions, and fragment
10/// insertion modes are not implemented. Use [`Tokenizer`] for explicit context.
11///
12/// Events borrow unchanged input. The reader retains no tokens and imposes no
13/// resource limits. Once exhausted, it keeps returning `None`.
14pub struct Reader<'a> {
15    tokenizer: Tokenizer<'a>,
16}
17
18impl<'a> Reader<'a> {
19    /// Read an already decoded UTF-8 HTML input.
20    pub fn new(source: &'a str) -> Self {
21        Self {
22            tokenizer: Tokenizer::new(source),
23        }
24    }
25
26    /// Return the consumed byte offset in the original UTF-8 input.
27    ///
28    /// This includes skipped input and incomplete tags discarded at EOF.
29    pub fn position(&self) -> usize {
30        self.tokenizer.position()
31    }
32
33    /// Select text states while applying
34    /// [`Tokenizer::next_with_attribute_buffer`]'s buffer contract.
35    #[inline(always)]
36    pub(crate) fn next_with_attribute_buffer(
37        &mut self,
38        attribute_buffer: &mut Vec<Attribute<'a>>,
39    ) -> Option<Token<'a>> {
40        let token = self.tokenizer.next_with_attribute_buffer(attribute_buffer);
41        if let Some(Token::StartTag(tag)) = &token {
42            let context = match tag.name.as_ref() {
43                "title" => Some((State::Rcdata, "title")),
44                "textarea" => Some((State::Rcdata, "textarea")),
45                "style" => Some((State::Rawtext, "style")),
46                "xmp" => Some((State::Rawtext, "xmp")),
47                "iframe" => Some((State::Rawtext, "iframe")),
48                "noembed" => Some((State::Rawtext, "noembed")),
49                "noframes" => Some((State::Rawtext, "noframes")),
50                "script" => Some((State::ScriptData, "script")),
51                "plaintext" => Some((State::Plaintext, "plaintext")),
52                _ => None,
53            };
54            if let Some((state, name)) = context {
55                self.tokenizer.set_static_text_state(state, name);
56            }
57        }
58        token
59    }
60}
61
62impl<'a> Iterator for Reader<'a> {
63    type Item = Token<'a>;
64
65    fn next(&mut self) -> Option<Self::Item> {
66        self.next_with_attribute_buffer(&mut Vec::new())
67    }
68}
69
70impl std::iter::FusedIterator for Reader<'_> {}