Skip to content

Add Excel .xlsx / ODS spreadsheet parser - #44

Merged
copyleftdev merged 1 commit into
mainfrom
feat/xlsx
May 31, 2026
Merged

copyleftdev merged 1 commit into
mainfrom
feat/xlsx

Conversation

@copyleftdev

@copyleftdev copyleftdev commented May 31, 2026 •

Copy link
Copy Markdown
Owner

Implements the Excel / OpenDocument plugin — issue #21.

What

The first worksheet of a workbook (.xlsx / .xls / .xlsb / .ods) becomes a
RecordSet: the first row is the header (column names), each subsequent row a
record, and every cell maps to the closed Value set — so all detectors apply
with no special-casing
. A date/time cell keeps its Excel serial number
(numeric, deterministic); blank and error cells are Null (honest absence).

Reading is delegated to calamine (pure Rust, reads all four formats).

Routing

  • Detected by the ZIP magic (PK\x03\x04) plus an xl/ part (xlsx/xlsb) or the
    OpenDocument-spreadsheet mimetype (ods) — so a .docx / .jar / plain zip is
    not claimed. The OLE2-based .xls is covered by extension.
  • Confidence MAGIC; extensions .xlsx / .xls / .xlsb / .ods.

Feature gating

Behind the default-on xlsx feature (binary format), so the text-only
--no-default-features build stays lean. Both builds verified.

Testing

The roundtrip test writes a tiny .xlsx in-memory with rust_xlsxwriter
(dev-dependency — no committed binary fixture) and parses it back;
data_to_value and header_name are unit-tested across every cell kind
(int/float/NaN/string/bool/date/empty/error); non-workbook input is a clean
AxError::Parse.

Gates

  • fmt / clippy -D warnings / full workspace tests green (default and
    --no-default-features).
  • Mutation gate: 0 surviving mutants on xlsx.rs.

Closes #21

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for parsing spreadsheet files in XLSX, XLS, XLSB, and ODS formats
    • Automatic spreadsheet detection now enabled by default
    • Spreadsheet content converted to structured columns and records with header extraction and value mapping

New XlsxParser in ax-normalize. The first worksheet of a workbook
(.xlsx/.xls/.xlsb/.ods) becomes a RecordSet: the first row is the header (column
names), each subsequent row a record, and every cell maps to the closed Value
set — so all detectors apply with no special-casing. A date/time cell keeps its
Excel serial number (numeric, deterministic); blank and error cells are Null.

Reading is delegated to calamine (pure Rust, all four formats). Detected by the
ZIP magic (PK\x03\x04) plus an xl/ part (xlsx/xlsb) or the OpenDocument-
spreadsheet mimetype (ods), so a docx/jar/plain zip is not claimed; the OLE2 xls
is covered by extension. Confidence MAGIC; extensions xlsx/xls/xlsb/ods. Behind
the default-on xlsx feature (binary format), so the text-only build stays lean.

Roundtrip test writes a tiny .xlsx in-memory with rust_xlsxwriter (dev-dep, no
committed binary); data_to_value and header_name are unit-tested across all
cell kinds; non-workbook input is a clean Parse error.

Mutation gate: 0 surviving mutants on the new file.

Closes #21

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR adds optional spreadsheet parsing to ax-normalize using the calamine crate. It introduces a new XlsxParser that detects and parses Excel/ODS files by reading the first worksheet into columns, with header inference and cell-to-Value conversion rules including NaN nullification and date serial preservation.

Changes

XLSX Parser Feature

Layer / File(s) Summary
Dependency and feature configuration
crates/ax-normalize/Cargo.toml
Adds calamine optional dependency and rust_xlsxwriter dev-dependency; introduces xlsx feature flag included in default alongside existing format parsers.
Parser module registration
crates/ax-normalize/src/parsers/mod.rs
Conditionally declares xlsx submodule, re-exports XlsxParser, and registers it in default_registry() when feature is enabled.
XlsxParser implementation
crates/ax-normalize/src/parsers/xlsx.rs
Implements FormatParser for XLSX/XLS/XLSB/ODS: sniffs via ZIP+spreadsheet markers, parses first sheet with string headers (trimmed) or fallback col{index} naming, maps cells to Value (non-finite floats and blanks→Null, dates→Float serial numbers).
Test coverage
crates/ax-normalize/src/parsers/xlsx.rs, crates/ax-normalize/src/parser.rs
Test utilities for in-memory spreadsheet generation; validates worksheet parsing, header rules, value mappings, sniff detection, extension matching, error handling, and registry resolution.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • copyleftdev/anomalyx#29: Both PRs extend the format parser registry in ax-normalize by adding parser IDs (xlsx in this PR; toml/ini in #29) to the module registry and the default_registry_lists_all_formats test.

Poem

🐰 A spreadsheet hops through calamine's gate,
First rows become headers, cells translate,
To Values they flow—no NaN too great,
Dates as floats, tests validate the fate!
One parser to rule them: xlsx's state. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding an Excel/ODS spreadsheet parser to the codebase.
Linked Issues check ✅ Passed All acceptance criteria from issue #21 are met: XlsxParser implements FormatParser trait, is registered in default_registry, includes roundtrip/sniff/error tests, passes cargo fmt/clippy, and has 0 surviving mutants.
Out of Scope Changes check ✅ Passed All changes are scoped to issue #21 requirements: new parser implementation, feature gating via Cargo.toml, and supporting test updates. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 85.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/xlsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@copyleftdev
copyleftdev merged commit 465e567 into main May 31, 2026
1 of 2 checks passed
@copyleftdev
copyleftdev deleted the feat/xlsx branch May 31, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an Excel .xlsx / ODS parser

1 participant