Files
craft-libs/crates/xlsx/README.md
T
Dylan M. Taylor 7dcc9950a1 craft-xlsx: pictures in cells and dynamic-array metadata
craft-xlsx: pictures in cells and dynamic-array metadata

* craft-xlsx: read parts with canonical prefixes so kept trees can be written back

The part layer read every part by local name, so extLst children (x14:, xr:, xm:),
mc:AlternateContent and r:id lost their namespaces and could not be written back.
Parts are now read with one fixed prefix per namespace (the main namespace unprefixed),
mc:Ignorable and mc:Choice/@Requires are rewritten to match, and ns::declare prepares a
kept tree for writing. craft-xml: a namespace listed with the empty prefix reads unprefixed.

* craft-xlsx: write SpreadsheetML workbooks (Document read and write)

Document holds a whole workbook: the workbook part, shared strings, styles, each worksheet's
rows and the rest of its part, and every other part kept as bytes. write regenerates the
workbook part, worksheets, shared strings and styles from the model, keeps unknown children
in place (schema order for regenerated ones), keeps part names and relationship ids, and
writes kept parts unchanged. Writers: Styles::to_xml and to_el for each table entry,
StringItem::to_el (phonetic runs checked as on reading), SheetPart::to_xml with rows
streamed, Workbook::to_el, XlsxKind. Row and DefinedName keep their other attributes;
CellStyle reads iLevel. craft-opc: LazyPackage::canonical gives a part's stored name.

* craft-xlsx: typed worksheet features and comments, written back

SheetPart::features lifts sheet views, print setup, data validation, conditional formats,
hyperlinks and the legacy drawing into types that mirror their elements (optional attributes,
unnamed ones kept) and writes them in schema order. Comments come from the comments part,
note shapes from the VML drawing; a sheet that gains comments without a VML drawing gets one.
A hyperlink to a new URL gets its own relationship. craft-sheet: Range derives Default.

* craft-xlsx: drawings, charts and the theme on craft-drawingml

Drawing reads a sheet's xdr:wsDr into anchors with typed placements and objects kept as
written; Anchor gives the object's kind, properties, chart or image relationship and xfrm.
Package::drawing, Package::chart (c: and cx: parts through craft-drawingml) and
Package::theme. Document worksheets hold their drawing part, regenerated with its
relationships kept by id; add_chart and add_picture add new chart parts (chartEx behind
mc:AlternateContent) and images with their anchors. More drawing namespaces get prefixes
(a16, asvg, aink, am3d, cx1-cx4, dgm).

* craft-xlsx: tables (ListObjects) read and written back

Table mirrors CT_Table: every attribute, columns with calculated and totals formulas, the
style info, autoFilter/sortState/extensions kept as written. Package::tables reads a sheet's
table parts; Document worksheets hold them as TableParts, regenerated with their
relationships and tableParts entries. Part names a document keeps are reserved before new
parts are named, so a new part never takes a name a later sheet keeps.

* craft-xlsx: PivotTables (caches, records, tables) read and written

Typed pivot cache definitions (source, fields, shared items, grouping),
streamed cache records and PivotTable definitions (location, fields and
items, axes, page and data fields, style), with every unmodelled child
and attribute kept. Workbook::pivot_caches lifts pivotCaches. A Document
holds caches and tables and regenerates their parts, relationships and
pivotCaches entries; an OLAP cache or a table on one stays a kept part.
SharedItems::describe gives Excel's shared-item flags and records_xml
writes a records part, after GridCraft's pivot writer.

* craft-xlsx: pictures in cells and dynamic-array metadata

Package::cell_pictures follows a cell's vm through the value and future
metadata to its _localImage rich value and image, bounded and lenient as
GridCraft's richdata.rs reads it. Document::add_cell_metadata writes the
metadata part (XLDAPR for dynamic arrays, XLRICHVALUE for pictures), the
rich-value parts and the images as GridCraft writes them.
2026-11-11 22:44:39 -05:00

