easydoc_reader/extractor/
table.rs1use std::path::Path;
4
5use easydoc_core::{CellData, DocError, DocxRow, Result, RowData};
6use office_oxide::ir::{DocumentIR, Element, InlineContent};
7
8pub fn extract_tables<T: DocxRow>(path: &Path) -> Result<Vec<Vec<T>>> {
17 let doc = office_oxide::Document::open(path)
18 .map_err(|e| DocError::Document(format!("failed to open document: {e}")))?;
19 let ir = doc.to_ir();
20 extract_tables_from_ir(&ir)
21}
22
23fn extract_tables_from_ir<T: DocxRow>(ir: &DocumentIR) -> Result<Vec<Vec<T>>> {
25 let mut all_tables: Vec<Vec<T>> = Vec::new();
26
27 for section in &ir.sections {
28 for element in §ion.elements {
29 if let Element::Table(table) = element {
30 let mut rows: Vec<T> = Vec::new();
31 let mut header_skipped = false;
32
33 for row in &table.rows {
34 if row.is_header && !header_skipped {
36 header_skipped = true;
37 continue;
38 }
39
40 let cells: Vec<CellData> = row
42 .cells
43 .iter()
44 .map(|cell| {
45 let text = cell_text(cell);
46 CellData::new(text)
47 })
48 .collect();
49
50 let row_data = RowData::new(cells);
51 match T::from_row(&row_data) {
52 Ok(item) => rows.push(item),
53 Err(e) => {
54 eprintln!("warning: skipping row: {e}");
56 }
57 }
58 }
59
60 if !rows.is_empty() {
61 all_tables.push(rows);
62 }
63 }
64 }
65 }
66
67 Ok(all_tables)
68}
69
70fn cell_text(cell: &office_oxide::ir::TableCell) -> String {
72 let mut text = String::new();
73 for element in &cell.content {
74 if let Element::Paragraph(para) = element {
75 if !text.is_empty() {
76 text.push('\n');
77 }
78 for inline in ¶.content {
79 if let InlineContent::Text(span) = inline {
80 text.push_str(&span.text);
81 }
82 }
83 }
84 }
85 text
86}