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}