1mod document;
10mod entities;
11mod tokenizer;
12mod tree_builder;
13
14pub use document::{Attribute, Children, Document, Node, NodeId, NodeKind};
15pub use tokenizer::{ParseError, ParseErrorKind, Position};
16
17use tokenizer::Tokenizer;
18use tree_builder::TreeBuilder;
19
20#[derive(Debug)]
26pub struct ParseResult {
27 pub document: Document,
28 pub errors: Vec<ParseError>,
29}
30
31pub fn parse(input: &str) -> ParseResult {
53 let mut tokenizer = Tokenizer::new(input);
54 let mut tree_builder = TreeBuilder::new();
55 while let Some(token) = tokenizer.next() {
56 if let Some(state) = tree_builder.process_token(&token.kind, token.position) {
57 tokenizer.switch_to(state);
58 }
59 tokenizer.set_in_foreign_content(tree_builder.is_in_foreign_content());
60 }
61 tree_builder.stop_parsing();
62 ParseResult {
63 document: tree_builder.into_document(),
64 errors: tokenizer.take_errors(),
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::parse;
71 use crate::document::{Document, NodeId, NodeKind};
72 use crate::tree_builder::{HTML_NAMESPACE, MATHML_NAMESPACE, SVG_NAMESPACE};
73
74 fn body_of(document: &Document) -> NodeId {
77 let root = document.root();
78 let html = document
82 .children(root)
83 .find(|&node| matches!(document.node(node).kind, NodeKind::Element { .. }))
84 .unwrap();
85 document.children(html).nth(1).unwrap()
86 }
87
88 #[test]
89 fn parses_a_minimal_document_into_the_expected_tree_shape() {
90 let document = parse(
91 "<!DOCTYPE html><html><head><title>Hi</title></head><body><p>Hello</p></body></html>",
92 )
93 .document;
94
95 let root = document.root();
96 let root_children: Vec<_> = document.children(root).collect();
97 assert_eq!(root_children.len(), 2);
98 assert_eq!(
99 document.node(root_children[0]).kind,
100 NodeKind::Doctype {
101 name: Some("html".to_owned()),
102 public_identifier: Some(String::new()),
103 system_identifier: Some(String::new()),
104 }
105 );
106
107 let html = root_children[1];
108 let html_children: Vec<_> = document.children(html).collect();
109 assert_eq!(html_children.len(), 2);
110 let (head, body) = (html_children[0], html_children[1]);
111
112 let title = document.children(head).next().unwrap();
113 let NodeKind::Element { name, .. } = &document.node(title).kind else {
114 unreachable!()
115 };
116 assert_eq!(name, "title");
117 let title_text = document.children(title).next().unwrap();
118 assert_eq!(
119 document.node(title_text).kind,
120 NodeKind::Text {
121 content: "Hi".to_owned()
122 }
123 );
124
125 let p = document.children(body).next().unwrap();
126 let NodeKind::Element { name, .. } = &document.node(p).kind else {
127 unreachable!()
128 };
129 assert_eq!(name, "p");
130 let p_text = document.children(p).next().unwrap();
131 assert_eq!(
132 document.node(p_text).kind,
133 NodeKind::Text {
134 content: "Hello".to_owned()
135 }
136 );
137 }
138
139 #[test]
140 fn parses_implied_html_head_body_when_missing() {
141 let document = parse("<p>Hello</p>").document;
142
143 let root = document.root();
144 assert_eq!(document.children(root).count(), 1);
145 let html = document.children(root).next().unwrap();
146 let html_children: Vec<_> = document.children(html).collect();
147 assert_eq!(html_children.len(), 2);
148 let body = html_children[1];
149
150 let p = document.children(body).next().unwrap();
151 let NodeKind::Element { name, .. } = &document.node(p).kind else {
152 unreachable!()
153 };
154 assert_eq!(name, "p");
155 }
156
157 #[test]
158 fn rcdata_element_content_is_not_parsed_as_markup() {
159 let document = parse("<title><b>not bold</b></title>").document;
160
161 let root = document.root();
162 let html = document.children(root).next().unwrap();
163 let head = document.children(html).next().unwrap();
164 let title = document.children(head).next().unwrap();
165 let text = document.children(title).next().unwrap();
166 assert_eq!(
167 document.node(text).kind,
168 NodeKind::Text {
169 content: "<b>not bold</b>".to_owned()
170 }
171 );
172 }
173
174 #[test]
175 fn parse_syncs_in_foreign_content_for_cdata_sections() {
176 let document = parse("<svg><![CDATA[hello]]></svg>").document;
181
182 let root = document.root();
183 let html = document.children(root).next().unwrap();
184 let body = document.children(html).nth(1).unwrap();
185 let svg = document.children(body).next().unwrap();
186 let content = document.children(svg).next().unwrap();
187 assert_eq!(
188 document.node(content).kind,
189 NodeKind::Text {
190 content: "hello".to_owned()
191 }
192 );
193 }
194
195 #[test]
196 fn cdata_outside_foreign_content_becomes_a_bogus_comment() {
197 let document = parse("<p><![CDATA[hello]]></p>").document;
198
199 let root = document.root();
200 let html = document.children(root).next().unwrap();
201 let body = document.children(html).nth(1).unwrap();
202 let p = document.children(body).next().unwrap();
203 let content = document.children(p).next().unwrap();
204 assert_eq!(
205 document.node(content).kind,
206 NodeKind::Comment {
207 content: "[CDATA[hello]]".to_owned()
208 }
209 );
210 }
211
212 #[test]
221 fn optional_end_tags_produce_sibling_li_elements() {
222 let document = parse("<ul><li>a<li>b</ul>").document;
224 let body = body_of(&document);
225 let ul = document.children(body).next().unwrap();
226 let items: Vec<_> = document.children(ul).collect();
227 assert_eq!(items.len(), 2);
228 for (&li, expected_text) in items.iter().zip(["a", "b"]) {
229 let NodeKind::Element { name, .. } = &document.node(li).kind else {
230 unreachable!()
231 };
232 assert_eq!(name, "li");
233 let text = document.children(li).next().unwrap();
234 assert_eq!(
235 document.node(text).kind,
236 NodeKind::Text {
237 content: expected_text.to_owned()
238 }
239 );
240 }
241 }
242
243 #[test]
244 fn svg_element_keeps_svg_namespace_end_to_end() {
245 let document = parse("<svg><circle/></svg>").document;
247 let body = body_of(&document);
248 let svg = document.children(body).next().unwrap();
249 assert_eq!(
250 document.node(svg).kind,
251 NodeKind::Element {
252 name: "svg".to_owned(),
253 namespace: Some(SVG_NAMESPACE.to_owned()),
254 attributes: vec![],
255 }
256 );
257 let circle = document.children(svg).next().unwrap();
258 assert_eq!(
259 document.node(circle).kind,
260 NodeKind::Element {
261 name: "circle".to_owned(),
262 namespace: Some(SVG_NAMESPACE.to_owned()),
263 attributes: vec![],
264 }
265 );
266 }
267
268 #[test]
269 fn mathml_element_keeps_mathml_namespace_end_to_end() {
270 let document = parse("<math><mi>x</mi></math>").document;
272 let body = body_of(&document);
273 let math = document.children(body).next().unwrap();
274 assert_eq!(
275 document.node(math).kind,
276 NodeKind::Element {
277 name: "math".to_owned(),
278 namespace: Some(MATHML_NAMESPACE.to_owned()),
279 attributes: vec![],
280 }
281 );
282 let mi = document.children(math).next().unwrap();
283 assert_eq!(
284 document.node(mi).kind,
285 NodeKind::Element {
286 name: "mi".to_owned(),
287 namespace: Some(MATHML_NAMESPACE.to_owned()),
288 attributes: vec![],
289 }
290 );
291 let text = document.children(mi).next().unwrap();
292 assert_eq!(
293 document.node(text).kind,
294 NodeKind::Text {
295 content: "x".to_owned()
296 }
297 );
298 }
299
300 #[test]
301 fn script_content_is_not_tokenized_as_markup() {
302 let document = parse("<script>1 < 2;</script>").document;
304 let root = document.root();
305 let html = document.children(root).next().unwrap();
306 let head = document.children(html).next().unwrap();
307 let script = document.children(head).next().unwrap();
308 let NodeKind::Element { name, .. } = &document.node(script).kind else {
309 unreachable!()
310 };
311 assert_eq!(name, "script");
312 let text = document.children(script).next().unwrap();
313 assert_eq!(
314 document.node(text).kind,
315 NodeKind::Text {
316 content: "1 < 2;".to_owned()
317 }
318 );
319 }
320
321 #[test]
322 fn style_content_is_not_tokenized_as_markup() {
323 let document = parse("<style>a{color:red}</style>").document;
325 let root = document.root();
326 let html = document.children(root).next().unwrap();
327 let head = document.children(html).next().unwrap();
328 let style = document.children(head).next().unwrap();
329 let NodeKind::Element { name, .. } = &document.node(style).kind else {
330 unreachable!()
331 };
332 assert_eq!(name, "style");
333 let text = document.children(style).next().unwrap();
334 assert_eq!(
335 document.node(text).kind,
336 NodeKind::Text {
337 content: "a{color:red}".to_owned()
338 }
339 );
340 }
341
342 #[test]
343 fn named_character_references_resolve_to_decoded_text() {
344 let document = parse("<p>& ©</p>").document;
346 let body = body_of(&document);
347 let p = document.children(body).next().unwrap();
348 let text = document.children(p).next().unwrap();
349 assert_eq!(
350 document.node(text).kind,
351 NodeKind::Text {
352 content: "& \u{a9}".to_owned()
353 }
354 );
355 }
356
357 #[test]
358 fn custom_element_gets_html_namespace_like_any_plain_element() {
359 let document = parse("<my-widget>hi</my-widget>").document;
361 let body = body_of(&document);
362 let widget = document.children(body).next().unwrap();
363 assert_eq!(
364 document.node(widget).kind,
365 NodeKind::Element {
366 name: "my-widget".to_owned(),
367 namespace: Some(HTML_NAMESPACE.to_owned()),
368 attributes: vec![],
369 }
370 );
371 let text = document.children(widget).next().unwrap();
372 assert_eq!(
373 document.node(text).kind,
374 NodeKind::Text {
375 content: "hi".to_owned()
376 }
377 );
378 }
379
380 #[test]
381 fn xml_lang_attribute_stays_a_literal_unnamespaced_attribute_name() {
382 let document = parse(r#"<p xml:lang="de">hi</p>"#).document;
391 let body = body_of(&document);
392 let p = document.children(body).next().unwrap();
393 let NodeKind::Element { attributes, .. } = &document.node(p).kind else {
394 unreachable!()
395 };
396 assert_eq!(attributes.len(), 1);
397 assert_eq!(attributes[0].name, "xml:lang");
398 assert_eq!(attributes[0].value, "de");
399 assert_eq!(attributes[0].namespace, None);
400 }
401
402 #[test]
403 fn table_without_tbody_or_tr_gets_them_synthesized() {
404 let document = parse("<table><td>x</td></table>").document;
407 let body = body_of(&document);
408 let table = document.children(body).next().unwrap();
409 let tbody = document.children(table).next().unwrap();
410 let NodeKind::Element { name, .. } = &document.node(tbody).kind else {
411 unreachable!()
412 };
413 assert_eq!(name, "tbody");
414 let tr = document.children(tbody).next().unwrap();
415 let NodeKind::Element { name, .. } = &document.node(tr).kind else {
416 unreachable!()
417 };
418 assert_eq!(name, "tr");
419 let td = document.children(tr).next().unwrap();
420 let NodeKind::Element { name, .. } = &document.node(td).kind else {
421 unreachable!()
422 };
423 assert_eq!(name, "td");
424 let text = document.children(td).next().unwrap();
425 assert_eq!(
426 document.node(text).kind,
427 NodeKind::Text {
428 content: "x".to_owned()
429 }
430 );
431 }
432
433 #[test]
434 fn adoption_agency_spec_example_misnested_b_i_tags() {
435 let document = parse("<p>1<b>2<i>3</b>4</i>5</p>").document;
441 let body = body_of(&document);
442 let p = document.children(body).next().unwrap();
443 let p_children: Vec<_> = document.children(p).collect();
444 assert_eq!(p_children.len(), 4);
445 assert_eq!(
446 document.node(p_children[0]).kind,
447 NodeKind::Text {
448 content: "1".to_owned()
449 }
450 );
451
452 let b = p_children[1];
453 let NodeKind::Element { name, .. } = &document.node(b).kind else {
454 unreachable!()
455 };
456 assert_eq!(name, "b");
457 let b_children: Vec<_> = document.children(b).collect();
458 assert_eq!(b_children.len(), 2);
459 assert_eq!(
460 document.node(b_children[0]).kind,
461 NodeKind::Text {
462 content: "2".to_owned()
463 }
464 );
465 let inner_i = b_children[1];
466 let NodeKind::Element { name, .. } = &document.node(inner_i).kind else {
467 unreachable!()
468 };
469 assert_eq!(name, "i");
470 let inner_i_text = document.children(inner_i).next().unwrap();
471 assert_eq!(
472 document.node(inner_i_text).kind,
473 NodeKind::Text {
474 content: "3".to_owned()
475 }
476 );
477
478 let outer_i = p_children[2];
479 let NodeKind::Element { name, .. } = &document.node(outer_i).kind else {
480 unreachable!()
481 };
482 assert_eq!(name, "i");
483 let outer_i_text = document.children(outer_i).next().unwrap();
484 assert_eq!(
485 document.node(outer_i_text).kind,
486 NodeKind::Text {
487 content: "4".to_owned()
488 }
489 );
490
491 assert_eq!(
494 document.node(p_children[3]).kind,
495 NodeKind::Text {
496 content: "5".to_owned()
497 }
498 );
499 }
500
501 #[test]
502 fn adoption_agency_spec_example_indirectly_nests_two_a_elements_via_table_misnesting() {
503 let document = parse(r#"<a href="a">a<table><a href="b">b</table>x"#).document;
511 let body = body_of(&document);
512 let body_children: Vec<_> = document.children(body).collect();
513 assert_eq!(body_children.len(), 2);
514
515 let a1 = body_children[0];
516 let a1_children: Vec<_> = document.children(a1).collect();
517 assert_eq!(a1_children.len(), 3);
518 assert_eq!(
519 document.node(a1_children[0]).kind,
520 NodeKind::Text {
521 content: "a".to_owned()
522 }
523 );
524 let a2 = a1_children[1];
525 let NodeKind::Element { name, .. } = &document.node(a2).kind else {
526 unreachable!()
527 };
528 assert_eq!(name, "a");
529 let a2_text = document.children(a2).next().unwrap();
530 assert_eq!(
531 document.node(a2_text).kind,
532 NodeKind::Text {
533 content: "b".to_owned()
534 }
535 );
536 let table = a1_children[2];
537 let NodeKind::Element { name, .. } = &document.node(table).kind else {
538 unreachable!()
539 };
540 assert_eq!(name, "table");
541 assert_eq!(document.children(table).count(), 0);
542
543 let a3 = body_children[1];
547 let NodeKind::Element { name, .. } = &document.node(a3).kind else {
548 unreachable!()
549 };
550 assert_eq!(name, "a");
551 let a3_text = document.children(a3).next().unwrap();
552 assert_eq!(
553 document.node(a3_text).kind,
554 NodeKind::Text {
555 content: "x".to_owned()
556 }
557 );
558 }
559
560 #[test]
561 fn quirks_mode_changes_whether_table_closes_an_open_p_element() {
562 let no_quirks = parse("<!DOCTYPE html><p><table></table>").document;
567 let body = body_of(&no_quirks);
568 let children: Vec<_> = no_quirks.children(body).collect();
569 assert_eq!(children.len(), 2);
570 let NodeKind::Element { name, .. } = &no_quirks.node(children[0]).kind else {
571 unreachable!()
572 };
573 assert_eq!(name, "p");
574 assert_eq!(no_quirks.children(children[0]).count(), 0);
575 let NodeKind::Element { name, .. } = &no_quirks.node(children[1]).kind else {
576 unreachable!()
577 };
578 assert_eq!(name, "table");
579
580 let quirks = parse("<p><table></table>").document; let body = body_of(&quirks);
582 let children: Vec<_> = quirks.children(body).collect();
583 assert_eq!(children.len(), 1);
584 let NodeKind::Element { name, .. } = &quirks.node(children[0]).kind else {
585 unreachable!()
586 };
587 assert_eq!(name, "p");
588 let p_children: Vec<_> = quirks.children(children[0]).collect();
589 assert_eq!(p_children.len(), 1);
590 let NodeKind::Element { name, .. } = &quirks.node(p_children[0]).kind else {
591 unreachable!()
592 };
593 assert_eq!(name, "table");
594 }
595
596 #[test]
604 fn end_tag_that_walks_out_of_foreign_content_does_not_loop_forever() {
605 let document = parse("<a><svg></a>").document;
614 let body = body_of(&document);
615 assert_eq!(document.children(body).count(), 1);
616 }
617
618 #[test]
619 fn template_end_tag_resets_a_stale_insertion_mode() {
620 let document = parse("<table><thead><template><td></template></table>").document;
631 let body = body_of(&document);
632 assert_eq!(document.children(body).count(), 1);
633 }
634
635 #[test]
636 fn frameset_document_replaces_body_and_ignores_stray_text() {
637 let document = parse("<!DOCTYPE html><frameset>test").document;
642
643 let root = document.root();
644 let root_children: Vec<_> = document.children(root).collect();
645 assert_eq!(root_children.len(), 2);
646 assert_eq!(
647 document.node(root_children[0]).kind,
648 NodeKind::Doctype {
649 name: Some("html".to_owned()),
650 public_identifier: Some(String::new()),
651 system_identifier: Some(String::new()),
652 }
653 );
654 let html = root_children[1];
655
656 let html_children: Vec<_> = document.children(html).collect();
657 assert_eq!(html_children.len(), 2);
658 let NodeKind::Element { name, .. } = &document.node(html_children[0]).kind else {
659 unreachable!()
660 };
661 assert_eq!(name, "head");
662 let frameset = html_children[1];
663 let NodeKind::Element { name, .. } = &document.node(frameset).kind else {
664 unreachable!()
665 };
666 assert_eq!(name, "frameset");
667 assert_eq!(document.children(frameset).count(), 0);
668 }
669
670 #[test]
671 fn template_content_is_a_separate_fragment_from_the_template_element() {
672 let document = parse("<body><template>Hello</template>").document;
676 let body = body_of(&document);
677
678 let template = document.children(body).next().unwrap();
679 let NodeKind::Element { name, .. } = &document.node(template).kind else {
680 unreachable!()
681 };
682 assert_eq!(name, "template");
683
684 let template_children: Vec<_> = document.children(template).collect();
685 assert_eq!(template_children.len(), 1);
686 let content = template_children[0];
687 assert_eq!(document.node(content).kind, NodeKind::DocumentFragment);
688
689 let content_children: Vec<_> = document.children(content).collect();
690 assert_eq!(content_children.len(), 1);
691 assert_eq!(
692 document.node(content_children[0]).kind,
693 NodeKind::Text {
694 content: "Hello".to_owned()
695 }
696 );
697 }
698
699 #[test]
700 fn selected_option_content_is_mirrored_into_selectedcontent() {
701 let document =
706 parse("<select><button><selectedcontent></button><option>X<option selected>Y").document;
707 let body = body_of(&document);
708
709 let select = document.children(body).next().unwrap();
710 let button = document.children(select).next().unwrap();
711 let selectedcontent = document.children(button).next().unwrap();
712 let selectedcontent_children: Vec<_> = document.children(selectedcontent).collect();
713 assert_eq!(selectedcontent_children.len(), 1);
714 assert_eq!(
715 document.node(selectedcontent_children[0]).kind,
716 NodeKind::Text {
717 content: "Y".to_owned()
718 }
719 );
720 }
721}