Skip to main content

tl/
vdom.rs

1use crate::Bytes;
2use crate::InnerNodeHandle;
3#[cfg(feature = "std")]
4use crate::ParserOptions;
5use crate::errors::ParseError;
6#[cfg(feature = "std")]
7use crate::inline::vec::InlineVecIter;
8use crate::parser::HTMLVersion;
9use crate::parser::NodeHandle;
10use crate::queryselector;
11use crate::queryselector::QuerySelectorIterator;
12use crate::{Node, Parser};
13use core::fmt;
14#[cfg(feature = "std")]
15use core::marker::PhantomData;
16
17/// VDom represents a [Document Object Model](https://developer.mozilla.org/en/docs/Web/API/Document_Object_Model)
18///
19/// It is the result of parsing an HTML document.
20/// Internally it is only a wrapper around the [`Parser`] struct, in which all of the HTML tags are stored.
21/// Many functions of the public API take a reference to a [`Parser`] as a parameter to resolve [`NodeHandle`]s to [`Node`]s.
22#[derive(Debug)]
23pub struct VDom<
24    'a,
25    const MAX_NODES: usize = 0,
26    const MAX_STACK: usize = 0,
27    const MAX_ROOTS: usize = 0,
28    const MAX_IDS: usize = 0,
29    const MAX_CLASSES: usize = 0,
30    const MAX_SELECTOR_NODES: usize = 0,
31> {
32    /// Internal parser
33    parser: Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>,
34}
35
36impl<
37    'a,
38    const MAX_NODES: usize,
39    const MAX_STACK: usize,
40    const MAX_ROOTS: usize,
41    const MAX_IDS: usize,
42    const MAX_CLASSES: usize,
43    const MAX_SELECTOR_NODES: usize,
44> From<Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>>
45    for VDom<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>
46{
47    fn from(
48        parser: Parser<
49            'a,
50            MAX_NODES,
51            MAX_STACK,
52            MAX_ROOTS,
53            MAX_IDS,
54            MAX_CLASSES,
55            MAX_SELECTOR_NODES,
56        >,
57    ) -> Self {
58        Self { parser }
59    }
60}
61
62impl<
63    'a,
64    const MAX_NODES: usize,
65    const MAX_STACK: usize,
66    const MAX_ROOTS: usize,
67    const MAX_IDS: usize,
68    const MAX_CLASSES: usize,
69    const MAX_SELECTOR_NODES: usize,
70> VDom<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>
71{
72    /// Returns a reference to the underlying parser
73    #[inline]
74    pub fn parser(
75        &self,
76    ) -> &Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>
77    {
78        &self.parser
79    }
80
81    /// Returns a mutable reference to the underlying parser
82    #[inline]
83    pub fn parser_mut(
84        &mut self,
85    ) -> &mut Parser<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, MAX_SELECTOR_NODES>
86    {
87        &mut self.parser
88    }
89
90    /// Finds an element by its `id` attribute.
91    pub fn get_element_by_id<'b, S>(&'b self, id: S) -> Option<NodeHandle>
92    where
93        S: Into<Bytes<'a>>,
94    {
95        let bytes: Bytes = id.into();
96        let parser = self.parser();
97
98        if parser.options.is_tracking_ids() {
99            parser.ids.get(&bytes).copied()
100        } else {
101            self.nodes()
102                .iter()
103                .enumerate()
104                .find(|(_, node)| {
105                    node.as_tag().is_some_and(|tag| {
106                        tag._attributes.id.as_ref().is_some_and(|x| x.eq(&bytes))
107                    })
108                })
109                .map(|(id, _)| NodeHandle::new(id as InnerNodeHandle))
110        }
111    }
112
113    /// Returns a slice of *all* the elements in the HTML document
114    ///
115    /// The difference between `children()` and `nodes()` is that children only returns the immediate children of the root node,
116    /// while `nodes()` returns all nodes, including nested tags.
117    ///
118    /// # Order
119    /// The order of the returned nodes is the same as the order of the nodes in the HTML document.
120    pub fn nodes(&self) -> &[Node<'a>] {
121        self.parser.tags.as_slice()
122    }
123
124    /// Returns a mutable slice of *all* the elements in the HTML document
125    ///
126    /// The difference between `children()` and `nodes()` is that children only returns the immediate children of the root node,
127    /// while `nodes()` returns all nodes, including nested tags.
128    pub fn nodes_mut(&mut self) -> &mut [Node<'a>] {
129        self.parser.tags.as_mut_slice()
130    }
131
132    /// Returns the topmost subnodes ("children") of this DOM
133    pub fn children(&self) -> &[NodeHandle] {
134        self.parser.ast.as_slice()
135    }
136
137    /// Returns a mutable reference to the topmost subnodes ("children") of this DOM
138    pub fn children_mut(&mut self) -> &mut [NodeHandle] {
139        self.parser.ast.as_mut_slice()
140    }
141
142    /// Returns the HTML version.
143    /// This is determined by the `<!DOCTYPE>` tag
144    pub fn version(&self) -> Option<HTMLVersion> {
145        self.parser.version
146    }
147
148    /// Writes the contained markup of all root elements without allocating.
149    pub fn write_outer_html<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
150        for handle in self.children() {
151            if let Some(node) = handle.get(&self.parser) {
152                node.write_outer_html(&self.parser, dest)?;
153            }
154        }
155
156        Ok(())
157    }
158
159    /// Tries to parse the query selector and returns an iterator over matching elements.
160    #[cfg(not(feature = "std"))]
161    pub fn query_selector<'b>(
162        &'b self,
163        selector: &'b str,
164    ) -> Result<
165        QuerySelectorIterator<
166            'a,
167            'b,
168            Self,
169            MAX_NODES,
170            MAX_STACK,
171            MAX_ROOTS,
172            MAX_IDS,
173            MAX_CLASSES,
174            MAX_SELECTOR_NODES,
175        >,
176        ParseError,
177    > {
178        let selector = crate::parse_query_selector::<MAX_SELECTOR_NODES>(selector)?;
179        Ok(queryselector::QuerySelectorIterator::new(
180            selector,
181            self.parser(),
182            self,
183        ))
184    }
185}
186
187#[cfg(feature = "std")]
188impl<
189    'a,
190    const MAX_NODES: usize,
191    const MAX_STACK: usize,
192    const MAX_ROOTS: usize,
193    const MAX_IDS: usize,
194    const MAX_CLASSES: usize,
195> VDom<'a, MAX_NODES, MAX_STACK, MAX_ROOTS, MAX_IDS, MAX_CLASSES, 0>
196{
197    /// Returns a list of elements that match a given class name.
198    pub fn get_elements_by_class_name<'b>(
199        &'b self,
200        id: &'b str,
201    ) -> ClassNameIterator<'a, 'b, MAX_NODES> {
202        let parser = self.parser();
203
204        if parser.options.is_tracking_classes() {
205            parser
206                .classes
207                .get(&Bytes::from(id.as_bytes()))
208                .map(|handles| ClassNameIterator::Tracked(handles.iter()))
209                .unwrap_or_else(|| ClassNameIterator::Empty)
210        } else {
211            ClassNameIterator::Scan {
212                member: id,
213                iter: self.nodes().iter().enumerate(),
214            }
215        }
216    }
217
218    /// Returns the contained markup of all of the elements in this DOM.
219    pub fn outer_html(&self) -> String {
220        let mut inner_html = String::with_capacity(self.parser.stream.len());
221
222        for node in self.children() {
223            let node = node.get(&self.parser).unwrap();
224            let _ = node.write_outer_html(&self.parser, &mut inner_html);
225        }
226
227        inner_html
228    }
229
230    /// Tries to parse the query selector and returns an iterator over elements that match the given query selector.
231    pub fn query_selector<'b>(
232        &'b self,
233        selector: &'b str,
234    ) -> Option<
235        QuerySelectorIterator<
236            'a,
237            'b,
238            Self,
239            MAX_NODES,
240            MAX_STACK,
241            MAX_ROOTS,
242            MAX_IDS,
243            MAX_CLASSES,
244            0,
245        >,
246    > {
247        let selector = crate::parse_query_selector(selector)?;
248        let iter = queryselector::QuerySelectorIterator::new(selector, self.parser(), self);
249        Some(iter)
250    }
251}
252
253/// Iterator returned by [`VDom::get_elements_by_class_name`].
254#[cfg(feature = "std")]
255pub enum ClassNameIterator<'a, 'b, const MAX_NODES: usize = 0> {
256    /// No matching tracked class exists.
257    Empty,
258    /// Iterates over a tracked class lookup table.
259    Tracked(InlineVecIter<'b, NodeHandle, MAX_NODES>),
260    /// Scans every node when class tracking was not enabled.
261    Scan {
262        member: &'b str,
263        iter: core::iter::Enumerate<core::slice::Iter<'b, Node<'a>>>,
264    },
265}
266
267#[cfg(feature = "std")]
268impl<'a, 'b, const MAX_NODES: usize> Iterator for ClassNameIterator<'a, 'b, MAX_NODES> {
269    type Item = NodeHandle;
270
271    fn next(&mut self) -> Option<Self::Item> {
272        match self {
273            Self::Empty => None,
274            Self::Tracked(iter) => iter.next().copied(),
275            Self::Scan { member, iter } => iter.find_map(|(id, node)| {
276                node.as_tag().and_then(|tag| {
277                    tag._attributes
278                        .is_class_member(*member)
279                        .then(|| NodeHandle::new(id as InnerNodeHandle))
280                })
281            }),
282        }
283    }
284}
285
286/// A RAII guarded version of VDom
287///
288/// The input string is freed once this struct goes out of scope.
289/// The only way to construct this is by calling `parse_owned()`.
290#[derive(Debug)]
291#[cfg(feature = "std")]
292pub struct VDomGuard {
293    /// Wrapped VDom instance
294    dom: VDom<
295        'static,
296        { crate::STD_INLINE_CLASS_HANDLES },
297        0,
298        0,
299        { crate::STD_INLINE_IDS },
300        { crate::STD_INLINE_CLASSES },
301        0,
302    >,
303    /// The leaked input string that is referenced by self.dom
304    _s: RawString,
305    /// PhantomData for self.dom
306    _phantom: PhantomData<&'static str>,
307}
308
309#[cfg(feature = "std")]
310unsafe impl Send for VDomGuard {}
311#[cfg(feature = "std")]
312unsafe impl Sync for VDomGuard {}
313
314#[cfg(feature = "std")]
315impl VDomGuard {
316    /// Parses the input string
317    pub(crate) fn parse(input: String, options: ParserOptions) -> Result<VDomGuard, ParseError> {
318        let input = RawString::new(input);
319
320        let ptr = input.as_ptr();
321
322        let input_ref: &'static str = unsafe { &*ptr };
323
324        // Parsing will either:
325        // a) succeed, and we return a VDom instance
326        //    that, when dropped, will free the input string
327        // b) fail, and we return a ParseError
328        //    and `RawString`s destructor will run and deallocate the string properly
329        let mut parser = Parser::<
330            { crate::STD_INLINE_CLASS_HANDLES },
331            0,
332            0,
333            { crate::STD_INLINE_IDS },
334            { crate::STD_INLINE_CLASSES },
335            0,
336        >::new(input_ref, options);
337        parser.parse()?;
338
339        Ok(Self {
340            _s: input,
341            dom: VDom::from(parser),
342            _phantom: PhantomData,
343        })
344    }
345}
346
347#[cfg(feature = "std")]
348impl VDomGuard {
349    /// Returns a reference to the inner DOM.
350    ///
351    /// The lifetime of the returned `VDom` is bound to self so that elements cannot outlive this `VDomGuard` struct.
352    pub fn get_ref<'a>(
353        &'a self,
354    ) -> &'a VDom<
355        'a,
356        { crate::STD_INLINE_CLASS_HANDLES },
357        0,
358        0,
359        { crate::STD_INLINE_IDS },
360        { crate::STD_INLINE_CLASSES },
361        0,
362    > {
363        &self.dom
364    }
365
366    /// Returns a mutable reference to the inner DOM.
367    ///
368    /// The lifetime of the returned `VDom` is bound to self so that elements cannot outlive this `VDomGuard` struct.
369    pub fn get_mut_ref<'a, 'b: 'a>(
370        &'b mut self,
371    ) -> &'b VDom<
372        'a,
373        { crate::STD_INLINE_CLASS_HANDLES },
374        0,
375        0,
376        { crate::STD_INLINE_IDS },
377        { crate::STD_INLINE_CLASSES },
378        0,
379    > {
380        &mut self.dom
381    }
382}
383
384#[derive(Debug)]
385#[cfg(feature = "std")]
386struct RawString(*mut str);
387
388#[cfg(feature = "std")]
389impl RawString {
390    pub fn new(s: String) -> Self {
391        Self(Box::into_raw(s.into_boxed_str()))
392    }
393
394    pub fn as_ptr(&self) -> *mut str {
395        self.0
396    }
397}
398
399#[cfg(feature = "std")]
400impl Drop for RawString {
401    fn drop(&mut self) {
402        // SAFETY: the pointer is always valid because `RawString` can only be constructed through `RawString::new()`
403        unsafe {
404            drop(Box::from_raw(self.0));
405        };
406    }
407}