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 {
57 let mut tokenizer = Tokenizer::new(input);
58 let mut tree_builder = TreeBuilder::new();
59 while let Some(token) = tokenizer.next() {
60 if let Some(state) = tree_builder.process_token(&token.kind, token.position) {
61 tokenizer.switch_to(state);
62 }
63 tokenizer.set_in_foreign_content(tree_builder.is_in_foreign_content());
64 }
65 tree_builder.stop_parsing();
66 let mut errors = tokenizer.take_errors();
67 errors.append(&mut tree_builder.take_errors());
68 errors.sort_by_key(|error| error.position.byte_offset);
72 ParseResult {
73 document: tree_builder.into_document(),
74 errors,
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::parse;
81 use crate::document::{Document, NodeId, NodeKind};
82 use crate::tree_builder::{HTML_NAMESPACE, MATHML_NAMESPACE, SVG_NAMESPACE};
83
84 fn body_of(document: &Document) -> NodeId {
87 let root = document.root();
88 let html = document
92 .children(root)
93 .find(|&node| matches!(document.node(node).kind, NodeKind::Element { .. }))
94 .unwrap();
95 document.children(html).nth(1).unwrap()
96 }
97
98 #[test]
99 fn parses_a_minimal_document_into_the_expected_tree_shape() {
100 let document = parse(
101 "<!DOCTYPE html><html><head><title>Hi</title></head><body><p>Hello</p></body></html>",
102 )
103 .document;
104
105 let root = document.root();
106 let root_children: Vec<_> = document.children(root).collect();
107 assert_eq!(root_children.len(), 2);
108 assert_eq!(
109 document.node(root_children[0]).kind,
110 NodeKind::Doctype {
111 name: Some("html".to_owned()),
112 public_identifier: Some(String::new()),
113 system_identifier: Some(String::new()),
114 }
115 );
116
117 let html = root_children[1];
118 let html_children: Vec<_> = document.children(html).collect();
119 assert_eq!(html_children.len(), 2);
120 let (head, body) = (html_children[0], html_children[1]);
121
122 let title = document.children(head).next().unwrap();
123 let NodeKind::Element { name, .. } = &document.node(title).kind else {
124 unreachable!()
125 };
126 assert_eq!(name, "title");
127 let title_text = document.children(title).next().unwrap();
128 assert_eq!(
129 document.node(title_text).kind,
130 NodeKind::Text {
131 content: "Hi".to_owned()
132 }
133 );
134
135 let p = document.children(body).next().unwrap();
136 let NodeKind::Element { name, .. } = &document.node(p).kind else {
137 unreachable!()
138 };
139 assert_eq!(name, "p");
140 let p_text = document.children(p).next().unwrap();
141 assert_eq!(
142 document.node(p_text).kind,
143 NodeKind::Text {
144 content: "Hello".to_owned()
145 }
146 );
147 }
148
149 #[test]
150 fn parses_implied_html_head_body_when_missing() {
151 let document = parse("<p>Hello</p>").document;
152
153 let root = document.root();
154 assert_eq!(document.children(root).count(), 1);
155 let html = document.children(root).next().unwrap();
156 let html_children: Vec<_> = document.children(html).collect();
157 assert_eq!(html_children.len(), 2);
158 let body = html_children[1];
159
160 let p = document.children(body).next().unwrap();
161 let NodeKind::Element { name, .. } = &document.node(p).kind else {
162 unreachable!()
163 };
164 assert_eq!(name, "p");
165 }
166
167 #[test]
168 fn rcdata_element_content_is_not_parsed_as_markup() {
169 let document = parse("<title><b>not bold</b></title>").document;
170
171 let root = document.root();
172 let html = document.children(root).next().unwrap();
173 let head = document.children(html).next().unwrap();
174 let title = document.children(head).next().unwrap();
175 let text = document.children(title).next().unwrap();
176 assert_eq!(
177 document.node(text).kind,
178 NodeKind::Text {
179 content: "<b>not bold</b>".to_owned()
180 }
181 );
182 }
183
184 #[test]
185 fn parse_syncs_in_foreign_content_for_cdata_sections() {
186 let document = parse("<svg><![CDATA[hello]]></svg>").document;
191
192 let root = document.root();
193 let html = document.children(root).next().unwrap();
194 let body = document.children(html).nth(1).unwrap();
195 let svg = document.children(body).next().unwrap();
196 let content = document.children(svg).next().unwrap();
197 assert_eq!(
198 document.node(content).kind,
199 NodeKind::Text {
200 content: "hello".to_owned()
201 }
202 );
203 }
204
205 #[test]
206 fn cdata_outside_foreign_content_becomes_a_bogus_comment() {
207 let document = parse("<p><![CDATA[hello]]></p>").document;
208
209 let root = document.root();
210 let html = document.children(root).next().unwrap();
211 let body = document.children(html).nth(1).unwrap();
212 let p = document.children(body).next().unwrap();
213 let content = document.children(p).next().unwrap();
214 assert_eq!(
215 document.node(content).kind,
216 NodeKind::Comment {
217 content: "[CDATA[hello]]".to_owned()
218 }
219 );
220 }
221
222 #[test]
231 fn optional_end_tags_produce_sibling_li_elements() {
232 let document = parse("<ul><li>a<li>b</ul>").document;
234 let body = body_of(&document);
235 let ul = document.children(body).next().unwrap();
236 let items: Vec<_> = document.children(ul).collect();
237 assert_eq!(items.len(), 2);
238 for (&li, expected_text) in items.iter().zip(["a", "b"]) {
239 let NodeKind::Element { name, .. } = &document.node(li).kind else {
240 unreachable!()
241 };
242 assert_eq!(name, "li");
243 let text = document.children(li).next().unwrap();
244 assert_eq!(
245 document.node(text).kind,
246 NodeKind::Text {
247 content: expected_text.to_owned()
248 }
249 );
250 }
251 }
252
253 #[test]
254 fn svg_element_keeps_svg_namespace_end_to_end() {
255 let document = parse("<svg><circle/></svg>").document;
257 let body = body_of(&document);
258 let svg = document.children(body).next().unwrap();
259 assert_eq!(
260 document.node(svg).kind,
261 NodeKind::Element {
262 name: "svg".to_owned(),
263 namespace: Some(SVG_NAMESPACE.to_owned()),
264 attributes: vec![],
265 }
266 );
267 let circle = document.children(svg).next().unwrap();
268 assert_eq!(
269 document.node(circle).kind,
270 NodeKind::Element {
271 name: "circle".to_owned(),
272 namespace: Some(SVG_NAMESPACE.to_owned()),
273 attributes: vec![],
274 }
275 );
276 }
277
278 #[test]
279 fn mathml_element_keeps_mathml_namespace_end_to_end() {
280 let document = parse("<math><mi>x</mi></math>").document;
282 let body = body_of(&document);
283 let math = document.children(body).next().unwrap();
284 assert_eq!(
285 document.node(math).kind,
286 NodeKind::Element {
287 name: "math".to_owned(),
288 namespace: Some(MATHML_NAMESPACE.to_owned()),
289 attributes: vec![],
290 }
291 );
292 let mi = document.children(math).next().unwrap();
293 assert_eq!(
294 document.node(mi).kind,
295 NodeKind::Element {
296 name: "mi".to_owned(),
297 namespace: Some(MATHML_NAMESPACE.to_owned()),
298 attributes: vec![],
299 }
300 );
301 let text = document.children(mi).next().unwrap();
302 assert_eq!(
303 document.node(text).kind,
304 NodeKind::Text {
305 content: "x".to_owned()
306 }
307 );
308 }
309
310 #[test]
311 fn script_content_is_not_tokenized_as_markup() {
312 let document = parse("<script>1 < 2;</script>").document;
314 let root = document.root();
315 let html = document.children(root).next().unwrap();
316 let head = document.children(html).next().unwrap();
317 let script = document.children(head).next().unwrap();
318 let NodeKind::Element { name, .. } = &document.node(script).kind else {
319 unreachable!()
320 };
321 assert_eq!(name, "script");
322 let text = document.children(script).next().unwrap();
323 assert_eq!(
324 document.node(text).kind,
325 NodeKind::Text {
326 content: "1 < 2;".to_owned()
327 }
328 );
329 }
330
331 #[test]
332 fn style_content_is_not_tokenized_as_markup() {
333 let document = parse("<style>a{color:red}</style>").document;
335 let root = document.root();
336 let html = document.children(root).next().unwrap();
337 let head = document.children(html).next().unwrap();
338 let style = document.children(head).next().unwrap();
339 let NodeKind::Element { name, .. } = &document.node(style).kind else {
340 unreachable!()
341 };
342 assert_eq!(name, "style");
343 let text = document.children(style).next().unwrap();
344 assert_eq!(
345 document.node(text).kind,
346 NodeKind::Text {
347 content: "a{color:red}".to_owned()
348 }
349 );
350 }
351
352 #[test]
353 fn named_character_references_resolve_to_decoded_text() {
354 let document = parse("<p>& ©</p>").document;
356 let body = body_of(&document);
357 let p = document.children(body).next().unwrap();
358 let text = document.children(p).next().unwrap();
359 assert_eq!(
360 document.node(text).kind,
361 NodeKind::Text {
362 content: "& \u{a9}".to_owned()
363 }
364 );
365 }
366
367 #[test]
368 fn custom_element_gets_html_namespace_like_any_plain_element() {
369 let document = parse("<my-widget>hi</my-widget>").document;
371 let body = body_of(&document);
372 let widget = document.children(body).next().unwrap();
373 assert_eq!(
374 document.node(widget).kind,
375 NodeKind::Element {
376 name: "my-widget".to_owned(),
377 namespace: Some(HTML_NAMESPACE.to_owned()),
378 attributes: vec![],
379 }
380 );
381 let text = document.children(widget).next().unwrap();
382 assert_eq!(
383 document.node(text).kind,
384 NodeKind::Text {
385 content: "hi".to_owned()
386 }
387 );
388 }
389
390 #[test]
391 fn xml_lang_attribute_stays_a_literal_unnamespaced_attribute_name() {
392 let document = parse(r#"<p xml:lang="de">hi</p>"#).document;
401 let body = body_of(&document);
402 let p = document.children(body).next().unwrap();
403 let NodeKind::Element { attributes, .. } = &document.node(p).kind else {
404 unreachable!()
405 };
406 assert_eq!(attributes.len(), 1);
407 assert_eq!(attributes[0].name, "xml:lang");
408 assert_eq!(attributes[0].value, "de");
409 assert_eq!(attributes[0].namespace, None);
410 }
411
412 #[test]
413 fn table_without_tbody_or_tr_gets_them_synthesized() {
414 let document = parse("<table><td>x</td></table>").document;
417 let body = body_of(&document);
418 let table = document.children(body).next().unwrap();
419 let tbody = document.children(table).next().unwrap();
420 let NodeKind::Element { name, .. } = &document.node(tbody).kind else {
421 unreachable!()
422 };
423 assert_eq!(name, "tbody");
424 let tr = document.children(tbody).next().unwrap();
425 let NodeKind::Element { name, .. } = &document.node(tr).kind else {
426 unreachable!()
427 };
428 assert_eq!(name, "tr");
429 let td = document.children(tr).next().unwrap();
430 let NodeKind::Element { name, .. } = &document.node(td).kind else {
431 unreachable!()
432 };
433 assert_eq!(name, "td");
434 let text = document.children(td).next().unwrap();
435 assert_eq!(
436 document.node(text).kind,
437 NodeKind::Text {
438 content: "x".to_owned()
439 }
440 );
441 }
442
443 #[test]
444 fn adoption_agency_spec_example_misnested_b_i_tags() {
445 let document = parse("<p>1<b>2<i>3</b>4</i>5</p>").document;
451 let body = body_of(&document);
452 let p = document.children(body).next().unwrap();
453 let p_children: Vec<_> = document.children(p).collect();
454 assert_eq!(p_children.len(), 4);
455 assert_eq!(
456 document.node(p_children[0]).kind,
457 NodeKind::Text {
458 content: "1".to_owned()
459 }
460 );
461
462 let b = p_children[1];
463 let NodeKind::Element { name, .. } = &document.node(b).kind else {
464 unreachable!()
465 };
466 assert_eq!(name, "b");
467 let b_children: Vec<_> = document.children(b).collect();
468 assert_eq!(b_children.len(), 2);
469 assert_eq!(
470 document.node(b_children[0]).kind,
471 NodeKind::Text {
472 content: "2".to_owned()
473 }
474 );
475 let inner_i = b_children[1];
476 let NodeKind::Element { name, .. } = &document.node(inner_i).kind else {
477 unreachable!()
478 };
479 assert_eq!(name, "i");
480 let inner_i_text = document.children(inner_i).next().unwrap();
481 assert_eq!(
482 document.node(inner_i_text).kind,
483 NodeKind::Text {
484 content: "3".to_owned()
485 }
486 );
487
488 let outer_i = p_children[2];
489 let NodeKind::Element { name, .. } = &document.node(outer_i).kind else {
490 unreachable!()
491 };
492 assert_eq!(name, "i");
493 let outer_i_text = document.children(outer_i).next().unwrap();
494 assert_eq!(
495 document.node(outer_i_text).kind,
496 NodeKind::Text {
497 content: "4".to_owned()
498 }
499 );
500
501 assert_eq!(
504 document.node(p_children[3]).kind,
505 NodeKind::Text {
506 content: "5".to_owned()
507 }
508 );
509 }
510
511 #[test]
512 fn adoption_agency_spec_example_indirectly_nests_two_a_elements_via_table_misnesting() {
513 let document = parse(r#"<a href="a">a<table><a href="b">b</table>x"#).document;
521 let body = body_of(&document);
522 let body_children: Vec<_> = document.children(body).collect();
523 assert_eq!(body_children.len(), 2);
524
525 let a1 = body_children[0];
526 let a1_children: Vec<_> = document.children(a1).collect();
527 assert_eq!(a1_children.len(), 3);
528 assert_eq!(
529 document.node(a1_children[0]).kind,
530 NodeKind::Text {
531 content: "a".to_owned()
532 }
533 );
534 let a2 = a1_children[1];
535 let NodeKind::Element { name, .. } = &document.node(a2).kind else {
536 unreachable!()
537 };
538 assert_eq!(name, "a");
539 let a2_text = document.children(a2).next().unwrap();
540 assert_eq!(
541 document.node(a2_text).kind,
542 NodeKind::Text {
543 content: "b".to_owned()
544 }
545 );
546 let table = a1_children[2];
547 let NodeKind::Element { name, .. } = &document.node(table).kind else {
548 unreachable!()
549 };
550 assert_eq!(name, "table");
551 assert_eq!(document.children(table).count(), 0);
552
553 let a3 = body_children[1];
557 let NodeKind::Element { name, .. } = &document.node(a3).kind else {
558 unreachable!()
559 };
560 assert_eq!(name, "a");
561 let a3_text = document.children(a3).next().unwrap();
562 assert_eq!(
563 document.node(a3_text).kind,
564 NodeKind::Text {
565 content: "x".to_owned()
566 }
567 );
568 }
569
570 #[test]
571 fn quirks_mode_changes_whether_table_closes_an_open_p_element() {
572 let no_quirks = parse("<!DOCTYPE html><p><table></table>").document;
577 let body = body_of(&no_quirks);
578 let children: Vec<_> = no_quirks.children(body).collect();
579 assert_eq!(children.len(), 2);
580 let NodeKind::Element { name, .. } = &no_quirks.node(children[0]).kind else {
581 unreachable!()
582 };
583 assert_eq!(name, "p");
584 assert_eq!(no_quirks.children(children[0]).count(), 0);
585 let NodeKind::Element { name, .. } = &no_quirks.node(children[1]).kind else {
586 unreachable!()
587 };
588 assert_eq!(name, "table");
589
590 let quirks = parse("<p><table></table>").document; let body = body_of(&quirks);
592 let children: Vec<_> = quirks.children(body).collect();
593 assert_eq!(children.len(), 1);
594 let NodeKind::Element { name, .. } = &quirks.node(children[0]).kind else {
595 unreachable!()
596 };
597 assert_eq!(name, "p");
598 let p_children: Vec<_> = quirks.children(children[0]).collect();
599 assert_eq!(p_children.len(), 1);
600 let NodeKind::Element { name, .. } = &quirks.node(p_children[0]).kind else {
601 unreachable!()
602 };
603 assert_eq!(name, "table");
604 }
605
606 #[test]
614 fn end_tag_that_walks_out_of_foreign_content_does_not_loop_forever() {
615 let document = parse("<a><svg></a>").document;
624 let body = body_of(&document);
625 assert_eq!(document.children(body).count(), 1);
626 }
627
628 #[test]
629 fn template_end_tag_resets_a_stale_insertion_mode() {
630 let document = parse("<table><thead><template><td></template></table>").document;
641 let body = body_of(&document);
642 assert_eq!(document.children(body).count(), 1);
643 }
644
645 #[test]
646 fn frameset_document_replaces_body_and_ignores_stray_text() {
647 let document = parse("<!DOCTYPE html><frameset>test").document;
652
653 let root = document.root();
654 let root_children: Vec<_> = document.children(root).collect();
655 assert_eq!(root_children.len(), 2);
656 assert_eq!(
657 document.node(root_children[0]).kind,
658 NodeKind::Doctype {
659 name: Some("html".to_owned()),
660 public_identifier: Some(String::new()),
661 system_identifier: Some(String::new()),
662 }
663 );
664 let html = root_children[1];
665
666 let html_children: Vec<_> = document.children(html).collect();
667 assert_eq!(html_children.len(), 2);
668 let NodeKind::Element { name, .. } = &document.node(html_children[0]).kind else {
669 unreachable!()
670 };
671 assert_eq!(name, "head");
672 let frameset = html_children[1];
673 let NodeKind::Element { name, .. } = &document.node(frameset).kind else {
674 unreachable!()
675 };
676 assert_eq!(name, "frameset");
677 assert_eq!(document.children(frameset).count(), 0);
678 }
679
680 #[test]
681 fn template_content_is_a_separate_fragment_from_the_template_element() {
682 let document = parse("<body><template>Hello</template>").document;
686 let body = body_of(&document);
687
688 let template = document.children(body).next().unwrap();
689 let NodeKind::Element { name, .. } = &document.node(template).kind else {
690 unreachable!()
691 };
692 assert_eq!(name, "template");
693
694 let template_children: Vec<_> = document.children(template).collect();
695 assert_eq!(template_children.len(), 1);
696 let content = template_children[0];
697 assert_eq!(document.node(content).kind, NodeKind::DocumentFragment);
698
699 let content_children: Vec<_> = document.children(content).collect();
700 assert_eq!(content_children.len(), 1);
701 assert_eq!(
702 document.node(content_children[0]).kind,
703 NodeKind::Text {
704 content: "Hello".to_owned()
705 }
706 );
707 }
708
709 #[test]
710 fn selected_option_content_is_mirrored_into_selectedcontent() {
711 let document =
716 parse("<select><button><selectedcontent></button><option>X<option selected>Y").document;
717 let body = body_of(&document);
718
719 let select = document.children(body).next().unwrap();
720 let button = document.children(select).next().unwrap();
721 let selectedcontent = document.children(button).next().unwrap();
722 let selectedcontent_children: Vec<_> = document.children(selectedcontent).collect();
723 assert_eq!(selectedcontent_children.len(), 1);
724 assert_eq!(
725 document.node(selectedcontent_children[0]).kind,
726 NodeKind::Text {
727 content: "Y".to_owned()
728 }
729 );
730 }
731}
732
733#[cfg(test)]
744mod tree_construction_error_tests {
745 use super::parse;
746 use crate::tokenizer::ParseErrorKind;
747
748 fn kinds(input: &str) -> Vec<ParseErrorKind> {
749 parse(input)
750 .errors
751 .into_iter()
752 .map(|error| error.kind)
753 .collect()
754 }
755
756 #[track_caller]
757 fn assert_raises(input: &str, expected: ParseErrorKind) {
758 let raised = kinds(input);
759 assert!(
760 raised.contains(&expected),
761 "expected {expected:?} for {input:?}, got {raised:?}"
762 );
763 }
764
765 #[track_caller]
766 fn assert_does_not_raise(input: &str, unexpected: ParseErrorKind) {
767 let raised = kinds(input);
768 assert!(
769 !raised.contains(&unexpected),
770 "expected no {unexpected:?} for {input:?}, got {raised:?}"
771 );
772 }
773
774 #[test]
778 fn implied_p_end_tag_with_unclosed_elements() {
779 assert_raises(
780 "<!doctype html><p><span><div>",
781 ParseErrorKind::ImpliedEndTagWithUnclosedElements,
782 );
783 assert_does_not_raise(
785 "<!doctype html><p>text<div>",
786 ParseErrorKind::ImpliedEndTagWithUnclosedElements,
787 );
788 }
789
790 #[test]
795 fn p_end_tag_without_p_in_button_scope() {
796 assert_raises(
797 "<!doctype html><body></p>",
798 ParseErrorKind::EndTagPWithoutPInButtonScope,
799 );
800 assert_does_not_raise(
801 "<!doctype html><p>text</p>",
802 ParseErrorKind::EndTagPWithoutPInButtonScope,
803 );
804 }
805
806 #[test]
809 fn stray_end_tag_with_no_matching_open_element() {
810 assert_raises("<!doctype html><body></span>", ParseErrorKind::StrayEndTag);
811 assert_does_not_raise("<!doctype html><span>x</span>", ParseErrorKind::StrayEndTag);
812 }
813
814 #[test]
815 fn end_tag_br() {
816 assert_raises("<!doctype html></br>", ParseErrorKind::EndTagBr);
817 }
818
819 #[test]
822 fn self_closing_syntax_on_a_non_void_element() {
823 assert_raises(
824 "<!doctype html><div/></div>",
825 ParseErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
826 );
827 assert_does_not_raise(
828 "<!doctype html><br/>",
829 ParseErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
830 );
831 assert_does_not_raise(
833 "<!doctype html><svg><rect/></svg>",
834 ParseErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
835 );
836 }
837
838 #[test]
841 fn eof_with_unclosed_elements() {
842 assert_raises(
843 "<!doctype html><div>",
844 ParseErrorKind::EofWithUnclosedElements,
845 );
846 assert_does_not_raise(
847 "<!doctype html><p>text",
848 ParseErrorKind::EofWithUnclosedElements,
849 );
850 }
851
852 #[test]
855 fn eof_in_text_mode() {
856 assert_raises(
857 "<!doctype html><script>var x = 1;",
858 ParseErrorKind::EofInTextMode,
859 );
860 assert_does_not_raise(
861 "<!doctype html><script>var x = 1;</script>",
862 ParseErrorKind::EofInTextMode,
863 );
864 }
865
866 #[test]
867 fn start_tag_image() {
868 assert_raises(
869 "<!doctype html><image src=x>",
870 ParseErrorKind::StartTagImage,
871 );
872 assert_does_not_raise("<!doctype html><img src=x>", ParseErrorKind::StartTagImage);
873 }
874
875 #[test]
876 fn nested_form() {
877 assert_raises("<!doctype html><form><form>", ParseErrorKind::NestedForm);
878 assert_does_not_raise(
879 "<!doctype html><form></form><form>",
880 ParseErrorKind::NestedForm,
881 );
882 }
883
884 #[test]
885 fn start_tag_table_while_a_table_is_open() {
886 assert_raises(
887 "<!doctype html><table><table></table></table>",
888 ParseErrorKind::StartTagTableInTable,
889 );
890 assert_does_not_raise(
891 "<!doctype html><table></table><table></table>",
892 ParseErrorKind::StartTagTableInTable,
893 );
894 }
895
896 #[test]
898 fn misplaced_token_in_table() {
899 assert_raises(
900 "<!doctype html><table><select></select></table>",
901 ParseErrorKind::MisplacedTokenInTable,
902 );
903 assert_raises(
904 "<!doctype html><table><input></table>",
905 ParseErrorKind::MisplacedTokenInTable,
906 );
907 assert_does_not_raise(
908 "<!doctype html><table><tr><td>x</td></tr></table>",
909 ParseErrorKind::MisplacedTokenInTable,
910 );
911 }
912
913 #[test]
916 fn non_space_characters_in_table() {
917 let raised = kinds("<!doctype html><table>text</table>");
918 assert_eq!(
919 raised
920 .iter()
921 .filter(|kind| **kind == ParseErrorKind::NonSpaceCharactersInTable)
922 .count(),
923 1,
924 "got {raised:?}"
925 );
926 assert_does_not_raise(
927 "<!doctype html><table> </table>",
928 ParseErrorKind::NonSpaceCharactersInTable,
929 );
930 }
931
932 #[test]
933 fn stray_end_tag_in_table() {
934 assert_raises(
935 "<!doctype html><table></tr></table>",
936 ParseErrorKind::StrayEndTagInTable,
937 );
938 }
939
940 #[test]
943 fn token_after_body() {
944 assert_raises(
945 "<!doctype html><body></body>text",
946 ParseErrorKind::TokenAfterBody,
947 );
948 assert_raises(
949 "<!doctype html><body></body><p>x</p>",
950 ParseErrorKind::TokenAfterBody,
951 );
952 assert_does_not_raise(
953 "<!doctype html><body></body>\n",
954 ParseErrorKind::TokenAfterBody,
955 );
956 }
957
958 #[test]
961 fn stray_doctype() {
962 assert_raises(
963 "<!doctype html><title>t</title><!doctype html>",
964 ParseErrorKind::StrayDoctype,
965 );
966 assert_raises(
967 "<!doctype html><body>x<!doctype html>",
968 ParseErrorKind::StrayDoctype,
969 );
970 assert_does_not_raise(
971 "<!doctype html><title>t</title>",
972 ParseErrorKind::StrayDoctype,
973 );
974 }
975
976 #[test]
981 fn valid_document_raises_no_errors() {
982 let raised = kinds(
983 "<!doctype html><html lang=en><head><title>t</title></head><body>\
984 <h1>x</h1><ul><li>a</li></ul><dl><dt>t</dt><dd>d</dd></dl>\
985 <form><p><b><i>x</i></b> <a href=/>y</a></p></form>\
986 <table><caption>c</caption><colgroup><col></colgroup>\
987 <thead><tr><th>h</th></tr></thead><tbody><tr><td>x</td></tr></tbody></table>\
988 <template><div></div></template><select><option>o</option></select>\
989 </body></html>\n",
990 );
991 assert!(raised.is_empty(), "got {raised:?}");
992 }
993
994 #[test]
998 fn stray_end_tags_with_no_element_in_scope() {
999 for input in [
1000 "<!doctype html><p>x</p></div>",
1001 "<!doctype html><p>x</p></header>",
1002 "<!doctype html><p>x</p></li>",
1003 "<!doctype html><p>x</p></dd>",
1004 "<!doctype html><p>x</p></h2>",
1005 "<!doctype html><p>x</p></form>",
1006 "<!doctype html><p>x</p></object>",
1007 "<!doctype html><object></body></object>",
1009 "<!doctype html><object></html></object>",
1010 ] {
1011 assert_raises(input, ParseErrorKind::StrayEndTag);
1012 }
1013 assert_does_not_raise("<!doctype html><h1>x</h2>", ParseErrorKind::StrayEndTag);
1015 assert_does_not_raise(
1016 "<!doctype html><ul><li>x</li></ul>",
1017 ParseErrorKind::StrayEndTag,
1018 );
1019 }
1020
1021 #[test]
1026 fn stray_end_tags_before_body() {
1027 for input in [
1028 "<!doctype html></p>",
1029 "<!doctype html><html></div>",
1030 "<!doctype html><head></div></head>",
1031 "<!doctype html><head></template></head>",
1032 "<!doctype html><head></head></div>",
1033 "<!doctype html><template></div></template>",
1034 ] {
1035 assert_raises(input, ParseErrorKind::StrayEndTag);
1036 }
1037 assert_does_not_raise(
1038 "<!doctype html><html><head></head><body></body></html>",
1039 ParseErrorKind::StrayEndTag,
1040 );
1041 }
1042
1043 #[test]
1047 fn stray_start_tag() {
1048 for input in [
1049 "<!doctype html><body><html lang=en>",
1050 "<!doctype html><head><html>",
1051 "<!doctype html><body><body>",
1052 "<!doctype html><body><frameset>",
1053 "<!doctype html><head><head>",
1054 "<!doctype html><head></head><head>",
1055 "<!doctype html><body><td>x",
1056 "<!doctype html><body><tr>",
1057 "<!doctype html><select><select>",
1058 ] {
1059 assert_raises(input, ParseErrorKind::StrayStartTag);
1060 }
1061 assert_does_not_raise(
1062 "<!doctype html><html><head></head><body><table><tr><td>x</td></tr></table>",
1063 ParseErrorKind::StrayStartTag,
1064 );
1065 }
1066
1067 #[test]
1070 fn nested_formatting_element() {
1071 assert_raises(
1072 "<!doctype html><a href=x><a href=y>",
1073 ParseErrorKind::NestedFormattingElement,
1074 );
1075 assert_raises(
1076 "<!doctype html><nobr><nobr>",
1077 ParseErrorKind::NestedFormattingElement,
1078 );
1079 assert_does_not_raise(
1080 "<!doctype html><a href=x>x</a><a href=y>y</a>",
1081 ParseErrorKind::NestedFormattingElement,
1082 );
1083 }
1084
1085 #[test]
1089 fn misnested_formatting_element() {
1090 let input = "<!doctype html><p><b><i>x</b></i></p>";
1091 assert_raises(input, ParseErrorKind::MisnestedFormattingElement);
1092 assert_raises(input, ParseErrorKind::StrayEndTag);
1093 assert_does_not_raise(
1094 "<!doctype html><p><b><i>x</i></b></p>",
1095 ParseErrorKind::MisnestedFormattingElement,
1096 );
1097 }
1098
1099 #[test]
1102 fn formatting_element_not_in_scope() {
1103 assert_raises(
1104 "<!doctype html><b><table></b></table>",
1105 ParseErrorKind::FormattingElementNotInScope,
1106 );
1107 assert_does_not_raise(
1108 "<!doctype html><b>x</b>",
1109 ParseErrorKind::FormattingElementNotInScope,
1110 );
1111 }
1112
1113 #[test]
1116 fn stray_end_tags_in_table_modes() {
1117 for input in [
1118 "<!doctype html><table><caption></td></caption></table>",
1119 "<!doctype html><table><colgroup></col></colgroup></table>",
1120 "<!doctype html><table><tbody></tr></tbody></table>",
1121 "<!doctype html><table><tbody></thead></tbody></table>",
1122 "<!doctype html><table><tr></td></tr></table>",
1123 "<!doctype html><table><tr><td></caption></td></tr></table>",
1124 "<!doctype html><table><tr><td></thead></td></tr></table>",
1125 ] {
1126 assert_raises(input, ParseErrorKind::StrayEndTagInTable);
1127 }
1128 assert_raises(
1130 "<!doctype html><table><form></table>",
1131 ParseErrorKind::MisplacedTokenInTable,
1132 );
1133 }
1134
1135 #[test]
1138 fn stray_doctype_in_foreign_content() {
1139 assert_raises(
1140 "<!doctype html><svg><!doctype html></svg>",
1141 ParseErrorKind::StrayDoctype,
1142 );
1143 }
1144
1145 #[test]
1148 fn errors_from_both_stages_are_merged_in_document_order() {
1149 let errors = parse("<!doctype html><p>¬AnEntity;<span><div></p>").errors;
1150 assert!(
1151 errors.len() >= 2,
1152 "expected both a tokenizer and a tree-construction error, got {errors:?}"
1153 );
1154 assert!(
1155 errors
1156 .windows(2)
1157 .all(|pair| pair[0].position.byte_offset <= pair[1].position.byte_offset),
1158 "not in document order: {errors:?}"
1159 );
1160 }
1161}