1use crate::converter::ConverterRegistry;
10use crate::error::Result;
11use crate::metadata::TableColumn;
12use crate::types::{CellData, DocValue, ErrorAction, RowData, TableData};
13
14pub trait DocxRow {
36 fn schema() -> &'static [TableColumn]
40 where
41 Self: Sized;
42
43 fn from_row(row: &RowData) -> Result<Self>
47 where
48 Self: Sized;
49
50 fn from_row_with_converters(row: &RowData, registry: &ConverterRegistry) -> Result<Self>
54 where
55 Self: Sized;
56
57 fn to_row(&self) -> Result<Vec<CellData>>;
61
62 fn to_row_with_converters(&self, registry: &ConverterRegistry) -> Result<Vec<CellData>>;
66}
67
68pub trait DocConverter<T> {
78 fn support_type() -> std::any::TypeId
82 where
83 Self: Sized;
84
85 fn to_doc_value(&self, value: &T, column: &TableColumn) -> Result<DocValue>;
93
94 fn from_doc_value(&self, value: &DocValue, column: &TableColumn) -> Result<T>;
102}
103
104#[derive(Debug, Clone)]
110pub struct DocReadContext {
111 pub path: String,
113 pub index: usize,
115}
116
117pub trait DocReadListener<T> {
121 fn invoke(&mut self, data: T, context: &DocReadContext) -> Result<()>;
129
130 fn invoke_table(&mut self, table: &TableData, context: &DocReadContext) -> Result<()> {
134 let _ = (table, context);
135 Ok(())
136 }
137
138 fn on_complete(&mut self, _context: &DocReadContext) {}
142
143 fn on_error(
150 &mut self,
151 _error: &crate::error::DocError,
152 _context: &DocReadContext,
153 ) -> ErrorAction {
154 ErrorAction::Stop
155 }
156
157 fn has_next(&self, _context: &DocReadContext) -> bool {
161 true
162 }
163}
164
165#[derive(Debug, Clone)]
171pub struct DocWriteContext {
172 pub path: String,
174}
175
176#[derive(Debug, Clone)]
178pub struct ParagraphContext {
179 pub index: usize,
181}
182
183#[derive(Debug, Clone)]
185pub struct TableWriteContext {
186 pub index: usize,
188 pub row_count: usize,
190}
191
192#[derive(Debug, Clone)]
194pub struct CellContext {
195 pub row: usize,
197 pub column: usize,
199 pub value: DocValue,
201}
202
203pub trait DocWriteHandler {
209 #[must_use]
213 fn order() -> i32 {
214 0
215 }
216
217 fn before_document(&mut self, _ctx: &DocWriteContext) -> Result<()> {
221 Ok(())
222 }
223
224 fn after_document(&mut self, _ctx: &DocWriteContext) -> Result<()> {
228 Ok(())
229 }
230
231 fn before_paragraph(&mut self, _ctx: &ParagraphContext) -> Result<()> {
233 Ok(())
234 }
235
236 fn after_paragraph(&mut self, _ctx: &ParagraphContext) -> Result<()> {
238 Ok(())
239 }
240
241 fn before_table(&mut self, _ctx: &TableWriteContext) -> Result<()> {
245 Ok(())
246 }
247
248 fn after_table(&mut self, _ctx: &TableWriteContext) -> Result<()> {
252 Ok(())
253 }
254
255 fn before_cell(&mut self, _ctx: &CellContext) -> Result<()> {
259 Ok(())
260 }
261
262 fn after_cell(&mut self, _ctx: &CellContext) -> Result<()> {
266 Ok(())
267 }
268}
269
270pub trait DocumentReader {
279 fn read_model(&self, path: &std::path::Path) -> crate::Result<crate::DocumentContent>;
284
285 fn read_events(&self, path: &std::path::Path, sink: &mut dyn EventSink) -> crate::Result<()>;
290}
291
292#[derive(Clone, Debug, PartialEq)]
300#[non_exhaustive]
301pub enum DocumentEvent {
302 Heading {
304 level: u8,
306 runs: Vec<crate::DocumentTextRun>,
308 },
309 Paragraph(Vec<crate::DocumentTextRun>),
311 Table(crate::DocumentTable),
313 List(crate::DocumentList),
315 Image(crate::DocumentImage),
317 PageBreak,
319 ColumnBreak,
321 CodeBlock {
323 language: Option<String>,
325 code: String,
327 },
328 Section {
330 section_type: Option<String>,
332 },
333 DocumentStart,
335 DocumentEnd,
337}
338
339pub trait EventSink {
348 fn on_event(&mut self, event: &DocumentEvent) -> crate::Result<()>;
353
354 fn on_complete(&mut self) {}
356}
357
358pub struct ContentCollector {
362 blocks: Vec<crate::DocumentBlock>,
363}
364
365impl ContentCollector {
366 #[must_use]
368 pub fn new() -> Self {
369 Self { blocks: Vec::new() }
370 }
371
372 #[must_use]
374 pub fn into_content(self) -> crate::DocumentContent {
375 crate::DocumentContent {
376 metadata: crate::DocumentMeta::default(),
377 blocks: self.blocks,
378 }
379 }
380}
381
382impl Default for ContentCollector {
383 fn default() -> Self {
384 Self::new()
385 }
386}
387
388impl EventSink for ContentCollector {
389 fn on_event(&mut self, event: &DocumentEvent) -> crate::Result<()> {
390 match event {
391 DocumentEvent::Heading { level, runs } => {
392 self.blocks.push(crate::DocumentBlock::Heading {
393 level: *level,
394 runs: runs.clone(),
395 });
396 }
397 DocumentEvent::Paragraph(runs) => {
398 self.blocks
399 .push(crate::DocumentBlock::Paragraph(runs.clone()));
400 }
401 DocumentEvent::Table(table) => {
402 self.blocks.push(crate::DocumentBlock::Table(table.clone()));
403 }
404 DocumentEvent::List(list) => {
405 self.blocks.push(crate::DocumentBlock::List(list.clone()));
406 }
407 DocumentEvent::Image(image) => {
408 self.blocks.push(crate::DocumentBlock::Image(image.clone()));
409 }
410 DocumentEvent::PageBreak => {
411 self.blocks.push(crate::DocumentBlock::PageBreak);
412 }
413 DocumentEvent::ColumnBreak => {
414 self.blocks.push(crate::DocumentBlock::ColumnBreak);
415 }
416 DocumentEvent::CodeBlock { language, code } => {
417 self.blocks.push(crate::DocumentBlock::CodeBlock {
418 language: language.clone(),
419 code: code.clone(),
420 });
421 }
422 DocumentEvent::Section { section_type } => {
423 self.blocks.push(crate::DocumentBlock::Section {
424 blocks: Vec::new(),
425 section_type: section_type.clone(),
426 });
427 }
428 DocumentEvent::DocumentStart | DocumentEvent::DocumentEnd => {}
429 }
430 Ok(())
431 }
432}
433
434#[cfg(test)]
435mod event_tests {
436 use super::*;
437
438 #[test]
439 fn document_event_debug() {
440 let event = DocumentEvent::DocumentStart;
441 assert_eq!(format!("{event:?}"), "DocumentStart");
442 }
443
444 #[test]
445 fn document_event_heading() {
446 let event = DocumentEvent::Heading {
447 level: 1,
448 runs: vec![crate::DocumentTextRun {
449 text: "Title".into(),
450 ..crate::DocumentTextRun::default()
451 }],
452 };
453 match &event {
454 DocumentEvent::Heading { level, runs } => {
455 assert_eq!(*level, 1);
456 assert_eq!(runs[0].text, "Title");
457 }
458 _ => panic!("expected Heading"),
459 }
460 }
461
462 #[test]
463 fn content_collector_roundtrip() {
464 let mut collector = ContentCollector::new();
465 collector.on_event(&DocumentEvent::DocumentStart).unwrap();
466 collector
467 .on_event(&DocumentEvent::Paragraph(vec![crate::DocumentTextRun {
468 text: "Hello".into(),
469 ..crate::DocumentTextRun::default()
470 }]))
471 .unwrap();
472 collector.on_event(&DocumentEvent::PageBreak).unwrap();
473 collector.on_event(&DocumentEvent::DocumentEnd).unwrap();
474
475 let content = collector.into_content();
476 assert_eq!(content.blocks.len(), 2);
477 assert!(matches!(
478 content.blocks[0],
479 crate::DocumentBlock::Paragraph(_)
480 ));
481 assert!(matches!(content.blocks[1], crate::DocumentBlock::PageBreak));
482 }
483
484 #[test]
485 fn content_collector_table_and_list() {
486 let mut collector = ContentCollector::new();
487 collector
488 .on_event(&DocumentEvent::Table(crate::DocumentTable { rows: vec![] }))
489 .unwrap();
490 collector
491 .on_event(&DocumentEvent::List(crate::DocumentList {
492 ordered: false,
493 start_number: None,
494 items: vec![],
495 }))
496 .unwrap();
497 let content = collector.into_content();
498 assert_eq!(content.blocks.len(), 2);
499 }
500
501 #[test]
502 fn content_collector_codeblock() {
503 let mut collector = ContentCollector::new();
504 collector
505 .on_event(&DocumentEvent::CodeBlock {
506 language: Some("rust".into()),
507 code: "fn main() {}".into(),
508 })
509 .unwrap();
510 let content = collector.into_content();
511 match &content.blocks[0] {
512 crate::DocumentBlock::CodeBlock { language, code } => {
513 assert_eq!(language.as_deref(), Some("rust"));
514 assert_eq!(code, "fn main() {}");
515 }
516 _ => panic!("expected CodeBlock"),
517 }
518 }
519
520 #[test]
521 fn content_collector_section() {
522 let mut collector = ContentCollector::new();
523 collector
524 .on_event(&DocumentEvent::Section {
525 section_type: Some("continuous".into()),
526 })
527 .unwrap();
528 let content = collector.into_content();
529 match &content.blocks[0] {
530 crate::DocumentBlock::Section {
531 blocks,
532 section_type,
533 } => {
534 assert!(blocks.is_empty());
535 assert_eq!(section_type.as_deref(), Some("continuous"));
536 }
537 _ => panic!("expected Section"),
538 }
539 }
540}
541
542#[cfg(test)]
543mod trait_coverage_tests {
544 use super::*;
545
546 struct NoopHandler;
547 impl DocWriteHandler for NoopHandler {}
548
549 #[test]
550 fn noop_handler_all_defaults() {
551 let mut h = NoopHandler;
552 assert_eq!(NoopHandler::order(), 0);
553 let ctx = DocWriteContext {
554 path: "test".into(),
555 };
556 h.before_document(&ctx).unwrap();
557 h.after_document(&ctx).unwrap();
558 let pctx = ParagraphContext { index: 0 };
559 h.before_paragraph(&pctx).unwrap();
560 h.after_paragraph(&pctx).unwrap();
561 let tctx = TableWriteContext {
562 index: 0,
563 row_count: 1,
564 };
565 h.before_table(&tctx).unwrap();
566 h.after_table(&tctx).unwrap();
567 let cctx = CellContext {
568 row: 0,
569 column: 0,
570 value: DocValue::Empty,
571 };
572 h.before_cell(&cctx).unwrap();
573 h.after_cell(&cctx).unwrap();
574 }
575
576 #[test]
577 fn read_listener_defaults() {
578 struct TestListener;
579 impl DocReadListener<String> for TestListener {
580 fn invoke(&mut self, _: String, _: &DocReadContext) -> crate::Result<()> {
581 Ok(())
582 }
583 }
584 let mut listener = TestListener;
585 let ctx = DocReadContext {
586 path: "test".into(),
587 index: 0,
588 };
589 assert!(listener.has_next(&ctx));
590 assert!(matches!(
591 listener.on_error(&crate::DocError::Document("x".into()), &ctx),
592 ErrorAction::Stop
593 ));
594 listener.on_complete(&ctx);
595 }
596
597 #[test]
598 fn read_listener_invoke_table_default() {
599 struct TestListener;
600 impl DocReadListener<String> for TestListener {
601 fn invoke(&mut self, _: String, _: &DocReadContext) -> crate::Result<()> {
602 Ok(())
603 }
604 }
605 let mut listener = TestListener;
606 let ctx = DocReadContext {
607 path: "test".into(),
608 index: 0,
609 };
610 let table = TableData {
611 headers: None,
612 rows: vec![],
613 };
614 listener.invoke_table(&table, &ctx).unwrap();
615 }
616
617 #[test]
618 fn content_collector_all_event_types() {
619 let mut c = ContentCollector::new();
620 c.on_event(&DocumentEvent::DocumentStart).unwrap();
621 c.on_event(&DocumentEvent::Heading {
622 level: 1,
623 runs: vec![],
624 })
625 .unwrap();
626 c.on_event(&DocumentEvent::Paragraph(vec![])).unwrap();
627 c.on_event(&DocumentEvent::Table(crate::DocumentTable { rows: vec![] }))
628 .unwrap();
629 c.on_event(&DocumentEvent::List(crate::DocumentList {
630 ordered: false,
631 start_number: None,
632 items: vec![],
633 }))
634 .unwrap();
635 c.on_event(&DocumentEvent::Image(crate::DocumentImage {
636 alt_text: None,
637 data: None,
638 extension: None,
639 }))
640 .unwrap();
641 c.on_event(&DocumentEvent::PageBreak).unwrap();
642 c.on_event(&DocumentEvent::ColumnBreak).unwrap();
643 c.on_event(&DocumentEvent::CodeBlock {
644 language: None,
645 code: String::new(),
646 })
647 .unwrap();
648 c.on_event(&DocumentEvent::Section { section_type: None })
649 .unwrap();
650 c.on_event(&DocumentEvent::DocumentEnd).unwrap();
651 c.on_complete();
652 let content = c.into_content();
653 assert_eq!(content.blocks.len(), 9); }
655
656 #[test]
657 fn content_collector_default() {
658 let c = ContentCollector::default();
659 let content = c.into_content();
660 assert!(content.blocks.is_empty());
661 }
662
663 #[test]
664 fn doc_read_context_clone_debug() {
665 let ctx = DocReadContext {
666 path: "test".into(),
667 index: 5,
668 };
669 let ctx2 = ctx.clone();
670 assert_eq!(ctx2.index, 5);
671 assert!(format!("{ctx:?}").contains("test"));
672 }
673
674 #[test]
675 fn doc_write_context_clone_debug() {
676 let ctx = DocWriteContext {
677 path: "out.docx".into(),
678 };
679 let ctx2 = ctx.clone();
680 assert_eq!(ctx2.path, "out.docx");
681 assert!(format!("{ctx:?}").contains("out.docx"));
682 }
683
684 #[test]
685 fn paragraph_context_clone_debug() {
686 let ctx = ParagraphContext { index: 3 };
687 let ctx2 = ctx.clone();
688 assert_eq!(ctx2.index, 3);
689 assert!(format!("{ctx:?}").contains('3'));
690 }
691
692 #[test]
693 fn table_write_context_clone_debug() {
694 let ctx = TableWriteContext {
695 index: 1,
696 row_count: 10,
697 };
698 let ctx2 = ctx.clone();
699 assert_eq!(ctx2.index, 1);
700 assert_eq!(ctx2.row_count, 10);
701 assert!(format!("{ctx:?}").contains("10"));
702 }
703
704 #[test]
705 fn cell_context_clone_debug() {
706 let ctx = CellContext {
707 row: 2,
708 column: 3,
709 value: DocValue::Int(42),
710 };
711 let ctx2 = ctx.clone();
712 assert_eq!(ctx2.row, 2);
713 assert!(format!("{ctx:?}").contains("42"));
714 }
715
716 #[test]
717 fn document_event_clone_debug() {
718 let events = vec![
719 DocumentEvent::DocumentStart,
720 DocumentEvent::DocumentEnd,
721 DocumentEvent::PageBreak,
722 DocumentEvent::ColumnBreak,
723 DocumentEvent::Heading {
724 level: 1,
725 runs: vec![],
726 },
727 DocumentEvent::Paragraph(vec![]),
728 DocumentEvent::Section { section_type: None },
729 DocumentEvent::CodeBlock {
730 language: None,
731 code: String::new(),
732 },
733 ];
734 for event in &events {
735 let _clone = event.clone();
736 let _debug = format!("{event:?}");
737 }
738 }
739}