1use html5ever::ParseOpts;
4use html5ever::tokenizer::TokenizerOpts;
5use html5ever::tree_builder::TreeBuilderOpts;
6use std::borrow::Cow;
7use std::cell::{Cell, Ref, RefCell, RefMut};
8
9use blitz_dom::node::Attribute;
10use blitz_dom::{DocumentMutator, HtmlParserProvider};
11use html5ever::{
12 QualName,
13 tendril::{StrTendril, TendrilSink},
14 tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink},
15};
16
17fn html5ever_to_blitz_attr(attr: html5ever::Attribute) -> Attribute {
20 Attribute {
21 name: attr.name,
22 value: attr.value.to_string(),
23 }
24}
25
26#[derive(Copy, Clone, Default, Debug)]
27pub struct HtmlProvider;
28
29impl HtmlParserProvider for HtmlProvider {
30 fn parse_inner_html<'m2, 'doc2>(
31 &self,
32 mutr: &'m2 mut DocumentMutator<'doc2>,
33 element_id: usize,
34 html: &str,
35 ) {
36 DocumentHtmlParser::parse_inner_html_into_mutator(mutr, element_id, html);
37 }
38}
39
40pub struct DocumentHtmlParser<'m, 'doc> {
41 document_mutator: RefCell<&'m mut DocumentMutator<'doc>>,
42
43 pub errors: RefCell<Vec<Cow<'static, str>>>,
45
46 pub quirks_mode: Cell<QuirksMode>,
48 pub is_xml: bool,
49}
50
51impl<'m, 'doc> DocumentHtmlParser<'m, 'doc> {
52 #[track_caller]
53 fn mutr(&self) -> RefMut<'_, &'m mut DocumentMutator<'doc>> {
55 self.document_mutator.borrow_mut()
56 }
57}
58
59impl<'m, 'doc> DocumentHtmlParser<'m, 'doc> {
60 pub fn new(mutr: &'m mut DocumentMutator<'doc>) -> DocumentHtmlParser<'m, 'doc> {
61 DocumentHtmlParser {
62 document_mutator: RefCell::new(mutr),
63 errors: RefCell::new(Vec::new()),
64 quirks_mode: Cell::new(QuirksMode::NoQuirks),
65 is_xml: false,
66 }
67 }
68
69 fn root_element_has_xhtml_namespace(html: &str) -> bool {
72 let rest = html.trim_start_matches('\u{feff}').trim_start();
73 let Some(rest) = rest.strip_prefix("<html") else {
74 return false;
75 };
76 let Some(tag_end) = rest.find('>') else {
77 return false;
78 };
79 rest[..tag_end].contains("xmlns=\"http://www.w3.org/1999/xhtml\"")
80 || rest[..tag_end].contains("xmlns='http://www.w3.org/1999/xhtml'")
81 }
82
83 pub fn parse_into_mutator<'a, 'd>(mutr: &'a mut DocumentMutator<'d>, html: &str) {
84 let mut sink = DocumentHtmlParser::new(mutr);
85
86 let is_xhtml_doc = html.starts_with("<?xml")
87 || html.starts_with("<!DOCTYPE") && {
88 let first_line = html.lines().next().unwrap();
89 first_line.contains("XHTML") || first_line.contains("xhtml")
90 }
91 || Self::root_element_has_xhtml_namespace(html);
92
93 if is_xhtml_doc {
94 sink.is_xml = true;
96 xml5ever::driver::parse_document(sink, Default::default())
97 .from_utf8()
98 .read_from(&mut html.as_bytes())
99 .unwrap();
100 } else {
101 sink.is_xml = false;
103 let opts = ParseOpts {
104 tokenizer: TokenizerOpts::default(),
105 tree_builder: TreeBuilderOpts {
106 exact_errors: false,
107 scripting_enabled: false, iframe_srcdoc: false,
109 drop_doctype: true,
110 quirks_mode: QuirksMode::NoQuirks,
111 },
112 };
113 html5ever::parse_document(sink, opts)
114 .from_utf8()
115 .read_from(&mut html.as_bytes())
116 .unwrap();
117 }
118 }
119
120 pub fn parse_inner_html_into_mutator<'a, 'd>(
121 mutr: &'a mut DocumentMutator<'d>,
122 element_id: usize,
123 html: &str,
124 ) {
125 let sink = DocumentHtmlParser::new(mutr);
126
127 let opts = ParseOpts {
128 tokenizer: TokenizerOpts::default(),
129 tree_builder: TreeBuilderOpts {
130 exact_errors: false,
131 scripting_enabled: false, iframe_srcdoc: false,
133 drop_doctype: true,
134 quirks_mode: QuirksMode::NoQuirks,
135 },
136 };
137 html5ever::driver::parse_fragment_for_element(sink, opts, element_id, false, None)
138 .from_utf8()
139 .read_from(&mut html.as_bytes())
140 .unwrap();
141
142 let fragment_root_id = mutr.last_child_id(0).unwrap();
145 let child_ids = mutr.child_ids(fragment_root_id);
146 mutr.append_children(element_id, &child_ids);
147 mutr.remove_node(fragment_root_id);
148 }
149}
150
151impl<'m, 'doc> TreeSink for DocumentHtmlParser<'m, 'doc> {
152 type Output = ();
153
154 type Handle = usize;
156
157 type ElemName<'a>
158 = Ref<'a, QualName>
159 where
160 Self: 'a;
161
162 fn finish(self) -> Self::Output {
163 #[cfg(feature = "tracing")]
164 for error in self.errors.borrow().iter() {
165 tracing::error!("{error}");
166 }
167 }
168
169 fn parse_error(&self, msg: Cow<'static, str>) {
170 self.errors.borrow_mut().push(msg);
171 }
172
173 fn get_document(&self) -> Self::Handle {
174 0
175 }
176
177 fn elem_name<'a>(&'a self, target: &'a Self::Handle) -> Self::ElemName<'a> {
178 Ref::map(self.document_mutator.borrow(), |docm| {
179 docm.element_name(*target)
180 .expect("TreeSink::elem_name called on a node which is not an element!")
181 })
182 }
183
184 fn create_element(
185 &self,
186 name: QualName,
187 attrs: Vec<html5ever::Attribute>,
188 _flags: ElementFlags,
189 ) -> Self::Handle {
190 let attrs = attrs.into_iter().map(html5ever_to_blitz_attr).collect();
191 self.mutr().create_element(name, attrs)
192 }
193
194 fn create_comment(&self, _text: StrTendril) -> Self::Handle {
195 self.mutr().create_comment_node()
196 }
197
198 fn create_pi(&self, _target: StrTendril, _data: StrTendril) -> Self::Handle {
199 self.mutr().create_comment_node()
200 }
201
202 fn append(&self, parent_id: &Self::Handle, child: NodeOrText<Self::Handle>) {
203 match child {
204 NodeOrText::AppendNode(id) => self.mutr().append_children(*parent_id, &[id]),
205 NodeOrText::AppendText(text) => {
208 let last_child_id = self.mutr().last_child_id(*parent_id);
209 let has_appended = if let Some(id) = last_child_id {
210 self.mutr().append_text_to_node(id, &text).is_ok()
211 } else {
212 false
213 };
214 if !has_appended {
215 let new_child_id = self.mutr().create_text_node(&text);
216 self.mutr().append_children(*parent_id, &[new_child_id]);
217 }
218 }
219 }
220 }
221
222 fn append_before_sibling(&self, sibling_id: &Self::Handle, new_node: NodeOrText<Self::Handle>) {
225 match new_node {
226 NodeOrText::AppendNode(id) => self.mutr().insert_nodes_before(*sibling_id, &[id]),
227 NodeOrText::AppendText(text) => {
230 let previous_sibling_id = self.mutr().previous_sibling_id(*sibling_id);
231 let has_appended = if let Some(id) = previous_sibling_id {
232 self.mutr().append_text_to_node(id, &text).is_ok()
233 } else {
234 false
235 };
236 if !has_appended {
237 let new_child_id = self.mutr().create_text_node(&text);
238 self.mutr()
239 .insert_nodes_before(*sibling_id, &[new_child_id]);
240 }
241 }
242 };
243 }
244
245 fn append_based_on_parent_node(
246 &self,
247 element: &Self::Handle,
248 prev_element: &Self::Handle,
249 child: NodeOrText<Self::Handle>,
250 ) {
251 if self.mutr().node_has_parent(*element) {
252 self.append_before_sibling(element, child);
253 } else {
254 self.append(prev_element, child);
255 }
256 }
257
258 fn append_doctype_to_document(
259 &self,
260 _name: StrTendril,
261 _public_id: StrTendril,
262 _system_id: StrTendril,
263 ) {
264 }
266
267 fn get_template_contents(&self, target: &Self::Handle) -> Self::Handle {
268 *target
270 }
271
272 fn same_node(&self, x: &Self::Handle, y: &Self::Handle) -> bool {
273 x == y
274 }
275
276 fn set_quirks_mode(&self, mode: QuirksMode) {
277 self.quirks_mode.set(mode);
278 }
279
280 fn add_attrs_if_missing(&self, target: &Self::Handle, attrs: Vec<html5ever::Attribute>) {
281 let attrs = attrs.into_iter().map(html5ever_to_blitz_attr).collect();
282 self.mutr().add_attrs_if_missing(*target, attrs);
283 }
284
285 fn remove_from_parent(&self, target: &Self::Handle) {
286 self.mutr().remove_node(*target);
287 }
288
289 fn reparent_children(&self, old_parent_id: &Self::Handle, new_parent_id: &Self::Handle) {
290 self.mutr()
291 .reparent_children(*old_parent_id, *new_parent_id);
292 }
293}
294
295#[test]
296fn parses_some_html() {
297 use blitz_dom::{BaseDocument, DocumentConfig};
298
299 let html = "<!DOCTYPE html><html><body><h1>hello world</h1></body></html>";
300 let mut doc = BaseDocument::new(DocumentConfig::default());
301 let mut mutr = doc.mutate();
302 let sink = DocumentHtmlParser::new(&mut mutr);
303
304 html5ever::parse_document(sink, Default::default())
305 .from_utf8()
306 .read_from(&mut html.as_bytes())
307 .unwrap();
308
309 drop(mutr);
310 doc.print_tree()
311
312 }