use std::path::Path;
use easydoc_core::{CellData, DocError, DocxRow, Result, RowData};
use office_oxide::ir::{DocumentIR, Element, InlineContent};
pub fn extract_tables<T: DocxRow>(path: &Path) -> Result<Vec<Vec<T>>> {
let doc = office_oxide::Document::open(path)
.map_err(|e| DocError::Document(format!("failed to open document: {e}")))?;
let ir = doc.to_ir();
extract_tables_from_ir(&ir)
}
fn extract_tables_from_ir<T: DocxRow>(ir: &DocumentIR) -> Result<Vec<Vec<T>>> {
let mut all_tables: Vec<Vec<T>> = Vec::new();
for section in &ir.sections {
for element in §ion.elements {
if let Element::Table(table) = element {
let mut rows: Vec<T> = Vec::new();
let mut header_skipped = false;
for row in &table.rows {
if row.is_header && !header_skipped {
header_skipped = true;
continue;
}
let cells: Vec<CellData> = row
.cells
.iter()
.map(|cell| {
let text = cell_text(cell);
CellData::new(text)
})
.collect();
let row_data = RowData::new(cells);
match T::from_row(&row_data) {
Ok(item) => rows.push(item),
Err(e) => {
eprintln!("warning: skipping row: {e}");
}
}
}
if !rows.is_empty() {
all_tables.push(rows);
}
}
}
}
Ok(all_tables)
}
fn cell_text(cell: &office_oxide::ir::TableCell) -> String {
let mut text = String::new();
for element in &cell.content {
if let Element::Paragraph(para) = element {
if !text.is_empty() {
text.push('\n');
}
for inline in ¶.content {
if let InlineContent::Text(span) = inline {
text.push_str(&span.text);
}
}
}
}
text
}