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, NodeId};
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: NodeId,
34 html: &str,
35 ) {
36 DocumentHtmlParser::parse_inner_html_into_mutator(mutr, element_id, html);
37 }
38
39 fn parse_document(
40 &self,
41 html: &str,
42 config: blitz_dom::DocumentConfig,
43 ) -> Box<dyn blitz_dom::Document> {
44 Box::new(crate::HtmlDocument::from_html(html, config))
45 }
46}
47
48pub struct DocumentHtmlParser<'m, 'doc> {
49 document_mutator: RefCell<&'m mut DocumentMutator<'doc>>,
50
51 pub errors: RefCell<Vec<Cow<'static, str>>>,
53
54 pub quirks_mode: Cell<QuirksMode>,
56 pub is_xml: bool,
57}
58
59impl<'m, 'doc> DocumentHtmlParser<'m, 'doc> {
60 #[track_caller]
61 fn mutr(&self) -> RefMut<'_, &'m mut DocumentMutator<'doc>> {
63 self.document_mutator.borrow_mut()
64 }
65}
66
67impl<'m, 'doc> DocumentHtmlParser<'m, 'doc> {
68 pub fn new(mutr: &'m mut DocumentMutator<'doc>) -> DocumentHtmlParser<'m, 'doc> {
69 DocumentHtmlParser {
70 document_mutator: RefCell::new(mutr),
71 errors: RefCell::new(Vec::new()),
72 quirks_mode: Cell::new(QuirksMode::NoQuirks),
73 is_xml: false,
74 }
75 }
76
77 fn root_element_has_xhtml_namespace(html: &str) -> bool {
80 let rest = html.trim_start_matches('\u{feff}').trim_start();
81 let Some(rest) = rest.strip_prefix("<html") else {
82 return false;
83 };
84 let Some(tag_end) = rest.find('>') else {
85 return false;
86 };
87 rest[..tag_end].contains("xmlns=\"http://www.w3.org/1999/xhtml\"")
88 || rest[..tag_end].contains("xmlns='http://www.w3.org/1999/xhtml'")
89 }
90
91 pub fn parse_into_mutator<'a, 'd>(mutr: &'a mut DocumentMutator<'d>, html: &str) {
92 let is_xhtml_doc = html.starts_with("<?xml")
93 || html.starts_with("<!DOCTYPE") && {
94 let first_line = html.lines().next().unwrap();
95 first_line.contains("XHTML") || first_line.contains("xhtml")
96 }
97 || Self::root_element_has_xhtml_namespace(html);
98
99 if is_xhtml_doc {
100 Self::parse_xml_into_mutator(mutr, html);
101 } else {
102 let mut sink = DocumentHtmlParser::new(mutr);
104 sink.is_xml = false;
105 let opts = ParseOpts {
106 tokenizer: TokenizerOpts::default(),
107 tree_builder: TreeBuilderOpts {
108 exact_errors: false,
109 scripting_enabled: false, iframe_srcdoc: false,
111 drop_doctype: true,
112 quirks_mode: QuirksMode::NoQuirks,
113 },
114 };
115 html5ever::parse_document(sink, opts)
116 .from_utf8()
117 .read_from(&mut html.as_bytes())
118 .unwrap();
119 }
120 }
121
122 pub fn parse_xml_into_mutator<'a, 'd>(mutr: &'a mut DocumentMutator<'d>, xml: &str) {
130 let mut sink = DocumentHtmlParser::new(mutr);
131 sink.is_xml = true;
132 xml5ever::driver::parse_document(sink, Default::default())
133 .from_utf8()
134 .read_from(&mut xml.as_bytes())
135 .unwrap();
136 }
137
138 pub fn parse_inner_html_into_mutator<'a, 'd>(
139 mutr: &'a mut DocumentMutator<'d>,
140 element_id: NodeId,
141 html: &str,
142 ) {
143 let sink = DocumentHtmlParser::new(mutr);
144
145 let opts = ParseOpts {
146 tokenizer: TokenizerOpts::default(),
147 tree_builder: TreeBuilderOpts {
148 exact_errors: false,
149 scripting_enabled: false, iframe_srcdoc: false,
151 drop_doctype: true,
152 quirks_mode: QuirksMode::NoQuirks,
153 },
154 };
155 html5ever::driver::parse_fragment_for_element(sink, opts, element_id, false, None)
156 .from_utf8()
157 .read_from(&mut html.as_bytes())
158 .unwrap();
159
160 let document_id = mutr.doc.root_node().id;
163 let fragment_root_id = mutr.last_child_id(document_id).unwrap();
164 let child_ids = mutr.child_ids(fragment_root_id);
165 mutr.append_children(element_id, &child_ids);
166 mutr.remove_and_drop_node(fragment_root_id);
167 }
168}
169
170impl<'m, 'doc> TreeSink for DocumentHtmlParser<'m, 'doc> {
171 type Output = ();
172
173 type Handle = NodeId;
175
176 type ElemName<'a>
177 = Ref<'a, QualName>
178 where
179 Self: 'a;
180
181 fn finish(self) -> Self::Output {
182 #[cfg(feature = "tracing")]
183 for error in self.errors.borrow().iter() {
184 tracing::error!("{error}");
185 }
186 }
187
188 fn parse_error(&self, msg: Cow<'static, str>) {
189 self.errors.borrow_mut().push(msg);
190 }
191
192 fn get_document(&self) -> Self::Handle {
193 self.document_mutator.borrow().doc.root_node().id
194 }
195
196 fn elem_name<'a>(&'a self, target: &'a Self::Handle) -> Self::ElemName<'a> {
197 Ref::map(self.document_mutator.borrow(), |docm| {
198 docm.element_name(*target)
199 .expect("TreeSink::elem_name called on a node which is not an element!")
200 })
201 }
202
203 fn create_element(
204 &self,
205 name: QualName,
206 attrs: Vec<html5ever::Attribute>,
207 _flags: ElementFlags,
208 ) -> Self::Handle {
209 let attrs = attrs.into_iter().map(html5ever_to_blitz_attr).collect();
210 self.mutr().create_element(name, attrs)
211 }
212
213 fn create_comment(&self, text: StrTendril) -> Self::Handle {
214 self.mutr().create_comment_node(&text)
215 }
216
217 fn create_pi(&self, _target: StrTendril, _data: StrTendril) -> Self::Handle {
218 self.mutr().create_comment_node("")
219 }
220
221 fn append(&self, parent_id: &Self::Handle, child: NodeOrText<Self::Handle>) {
222 match child {
223 NodeOrText::AppendNode(id) => self.mutr().append_children(*parent_id, &[id]),
224 NodeOrText::AppendText(text) => {
227 let last_child_id = self.mutr().last_child_id(*parent_id);
228 let has_appended = if let Some(id) = last_child_id {
229 self.mutr().append_text_to_node(id, &text).is_ok()
230 } else {
231 false
232 };
233 if !has_appended {
234 let new_child_id = self.mutr().create_text_node(&text);
235 self.mutr().append_children(*parent_id, &[new_child_id]);
236 }
237 }
238 }
239 }
240
241 fn append_before_sibling(&self, sibling_id: &Self::Handle, new_node: NodeOrText<Self::Handle>) {
244 match new_node {
245 NodeOrText::AppendNode(id) => self.mutr().insert_nodes_before(*sibling_id, &[id]),
246 NodeOrText::AppendText(text) => {
249 let previous_sibling_id = self.mutr().previous_sibling_id(*sibling_id);
250 let has_appended = if let Some(id) = previous_sibling_id {
251 self.mutr().append_text_to_node(id, &text).is_ok()
252 } else {
253 false
254 };
255 if !has_appended {
256 let new_child_id = self.mutr().create_text_node(&text);
257 self.mutr()
258 .insert_nodes_before(*sibling_id, &[new_child_id]);
259 }
260 }
261 };
262 }
263
264 fn append_based_on_parent_node(
265 &self,
266 element: &Self::Handle,
267 prev_element: &Self::Handle,
268 child: NodeOrText<Self::Handle>,
269 ) {
270 if self.mutr().node_has_parent(*element) {
271 self.append_before_sibling(element, child);
272 } else {
273 self.append(prev_element, child);
274 }
275 }
276
277 fn append_doctype_to_document(
278 &self,
279 _name: StrTendril,
280 _public_id: StrTendril,
281 _system_id: StrTendril,
282 ) {
283 }
285
286 fn get_template_contents(&self, target: &Self::Handle) -> Self::Handle {
287 *target
289 }
290
291 fn same_node(&self, x: &Self::Handle, y: &Self::Handle) -> bool {
292 x == y
293 }
294
295 fn set_quirks_mode(&self, mode: QuirksMode) {
296 self.quirks_mode.set(mode);
297 }
298
299 fn add_attrs_if_missing(&self, target: &Self::Handle, attrs: Vec<html5ever::Attribute>) {
300 let attrs = attrs.into_iter().map(html5ever_to_blitz_attr).collect();
301 self.mutr().add_attrs_if_missing(*target, attrs);
302 }
303
304 fn remove_from_parent(&self, target: &Self::Handle) {
305 self.mutr().remove_node(*target);
306 }
307
308 fn reparent_children(&self, old_parent_id: &Self::Handle, new_parent_id: &Self::Handle) {
309 self.mutr()
310 .reparent_children(*old_parent_id, *new_parent_id);
311 }
312}
313
314#[test]
315fn parses_some_html() {
316 use blitz_dom::{BaseDocument, DocumentConfig};
317
318 let html = "<!DOCTYPE html><html><body><h1>hello world</h1></body></html>";
319 let mut doc = BaseDocument::new(DocumentConfig::default());
320 let mut mutr = doc.mutate();
321 let sink = DocumentHtmlParser::new(&mut mutr);
322
323 html5ever::parse_document(sink, Default::default())
324 .from_utf8()
325 .read_from(&mut html.as_bytes())
326 .unwrap();
327
328 drop(mutr);
329 doc.print_tree()
330
331 }