From ba1570239c6e5366b1064dce1a018fed95dba2b7 Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:40 +0800 Subject: [PATCH 1/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- src/model/table.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/model/table.rs b/src/model/table.rs index 42f16ca..0cc3809 100644 --- a/src/model/table.rs +++ b/src/model/table.rs @@ -16,6 +16,15 @@ pub struct Table { pub header_rows: usize, /// Whether the source used this table for data or for layout. pub kind: TableKind, + /// Spreadsheet worksheet name when the table came from a workbook sheet. + /// Always set for Excel/ODS sheets (including single-sheet workbooks). + pub sheet_name: Option, + /// Zero-based absolute row of the used-range origin in the source sheet + /// (e.g. Excel `C3` → `source_row = 2`). Grid index `(r, c)` maps back + /// with `(source_row + r, source_col + c)`. + pub source_row: Option, + /// Zero-based absolute column of the used-range origin in the source sheet. + pub source_col: Option, } /// What a table is for. @@ -265,7 +274,14 @@ impl GridBuilder { } } } - Table { grid: self.grid, header_rows: 0, kind } + Table { + grid: self.grid, + header_rows: 0, + kind, + sheet_name: None, + source_row: None, + source_col: None, + } } } From 166a80071e899e6391817f1f094116b7ecef6615 Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:43 +0800 Subject: [PATCH 2/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- src/formats/sheet/mod.rs | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/formats/sheet/mod.rs b/src/formats/sheet/mod.rs index f61ebcc..8595756 100644 --- a/src/formats/sheet/mod.rs +++ b/src/formats/sheet/mod.rs @@ -105,6 +105,11 @@ pub fn parse(bytes: &[u8]) -> Result { continue; } table.header_rows = resolve_header_rows(&table, 0); + // Preserve worksheet identity and the used-range origin so callers can + // map cropped grid cells back to absolute sheet coordinates (C3, …). + table.sheet_name = Some(name.clone()); + table.source_row = Some(start.0); + table.source_col = Some(start.1); if multi_sheet { doc.blocks.push(Block::heading(2, vec![Inline::plain(name.clone())])); } @@ -307,4 +312,56 @@ mod tests { assert_eq!(format_duration_days(days), "26:30:15"); assert_eq!(format_duration_days(-0.5), "-12:00:00"); } + + /// Minimal xlsx: single sheet "Data Sheet", sole value at C3. + fn xlsx_value_at_c3() -> Vec { + let sheet = r#"Only value"#; + let parts: &[(&str, &str)] = &[ + ( + "[Content_Types].xml", + r#""#, + ), + ( + "_rels/.rels", + r#""#, + ), + ( + "xl/workbook.xml", + r#""#, + ), + ( + "xl/_rels/workbook.xml.rels", + r#""#, + ), + ]; + let mut w = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); + for (name, body) in parts { + w.start_file(*name, zip::write::SimpleFileOptions::default()).unwrap(); + w.write_all(body.as_bytes()).unwrap(); + } + w.start_file("xl/worksheets/sheet1.xml", zip::write::SimpleFileOptions::default()) + .unwrap(); + w.write_all(sheet.as_bytes()).unwrap(); + w.finish().unwrap().into_inner() + } + + #[test] + fn single_sheet_exposes_name_and_used_range_origin() { + let doc = parse(&xlsx_value_at_c3()).unwrap(); + let Some(Block::Table(t)) = doc.blocks.first() else { + panic!("expected a table, got {:?}", doc.blocks.first()); + }; + assert_eq!(t.sheet_name.as_deref(), Some("Data Sheet")); + // C3 is zero-based (2, 2); the cropped grid still starts at [0][0]. + assert_eq!(t.source_row, Some(2)); + assert_eq!(t.source_col, Some(2)); + assert_eq!(t.grid.len(), 1); + assert_eq!(t.grid[0].len(), 1); + match &t.grid[0][0] { + crate::model::CellSlot::Origin(c) => { + assert!(!c.is_empty()); + } + other => panic!("expected origin cell, got {other:?}"), + } + } } From 1cd66ad936d480b77f42c247c2eb453325677f92 Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:47 +0800 Subject: [PATCH 3/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- src/formats/odf/table.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/formats/odf/table.rs b/src/formats/odf/table.rs index 48e42fb..2784d7f 100644 --- a/src/formats/odf/table.rs +++ b/src/formats/odf/table.rs @@ -410,7 +410,17 @@ pub fn parse_spreadsheet(sheet: &Element, ctx: &Ctx) -> Result, Conve if multi_sheet { blocks.push(Block::heading(2, vec![Inline::plain(name)])); } - blocks.extend(content); + for block in content { + match block { + Block::Table(mut t) => { + if !name.is_empty() { + t.sheet_name = Some(name.to_string()); + } + blocks.push(Block::Table(t)); + } + other => blocks.push(other), + } + } } Ok(blocks) } From 483487005a4c5841d2f5b2b2a0663d4f3c563d5c Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:50 +0800 Subject: [PATCH 4/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- python/src/document.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/python/src/document.rs b/python/src/document.rs index e24cb66..2b5567f 100644 --- a/python/src/document.rs +++ b/python/src/document.rs @@ -267,6 +267,12 @@ pub struct Table { /// data (a real data table) or layout (layout scaffolding: text boxes, /// positioning tables). kind: &'static str, + /// Spreadsheet worksheet name when known. + sheet_name: Option, + /// Zero-based absolute row of the used-range origin in the source sheet. + source_row: Option, + /// Zero-based absolute column of the used-range origin in the source sheet. + source_col: Option, } fn table(py: Python<'_>, table: model::Table) -> PyResult { @@ -281,6 +287,9 @@ fn table(py: Python<'_>, table: model::Table) -> PyResult
{ model::TableKind::Data => "data", model::TableKind::Layout => "layout", }, + sheet_name: table.sheet_name, + source_row: table.source_row, + source_col: table.source_col, }) } From d2c5871bb78fedbe95ccf65f211ff2f040a015ce Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:52 +0800 Subject: [PATCH 5/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- node/src/document.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/node/src/document.rs b/node/src/document.rs index 1cd6617..c4fad23 100644 --- a/node/src/document.rs +++ b/node/src/document.rs @@ -331,6 +331,12 @@ pub struct Table { /// Number of leading rows that are header rows (0 = no header). pub header_rows: u32, pub kind: TableKind, + /// Spreadsheet worksheet name when known. + pub sheet_name: Option, + /// Zero-based absolute row of the used-range origin in the source sheet. + pub source_row: Option, + /// Zero-based absolute column of the used-range origin in the source sheet. + pub source_col: Option, } impl From for Table { @@ -346,6 +352,9 @@ impl From for Table { model::TableKind::Data => TableKind::data, model::TableKind::Layout => TableKind::layout, }, + sheet_name: table.sheet_name, + source_row: table.source_row, + source_col: table.source_col, } } } From 8a68bcc44d34d1789e3f4c7fa0028216915fe775 Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:54 +0800 Subject: [PATCH 6/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- node/index.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/node/index.d.ts b/node/index.d.ts index 27ceba0..d30a9b1 100644 --- a/node/index.d.ts +++ b/node/index.d.ts @@ -253,6 +253,12 @@ export interface Table { /** Number of leading rows that are header rows (0 = no header). */ headerRows: number kind: TableKind + /** Spreadsheet worksheet name when known. */ + sheetName?: string + /** Zero-based absolute row of the used-range origin in the source sheet. */ + sourceRow?: number + /** Zero-based absolute column of the used-range origin in the source sheet. */ + sourceCol?: number } export declare const enum TableKind { From a759f3fed1211989a345bfce641bf1079754a731 Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:54:57 +0800 Subject: [PATCH 7/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- wasm/src/document.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/wasm/src/document.rs b/wasm/src/document.rs index 8b5d971..6927d1c 100644 --- a/wasm/src/document.rs +++ b/wasm/src/document.rs @@ -355,6 +355,15 @@ pub struct Table { /// Number of leading rows that are header rows (0 = no header). pub header_rows: u32, pub kind: TableKind, + /// Spreadsheet worksheet name when known. + #[serde(skip_serializing_if = "Option::is_none")] + pub sheet_name: Option, + /// Zero-based absolute row of the used-range origin in the source sheet. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_row: Option, + /// Zero-based absolute column of the used-range origin in the source sheet. + #[serde(skip_serializing_if = "Option::is_none")] + pub source_col: Option, } impl From for Table { @@ -370,6 +379,9 @@ impl From for Table { model::TableKind::Data => TableKind::Data, model::TableKind::Layout => TableKind::Layout, }, + sheet_name: table.sheet_name, + source_row: table.source_row, + source_col: table.source_col, } } } From 48736ad2bb3b9daad25df8e1eeeb7e7adb476688 Mon Sep 17 00:00:00 2001 From: f1vwpbleiia7ie Date: Sat, 8 Aug 2026 22:55:00 +0800 Subject: [PATCH 8/8] feat(sheet): expose worksheet name and used-range origin on Table Signed-off-by: f1vwpbleiia7ie --- wasm/src/typescript.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wasm/src/typescript.rs b/wasm/src/typescript.rs index 9a5cba6..6e4fadc 100644 --- a/wasm/src/typescript.rs +++ b/wasm/src/typescript.rs @@ -178,6 +178,12 @@ export interface Table { /** Number of leading rows that are header rows (0 = no header). */ headerRows: number kind: TableKind + /** Spreadsheet worksheet name when known. */ + sheetName?: string + /** Zero-based absolute row of the used-range origin in the source sheet. */ + sourceRow?: number + /** Zero-based absolute column of the used-range origin in the source sheet. */ + sourceCol?: number } export type CellSlotKind = 'origin' | 'covered'