Skip to content

fix: template rendering corrupts workbooks that contain formulas (calcChain + $= cell serialization)#990

Open
martin-vitous wants to merge 1 commit into
mini-software:v1.x-maintenancefrom
martin-vitous:fix/template-calcchain-formula-cells
Open

fix: template rendering corrupts workbooks that contain formulas (calcChain + $= cell serialization)#990
martin-vitous wants to merge 1 commit into
mini-software:v1.x-maintenancefrom
martin-vitous:fix/template-calcchain-formula-cells

Conversation

@martin-vitous

@martin-vitous martin-vitous commented Jul 23, 2026

Copy link
Copy Markdown

Problem

Rendering a template that contains any formula produces a file that Excel refuses to open ("the file is corrupt and cannot be opened" — repair also fails). Two independent defects in SaveAsByTemplate, isolated against Excel (Microsoft 365):

1. Broken calcChain.xml after rendering

A template containing formulas carries xl/calcChain.xml, whose <c> entries point at formula-cell addresses. Rendering an IEnumerable inserts rows and shifts those cells, so the chain is regenerated (#491) — but the regeneration is broken in three ways:

  • Wrong cell references. ProcessFormulas derives the calcChain ref from the cell's position in the row's child list (ConvertXyToCell(ci + 1, rowIndex)). In a sparse row (e.g. a label in column A and a $= formula in column F, no cells between), the formula is child Query Support Sheet Xml c without r #2 and gets recorded as column B. Excel rejects a chain pointing at non-formula cells.
  • Schema-invalid empty chain. Only $= cells are collected. When a template's formulas are all static Excel formulas (a footer VLOOKUP, a =E27*B27 row total, …), the regenerated chain has zero entries — and a calcChain with no <c> children is schema-invalid (ECMA-376), so Excel rejects the whole package.
  • Stale refs accumulate. _calcChainCellRefs is never cleared between sheets, so a multi-sheet template duplicates sheet 1's refs into sheet 2's entries; and one code path copied the template's original calcChain into the output verbatim, whose refs are stale the moment rows shift.

2. $= formulas serialize as invalid cells

ProcessFormulas creates the f element without a namespace prefix; it then relies on a default xmlns declaration that CleanXml strips, leaving <f> in no namespace. The cell also keeps its t="inlineStr" attribute while no longer containing an <is> child:

<x:c r="F14" s="0" t="inlineStr"><f>SUM(G11:G13)</f></x:c>   <!-- rejected by Excel -->

Either defect alone is enough for Excel to refuse the file, so in 1.45.0 the template-formula feature and any formula-bearing template are unusable. Likely the root cause behind reports like #632/#857; reproduced identically on 2.0.0-preview.4.

Fix

ExcelOpenXmlTemplate.Impl.csProcessFormulas:

  • create the f element with the document's prefix so it survives CleanXml (<x:f>…</x:f>);
  • remove the cell's t attribute (the cell no longer holds an inline string);
  • take the calcChain ref's column from the cell's own r attribute instead of its child-list position (falls back to the old computation when r is absent).

ExcelOpenXmlTemplate.csSaveAsByTemplate:

  • clear _calcChainCellRefs after each sheet;
  • write the regenerated calcChain only when it has entries; otherwise drop the part entirely — the calculation chain is a rebuildable cache that Excel regenerates on open, while an empty or stale one makes the file unopenable;
  • remove the branch that copied the template's original (stale) calcChain into the output.

Validation

  • New regression tests (MiniExcelTemplateCalcChainTests) fail on current 1.45.0 sources and pass with the fix. Templates are authored in-test with ClosedXML (already a test dependency).
  • Full MiniExcelTests suite: 360/360 pass.
  • Manually verified with Excel (COM automation) on a matrix of templates — plain, number formats, merged cells, hidden rows + defined names, static VLOOKUP, $=SUM, and a real-world quote template (embedded images, custom XML parts, conditional formatting, dozens of static formulas). All rendered files that previously failed now open, and the formulas compute ($=SUM ranges resolve to the expanded rows, per-row static formulas recalculate).

Notes

  • When the chain is dropped, the workbook relationship and [Content_Types] override still reference the absent part. Excel tolerates this; strict consumers (System.IO.Packaging) do not. Cleaning those two entries requires touching the entry-copy path — happy to add it to this PR if you prefer.
  • Unrelated pre-existing quirk (not addressed here): defined names whose range lies below the inserted rows are not shifted, so formulas referencing them can mis-resolve after rendering.
  • The same two defects reproduce on the 2.0.0-preview line; I can port this fix there if wanted.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed formula cells generated from templates so they retain correct Excel formatting and cell references, including sparse rows.
    • Prevented invalid inline-string formula cells from being created.
    • Corrected calculation-chain entries after template rendering, avoiding stale or empty formula references.

Rendered '\$=' formulas were written as an un-namespaced <f> inside a t=\"inlineStr\" cell, and the regenerated calcChain.xml carried wrong cell references (or none, which is schema-invalid) - either defect makes Excel refuse the output. Fixes the formula-cell serialization, derives calcChain refs from the cell's own address, drops the chain when empty instead of writing an invalid part, and clears collected refs between sheets.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09d09b40-ce9d-4608-a875-71d6d22d68df

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs (1)

135-152: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove calcChain package metadata when omitting the part.

When _calcChainContent is empty, this drops xl/calcChain.xml, but the copied xl/_rels/workbook.xml.rels still targets it and [Content_Types].xml still declares it. That leaves an invalid package relationship. Remove the calcChain relationship and its content-type override whenever no calcChain part is emitted; extend TemplateWithStaticFormula_DoesNotWriteStaleOrEmptyCalcChain to assert both are absent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs` around lines 135 - 152,
Update the calcChain handling around _calcChainContent and
CalcChainHelper.GenerateCalcChainSheet so that when no calcChain content is
emitted, the corresponding calcChain relationship in workbook.xml.rels and its
[Content_Types].xml override are also removed. Preserve generation of the part
and metadata when content exists, and extend
TemplateWithStaticFormula_DoesNotWriteStaleOrEmptyCalcChain to assert both
metadata entries are absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs`:
- Around line 1018-1023: Update the formula-processing logic around celRef to
use the complete cell reference from c.GetAttribute("r") directly, preserving
the existing fallback to ExcelOpenXmlUtils.ConvertXyToCell only when the r
attribute is absent. Ensure calcChain entries use the rendered address rather
than the advanced newRowIndex, and add a regression case covering $= inside an
expanded enumerable row.

---

Outside diff comments:
In `@src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs`:
- Around line 135-152: Update the calcChain handling around _calcChainContent
and CalcChainHelper.GenerateCalcChainSheet so that when no calcChain content is
emitted, the corresponding calcChain relationship in workbook.xml.rels and its
[Content_Types].xml override are also removed. Preserve generation of the part
and metadata when content exists, and extend
TemplateWithStaticFormula_DoesNotWriteStaleOrEmptyCalcChain to assert both
metadata entries are absent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a81869c-b9cc-4d08-9002-2a8802662a70

📥 Commits

Reviewing files that changed from the base of the PR and between a32b538 and 4e74b72.

📒 Files selected for processing (3)
  • src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs
  • src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.cs
  • tests/MiniExcelTests/SaveByTemplate/MiniExcelTemplateCalcChainTests.cs

Comment thread src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs
@michelebastione

Copy link
Copy Markdown
Collaborator

@martin-vitous Thank you for your contribution, I'm reviewing it now. If you could please prepare a PR for the main branch it would be appreciated.

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.

2 participants