Skip to main content

tl/
vdom.rs

1use crate::errors::ParseError;
2use crate::parser::HTMLVersion;
3use crate::parser::NodeHandle;
4use crate::queryselector;
5use crate::queryselector::QuerySelectorIterator;
6use crate::Bytes;
7use crate::InnerNodeHandle;
8use crate::ParserOptions;
9use crate::{Node, Parser};
10use std::marker::PhantomData;
11
12/// VDom represents a [Document Object Model](https://developer.mozilla.org/en/docs/Web/API/Document_Object_Model)
13///
14/// It is the result of parsing an HTML document.
15/// Internally it is only a wrapper around the [`Parser`] struct, in which all of the HTML tags are stored.
16/// Many functions of the public API take a reference to a [`Parser`] as a parameter to resolve [`NodeHandle`]s to [`Node`]s.
17#[derive(Debug)]
18pub struct VDom<'a> {
19    /// Internal parser
20    parser: Parser<'a>,
21}
22
23impl<'a> From<Parser<'a>> for VDom<'a> {
24    fn from(parser: Parser<'a>) -> Self {
25        Self { parser }
26    }
27}
28
29impl<'a> VDom<'a> {
30    /// Returns a reference to the underlying parser
31    #[inline]
32    pub fn parser(&self) -> &Parser<'a> {
33        &self.parser
34    }
35
36    /// Returns a mutable reference to the underlying parser
37    #[inline]
38    pub fn parser_mut(&mut self) -> &mut Parser<'a> {
39        &mut self.parser
40    }
41
42    /// Finds an element by its `id` attribute.
43    pub fn get_element_by_id<'b, S>(&'b self, id: S) -> Option<NodeHandle>
44    where
45        S: Into<Bytes<'a>>,
46    {
47        let bytes: Bytes = id.into();
48        let parser = self.parser();
49
50        if parser.options.is_tracking_ids() {
51            parser.ids.get(&bytes).copied()
52        } else {
53            self.nodes()
54                .iter()
55                .enumerate()
56                .find(|(_, node)| {
57                    node.as_tag()
58                        .is_some_and(|tag| tag._attributes.id().is_some_and(|x| x.eq(&bytes)))
59                })
60                .map(|(id, _)| NodeHandle::new(id as InnerNodeHandle))
61        }
62    }
63
64    /// Returns a list of elements that match a given class name.
65    pub fn get_elements_by_class_name<'b>(
66        &'b self,
67        id: &'b str,
68    ) -> Box<dyn Iterator<Item = NodeHandle> + 'b> {
69        let parser = self.parser();
70
71        if parser.options.is_tracking_classes() {
72            parser
73                .classes
74                .get(&Bytes::from(id.as_bytes()))
75                .map(|x| Box::new(x.iter().cloned()) as Box<dyn Iterator<Item = NodeHandle>>)
76                .unwrap_or_else(|| Box::new(std::iter::empty()))
77        } else {
78            let member = id;
79
80            let iter = self
81                .nodes()
82                .iter()
83                .enumerate()
84                .filter_map(move |(id, node)| {
85                    node.as_tag().and_then(|tag| {
86                        tag._attributes
87                            .is_class_member(member)
88                            .then(|| NodeHandle::new(id as InnerNodeHandle))
89                    })
90                });
91
92            Box::new(iter)
93        }
94    }
95
96    /// Returns a slice of *all* the elements in the HTML document
97    ///
98    /// The difference between `children()` and `nodes()` is that children only returns the immediate children of the root node,
99    /// while `nodes()` returns all nodes, including nested tags.
100    ///
101    /// # Order
102    /// The order of the returned nodes is the same as the order of the nodes in the HTML document.
103    pub fn nodes(&self) -> &[Node<'a>] {
104        &self.parser.tags
105    }
106
107    /// Returns a mutable slice of *all* the elements in the HTML document
108    ///
109    /// The difference between `children()` and `nodes()` is that children only returns the immediate children of the root node,
110    /// while `nodes()` returns all nodes, including nested tags.
111    pub fn nodes_mut(&mut self) -> &mut [Node<'a>] {
112        &mut self.parser.tags
113    }
114
115    /// Returns the topmost subnodes ("children") of this DOM
116    pub fn children(&self) -> &[NodeHandle] {
117        &self.parser.ast
118    }
119
120    /// Returns a mutable reference to the topmost subnodes ("children") of this DOM
121    pub fn children_mut(&mut self) -> &mut [NodeHandle] {
122        &mut self.parser.ast
123    }
124
125    /// Returns the HTML version.
126    /// This is determined by the `<!DOCTYPE>` tag
127    pub fn version(&self) -> Option<HTMLVersion> {
128        self.parser.version
129    }
130
131    /// Returns the contained markup of all of the elements in this DOM.
132    ///
133    /// Equivalent to [Element#outerHTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML) in browsers)
134    ///
135    /// # Example
136    /// ```
137    /// let html = r#"<div><p href="/about" id="find-me">Hello world</p></div>"#;
138    /// let mut dom = tl::parse(html, Default::default()).unwrap();
139    ///
140    /// let element = dom.get_element_by_id("find-me")
141    ///     .unwrap()
142    ///     .get_mut(dom.parser_mut())
143    ///     .unwrap()
144    ///     .as_tag_mut()
145    ///     .unwrap();
146    ///
147    /// element.attributes_mut().get_mut("href").flatten().unwrap().set("/");
148    ///
149    /// assert_eq!(dom.outer_html(), r#"<div><p href="/" id="find-me">Hello world</p></div>"#);
150    /// ```
151    pub fn outer_html(&self) -> String {
152        let mut inner_html = String::with_capacity(self.parser.stream.len());
153
154        for node in self.children() {
155            let node = node.get(&self.parser).unwrap();
156            inner_html.push_str(&node.outer_html(&self.parser));
157        }
158
159        inner_html
160    }
161
162    /// Tries to parse the query selector and returns an iterator over elements that match the given query selector.
163    ///
164    /// # Example
165    /// ```
166    /// let dom = tl::parse("<div><p class=\"foo\">bar</div>", tl::ParserOptions::default()).unwrap();
167    /// let handle = dom.query_selector("p.foo").and_then(|mut iter| iter.next()).unwrap();
168    /// let node = handle.get(dom.parser()).unwrap();
169    /// assert_eq!(node.inner_text(dom.parser()), "bar");
170    /// ```
171    pub fn query_selector<'b>(
172        &'b self,
173        selector: &'b str,
174    ) -> Option<QuerySelectorIterator<'a, 'b, Self>> {
175        let selector = crate::parse_query_selector(selector)?;
176        let iter = queryselector::QuerySelectorIterator::new(selector, self.parser(), self);
177        Some(iter)
178    }
179}
180
181/// A RAII guarded version of VDom
182///
183/// The input string is freed once this struct goes out of scope.
184/// The only way to construct this is by calling `parse_owned()`.
185#[derive(Debug)]
186pub struct VDomGuard {
187    /// Wrapped VDom instance
188    dom: VDom<'static>,
189    /// The leaked input string that is referenced by self.dom
190    _s: RawString,
191    /// PhantomData for self.dom
192    _phantom: PhantomData<&'static str>,
193}
194
195unsafe impl Send for VDomGuard {}
196unsafe impl Sync for VDomGuard {}
197
198impl VDomGuard {
199    /// Parses the input string
200    pub(crate) fn parse(input: String, options: ParserOptions) -> Result<VDomGuard, ParseError> {
201        let input = RawString::new(input);
202
203        let ptr = input.as_ptr();
204
205        let input_ref: &'static str = unsafe { &*ptr };
206
207        // Parsing will either:
208        // a) succeed, and we return a VDom instance
209        //    that, when dropped, will free the input string
210        // b) fail, and we return a ParseError
211        //    and `RawString`s destructor will run and deallocate the string properly
212        let mut parser = Parser::new(input_ref, options);
213        parser.parse()?;
214
215        Ok(Self {
216            _s: input,
217            dom: VDom::from(parser),
218            _phantom: PhantomData,
219        })
220    }
221}
222
223impl VDomGuard {
224    /// Returns a reference to the inner DOM.
225    ///
226    /// The lifetime of the returned `VDom` is bound to self so that elements cannot outlive this `VDomGuard` struct.
227    pub fn get_ref<'a>(&'a self) -> &'a VDom<'a> {
228        &self.dom
229    }
230
231    /// Returns a mutable reference to the inner DOM.
232    ///
233    /// The lifetime of the returned `VDom` is bound to self so that elements cannot outlive this `VDomGuard` struct.
234    pub fn get_mut_ref<'a, 'b: 'a>(&'b mut self) -> &'b VDom<'a> {
235        &mut self.dom
236    }
237}
238
239#[derive(Debug)]
240struct RawString(*mut str);
241
242impl RawString {
243    pub fn new(s: String) -> Self {
244        Self(Box::into_raw(s.into_boxed_str()))
245    }
246
247    pub fn as_ptr(&self) -> *mut str {
248        self.0
249    }
250}
251
252impl Drop for RawString {
253    fn drop(&mut self) {
254        // SAFETY: the pointer is always valid because `RawString` can only be constructed through `RawString::new()`
255        unsafe {
256            drop(Box::from_raw(self.0));
257        };
258    }
259}