Skip to content

This closes #2307, add RecalcCell to persist a single formula cell's cached value#2311

Open
krystophny wants to merge 2 commits into
qax-os:masterfrom
sloppy-org:2303-recalc-cell
Open

This closes #2307, add RecalcCell to persist a single formula cell's cached value#2311
krystophny wants to merge 2 commits into
qax-os:masterfrom
sloppy-org:2303-recalc-cell

Conversation

@krystophny
Copy link
Copy Markdown
Contributor

@krystophny krystophny commented Apr 23, 2026

Description

Add (*File).RecalcCell(sheet, cell string) error. It evaluates the formula in the given cell via the existing recursive calc engine, then writes the typed result into <v>/<t> through a private setCellCachedValue helper that leaves <f>, the shared-formula master, ref span, and si index intact. A new sentinel ErrCellNoFormula is returned when the target cell has no formula.

Public surface introduced by this PR:

  • (*File).RecalcCell(sheet, cell string) error
  • ErrCellNoFormula

No existing API is changed or removed.

Related Issue

Closes #2307. Companion to the umbrella #2303; the whole-workbook File.Recalc entry point built on top of RecalcCell will arrive in a separate PR once this lands.

Documentation

Reference-site entries for this PR ship in a companion docs PR: xuri/excelize-doc#31. That PR covers the English version; other language versions follow the project's usual rollout cadence.

Motivation and Context

CalcCellValue returns a formatted string but does not persist it, and every SetCell* writer strips the formula while writing a new value, which makes them useless for this purpose. Downstream readers that honour <v> without recomputing (LibreOffice headless, Numbers) therefore see stale results after a round trip through excelize. RecalcCell is the smallest primitive that closes that gap; it has zero cost to callers who do not use it.

Design notes reviewers are likely to ask about:

  • Fixed-point iteration. Not needed. calcCellValue already recurses into referenced cells during evaluation, so one call converges a chain.
  • Circular references. Bounded by f.options.MaxCalcIterations, the same option that governs the existing calc engine. No new knob. TestRecalcCellCircularReferenceBounded locks termination.
  • Shared-formula groups. setCellCachedValue touches only <v> and <t>; <f> with its t="shared", ref, and si attributes is left untouched, and sibling children in the same band are not modified by a write to the master or another child. TestRecalcCellSharedFormulaBand locks this.
  • t="s" regression guard. TestRecalcCellNoTypeSRegression pins the behaviour that motivated this work: numeric results must be stored with an implicit t attribute, not as a shared string, or downstream aggregates silently sum to 0.

How Has This Been Tested

$ go test -run 'TestRecalcCell' -timeout 60s -v .
=== RUN   TestRecalcCellTypes
=== RUN   TestRecalcCellTypes/numeric
=== RUN   TestRecalcCellTypes/boolean
=== RUN   TestRecalcCellTypes/chained
--- PASS: TestRecalcCellTypes (0.00s)
    --- PASS: TestRecalcCellTypes/numeric (0.00s)
    --- PASS: TestRecalcCellTypes/boolean (0.00s)
    --- PASS: TestRecalcCellTypes/chained (0.00s)
=== RUN   TestRecalcCellNoFormula
--- PASS: TestRecalcCellNoFormula (0.00s)
=== RUN   TestRecalcCellNoTypeSRegression
--- PASS: TestRecalcCellNoTypeSRegression (0.00s)
=== RUN   TestRecalcCellSharedFormulaBand
--- PASS: TestRecalcCellSharedFormulaBand (0.00s)
=== RUN   TestRecalcCellCircularReferenceBounded
--- PASS: TestRecalcCellCircularReferenceBounded (0.00s)
=== RUN   TestRecalcCellRoundTrip
--- PASS: TestRecalcCellRoundTrip (0.00s)
PASS
ok  	github.com/xuri/excelize/v2	0.012s

Full suite: go test -skip TestZip64 -timeout 10m . passes cleanly. gofmt -s -l . and go vet ./... are clean. ARM cross-builds (GOARM=5|6|7, arm64, android/arm64) all build.

Types of changes

  • Docs change / refactoring / dependency upgrade
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

Documentation for the new exported surface lives in the doc comments on RecalcCell and ErrCellNoFormula.

CalcCellValue returns a formatted string but does not touch the
stored workbook. Every SetCell* writer strips the <f> element, so
callers that need to refresh the cached value of a formula cell
without losing the formula currently have no path. Readers that
honour <v> without recomputing (LibreOffice headless, Numbers,
etc.) therefore see stale numbers after any excelize round trip.

Add (*File).RecalcCell(sheet, cell string) error. It evaluates
the formula via the existing recursive calc engine, then writes
the typed result into <v>/<t> through a private
setCellCachedValue helper that leaves <f>, the shared-formula
master, ref span, and si index intact. A new sentinel
ErrCellNoFormula is returned when the target cell has no
formula. Dependency resolution reuses calcCellValue, so chained
formulas across cells converge in one pass; circular references
are bounded by the workbook's existing MaxCalcIterations option.

Signed-off-by: Christopher Albert <albert@tugraz.at>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add (*File).RecalcCell to persist a single formula cell's cached value

2 participants