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)]
300pub enum DocumentEvent {
301 Heading {
303 level: u8,
305 runs: Vec<crate::DocumentTextRun>,
307 },
308 Paragraph(Vec<crate::DocumentTextRun>),
310 Table(crate::DocumentTable),
312 List(crate::DocumentList),
314 Image(crate::DocumentImage),
316 PageBreak,
318 ColumnBreak,
320 CodeBlock {
322 language: Option<String>,
324 code: String,
326 },
327 Section {
329 section_type: Option<String>,
331 },
332 DocumentStart,
334 DocumentEnd,
336}
337
338pub trait EventSink {
347 fn on_event(&mut self, event: &DocumentEvent) -> crate::Result<()>;
352
353 fn on_complete(&mut self) {}
355}
356
357pub struct ContentCollector {
361 blocks: Vec<crate::DocumentBlock>,
362}
363
364impl ContentCollector {
365 #[must_use]
367 pub fn new() -> Self {
368 Self { blocks: Vec::new() }
369 }
370
371 #[must_use]
373 pub fn into_content(self) -> crate::DocumentContent {
374 crate::DocumentContent {
375 metadata: crate::DocumentMeta::default(),
376 blocks: self.blocks,
377 }
378 }
379}
380
381impl Default for ContentCollector {
382 fn default() -> Self {
383 Self::new()
384 }
385}
386
387impl EventSink for ContentCollector {
388 fn on_event(&mut self, event: &DocumentEvent) -> crate::Result<()> {
389 match event {
390 DocumentEvent::Heading { level, runs } => {
391 self.blocks.push(crate::DocumentBlock::Heading {
392 level: *level,
393 runs: runs.clone(),
394 });
395 }
396 DocumentEvent::Paragraph(runs) => {
397 self.blocks
398 .push(crate::DocumentBlock::Paragraph(runs.clone()));
399 }
400 DocumentEvent::Table(table) => {
401 self.blocks.push(crate::DocumentBlock::Table(table.clone()));
402 }
403 DocumentEvent::List(list) => {
404 self.blocks.push(crate::DocumentBlock::List(list.clone()));
405 }
406 DocumentEvent::Image(image) => {
407 self.blocks.push(crate::DocumentBlock::Image(image.clone()));
408 }
409 DocumentEvent::PageBreak => {
410 self.blocks.push(crate::DocumentBlock::PageBreak);
411 }
412 DocumentEvent::ColumnBreak => {
413 self.blocks.push(crate::DocumentBlock::ColumnBreak);
414 }
415 DocumentEvent::CodeBlock { language, code } => {
416 self.blocks.push(crate::DocumentBlock::CodeBlock {
417 language: language.clone(),
418 code: code.clone(),
419 });
420 }
421 DocumentEvent::Section { section_type } => {
422 self.blocks.push(crate::DocumentBlock::Section {
423 blocks: Vec::new(),
424 section_type: section_type.clone(),
425 });
426 }
427 DocumentEvent::DocumentStart | DocumentEvent::DocumentEnd => {}
428 }
429 Ok(())
430 }
431}
432
433#[cfg(test)]
434mod event_tests {
435 use super::*;
436
437 #[test]
438 fn document_event_debug() {
439 let event = DocumentEvent::DocumentStart;
440 assert_eq!(format!("{event:?}"), "DocumentStart");
441 }
442
443 #[test]
444 fn document_event_heading() {
445 let event = DocumentEvent::Heading {
446 level: 1,
447 runs: vec![crate::DocumentTextRun {
448 text: "Title".into(),
449 ..crate::DocumentTextRun::default()
450 }],
451 };
452 match &event {
453 DocumentEvent::Heading { level, runs } => {
454 assert_eq!(*level, 1);
455 assert_eq!(runs[0].text, "Title");
456 }
457 _ => panic!("expected Heading"),
458 }
459 }
460
461 #[test]
462 fn content_collector_roundtrip() {
463 let mut collector = ContentCollector::new();
464 collector.on_event(&DocumentEvent::DocumentStart).unwrap();
465 collector
466 .on_event(&DocumentEvent::Paragraph(vec![crate::DocumentTextRun {
467 text: "Hello".into(),
468 ..crate::DocumentTextRun::default()
469 }]))
470 .unwrap();
471 collector.on_event(&DocumentEvent::PageBreak).unwrap();
472 collector.on_event(&DocumentEvent::DocumentEnd).unwrap();
473
474 let content = collector.into_content();
475 assert_eq!(content.blocks.len(), 2);
476 assert!(matches!(
477 content.blocks[0],
478 crate::DocumentBlock::Paragraph(_)
479 ));
480 assert!(matches!(content.blocks[1], crate::DocumentBlock::PageBreak));
481 }
482
483 #[test]
484 fn content_collector_table_and_list() {
485 let mut collector = ContentCollector::new();
486 collector
487 .on_event(&DocumentEvent::Table(crate::DocumentTable { rows: vec![] }))
488 .unwrap();
489 collector
490 .on_event(&DocumentEvent::List(crate::DocumentList {
491 ordered: false,
492 start_number: None,
493 items: vec![],
494 }))
495 .unwrap();
496 let content = collector.into_content();
497 assert_eq!(content.blocks.len(), 2);
498 }
499
500 #[test]
501 fn content_collector_codeblock() {
502 let mut collector = ContentCollector::new();
503 collector
504 .on_event(&DocumentEvent::CodeBlock {
505 language: Some("rust".into()),
506 code: "fn main() {}".into(),
507 })
508 .unwrap();
509 let content = collector.into_content();
510 match &content.blocks[0] {
511 crate::DocumentBlock::CodeBlock { language, code } => {
512 assert_eq!(language.as_deref(), Some("rust"));
513 assert_eq!(code, "fn main() {}");
514 }
515 _ => panic!("expected CodeBlock"),
516 }
517 }
518
519 #[test]
520 fn content_collector_section() {
521 let mut collector = ContentCollector::new();
522 collector
523 .on_event(&DocumentEvent::Section {
524 section_type: Some("continuous".into()),
525 })
526 .unwrap();
527 let content = collector.into_content();
528 match &content.blocks[0] {
529 crate::DocumentBlock::Section {
530 blocks,
531 section_type,
532 } => {
533 assert!(blocks.is_empty());
534 assert_eq!(section_type.as_deref(), Some("continuous"));
535 }
536 _ => panic!("expected Section"),
537 }
538 }
539}
540
541#[cfg(test)]
542mod trait_coverage_tests {
543 use super::*;
544
545 struct NoopHandler;
546 impl DocWriteHandler for NoopHandler {}
547
548 #[test]
549 fn noop_handler_all_defaults() {
550 let mut h = NoopHandler;
551 assert_eq!(NoopHandler::order(), 0);
552 let ctx = DocWriteContext {
553 path: "test".into(),
554 };
555 h.before_document(&ctx).unwrap();
556 h.after_document(&ctx).unwrap();
557 let pctx = ParagraphContext { index: 0 };
558 h.before_paragraph(&pctx).unwrap();
559 h.after_paragraph(&pctx).unwrap();
560 let tctx = TableWriteContext {
561 index: 0,
562 row_count: 1,
563 };
564 h.before_table(&tctx).unwrap();
565 h.after_table(&tctx).unwrap();
566 let cctx = CellContext {
567 row: 0,
568 column: 0,
569 value: DocValue::Empty,
570 };
571 h.before_cell(&cctx).unwrap();
572 h.after_cell(&cctx).unwrap();
573 }
574
575 #[test]
576 fn read_listener_defaults() {
577 struct TestListener;
578 impl DocReadListener<String> for TestListener {
579 fn invoke(&mut self, _: String, _: &DocReadContext) -> crate::Result<()> {
580 Ok(())
581 }
582 }
583 let mut listener = TestListener;
584 let ctx = DocReadContext {
585 path: "test".into(),
586 index: 0,
587 };
588 assert!(listener.has_next(&ctx));
589 assert!(matches!(
590 listener.on_error(&crate::DocError::Document("x".into()), &ctx),
591 ErrorAction::Stop
592 ));
593 listener.on_complete(&ctx);
594 }
595
596 #[test]
597 fn read_listener_invoke_table_default() {
598 struct TestListener;
599 impl DocReadListener<String> for TestListener {
600 fn invoke(&mut self, _: String, _: &DocReadContext) -> crate::Result<()> {
601 Ok(())
602 }
603 }
604 let mut listener = TestListener;
605 let ctx = DocReadContext {
606 path: "test".into(),
607 index: 0,
608 };
609 let table = TableData {
610 headers: None,
611 rows: vec![],
612 };
613 listener.invoke_table(&table, &ctx).unwrap();
614 }
615
616 #[test]
617 fn content_collector_all_event_types() {
618 let mut c = ContentCollector::new();
619 c.on_event(&DocumentEvent::DocumentStart).unwrap();
620 c.on_event(&DocumentEvent::Heading {
621 level: 1,
622 runs: vec![],
623 })
624 .unwrap();
625 c.on_event(&DocumentEvent::Paragraph(vec![])).unwrap();
626 c.on_event(&DocumentEvent::Table(crate::DocumentTable { rows: vec![] }))
627 .unwrap();
628 c.on_event(&DocumentEvent::List(crate::DocumentList {
629 ordered: false,
630 start_number: None,
631 items: vec![],
632 }))
633 .unwrap();
634 c.on_event(&DocumentEvent::Image(crate::DocumentImage {
635 alt_text: None,
636 data: None,
637 extension: None,
638 }))
639 .unwrap();
640 c.on_event(&DocumentEvent::PageBreak).unwrap();
641 c.on_event(&DocumentEvent::ColumnBreak).unwrap();
642 c.on_event(&DocumentEvent::CodeBlock {
643 language: None,
644 code: String::new(),
645 })
646 .unwrap();
647 c.on_event(&DocumentEvent::Section { section_type: None })
648 .unwrap();
649 c.on_event(&DocumentEvent::DocumentEnd).unwrap();
650 c.on_complete();
651 let content = c.into_content();
652 assert_eq!(content.blocks.len(), 9); }
654
655 #[test]
656 fn content_collector_default() {
657 let c = ContentCollector::default();
658 let content = c.into_content();
659 assert!(content.blocks.is_empty());
660 }
661
662 #[test]
663 fn doc_read_context_clone_debug() {
664 let ctx = DocReadContext {
665 path: "test".into(),
666 index: 5,
667 };
668 let ctx2 = ctx.clone();
669 assert_eq!(ctx2.index, 5);
670 assert!(format!("{ctx:?}").contains("test"));
671 }
672
673 #[test]
674 fn doc_write_context_clone_debug() {
675 let ctx = DocWriteContext {
676 path: "out.docx".into(),
677 };
678 let ctx2 = ctx.clone();
679 assert_eq!(ctx2.path, "out.docx");
680 assert!(format!("{ctx:?}").contains("out.docx"));
681 }
682
683 #[test]
684 fn paragraph_context_clone_debug() {
685 let ctx = ParagraphContext { index: 3 };
686 let ctx2 = ctx.clone();
687 assert_eq!(ctx2.index, 3);
688 assert!(format!("{ctx:?}").contains('3'));
689 }
690
691 #[test]
692 fn table_write_context_clone_debug() {
693 let ctx = TableWriteContext {
694 index: 1,
695 row_count: 10,
696 };
697 let ctx2 = ctx.clone();
698 assert_eq!(ctx2.index, 1);
699 assert_eq!(ctx2.row_count, 10);
700 assert!(format!("{ctx:?}").contains("10"));
701 }
702
703 #[test]
704 fn cell_context_clone_debug() {
705 let ctx = CellContext {
706 row: 2,
707 column: 3,
708 value: DocValue::Int(42),
709 };
710 let ctx2 = ctx.clone();
711 assert_eq!(ctx2.row, 2);
712 assert!(format!("{ctx:?}").contains("42"));
713 }
714
715 #[test]
716 fn document_event_clone_debug() {
717 let events = vec![
718 DocumentEvent::DocumentStart,
719 DocumentEvent::DocumentEnd,
720 DocumentEvent::PageBreak,
721 DocumentEvent::ColumnBreak,
722 DocumentEvent::Heading {
723 level: 1,
724 runs: vec![],
725 },
726 DocumentEvent::Paragraph(vec![]),
727 DocumentEvent::Section { section_type: None },
728 DocumentEvent::CodeBlock {
729 language: None,
730 code: String::new(),
731 },
732 ];
733 for event in &events {
734 let _clone = event.clone();
735 let _debug = format!("{event:?}");
736 }
737 }
738}