Fix silent drop of named LAMBDA / non-range defined names on round-trip#71
Open
senoff wants to merge 1 commit into
Open
Fix silent drop of named LAMBDA / non-range defined names on round-trip#71senoff wants to merge 1 commit into
senoff wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Original Problem
Modern Excel workbooks can define named LAMBDA functions and other formula expressions at the workbook level (
definedNameelements whose value is a formula likeLAMBDA(x,x*2)rather than a range address). ExcelJS silently dropped these on load becauseDefinedNamesXform.extractRanges()returned an empty array for non-range values, andparseClose()discarded values with no ranges. On round-trip, all named LAMBDAs were gone.Cause
extractRanges()was designed for range lists and had no concept of formula expressions. Values likeLAMBDA(x,x*2)were split by comma, each fragment tested as a range (isValidRange), and — since none passed — the result was an empty array. The caller then stored{name: 'MyDouble', ranges: []}and threw the body away. Additionally,DefinedNamesXform.render()always calledmodel.ranges.join(','), which produced an empty string for non-range names.Fix
Three surgical changes:
lib/xlsx/xform/book/defined-name-xform.jsextractRanges(): added a quote-aware formula heuristic. A(that appears outside single-quoted sheet names (i.e. not in'Data (2026)'!$A$1) signals a formula expression. Return[]immediately to prevent incorrectly splitting the formula body.Heuristic: count single-quote characters before the first
(. If the count is even (zero included), the(is not inside a quoted sheet name — treat as formula.parseClose(): whenranges.length === 0and the text is non-empty, storemodel.formula = textverbatim.render(): writemodel.formulaverbatim instead ofmodel.ranges.join(',')whenformulais set.lib/doc/workbook.jsAdded
this._formulaDefinedNames = []field to store formula-typed defined names separately (theDefinedNames/CellMatrixlayer does not handle non-range entries).get model(): concatenates_formulaDefinedNamesinto thedefinedNamesarray.set model(value): splits incomingdefinedNamesinto range-based (→_definedNames) and formula-based (→_formulaDefinedNames).Files changed
lib/xlsx/xform/book/defined-name-xform.js—render,parseClose,extractRangeslib/doc/workbook.js— constructor,get model,set modelspec/unit/xlsx/xform/book/defined-name-xform.spec.js— 3 new test cases (LAMBDA, LAMBDA multi-param, sheet-with-parens regression)spec/integration/pr/lambda-defined-name.spec.js— new integration test (3 round-trip cases)Test Run
Unit: 16 passing (10ms)
Integration: 3 passing (32ms)
Regression test for sheet names containing parentheses (
'Data (2026)'!$A$1) confirms the formula heuristic does not misclassify valid range references.Cross-PR check
lib/doc/workbook.jsis also touched by PR #67 (getWorksheet case-insensitive). PR #67 touches onlygetWorksheet()body (lines ~112–116); this fix touches the constructor (line 31),get model(line 163), andset model(line 223–225). No overlap.lib/xlsx/xform/book/defined-name-xform.jsis not touched by any other open senoff PR.Note:
.prettierrcedit removed from this PR. The canonical.prettierrc/.eslintrcfix is in PR #69.Excel/soffice verification
This fix touches the serialization of workbook-level defined names (
xl/workbook.xml).sofficeis not available in this worker environment. The round-trip tests confirm LAMBDA definitions survive the write/load cycle via JSZip XML inspection. Recommend maintainer open a workbook with named LAMBDAs in Excel after merge to verify the functions are usable.grace-review summary
Run 1 — MEDIUM (gpt-5.5):
extractRanges(heuristic misclassified'Data (2026)'!$A$1as formula. Fixed: heuristic is now quote-aware (counts single quotes before first(). Added regression test.Run 2 — openai:gpt-5.5: No defects found. gemini: LOW (whitespace-only defined name body not preserved) — deferred, whitespace-only defined names don't appear in real Excel files, current behavior (drop) is safe.
Note: committed with
--no-verifyper AGENTS.md Rule 1.