Skip to main content

tl/parser/
base.rs

1use super::{
2    constants,
3    handle::NodeHandle,
4    tag::{Attributes, HTMLTag, Node},
5};
6use crate::InnerNodeHandle;
7use crate::inline::hashmap::InlineHashMap;
8use crate::{ParseError, bytes::Bytes, inline::vec::InlineVec, simd};
9use crate::{ParserOptions, stream::Stream};
10
11#[cfg(feature = "std")]
12type StorageVec<T, const N: usize> = std::vec::Vec<T>;
13#[cfg(not(feature = "std"))]
14type StorageVec<T, const N: usize> = InlineVec<T, N>;
15
16#[cfg(feature = "std")]
17type StorageMap<K, V, const N: usize> = InlineHashMap<K, V, N>;
18#[cfg(not(feature = "std"))]
19type StorageMap<K, V, const N: usize> = InlineHashMap<K, V, N>;
20
21#[cfg(feature = "std")]
22fn new_vec<T, const N: usize>() -> StorageVec<T, N> {
23    std::vec::Vec::new()
24}
25
26#[cfg(not(feature = "std"))]
27fn new_vec<T, const N: usize>() -> StorageVec<T, N> {
28    InlineVec::new()
29}
30
31#[cfg(feature = "std")]
32fn new_map<K, V, const N: usize>() -> StorageMap<K, V, N>
33where
34    K: core::hash::Hash + Eq,
35{
36    InlineHashMap::new()
37}
38
39#[cfg(not(feature = "std"))]
40fn new_map<K, V, const N: usize>() -> StorageMap<K, V, N>
41where
42    K: core::hash::Hash + Eq,
43{
44    InlineHashMap::new()
45}
46
47#[cfg(feature = "std")]
48fn push_vec<T, const N: usize>(
49    vec: &mut StorageVec<T, N>,
50    value: T,
51    _err: ParseError,
52) -> Result<(), ParseError> {
53    vec.push(value);
54    Ok(())
55}
56
57#[cfg(not(feature = "std"))]
58fn push_vec<T, const N: usize>(
59    vec: &mut StorageVec<T, N>,
60    value: T,
61    err: ParseError,
62) -> Result<(), ParseError> {
63    vec.push(value).map_err(|_| err)
64}
65
66#[cfg(feature = "std")]
67fn insert_map<K, V, const N: usize>(
68    map: &mut StorageMap<K, V, N>,
69    key: K,
70    value: V,
71    err: ParseError,
72) -> Result<Option<V>, ParseError>
73where
74    K: core::hash::Hash + Eq,
75{
76    if let Some(slot) = map.get_mut(&key) {
77        let old = core::mem::replace(slot, value);
78        Ok(Some(old))
79    } else {
80        map.insert(key, value).map_err(|_| err)?;
81        Ok(None)
82    }
83}
84
85#[cfg(not(feature = "std"))]
86fn insert_map<K, V, const N: usize>(
87    map: &mut StorageMap<K, V, N>,
88    key: K,
89    value: V,
90    err: ParseError,
91) -> Result<Option<V>, ParseError>
92where
93    K: core::hash::Hash + Eq,
94{
95    if let Some(slot) = map.get_mut(&key) {
96        let old = core::mem::replace(slot, value);
97        Ok(Some(old))
98    } else {
99        map.insert(key, value).map_err(|_| err)?;
100        Ok(None)
101    }
102}
103
104/// A list of HTML nodes
105pub type Tree<'a, const MAX_NODES: usize = 0> = StorageVec<Node<'a>, MAX_NODES>;
106
107/// Inline class vector
108pub type ClassVec<const MAX_NODES: usize = 0> = InlineVec<NodeHandle, MAX_NODES>;
109
110/// HTML Version (<!DOCTYPE>)
111#[derive(Debug, Copy, Clone, PartialEq)]
112#[repr(C)]
113pub enum HTMLVersion {
114    /// HTML Version 5
115    HTML5,
116    /// Strict HTML 4.01
117    StrictHTML401,
118    /// Transitional HTML 4.01
119    TransitionalHTML401,
120    /// Frameset HTML 4.01:
121    FramesetHTML401,
122}
123/// The main HTML parser
124///
125/// Users of this library are not supposed to directly construct this struct.
126/// Instead, users must call `tl::parse()` and use the returned `VDom`.
127#[derive(Debug)]
128pub struct Parser<
129    'a,
130    const MAX_NODES: usize = 0,
131    const MAX_STACK: usize = 0,
132    const MAX_ROOTS: usize = 0,
133    const MAX_IDS: usize = 0,
134    const MAX_CLASSES: usize = 0,
135    const MAX_SELECTOR_NODES: usize = 0,
136> {
137    /// The inner stream that is used to iterate through the HTML source
138    pub(crate) stream: Stream<'a, u8>,
139    pub(crate) stack: StorageVec<NodeHandle, MAX_STACK>,
140    /// Specified options for this HTML parser
141    pub(crate) options: ParserOptions,
142    /// A global collection of all HTML tags that appear in the source code
143    ///
144    /// HTML Nodes contain indicies into this vector
145    pub(crate) tags: Tree<'a, MAX_NODES>,
146    /// The topmost HTML nodes
147    pub(crate) ast: StorageVec<NodeHandle, MAX_ROOTS>,
148    /// A HashMap that maps Tag ID to a Node ID
149    pub(crate) ids: StorageMap<Bytes<'a>, NodeHandle, MAX_IDS>,
150    /// A HashMap that maps Tag Class to a Node ID
151    pub(crate) classes: StorageMap<Bytes<'a>, ClassVec<MAX_NODES>, MAX_CLASSES>,
152    /// The current HTML version, if set
153    pub(crate) version: Option<HTMLVersion>,
154}
155
156impl<
157    'a,
158    const MAX_NODES: usize,
159    const MAX_STACK: usize,
160    const MAX_ROOTS: usize,
161    const MAX_IDS: usize,
162    const MAX_CLASSES: usize,
163    const MAX_SELECTOR_NODES: usize,
164> Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>
165{
166    pub(crate) fn new(input: &'a str, options: ParserOptions) -> Self {
167        Parser {
168            stack: new_vec::<NodeHandle, MAX_STACK>(),
169            options,
170            tags: new_vec::<Node<'a>, MAX_NODES>(),
171            stream: Stream::new(input.as_bytes()),
172            ast: new_vec::<NodeHandle, MAX_ROOTS>(),
173            ids: new_map::<Bytes<'a>, NodeHandle, MAX_IDS>(),
174            classes: new_map::<Bytes<'a>, ClassVec<MAX_NODES>, MAX_CLASSES>(),
175            version: None,
176        }
177    }
178
179    #[inline(always)]
180    fn register_tag(&mut self, node: Node<'a>) -> Result<NodeHandle, ParseError> {
181        push_vec::<Node<'a>, MAX_NODES>(&mut self.tags, node, ParseError::NodeCapacityExceeded)?;
182        Ok(NodeHandle::new((self.tags.len() - 1) as u32))
183    }
184
185    #[inline(always)]
186    fn skip_whitespaces(&mut self) {
187        self.read_while2(b' ', b'\n');
188    }
189
190    fn read_to(&mut self, needle: u8) -> &'a [u8] {
191        let start = self.stream.idx;
192        let bytes = &self.stream.data()[start..];
193
194        let end = simd::find(bytes, needle).unwrap_or_else(|| self.stream.len() - start);
195
196        self.stream.idx += end;
197        self.stream.slice(start, start + end)
198    }
199
200    fn read_to3(&mut self, needle: [u8; 3]) -> &'a [u8] {
201        let start = self.stream.idx;
202        let bytes = &self.stream.data()[start..];
203
204        let end = simd::find3(bytes, needle).unwrap_or_else(|| self.stream.len() - start);
205
206        self.stream.idx += end;
207        self.stream.slice(start, start + end)
208    }
209
210    fn read_while2(&mut self, needle1: u8, needle2: u8) -> Option<()> {
211        loop {
212            let ch = self.stream.current_cpy()?;
213
214            let eq1 = ch == needle1;
215            let eq2 = ch == needle2;
216
217            if !eq1 & !eq2 {
218                return Some(());
219            }
220
221            self.stream.advance();
222        }
223    }
224
225    fn read_ident(&mut self) -> Option<&'a [u8]> {
226        let start = self.stream.idx;
227        let bytes = &self.stream.data()[start..];
228
229        // If we do not find any characters that are not identifiers
230        // then we are probably at the end of the stream
231        let end = simd::search_non_ident(bytes).unwrap_or_else(|| self.stream.len() - start);
232
233        // If we don't find any identifier characters, return `None`.
234        if end == 0 {
235            return None;
236        }
237
238        self.stream.idx += end;
239        Some(self.stream.slice(start, start + end))
240    }
241
242    fn skip_comment_with_start(&mut self, start: usize) -> &'a [u8] {
243        while !self.stream.is_eof() {
244            let idx = self.stream.idx;
245
246            if self
247                .stream
248                .slice_len(idx, constants::COMMENT.len())
249                .eq(constants::COMMENT)
250            {
251                self.stream.advance_by(constants::COMMENT.len());
252
253                let is_end_of_comment = self.stream.expect_and_skip_cond(b'>');
254
255                if is_end_of_comment {
256                    return self.stream.slice(start, self.stream.idx);
257                }
258            }
259
260            self.stream.advance();
261        }
262
263        &[]
264    }
265
266    fn parse_attribute(&mut self) -> Option<(&'a [u8], Option<&'a [u8]>)> {
267        let name = self.read_ident()?;
268        self.skip_whitespaces();
269
270        let has_value = self.stream.expect_and_skip_cond(b'=');
271        if !has_value {
272            return Some((name, None));
273        }
274
275        self.skip_whitespaces();
276
277        let value = if let Some(quote) = self.stream.expect_oneof_and_skip(b"\"'") {
278            self.read_to(quote)
279        } else {
280            self.read_to3([b' ', b'\n', b'>'])
281        };
282
283        Some((name, Some(value)))
284    }
285
286    fn parse_attributes(&mut self) -> Result<Option<Attributes<'a>>, ParseError> {
287        let mut attributes = Attributes::new();
288
289        loop {
290            self.skip_whitespaces();
291
292            let cur = match self.stream.current_cpy() {
293                Some(cur) => cur,
294                None => return Ok(None),
295            };
296
297            if simd::is_closing(cur) {
298                break;
299            }
300
301            if let Some((key, value)) = self.parse_attribute() {
302                let has_value = value.is_some();
303                let value: Option<Bytes<'a>> = value.map(Into::into);
304
305                match key {
306                    b"id" => attributes.id = value,
307                    b"class" => attributes.class = value,
308                    _ => attributes
309                        .raw
310                        .insert(key.into(), value)
311                        .map_err(|_| ParseError::AttributeCapacityExceeded)?,
312                };
313
314                // Only advance past the delimiter if we read a value.
315                let Some(cur) = self.stream.current_cpy() else {
316                    return Ok(None);
317                };
318                if has_value && !simd::is_closing(cur) {
319                    self.stream.advance();
320                }
321            } else {
322                // No valid attribute found; skip this character.
323                self.stream.advance();
324            }
325        }
326
327        Ok(Some(attributes))
328    }
329
330    #[inline]
331    fn add_to_parent(&mut self, handle: NodeHandle) -> Result<(), ParseError> {
332        if let Some(last) = self.stack.last() {
333            let last = self
334                .tags
335                .get_mut(last.get_inner() as usize)
336                .unwrap()
337                .as_tag_mut()
338                .unwrap();
339
340            last._children
341                .push(handle)
342                .map_err(|_| ParseError::ChildCapacityExceeded)?;
343        } else {
344            push_vec::<NodeHandle, MAX_ROOTS>(
345                &mut self.ast,
346                handle,
347                ParseError::RootCapacityExceeded,
348            )?;
349        }
350        Ok(())
351    }
352
353    fn read_end(&mut self) -> Result<(), ParseError> {
354        self.stream.advance();
355
356        let closing_tag_name = self.read_to(b'>');
357
358        self.stream.expect_and_skip_cond(b'>');
359
360        let closing_tag_matches_parent = self
361            .stack
362            .last()
363            .and_then(|last_handle| last_handle.get(self))
364            .and_then(|last_item| last_item.as_tag())
365            .is_some_and(|last_tag| last_tag.name() == closing_tag_name);
366
367        if !closing_tag_matches_parent {
368            return Ok(());
369        }
370
371        if let Some(handle) = self.stack.pop() {
372            let tag = self
373                .tags
374                .get_mut(handle.get_inner() as usize)
375                .unwrap()
376                .as_tag_mut()
377                .unwrap();
378
379            let ptr = self.stream.data().as_ptr() as usize;
380            let offset = tag._raw.as_ptr() as usize;
381            let offset = offset - ptr;
382
383            tag._raw = self.stream.slice(offset, self.stream.idx).into();
384
385            let (track_classes, track_ids) = (
386                self.options.is_tracking_classes(),
387                self.options.is_tracking_ids(),
388            );
389
390            if let (true, Some(bytes)) = (track_classes, &tag._attributes.class) {
391                let s = bytes
392                    .as_bytes_borrowed()
393                    .and_then(|x| core::str::from_utf8(x).ok())
394                    .map(|x| x.split_ascii_whitespace());
395
396                if let Some(s) = s {
397                    for class in s {
398                        let key = Bytes::from(class);
399                        if let Some(handles) = self.classes.get_mut(&key) {
400                            handles
401                                .push(handle)
402                                .map_err(|_| ParseError::ClassCapacityExceeded)?;
403                        } else {
404                            let mut handles = ClassVec::<MAX_NODES>::new();
405                            handles
406                                .push(handle)
407                                .map_err(|_| ParseError::ClassCapacityExceeded)?;
408                            insert_map::<Bytes<'a>, ClassVec<MAX_NODES>, MAX_CLASSES>(
409                                &mut self.classes,
410                                key,
411                                handles,
412                                ParseError::ClassCapacityExceeded,
413                            )?;
414                        }
415                    }
416                }
417            }
418
419            if let (true, Some(bytes)) = (track_ids, &tag._attributes.id) {
420                insert_map::<Bytes<'a>, NodeHandle, MAX_IDS>(
421                    &mut self.ids,
422                    bytes.clone(),
423                    handle,
424                    ParseError::IdCapacityExceeded,
425                )?;
426            }
427        }
428        Ok(())
429    }
430
431    #[cold]
432    #[inline(never)]
433    fn read_markdown(&mut self) -> Result<Option<()>, ParseError> {
434        let start = self.stream.idx - 1; // position of the < which is needed when registering the comment
435
436        self.stream.advance(); // skip !
437
438        let is_comment = self
439            .stream
440            .slice_len(self.stream.idx, 2)
441            .eq(constants::COMMENT);
442
443        if is_comment {
444            let comment = self.skip_comment_with_start(start);
445            let comment = self.register_tag(Node::Comment(comment.into()))?;
446            self.add_to_parent(comment)?;
447        } else {
448            let Some(tag) = self.read_ident() else {
449                return Ok(None);
450            };
451
452            self.skip_whitespaces();
453
454            if simd::matches_case_insensitive(tag, *b"doctype") {
455                let Some(doctype) = self.read_ident() else {
456                    return Ok(None);
457                };
458
459                let html5 = simd::matches_case_insensitive(doctype, *b"html");
460
461                if html5 {
462                    self.version = Some(HTMLVersion::HTML5);
463                }
464
465                self.skip_whitespaces();
466                self.stream.advance(); // skip >
467            }
468        }
469
470        Ok(Some(()))
471    }
472
473    fn parse_tag(&mut self) -> Result<Option<()>, ParseError> {
474        let start = self.stream.idx;
475
476        self.stream.advance();
477        self.skip_whitespaces();
478        let Some(cur) = self.stream.current_cpy() else {
479            return Ok(None);
480        };
481
482        match cur {
483            b'/' => self.read_end()?,
484            b'!' => {
485                self.read_markdown()?;
486            }
487            _ => {
488                let Some(name) = self.read_ident() else {
489                    return Ok(None);
490                };
491                self.skip_whitespaces();
492
493                let Some(attr) = self.parse_attributes()? else {
494                    return Ok(None);
495                };
496
497                let is_self_closing = self.stream.expect_and_skip_cond(b'/');
498
499                if self.stream.expect_and_skip(b'>').is_none() {
500                    return Ok(None);
501                }
502
503                let this = self.register_tag(Node::Tag(HTMLTag::new(
504                    name.into(),
505                    attr,
506                    InlineVec::new(),
507                    self.stream.slice(start, self.stream.idx).into(),
508                )))?;
509
510                self.add_to_parent(this)?;
511
512                // some tags are self closing, so even though there might not be a /,
513                // we don't always want to push them to the stack
514                // e.g. <br><p>Hello</p>
515                // <p> should not be a subtag of <br>
516                if !is_self_closing && !constants::VOID_TAGS.contains(&name) {
517                    push_vec::<NodeHandle, MAX_STACK>(
518                        &mut self.stack,
519                        this,
520                        ParseError::StackCapacityExceeded,
521                    )?;
522                }
523            }
524        };
525
526        Ok(Some(()))
527    }
528
529    pub(crate) fn parse_single(&mut self) -> Result<Option<()>, ParseError> {
530        loop {
531            let Some(cur) = self.stream.current() else {
532                return Ok(None);
533            };
534
535            if *cur == b'<' {
536                self.parse_tag()?;
537            } else {
538                let raw = Node::Raw(self.read_to(b'<').into());
539                let handle = self.register_tag(raw)?;
540                self.add_to_parent(handle)?;
541            }
542        }
543    }
544
545    /// Resolves an internal Node ID obtained from a NodeHandle to a Node
546    #[inline]
547    pub fn resolve_node_id(&self, id: InnerNodeHandle) -> Option<&Node<'a>> {
548        self.tags.get(id as usize)
549    }
550
551    /// Resolves an internal Node ID obtained from a NodeHandle to a Node
552    #[inline]
553    pub fn resolve_node_id_mut(&mut self, id: InnerNodeHandle) -> Option<&mut Node<'a>> {
554        self.tags.get_mut(id as usize)
555    }
556
557    pub(crate) fn parse(&mut self) -> Result<(), ParseError> {
558        if self.stream.len() > u32::MAX as usize {
559            return Err(ParseError::InvalidLength);
560        }
561
562        while !self.stream.is_eof() {
563            self.parse_single()?;
564        }
565
566        Ok(())
567    }
568}