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