fix(sheet): render xlsx number formats (percent, currency, thousands) - #72
Open
SomSamantray wants to merge 6 commits into
Open
fix(sheet): render xlsx number formats (percent, currency, thousands)#72SomSamantray wants to merge 6 commits into
SomSamantray wants to merge 6 commits into
Conversation
Add an ssfmt-based number-format pipeline: the renderer adapter (src/formats/sheet/numfmt.rs) resolves builtin and custom format codes with date/text/General guards and fallback, and the OOXML styles reader (src/formats/sheet/styles.rs) maps cells to their format codes from styles.xml and the worksheet parts. Nothing is wired into the parse loop yet.
Wire the format lookup into parse(): OOXML workbooks (xlsx/xlsm/xlam) map each cell to its format code from styles.xml and the worksheet parts, and Data::Float/Data::Int cells render through the ssfmt adapter with raw fallback. Date/time cells keep calamine's DateTime path, string cells ignore codes, and the LibreOffice corpus fixture now renders percent, currency, and thousands cells as Excel displays them.
handmade-numberformats.xlsx pins percent, currency, thousands, decimals, scientific, negative-section, currency-locale, fraction, General, string, date, and pipe-literal format cells end-to-end through the markdown pipeline, with the escaped pipe pinned in the snapshot.
- from_ooxml degrades through Package::optional_part / is_fatal so a corrupt styles or worksheet part falls back to raw rendering instead of failing the conversion, matching the crate's recovery policy - code_for_style routes builtin ids through the shared renderability guard instead of a hand-maintained id list (locale-currency 41-44 stay excluded) - tests share one xlsx zip builder (test_util), rebasing xlsx_with_merge onto it
- gate the Data::Int format path on i64->f64 round-trip exactness so integers beyond 2^53 keep their exact digits (raw fallback) - bound parse_cell_ref with checked arithmetic and Excel's XFD column cap so adversarial references degrade instead of overflowing - propagate fatal ResourceLimit errors from the styles/worksheet XML parse instead of swallowing them (crate limits policy), with a warn log when a sheet part is unparseable - scan only the first format section for renderability, so fourth-section text placeholders (0.00%;...;@) stay renderable like Excel/calamine - render present-but-empty selected sections as blank, matching Excel display for 0.00; and 0.00;; - drop the no-op 41..=44 builtin guard (ssfmt already yields None) - tests: multi-sheet mapping, ResourceLimit propagation, absurd cell refs, non-A1 range alignment, large-integer precision, 4-section codes, empty sections
SomSamantray
force-pushed
the
fix/xlsx-number-formats
branch
from
August 8, 2026 19:14
eabd483 to
242337f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spreadsheet numbers now render the way Excel displays them: a cell storing
0.155with a0.0%format converts to15.5%instead of the raw float, and currency and thousands-grouped cells carry their symbols and separators. Before, every numeric cell was formatted identically (15 significant digits), so formatted values like percentages were silently wrong.0.0%)0.15515.5%[$$-409]#,##0.00)1234.5$1,234.50#,##0)98765439,876,543Dates, durations, booleans, and strings are untouched: date/time cells already convert through calamine's
datesfeature.Design
ssfmt(MIT/Apache-2.0, zero dependencies withdefault-features = false, pinned 0.1.2) is an Excel-compatible ECMA-376 format renderer (99.9999% parity with SheetJS SSF over 19.5M cases). The adapter (src/formats/sheet/numfmt.rs) decides which codes are eligible at all:General, empty, date/time-like, and text codes keep today's raw rendering, and any renderer rejection falls back to raw. Nothing ever fails the conversion.xl/styles.xml(numFmts+cellXfs, honoringapplyNumberFormat) and per-sheetsheetN.xml(cellr/sattributes, positional fallback) are parsed with the existing package machinery and aligned to calamine's used range. Malformed parts degrade to raw with a log; resource-limit errors propagate per the crate's policy. xls/xlsb and ods keep their current output (documented limitation).[$$-409]), negative sections, fractions, and scientific notation render per Excel; a present-but-empty selected section displays blank. Known renderer quirks are pinned in unit tests (integer fast-path truncation on exact scaling halves; empty sections render as absent when not selected).Data::Intformat path is gated on i64-to-f64 round-trip exactness (integers beyond 2^53 keep their exact digits); cell-reference parsing uses checked arithmetic bounded to Excel's XFD column cap.Testing
Full suite green (
cargo test --locked: 245 lib tests + integration + snapshot corpus),clippy -D warningsandfmt --checkclean. The new corpus fixturehandmade-numberformats.xlsxpins percent, currency, thousands, decimals, scientific, negative-section, currency-locale, fraction, General, string, date, and pipe-literal cells end-to-end; the LibreOffice fixture snapshot changed exactly where its formatted cells live; an openpyxl-generated workbook cross-checks a second real producer.Fixes #27
Related: #64