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]
796 fn p_end_tag_without_p_in_button_scope() {
797 assert_raises(
798 "<!doctype html><body></p>",
799 ParseErrorKind::EndTagPWithoutPInButtonScope,
800 );
801 assert_does_not_raise(
802 "<!doctype html><p>text</p>",
803 ParseErrorKind::EndTagPWithoutPInButtonScope,
804 );
805 }
806
807 #[test]
810 fn stray_end_tag_with_no_matching_open_element() {
811 assert_raises("<!doctype html><body></span>", ParseErrorKind::StrayEndTag);
812 assert_does_not_raise("<!doctype html><span>x</span>", ParseErrorKind::StrayEndTag);
813 }
814
815 #[test]
816 fn end_tag_br() {
817 assert_raises("<!doctype html></br>", ParseErrorKind::EndTagBr);
818 }
819
820 #[test]
823 fn self_closing_syntax_on_a_non_void_element() {
824 assert_raises(
825 "<!doctype html><div/></div>",
826 ParseErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
827 );
828 assert_does_not_raise(
829 "<!doctype html><br/>",
830 ParseErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
831 );
832 assert_does_not_raise(
834 "<!doctype html><svg><rect/></svg>",
835 ParseErrorKind::NonVoidHtmlElementStartTagWithTrailingSolidus,
836 );
837 }
838
839 #[test]
842 fn eof_with_unclosed_elements() {
843 assert_raises(
844 "<!doctype html><div>",
845 ParseErrorKind::EofWithUnclosedElements,
846 );
847 assert_does_not_raise(
848 "<!doctype html><p>text",
849 ParseErrorKind::EofWithUnclosedElements,
850 );
851 }
852
853 #[test]
856 fn eof_in_text_mode() {
857 assert_raises(
858 "<!doctype html><script>var x = 1;",
859 ParseErrorKind::EofInTextMode,
860 );
861 assert_does_not_raise(
862 "<!doctype html><script>var x = 1;</script>",
863 ParseErrorKind::EofInTextMode,
864 );
865 }
866
867 #[test]
868 fn start_tag_image() {
869 assert_raises(
870 "<!doctype html><image src=x>",
871 ParseErrorKind::StartTagImage,
872 );
873 assert_does_not_raise("<!doctype html><img src=x>", ParseErrorKind::StartTagImage);
874 }
875
876 #[test]
877 fn nested_form() {
878 assert_raises("<!doctype html><form><form>", ParseErrorKind::NestedForm);
879 assert_does_not_raise(
880 "<!doctype html><form></form><form>",
881 ParseErrorKind::NestedForm,
882 );
883 }
884
885 #[test]
886 fn start_tag_table_while_a_table_is_open() {
887 assert_raises(
888 "<!doctype html><table><table></table></table>",
889 ParseErrorKind::StartTagTableInTable,
890 );
891 assert_does_not_raise(
892 "<!doctype html><table></table><table></table>",
893 ParseErrorKind::StartTagTableInTable,
894 );
895 }
896
897 #[test]
899 fn misplaced_token_in_table() {
900 assert_raises(
901 "<!doctype html><table><select></select></table>",
902 ParseErrorKind::MisplacedTokenInTable,
903 );
904 assert_raises(
905 "<!doctype html><table><input></table>",
906 ParseErrorKind::MisplacedTokenInTable,
907 );
908 assert_does_not_raise(
909 "<!doctype html><table><tr><td>x</td></tr></table>",
910 ParseErrorKind::MisplacedTokenInTable,
911 );
912 }
913
914 #[test]
917 fn non_space_characters_in_table() {
918 let raised = kinds("<!doctype html><table>text</table>");
919 assert_eq!(
920 raised
921 .iter()
922 .filter(|kind| **kind == ParseErrorKind::NonSpaceCharactersInTable)
923 .count(),
924 1,
925 "got {raised:?}"
926 );
927 assert_does_not_raise(
928 "<!doctype html><table> </table>",
929 ParseErrorKind::NonSpaceCharactersInTable,
930 );
931 }
932
933 #[test]
934 fn stray_end_tag_in_table() {
935 assert_raises(
936 "<!doctype html><table></tr></table>",
937 ParseErrorKind::StrayEndTagInTable,
938 );
939 }
940
941 #[test]
944 fn token_after_body() {
945 assert_raises(
946 "<!doctype html><body></body>text",
947 ParseErrorKind::TokenAfterBody,
948 );
949 assert_raises(
950 "<!doctype html><body></body><p>x</p>",
951 ParseErrorKind::TokenAfterBody,
952 );
953 assert_does_not_raise(
954 "<!doctype html><body></body>\n",
955 ParseErrorKind::TokenAfterBody,
956 );
957 }
958
959 #[test]
962 fn stray_doctype() {
963 assert_raises(
964 "<!doctype html><title>t</title><!doctype html>",
965 ParseErrorKind::StrayDoctype,
966 );
967 assert_raises(
968 "<!doctype html><body>x<!doctype html>",
969 ParseErrorKind::StrayDoctype,
970 );
971 assert_does_not_raise(
972 "<!doctype html><title>t</title>",
973 ParseErrorKind::StrayDoctype,
974 );
975 }
976
977 #[test]
980 fn errors_from_both_stages_are_merged_in_document_order() {
981 let errors = parse("<!doctype html><p>¬AnEntity;<span><div></p>").errors;
982 assert!(
983 errors.len() >= 2,
984 "expected both a tokenizer and a tree-construction error, got {errors:?}"
985 );
986 assert!(
987 errors
988 .windows(2)
989 .all(|pair| pair[0].position.byte_offset <= pair[1].position.byte_offset),
990 "not in document order: {errors:?}"
991 );
992 }
993}