Files
craft-libs/crates/xlsx
Dylan M. Taylorandkiwi4boy 119415f46f Add craft-xlsx: XLSX and XLSB part layer (workbook, strings, styles, streamed rows) (#976)
* Add craft-sheet: the cell positions, values and tables the spreadsheet formats share

craft-xls carried its own CellRef, Range, Value, CellError, Visibility, sheet-name
quoting, built-in number format table and indexed palette. craft-xlsx and craft-ods
need the same ones, so they move to a crate with no dependencies and craft-xls
re-exports them: an application gets one Value and one CellRef whichever file it read.

Added on the way: A1 parsing (CellRef::parse, Range::parse), Range::count and a1,
CellError::parse and the newer error values SpreadsheetML stores as text,
builtin_format_id for writers.

* Add craft-xlsx: the part layer of XLSX and XLSB workbooks

Package::open finds the workbook part through craft-opc and reads the workbook
part, shared strings (runs and phonetic readings), the styles tables as indexed
tables, and a worksheet's rows one at a time with the rest of the part as a tree.
Binary workbooks give the same workbook, strings, rows and merged cells from their
records. Every other part stays reachable through Package::opc.

From GridCraft crates/xlsx (7e0d96d7): read.rs, sheet_read.rs, styles.rs, xml.rs
(string items, xstring escapes), xlsb.rs and its tests, split from GridCraft's
document model.

* craft-xlsx: XLSB opens up to 4 million cells, records counted once over the workbook

Binary workbooks over 1 million cells were refused, a quarter of what .xls allows,
and an allowance of 5 million records per part refused ordinary files: Excel writes
four records per non-empty row besides the cells, and one BrtCellBlank for every
formatted empty cell. Cells are now 4 million over the workbook (also the most
shared strings), text 256 MiB, and records 40 million for every part together, as
in craft-xls.

Ported from storytold/gridcraft#3807.

---------

Co-authored-by: kiwi4boy <kiwi4boy@gmail.com>
2026-11-11 19:32:04 -05:00
..

craft-xlsx

Excel workbooks: .xlsx, .xlsm, .xltx, .xltm (SpreadsheetML in an OPC package, transitional and strict) and .xlsb (the same package with binary parts). Clean-room, from ECMA-376 Part 1 (SpreadsheetML), [MS-XLSB] and observed files. No unsafe.

This is the part layer. Package::open opens the container through craft-opc and reads:

Call Gives
workbook() Workbook: sheets in tab order (name, id, visibility, kind, part), the 1904 date flag, defined names, the active tab, and the whole part as a tree
shared_strings() StringItems: text, formatted runs, phonetic readings
styles() Styles: number formats, fonts, fills, borders, cell and cell-style formats, named styles, differential formats, the indexed palette, as indexed tables
sheet(entry, each) each Row of Cells to a callback, then a SheetPart: the rest of the worksheet as a tree, its relationships, columns and merged cells
opc(), read_xml(), optional_xml(), rels() any other part

A cell keeps the file's terms: a shared-string index, a format index, the formula text as the file spells it, value and cell metadata indexes. CellValue::resolve gives the value it shows. Shared formulas are not expanded and formulas are not parsed: that needs a formula grammar.

A binary workbook gives the same Workbook, strings, rows and merged cells, with saved values only: its formulas are token streams that are not decoded, and its styles are not read (each cell keeps its format index).

Hostile input

  • Limits.package: ZIP entries, bytes per part and in all (craft-opc).
  • Limits.xml: one XML part. Rows and shared strings are streamed, so this bounds one row, one string and what is left of a part, not a sheet's length.
  • Limits.binary_records, binary_cells, binary_text: a binary workbook, over all its parts (40 million records, 4 million cells and as many shared strings, 256 MiB of text, as craft-xls allows). Fixed caps: 1024 sheets, 100,000 merged ranges per sheet, 64 nested extension blocks.
  • Cells and rows outside the 1,048,576 × 16,384 grid are dropped and counted.

SpreadsheetML is read leniently: a damaged optional part is a warning, a damaged worksheet keeps the rows before the damage. A binary part that does not parse fails the read, since skipping a record would lose data without a trace.

Shared with craft-xls and craft-ods through craft-sheet: CellRef, Range, Value, CellError, Visibility, sheet-name quoting, built-in number formats and the palette.