107 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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()` | `StringItem`s: 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 `Cell`s 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 |
Every part is read with fixed prefixes (`ns::CANONICAL`): the SpreadsheetML main namespace
(transitional or strict) unprefixed, the others by `ns::NAMESPACES` (`r:id`, `mc:AlternateContent`,
`x14:sparklineGroups`, `xm:sqref`), and the prefixes in `mc:Ignorable` and `mc:Choice/@Requires`
rewritten to match. A tree kept from a part goes back out with `ns::declare`, which declares what
it uses. A name in a namespace that is not listed reads as `?:name` and is not written.
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.
## Writing
`Document::read` holds a whole SpreadsheetML workbook: the `Workbook`, shared strings, `Styles`,
each worksheet's `Row`s and `SheetPart`, and every other part as a `KeptPart` (bytes and content
type: drawings, charts, the theme, document properties, a VBA project, their relationships).
`Document::write` regenerates the workbook part (sheets, defined names, date system, active tab),
the worksheets (dimension, columns, merged cells, rows), the shared strings and the styles tables
from those fields, keeps the rest of each part's tree in place, and writes the kept parts as they
were. Part names and relationship ids are kept, so a file read and written without changes points
where it did, and writing it twice gives the same bytes. The calculation chain is dropped (Excel
rebuilds it). `XlsxKind` is the workbook part's content type (`.xlsx`, `.xlsm`, `.xltx`, `.xltm`).
Typed worksheet features (`SheetPart::features`), each mirroring its element with optional
attributes and the unnamed ones kept: sheet views (panes, selections), print setup (options,
margins, page setup, header and footer, page breaks), data validation (also read from the `x14`
extension), conditional formats (colour scales, data bars, icon sets, rule extensions kept),
hyperlinks (with their URLs), the legacy drawing. Comments (`Comments`, from the comments part)
and the note shapes of the VML drawing (`NoteShape`). A sheet that gains comments and has no VML
drawing gets one (`notes_vml`, in a shape block no kept drawing uses); an existing VML drawing is
kept as read. Extensions in `extLst` are carried through unchanged.
Drawings and charts, on `craft-drawingml`: `Package::drawing` reads a sheet's `xdr:wsDr` into a
`Drawing` of `Anchor`s, each with a typed `Placement` (two-cell, one-cell, absolute, with EMU
offsets) and its object kept as written; `Anchor` names the object's kind, its `cNvPr`, its chart
or image relationship and its `a:xfrm`, and `craft_drawingml::read` reads its fill, line and text.
`mc:AlternateContent` items (Office 2016 charts, ink) are kept in place, and
`Drawing::alternate_anchors` reads the anchors inside them. `Package::chart` reads a chart part
(`c:` or `cx:`) into `craft_drawingml::chart::Chart`; `Package::theme` reads the theme. A
`Document` worksheet holds its `DrawingPart` (regenerated, its relationships kept by id);
`Document::add_chart` and `Document::add_picture` add a chart part written by `craft-drawingml`
(a `chartEx` part behind `mc:AlternateContent` for the Office 2016 types) or an image, with its
anchor. Chart parts and media are kept parts.
Tables: `Package::tables` reads a sheet's table parts (in `tableParts` order) into `Table`
(every `CT_Table` attribute, `TableColumn`s with calculated and totals formulas,
`TableStyleInfo`; `autoFilter`, `sortState` and extensions kept as written). A `Document`
worksheet holds them as `TablePart`s, regenerated on writing with their relationships and
`tableParts` entries; a removed table takes both with it. Every part name a document keeps is
reserved before new parts are named, so a new part never takes a name another sheet's part
keeps.
PivotTables: `Workbook::pivot_caches` lists the caches by id; `Package::pivot_cache` reads a cache
definition (`PivotCacheDefinition`: source, fields with shared items and grouping),
`Package::pivot_records` streams its records, and `Package::pivot_tables` reads a sheet's
PivotTables (`PivotTableDefinition`: location, fields and items, axes, page and data fields,
style) with the cache each uses. `SharedItems::describe` gives the flags Excel writes for a field's
values, and `records_xml` writes a records part. A `Document` holds caches as `PivotCachePart`s
and a worksheet's tables as `PivotTablePart`s, regenerated with their relationships and
`pivotCaches` entries; an OLAP cache (and a table on one) stays a kept part.
Cell metadata: `Package::cell_pictures` (and `Document::cell_pictures`) follows a cell's `vm`
through the value and future metadata to its `_localImage` rich value and image, as
`CellPicture` (PNG or JPEG, alternative text). Every table is bounded (`MAX_RECORDS`, `MAX_PICTURE`,
`MAX_PICTURES_TOTAL`); what does not resolve is `None` with a warning, and the cell keeps its saved
value. `Document::add_cell_metadata` writes the metadata part, the rich-value parts and the images
for new pictures (`vm = i + 1`) and the `XLDAPR` dynamic-array metadata (`cm = 1`).
The parts this crate writes use the transitional namespaces; a strict file's kept parts stay as
they were. A binary workbook is not written.
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.