Don't infer cell t attribute from formula source when pre-calc is off#4864
Conversation
…s off writeCellFormula() falls back to using $cellValue (the formula source) as $calculatedValue when getPreCalculateFormulas() is false. The source is always a string, so the is_string($result) branch always fires, every formula cell ends up with t="str" — even those whose formula evaluates to a number or boolean. Some readers (notably older LibreOffice and Gnumeric) treat t="str" as a hint that the cell is a string, and either skip recomputation or present a #NAME error for numeric formulas they would otherwise compute correctly. Fix: leave $calculatedValue null and $calculatedValueString empty when pre-calc is off. The type-inference branches no longer fire, so no t attribute is written. The surrounding writeElementIf already guards <v> emission on getPreCalculateFormulas(), so no cached value is written either — readers see "formula with no cached value, no type hint", respect the workbook's fullCalcOnLoad="1", and recompute on open. Updates the existing PreCalcTest assertion to match: <c r="B2"><f>3+A3</f></c> instead of <c r="B2" t="str"><f>3+A3</f></c>.
|
Hi, I made this PR using @claude Code for one of my clients. I have read everything twice. We did patch the composer library to test if it worked fine. Please let me know what you think of this PR, I can jump into manual edits. |
|
Thank you for your contribution. Sorry I didn't notice the second problem when I fixed the first; glad you were able to notice and do something about it. |
Thank you! Something about SpartnerNL/Laravel-Excel#4345 |
|
The release 1 branch now receives security patches only. Sorry, |
|
You might want to add a comment at SpartnerNL/Laravel-Excel#4345, which I believe you referenced above, and which contains instructions for a workaround which multiple others are satisfied with, e.g. SpartnerNL/Laravel-Excel#4345 (comment) |
Yeah okay haha I will keep my vendor patch, it works fine and I will have no trouble with it Thanks anyway |
Don't infer cell
tattribute from formula source when pre-calc is offWhat
When
setPreCalculateFormulas(false)is set on the Xlsx writer, every formula cell is written witht="str"— even when the formula evaluates to a number or boolean. This PR removes that spurious type attribute.Why this matters
Spreadsheet readers use the
tattribute on a formula cell as a hint about the result type. Writingt="str"for a=SUMIFS(...)cell tells readers "this formula's result is a string" — which is wrong, and triggers two real failures in the wild:t="str"as authoritative when there is no cached<v>. They either skip recomputation, render the formula text literally, or surface#NAMEerrors for numeric formulas they would otherwise compute correctly.Internally we hit this when generating a TVA report whose Sommaire sheet uses
SUMIFSformulas referencing a 20k+ row data sheet. With pre-calc on, PhpSpreadsheet's calc engine iterates whole-column refs to Excel's 1,048,576-row max per condition per formula and the export hangs. Disabling pre-calc unblocks the export but produces a file where everySUMIFSdisplays as=_xlfn.sumifs(...)returning#NAMEbecause of thet="str"plus the formula prefix combination.Root cause (
src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php,writeCellFormula)$cellValueis the formula source (e.g."=SUMIFS(...)"). It is always a string, so theis_stringbranch always fires when pre-calc is off, regardless of what the formula would actually evaluate to.Fix
Leave
$calculatedValuenull(and$calculatedValueStringempty) when pre-calc is off. Theis_string/is_booltype-inference branches no longer fire and notattribute is written. The<v>element is already correctly suppressed by the existinggetPreCalculateFormulas()guard at thewriteElementIfsite, so no cached value is written either.Result: cells written without pre-calc now look like
instead of
The workbook's
<calcPr fullCalcOnLoad="1" forceFullCalc="1"/>is unchanged, so readers correctly recompute on open.Backwards compatibility
$calculatedValuestill comes from$cell->getCalculatedValue()and the existing type-inference logic runs unchanged.t="str"and (until very recently)<v>0</v>. The<v>0</v>was already removed in master; this PR removes the spurioust="str". Readers that handled the old output continue to work, and readers that didn't (per the issues above) now do.Tests
tests/PhpSpreadsheetTests/Writer/PreCalcTest.phpline 111 to expect<c r="B2"><f>3+A3</f></c>(not="str") when$preCalc === false. The Xls / Ods / Html / Csv branches are unaffected.t="str"on formula cells (Issue2082Test, XlfnFunctionsTest, …) are all in pre-calc=on scenarios with<v>...</v>cached values, and continue to pass unchanged.tests/PhpSpreadsheetTests/Writer/Xlsx/suite passes (223 tests), fullFunctional/+Calculation/suites pass (16,265 tests, 0 failures).Related
_xlfn.prefix being added to formula cells when pre-calc is off (which causes#NAMEin pre-7 LibreOffice / Gnumeric) is intentionally not addressed in this PR. The_xlfn.prefix is correct OOXML and would touch many more tests; it can be a separate discussion if the project wants to make that conditional too.More
Commit 406988e — "SetCalculatedValue Avoid Casting String to Numeric (#3685)" by oleibman, 2023-08-26.
The commit message explicitly acknowledges our exact bug: