Skip to content

Fix silent drop of named LAMBDA / non-range defined names on round-trip#71

Open
senoff wants to merge 1 commit into
protobi:masterfrom
senoff:senoff/fix-lambda-silent-drop
Open

Fix silent drop of named LAMBDA / non-range defined names on round-trip#71
senoff wants to merge 1 commit into
protobi:masterfrom
senoff:senoff/fix-lambda-silent-drop

Conversation

@senoff
Copy link
Copy Markdown

@senoff senoff commented May 7, 2026

Original Problem

Modern Excel workbooks can define named LAMBDA functions and other formula expressions at the workbook level (definedName elements whose value is a formula like LAMBDA(x,x*2) rather than a range address). ExcelJS silently dropped these on load because DefinedNamesXform.extractRanges() returned an empty array for non-range values, and parseClose() 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 like LAMBDA(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 called model.ranges.join(','), which produced an empty string for non-range names.

Fix

Three surgical changes:

lib/xlsx/xform/book/defined-name-xform.js

  1. extractRanges(): 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.

  2. parseClose(): when ranges.length === 0 and the text is non-empty, store model.formula = text verbatim.

  3. render(): write model.formula verbatim instead of model.ranges.join(',') when formula is set.

lib/doc/workbook.js

  1. Added this._formulaDefinedNames = [] field to store formula-typed defined names separately (the DefinedNames/CellMatrix layer does not handle non-range entries).

  2. get model(): concatenates _formulaDefinedNames into the definedNames array.

  3. set model(value): splits incoming definedNames into range-based (→ _definedNames) and formula-based (→ _formulaDefinedNames).

Files changed

  • lib/xlsx/xform/book/defined-name-xform.jsrender, parseClose, extractRanges
  • lib/doc/workbook.js — constructor, get model, set model
  • spec/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.js is also touched by PR #67 (getWorksheet case-insensitive). PR #67 touches only getWorksheet() body (lines ~112–116); this fix touches the constructor (line 31), get model (line 163), and set model (line 223–225). No overlap. lib/xlsx/xform/book/defined-name-xform.js is not touched by any other open senoff PR.

Note: .prettierrc edit removed from this PR. The canonical .prettierrc / .eslintrc fix is in PR #69.

Excel/soffice verification

This fix touches the serialization of workbook-level defined names (xl/workbook.xml). soffice is 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$1 as 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-verify per AGENTS.md Rule 1.

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.

1 participant