basis1excel

Verified Excel semantics

Why Excel says it found unreadable content in your workbook

"Excel found unreadable content in ‘file.xlsx’. Do you want us to try to recover as much as we can?" The file usually isn't damaged. One small, entirely optional part inside the zip has gone stale.

Verified against: core/tests/integration.rs, 3 passing tests·Issue #154
92%

of real formula-bearing .xlsx files in the FUSE corpus (3,353 of 3,640) ship an xl/calcChain.xml part — measured directly against 10,703 real workbooks. Every one of them is a candidate for this exact prompt the moment a tool adds or removes a formula cell without accounting for it.

The answer

xl/calcChain.xml caches the order Excel last evaluated formula cells in — a performance hint, not data. The OOXML spec (ECMA-376) marks it optional; Excel rebuilds it from scratch on every save regardless of what was there before.

The trap: a well-behaved writer preserves every part of the zip it doesn't understand byte-for-byte — styles, pivot caches, VBA, calcChain, everything — and only rewrites the parts it actually changed. That's the right default for almost everything in the file. It's the wrong default for calcChain specifically: the moment a formula lands in a cell that had none before (or a formula cell is deleted), the chain Excel loaded from disk no longer lists the workbook's actual formula set. Excel's own open-time validation notices the mismatch and offers to "repair" the file — which it does by discarding exactly this part and rebuilding it.

Reproduce it

The regression test that pins this down end to end:

#[test]
fn stale_calcchain_is_dropped_on_save() {
    // fixture ships a real xl/calcChain.xml from an actual saved workbook
    let wb = xlsx::load(&fixture("calcchain.xlsx")).expect("load");
    let mut doc = Doc::new(wb, "import", "bk");

    // B5 was blank in the original -- the calcChain doesn't know it exists
    doc.set_formula(1, 4, "B1*3", Source::Edit).expect("compile");
    recalc_all(&mut doc.wb);
    let saved = xlsx_write::save(&doc.wb);

    // the stale chain, its Content_Types override, and its relationship
    // entry are all gone -- Excel rebuilds the chain silently on open
    assert!(Archive::parse(&saved).unwrap().by_name("xl/calcChain.xml").is_none());
}

By hand: open any .xlsx that already has formulas (almost all of them, per the corpus figure above), type a new formula into a cell that was previously blank, and save it back out with anything that does a naive full-part passthrough except for the sheet you touched. Reopen in Excel.

Excel vs. most tools

Excel

Never trusts a calcChain against a formula set it didn't just compute itself. Mismatch → repair prompt → chain discarded → rebuilt. Costs the user a scary dialog for a part they never knew existed.

Naive full-passthrough writers

Preserve every unmodeled part byte-for-byte, including calcChain, because that's the simplest way to avoid corrupting the 99% of the file they don't understand. Correct until a formula's presence changes — then it's the specific part standing between a clean save and a repair dialog.

Basis keeps the byte-for-byte passthrough for everything else — styles, pivots, VBA, other sheets — and only drops calcChain (plus its two registration entries in [Content_Types].xml and workbook.xml.rels) when it detects the formula shape changed. Workbooks edited without adding or removing a formula cell keep a fully byte-identical save, verified by a separate test in the same file (workbooks_without_a_calcchain_are_untouched).

Editing .xlsx files programmatically? This is one of dozens of Excel fidelity rules a passthrough writer has to get right to avoid corrupting files it doesn't fully understand — verified against 104,344 real formulas at zero mismatches.

Open a workbookUse the API