1use alloc::vec::Vec;
8
9use crate::{Error, Event, EventSink, Result};
10
11macro_rules! block_kinds {
20 ( $( $kind:ident => ( $start:ident, $end:ident ) ),+ $(,)? ) => {
21 #[inline]
23 #[must_use]
24 pub fn block_kind_for_start(event: &Event) -> Option<BlockKind> {
25 match event {
26 $( Event::$start { .. } => Some(BlockKind::$kind), )+
27 _ => None,
28 }
29 }
30
31 #[inline]
33 #[must_use]
34 pub fn block_kind_for_end(event: &Event) -> Option<BlockKind> {
35 match event {
36 $( Event::$end => Some(BlockKind::$kind), )+
37 _ => None,
38 }
39 }
40
41 #[inline]
43 #[must_use]
44 pub fn end_event_for(kind: BlockKind) -> Event {
45 match kind {
46 $( BlockKind::$kind => Event::$end, )+
47 }
48 }
49 };
50}
51
52#[derive(Clone, Copy, PartialEq, Eq, Debug)]
57#[non_exhaustive]
58pub enum BlockKind {
59 Blockquote,
61 Caption,
63 DefinitionDetail,
65 DefinitionList,
67 DefinitionTerm,
69 Document,
71 Footnote,
73 Heading,
75 Link,
77 OrderedListItem,
79 Paragraph,
81 Preformatted,
83 Table,
85 TableCell,
87 TableHeader,
89 TableRow,
91 UnorderedListItem,
93}
94
95pub struct StackTrackingSink<S: EventSink> {
111 document_finished: bool,
112 sink: S,
113 stack: Vec<BlockKind>,
114}
115
116impl<S: EventSink> StackTrackingSink<S> {
117 fn handle_end_document(&mut self) -> Result<()> {
120 if !self.stack.contains(&BlockKind::Document) {
121 return Err(Error::InvalidSequence {
122 expected: "open Document".to_string(),
123 found: "EndDocument".to_string(),
124 message: "EndDocument received without StartDocument".to_string(),
125 });
126 }
127 while let Some(kind) = self.stack.pop() {
128 if kind != BlockKind::Document {
129 self.sink.handle_event(end_event_for(kind))?;
130 }
131 }
132 self.document_finished = true;
133 self.sink.handle_event(Event::EndDocument)
134 }
135
136 fn handle_end_event(&mut self, event: Event) -> Result<()> {
139 let Some(target_kind) = block_kind_for_end(&event) else {
140 return Err(Error::InvalidSequence {
141 expected: "valid End event".to_string(),
142 found: format!("{event:?}"),
143 message: "handle_end_event called with non-End event".to_string(),
144 });
145 };
146 if self.stack.is_empty() {
147 return Err(Error::InvalidSequence {
148 expected: "open block".to_string(),
149 found: format!("{target_kind:?}"),
150 message: "received End event with empty stack".to_string(),
151 });
152 }
153 if self.stack.contains(&target_kind) {
154 while self.stack.last() != Some(&target_kind) {
155 if let Some(popped_kind) = self.stack.pop() {
156 self.sink.handle_event(end_event_for(popped_kind))?;
157 }
158 }
159 self.stack.pop();
160 return self.sink.handle_event(event);
161 }
162 Err(Error::InvalidSequence {
163 expected: self
164 .stack
165 .last()
166 .map_or("empty".to_string(), |k| format!("{k:?}")),
167 found: format!("{target_kind:?}"),
168 message: format!("End event for {target_kind:?} does not match any open block"),
169 })
170 }
171
172 fn handle_other_event(&mut self, event: Event) -> Result<()> {
178 if matches!(event, Event::ThematicBreak { .. })
179 && self.stack.last() == Some(&BlockKind::Paragraph)
180 {
181 self.stack.pop();
182 self.sink.handle_event(Event::EndParagraph)?;
183 }
184
185 if matches!(event, Event::Text { .. }) && !self.has_open_content() {
186 let para = Event::StartParagraph {
187 alignment: None,
188 id: None,
189 };
190 self.stack.push(BlockKind::Paragraph);
191 self.sink.handle_event(para)?;
192 }
193
194 self.sink.handle_event(event)
195 }
196
197 fn handle_start_event(&mut self, kind: BlockKind, event: Event) -> Result<()> {
200 if kind == BlockKind::Document {
201 if self.stack.contains(&BlockKind::Document) {
202 return Err(Error::InvalidSequence {
203 expected: "single Document".to_string(),
204 found: "StartDocument".to_string(),
205 message: "StartDocument received while Document already open".to_string(),
206 });
207 }
208 if self.document_finished {
209 return Err(Error::InvalidSequence {
210 expected: "end of stream".to_string(),
211 found: "StartDocument".to_string(),
212 message: "StartDocument received after document already finished".to_string(),
213 });
214 }
215 }
216 if kind == BlockKind::Link && self.stack.contains(&BlockKind::Link) {
218 return Err(Error::InvalidSequence {
219 expected: "no nested links".to_string(),
220 found: "StartLink".to_string(),
221 message: "StartLink received while another link is already open".to_string(),
222 });
223 }
224 if kind != BlockKind::Link && self.stack.last() == Some(&BlockKind::Paragraph) {
225 self.stack.pop();
226 self.sink.handle_event(Event::EndParagraph)?;
227 }
228 self.stack.push(kind);
229 self.sink.handle_event(event)
230 }
231
232 #[inline]
246 pub fn has_open_content(&self) -> bool {
247 self.stack.iter().any(|kind| {
248 matches!(
249 kind,
250 BlockKind::Heading
251 | BlockKind::Paragraph
252 | BlockKind::Preformatted
253 | BlockKind::Link
254 | BlockKind::DefinitionTerm
255 )
256 })
257 }
258
259 #[inline]
261 pub fn is_inside(&self, kind: BlockKind) -> bool {
262 self.stack.contains(&kind)
263 }
264
265 #[inline]
267 pub fn new(sink: S) -> Self {
268 Self {
269 document_finished: false,
270 sink,
271 stack: Vec::new(),
272 }
273 }
274
275 #[cfg(test)]
277 fn sink(&self) -> &S {
278 &self.sink
279 }
280
281 #[inline]
286 pub fn stack(&self) -> &[BlockKind] {
287 &self.stack
288 }
289
290 #[cfg(test)]
292 fn stack_mut(&mut self) -> &mut Vec<BlockKind> {
293 &mut self.stack
294 }
295}
296
297impl<S: EventSink> EventSink for StackTrackingSink<S> {
298 #[inline]
299 fn finish(self) -> Result<()> {
300 self.sink.finish()
301 }
302
303 #[inline]
304 fn handle_event(&mut self, event: Event) -> Result<()> {
305 if self.document_finished && !matches!(event, Event::StartDocument { .. }) {
308 return Err(Error::InvalidSequence {
309 expected: "end of stream".to_string(),
310 found: format!("{event:?}"),
311 message: "event received after document already finished".to_string(),
312 });
313 }
314
315 if let Some(kind) = block_kind_for_start(&event) {
316 return self.handle_start_event(kind, event);
317 }
318
319 if matches!(event, Event::EndDocument) {
320 return self.handle_end_document();
321 }
322
323 if block_kind_for_end(&event).is_some() {
324 return self.handle_end_event(event);
325 }
326
327 self.handle_other_event(event)
328 }
329}
330
331block_kinds! {
332 Blockquote => (StartBlockQuote, EndBlockQuote),
333 Caption => (StartCaption, EndCaption),
334 DefinitionDetail => (StartDefinitionDetail, EndDefinitionDetail),
335 DefinitionList => (StartDefinitionList, EndDefinitionList),
336 DefinitionTerm => (StartDefinitionTerm, EndDefinitionTerm),
337 Document => (StartDocument, EndDocument),
338 Footnote => (StartFootnote, EndFootnote),
339 Heading => (StartHeading, EndHeading),
340 Link => (StartLink, EndLink),
341 OrderedListItem => (StartOrderedListItem, EndOrderedListItem),
342 Paragraph => (StartParagraph, EndParagraph),
343 Preformatted => (StartPreformatted, EndPreformatted),
344 Table => (StartTable, EndTable),
345 TableCell => (StartTableCell, EndTableCell),
346 TableHeader => (StartTableHeader, EndTableHeader),
347 TableRow => (StartTableRow, EndTableRow),
348 UnorderedListItem => (StartUnorderedListItem, EndUnorderedListItem),
349}
350
351#[cfg(test)]
352mod tests {
353 use alloc::vec::Vec;
354
355 use super::*;
356 use crate::TextStyle;
357
358 struct MockSink {
359 events: Vec<Event>,
360 }
361
362 impl MockSink {
363 fn new() -> Self {
364 Self { events: Vec::new() }
365 }
366 }
367
368 impl EventSink for MockSink {
369 fn finish(self) -> Result<()> {
370 Ok(())
371 }
372
373 fn handle_event(&mut self, event: Event) -> Result<()> {
374 self.events.push(event);
375 Ok(())
376 }
377 }
378
379 fn send(sink: &mut StackTrackingSink<MockSink>, event: Event) {
380 let result = sink.handle_event(event);
381 assert!(result.is_ok());
382 }
383
384 #[test]
385 fn has_open_content_with_blockquote_returns_false() {
386 let mock = MockSink::new();
387 let mut sink = StackTrackingSink::new(mock);
388 sink.stack_mut().push(BlockKind::Document);
389 sink.stack_mut().push(BlockKind::Blockquote);
390 assert!(!sink.has_open_content());
392 }
393
394 #[test]
395 fn has_open_content_with_heading() {
396 let mock = MockSink::new();
397 let mut sink = StackTrackingSink::new(mock);
398 sink.stack_mut().push(BlockKind::Document);
399 sink.stack_mut().push(BlockKind::Heading);
400 assert!(sink.has_open_content());
401 }
402
403 #[test]
404 fn has_open_content_with_paragraph() {
405 let mock = MockSink::new();
406 let mut sink = StackTrackingSink::new(mock);
407 sink.stack_mut().push(BlockKind::Document);
408 sink.stack_mut().push(BlockKind::Paragraph);
409 assert!(sink.has_open_content());
410 }
411
412 #[test]
413 fn has_open_content_with_preformatted() {
414 let mock = MockSink::new();
415 let mut sink = StackTrackingSink::new(mock);
416 sink.stack_mut().push(BlockKind::Document);
417 sink.stack_mut().push(BlockKind::Preformatted);
418 assert!(sink.has_open_content());
419 }
420
421 #[test]
422 fn has_open_content_without_content_blocks() {
423 let mock = MockSink::new();
424 let mut sink = StackTrackingSink::new(mock);
425 sink.stack_mut().push(BlockKind::Document);
426 sink.stack_mut().push(BlockKind::Table);
427 sink.stack_mut().push(BlockKind::TableRow);
428 assert!(!sink.has_open_content());
429 }
430
431 #[test]
432 fn is_inside_finds_nested_kind() {
433 let mock = MockSink::new();
434 let mut sink = StackTrackingSink::new(mock);
435 sink.stack_mut().push(BlockKind::Document);
436 sink.stack_mut().push(BlockKind::Blockquote);
437 sink.stack_mut().push(BlockKind::Paragraph);
438 assert!(sink.is_inside(BlockKind::Blockquote));
439 }
440
441 #[test]
442 fn is_inside_returns_false_for_missing_kind() {
443 let mock = MockSink::new();
444 let mut sink = StackTrackingSink::new(mock);
445 sink.stack_mut().push(BlockKind::Document);
446 sink.stack_mut().push(BlockKind::Paragraph);
447 assert!(!sink.is_inside(BlockKind::Blockquote));
448 }
449
450 #[test]
451 fn stack_returns_current_stack() {
452 let mock = MockSink::new();
453 let mut sink = StackTrackingSink::new(mock);
454 sink.stack_mut().push(BlockKind::Document);
455 sink.stack_mut().push(BlockKind::Paragraph);
456 let stack = sink.stack();
457 assert_eq!(stack.len(), 2);
458 assert_eq!(stack.first(), Some(&BlockKind::Document));
459 assert_eq!(stack.get(1), Some(&BlockKind::Paragraph));
460 }
461
462 #[test]
463 fn passthrough_forwards_all_events() {
464 let mock = MockSink::new();
465 let mut sink = StackTrackingSink::new(mock);
466
467 send(
468 &mut sink,
469 Event::StartDocument {
470 id: None,
471 language: None,
472 metadata: None,
473 },
474 );
475 send(
476 &mut sink,
477 Event::StartParagraph {
478 alignment: None,
479 id: None,
480 },
481 );
482 send(
483 &mut sink,
484 Event::Text {
485 content: "hello".to_string(),
486 style: TextStyle::default(),
487 },
488 );
489 send(&mut sink, Event::EndParagraph);
490 send(&mut sink, Event::EndDocument);
491
492 assert_eq!(sink.sink().events.len(), 5);
493 assert!(matches!(
494 sink.sink().events.first(),
495 Some(Event::StartDocument { .. })
496 ));
497 assert!(matches!(
498 sink.sink().events.get(1),
499 Some(Event::StartParagraph { .. })
500 ));
501 assert!(matches!(
502 sink.sink().events.get(2),
503 Some(Event::Text { .. })
504 ));
505 assert!(matches!(
506 sink.sink().events.get(3),
507 Some(Event::EndParagraph)
508 ));
509 assert!(matches!(
510 sink.sink().events.get(4),
511 Some(Event::EndDocument)
512 ));
513 assert!(sink.stack().is_empty());
514 }
515
516 #[test]
517 fn orphan_text_gets_paragraph() {
518 let mock = MockSink::new();
519 let mut sink = StackTrackingSink::new(mock);
520
521 send(
522 &mut sink,
523 Event::StartDocument {
524 id: None,
525 language: None,
526 metadata: None,
527 },
528 );
529 send(
530 &mut sink,
531 Event::Text {
532 content: "hello".to_string(),
533 style: TextStyle::default(),
534 },
535 );
536 send(&mut sink, Event::EndDocument);
537
538 assert_eq!(sink.sink().events.len(), 5);
539 assert!(matches!(
540 sink.sink().events.first(),
541 Some(Event::StartDocument { .. })
542 ));
543 assert_eq!(
544 sink.sink().events.get(1),
545 Some(&Event::StartParagraph {
546 alignment: None,
547 id: None
548 })
549 );
550 assert!(matches!(
551 sink.sink().events.get(2),
552 Some(Event::Text { .. })
553 ));
554 assert_eq!(sink.sink().events.get(3), Some(&Event::EndParagraph));
555 assert!(matches!(
556 sink.sink().events.get(4),
557 Some(Event::EndDocument)
558 ));
559 }
560
561 #[test]
562 fn orphan_text_inside_table_cell_gets_paragraph() {
563 let mock = MockSink::new();
564 let mut sink = StackTrackingSink::new(mock);
565
566 send(
567 &mut sink,
568 Event::StartDocument {
569 id: None,
570 language: None,
571 metadata: None,
572 },
573 );
574 send(&mut sink, Event::StartTable { id: None });
575 send(&mut sink, Event::StartTableRow { id: None });
576 send(
577 &mut sink,
578 Event::StartTableCell {
579 colspan: None,
580 id: None,
581 rowspan: None,
582 },
583 );
584 send(
585 &mut sink,
586 Event::Text {
587 content: "cell".to_string(),
588 style: TextStyle::default(),
589 },
590 );
591 send(&mut sink, Event::EndTableCell);
592 send(&mut sink, Event::EndTableRow);
593 send(&mut sink, Event::EndTable);
594 send(&mut sink, Event::EndDocument);
595
596 assert_eq!(sink.sink().events.len(), 11);
597 assert_eq!(
598 sink.sink().events.get(4),
599 Some(&Event::StartParagraph {
600 alignment: None,
601 id: None
602 })
603 );
604 assert_eq!(sink.sink().events.get(6), Some(&Event::EndParagraph));
605 }
606
607 #[test]
608 fn orphan_text_inside_blockquote_gets_paragraph() {
609 let mock = MockSink::new();
610 let mut sink = StackTrackingSink::new(mock);
611
612 send(
613 &mut sink,
614 Event::StartDocument {
615 id: None,
616 language: None,
617 metadata: None,
618 },
619 );
620 send(&mut sink, Event::StartBlockQuote { id: None });
621 send(
622 &mut sink,
623 Event::Text {
624 content: "quoted".to_string(),
625 style: TextStyle::default(),
626 },
627 );
628 send(&mut sink, Event::EndBlockQuote);
629 send(&mut sink, Event::EndDocument);
630
631 assert_eq!(sink.sink().events.len(), 7);
633 assert!(matches!(
634 sink.sink().events.first(),
635 Some(Event::StartDocument { .. })
636 ));
637 assert!(matches!(
638 sink.sink().events.get(1),
639 Some(Event::StartBlockQuote { .. })
640 ));
641 assert_eq!(
642 sink.sink().events.get(2),
643 Some(&Event::StartParagraph {
644 alignment: None,
645 id: None
646 })
647 );
648 assert!(matches!(
649 sink.sink().events.get(3),
650 Some(Event::Text { .. })
651 ));
652 assert_eq!(sink.sink().events.get(4), Some(&Event::EndParagraph));
653 assert!(matches!(
654 sink.sink().events.get(5),
655 Some(Event::EndBlockQuote)
656 ));
657 assert!(matches!(
658 sink.sink().events.get(6),
659 Some(Event::EndDocument)
660 ));
661 }
662
663 #[test]
664 fn text_inside_paragraph_no_extra_insert() {
665 let mock = MockSink::new();
666 let mut sink = StackTrackingSink::new(mock);
667
668 send(
669 &mut sink,
670 Event::StartDocument {
671 id: None,
672 language: None,
673 metadata: None,
674 },
675 );
676 send(
677 &mut sink,
678 Event::StartParagraph {
679 alignment: None,
680 id: None,
681 },
682 );
683 send(
684 &mut sink,
685 Event::Text {
686 content: "hello".to_string(),
687 style: TextStyle::default(),
688 },
689 );
690 send(
691 &mut sink,
692 Event::Text {
693 content: "world".to_string(),
694 style: TextStyle::default(),
695 },
696 );
697 send(&mut sink, Event::EndParagraph);
698 send(&mut sink, Event::EndDocument);
699
700 assert_eq!(sink.sink().events.len(), 6);
701 }
702
703 #[test]
704 fn auto_close_paragraph_on_end_table() {
705 let mock = MockSink::new();
706 let mut sink = StackTrackingSink::new(mock);
707
708 send(
709 &mut sink,
710 Event::StartDocument {
711 id: None,
712 language: None,
713 metadata: None,
714 },
715 );
716 send(&mut sink, Event::StartTable { id: None });
717 send(&mut sink, Event::StartTableRow { id: None });
718 send(
719 &mut sink,
720 Event::StartTableCell {
721 colspan: None,
722 id: None,
723 rowspan: None,
724 },
725 );
726 send(
727 &mut sink,
728 Event::StartParagraph {
729 alignment: None,
730 id: None,
731 },
732 );
733 send(
734 &mut sink,
735 Event::Text {
736 content: "cell".to_string(),
737 style: TextStyle::default(),
738 },
739 );
740 send(&mut sink, Event::EndTable);
741
742 assert_eq!(sink.sink().events.len(), 10);
743 assert_eq!(sink.sink().events.get(6), Some(&Event::EndParagraph));
744 assert_eq!(sink.sink().events.get(7), Some(&Event::EndTableCell));
745 assert_eq!(sink.sink().events.get(8), Some(&Event::EndTableRow));
746 assert_eq!(sink.sink().events.get(9), Some(&Event::EndTable));
747 }
748
749 #[test]
750 fn auto_close_on_end_blockquote() {
751 let mock = MockSink::new();
752 let mut sink = StackTrackingSink::new(mock);
753
754 send(
755 &mut sink,
756 Event::StartDocument {
757 id: None,
758 language: None,
759 metadata: None,
760 },
761 );
762 send(&mut sink, Event::StartBlockQuote { id: None });
763 send(
764 &mut sink,
765 Event::StartParagraph {
766 alignment: None,
767 id: None,
768 },
769 );
770 send(
771 &mut sink,
772 Event::Text {
773 content: "quote".to_string(),
774 style: TextStyle::default(),
775 },
776 );
777 send(&mut sink, Event::EndBlockQuote);
778
779 assert_eq!(sink.sink().events.len(), 6);
780 assert_eq!(sink.sink().events.get(4), Some(&Event::EndParagraph));
781 assert_eq!(sink.sink().events.get(5), Some(&Event::EndBlockQuote));
782 }
783
784 #[test]
785 fn start_block_closes_open_paragraph() {
786 let mock = MockSink::new();
787 let mut sink = StackTrackingSink::new(mock);
788
789 send(
790 &mut sink,
791 Event::StartDocument {
792 id: None,
793 language: None,
794 metadata: None,
795 },
796 );
797 send(
798 &mut sink,
799 Event::StartParagraph {
800 alignment: None,
801 id: None,
802 },
803 );
804 send(&mut sink, Event::StartHeading { id: None, level: 1 });
805
806 assert_eq!(sink.sink().events.get(2), Some(&Event::EndParagraph));
807 assert!(matches!(
808 sink.sink().events.get(3),
809 Some(Event::StartHeading { .. })
810 ));
811 }
812
813 #[test]
814 fn start_document_while_document_open_returns_error() {
815 let mock = MockSink::new();
816 let mut sink = StackTrackingSink::new(mock);
817
818 send(
819 &mut sink,
820 Event::StartDocument {
821 id: None,
822 language: None,
823 metadata: None,
824 },
825 );
826 let result = sink.handle_event(Event::StartDocument {
827 id: None,
828 language: None,
829 metadata: None,
830 });
831
832 assert!(matches!(result, Err(Error::InvalidSequence { .. })));
833 }
834
835 #[test]
836 fn thematic_break_closes_open_paragraph() {
837 let mock = MockSink::new();
838 let mut sink = StackTrackingSink::new(mock);
839
840 send(
841 &mut sink,
842 Event::StartDocument {
843 id: None,
844 language: None,
845 metadata: None,
846 },
847 );
848 send(
849 &mut sink,
850 Event::StartParagraph {
851 alignment: None,
852 id: None,
853 },
854 );
855 send(&mut sink, Event::ThematicBreak { id: None });
856
857 assert_eq!(sink.sink().events.get(2), Some(&Event::EndParagraph));
858 assert!(matches!(
859 sink.sink().events.get(3),
860 Some(Event::ThematicBreak { .. })
861 ));
862 }
863
864 #[test]
865 fn end_document_closes_all() {
866 let mock = MockSink::new();
867 let mut sink = StackTrackingSink::new(mock);
868
869 send(
870 &mut sink,
871 Event::StartDocument {
872 id: None,
873 language: None,
874 metadata: None,
875 },
876 );
877 send(&mut sink, Event::StartBlockQuote { id: None });
878 send(
879 &mut sink,
880 Event::StartParagraph {
881 alignment: None,
882 id: None,
883 },
884 );
885 send(&mut sink, Event::EndDocument);
886
887 assert_eq!(sink.sink().events.len(), 6);
888 assert_eq!(sink.sink().events.get(3), Some(&Event::EndParagraph));
889 assert_eq!(sink.sink().events.get(4), Some(&Event::EndBlockQuote));
890 assert_eq!(sink.sink().events.get(5), Some(&Event::EndDocument));
891 assert!(sink.stack().is_empty());
892 }
893
894 #[test]
895 fn end_event_for_all_kinds() {
896 assert_eq!(end_event_for(BlockKind::Blockquote), Event::EndBlockQuote);
897 assert_eq!(end_event_for(BlockKind::Caption), Event::EndCaption);
898 assert_eq!(
899 end_event_for(BlockKind::DefinitionDetail),
900 Event::EndDefinitionDetail
901 );
902 assert_eq!(
903 end_event_for(BlockKind::DefinitionList),
904 Event::EndDefinitionList
905 );
906 assert_eq!(
907 end_event_for(BlockKind::DefinitionTerm),
908 Event::EndDefinitionTerm
909 );
910 assert_eq!(end_event_for(BlockKind::Document), Event::EndDocument);
911 assert_eq!(end_event_for(BlockKind::Footnote), Event::EndFootnote);
912 assert_eq!(end_event_for(BlockKind::Heading), Event::EndHeading);
913 assert_eq!(end_event_for(BlockKind::Link), Event::EndLink);
914 assert_eq!(
915 end_event_for(BlockKind::OrderedListItem),
916 Event::EndOrderedListItem
917 );
918 assert_eq!(
919 end_event_for(BlockKind::UnorderedListItem),
920 Event::EndUnorderedListItem
921 );
922 assert_eq!(end_event_for(BlockKind::Paragraph), Event::EndParagraph);
923 assert_eq!(
924 end_event_for(BlockKind::Preformatted),
925 Event::EndPreformatted
926 );
927 assert_eq!(end_event_for(BlockKind::Table), Event::EndTable);
928 assert_eq!(end_event_for(BlockKind::TableCell), Event::EndTableCell);
929 assert_eq!(end_event_for(BlockKind::TableHeader), Event::EndTableHeader);
930 assert_eq!(end_event_for(BlockKind::TableRow), Event::EndTableRow);
931 }
932}