Prefix post-2007 function names with _xlfn so formulas work in Excel - #1742
Prefix post-2007 function names with _xlfn so formulas work in Excel#1742Mike-Crowley wants to merge 4 commits into
Conversation
Formulas using functions added to Excel after the 2007 file format was defined (IFS, CONCAT, TEXTJOIN, XLOOKUP and many more) showed #NAME? when a file written by this module was opened in Excel, until each cell was manually re-entered. Excel stores such functions in the sheet XML with an "_xlfn." prefix (SORT and FILTER as "_xlfn._xlws.") and expects the prefix to be present; EPPlus writes formula text verbatim. Formulas set through Export-Excel, Set-ExcelRange, Set-ExcelRow and Set-ExcelColumn now have the prefix inserted automatically. String literals, quoted sheet names, structured table references, already prefixed names and user-defined names are left untouched, and setting $env:NoXlFn disables the rewrite. The function list was generated by entering every candidate function into Excel (Microsoft 365) via COM and reading back what Excel stored; NETWORKDAYS.INTL, WORKDAY.INTL and ISO.CEILING are excluded because Excel stores them unprefixed. Because EPPlus's calculation engine looks functions up by the stored name, Close-ExcelPackage -Calculate and Export-Excel -Calculate now register each implemented function under its _xlfn. alias first, so functions the engine could calculate before (IFNA, RANK.AVG, STDEV.S, etc.) still calculate after prefixing. Also fixes Set-ExcelRange -Value: a value beginning with "=" was documented to be treated as a formula but wrote an empty formula, because the code set $PSBoundParameters['Formula'] without setting the $Formula variable it reads back. The rewrite uses compiled regexes with a cheap pre-test, adding no measurable cost to exports without affected formulas and ~0.1 ms per rewritten formula cell. Fixes dfinke#1728 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add Z.TEST, which was missing from the Excel 2010 group (verified via Excel COM read-back and the XlsxWriter/PhpSpreadsheet lists), replace a retired Microsoft support link, correct the comment describing why the two replacement passes cannot interfere, and drop RegexOptions.Compiled - it cost ~70ms at module load and measured no faster than the interpreted engine on these patterns. The NoXlFn test now restores the environment variable in AfterAll so a failure cannot leak it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Note on the failing Azure Pipelines checks: same pre-existing pipeline issue as noted on #1741 — every job stops at |
The ~150-name table in Private/XlFnFormula.ps1 came from entering every candidate function into a real Excel via COM and reading back what Excel stored in the worksheet XML. Committing the script makes the list reproducible and reviewable rather than taken on trust: running it against the installed Excel reports, for every candidate, the prefix Excel stores and whether the module's table agrees (currently 171/171 match, including the controls and the deliberately unprefixed NETWORKDAYS.INTL, WORKDAY.INTL and ISO.CEILING). The output snapshot is committed alongside, and the table's header comment points to both. The script requires Excel, so it is not a Pester file and never runs in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds automatic _xlfn. (and _xlfn._xlws.) prefixing for post-2007 Excel function names when formulas are written via ImportExcel cmdlets, preventing #NAME? errors when opening generated files in Excel (Fixes #1728). It also registers _xlfn.* aliases with EPPlus’ calculation engine so -Calculate continues to work for functions EPPlus already implements, and fixes a Set-ExcelRange -Value '=...' formula-handling bug.
Changes:
- Introduces
Expand-XlFnFormulaand applies it across the cmdlet paths that write formulas (Export-Excel,Set-ExcelRange,Set-ExcelRow,Set-ExcelColumn). - Registers
_xlfn.*function aliases before EPPlus calculation runs inExport-Excel -CalculateandClose-ExcelPackage -Calculate. - Adds Pester coverage and supporting test artifacts for the prefixing behavior and opt-out flag.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Public/Set-ExcelRow.ps1 | Uses Expand-XlFnFormula when writing row formulas from =-prefixed values. |
| Public/Set-ExcelRange.ps1 | Fixes -Value '=...' being treated as an empty formula; routes formulas through Expand-XlFnFormula. |
| Public/Set-ExcelColumn.ps1 | Uses Expand-XlFnFormula when writing column formulas from =-prefixed values. |
| Public/Export-Excel.ps1 | Rewrites formula-like cell values via _xlfn expansion and registers aliases before -Calculate. |
| Public/Close-ExcelPackage.ps1 | Registers _xlfn aliases before workbook calculation. |
| Private/XlFnFormula.ps1 | Implements formula expansion and EPPlus function-alias registration. |
| tests/XlFnFunctionList.txt | Captures Excel-vs-module prefix expectations for the function table. |
| tests/XlFnFormula.tests.ps1 | Adds Pester tests validating rewrite behavior, call-site coverage, and -Calculate compatibility. |
| tests/Get-XlFnFunctionList.ps1 | Adds a local-only COM-based regeneration/verifier for the function prefix table. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
FILTER and SORT are rewritten to _xlfn._xlws. names, so if a future EPPlus implements either, the calculation aliases now cover that form as well. The EPPlus bundled today implements neither, so this changes nothing yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #1728
Problem
Formulas using functions added to Excel after the 2007 file format was defined — IFS, CONCAT, TEXTJOIN, SWITCH, XLOOKUP, and ~150 others — show
#NAME?when a file written by this module is opened in Excel, until each cell is manually re-entered (which is exactly the behavior reported in #1728).Calculate Nowdoesn't fix them, andClose-ExcelPackage -Calculatedoesn't either.Root cause
Excel stores these "future functions" in the sheet XML with an
_xlfn.prefix (_xlfn.IFS,_xlfn.CONCAT; SORT and FILTER use_xlfn._xlws.). Excel adds the prefix invisibly when a formula is typed in and hides it on display — but when the formula is written directly into the file, as EPPlus does, the prefix must already be present. Without it, Excel resolves the name as an unknown defined name →#NAME?. Re-entering the cell makes Excel re-parse and store the prefixed form, which is why manual editing "fixes" it.Verified with the issue's exact repro: the same formula stored as
_xlfn.IFS(... _xlfn.CONCAT(...))calculates to the expected value on open, with Excel displaying it exactly as the user wrote it. (The stray@the issue also mentions is Excel's implicit-intersection marker being added around unrecognized names — it disappears once the functions resolve.)Fix
Private/XlFnFormula.ps1:Expand-XlFnFormulainserts the prefixes into formula text. It leaves alone:"string literals",'quoted sheet names',[structured references](including nested[[#This Row],[Col]]forms and escaped chars), already-prefixed names, and user-defined names (MYCONCAT(etc.). Setting$env:NoXlFndisables the rewrite entirely.Register-XlFnFunctionregisters_xlfn.aliases for every function EPPlus's calculation engine implements, so-Calculatekeeps working for the functions it could calculate before (IFNA, RANK.AVG, STDEV.S, ISOWEEKNUM, …). Functions the engine never implemented still produce#NAME?in the cached value exactly as before — and since EPPlus flags formula cells for full calculation on load, Excel recomputes them correctly when the file opens.Export-Excel(values starting with=),Set-ExcelRange(-Formula,-ArrayFormula, and-Value '=...'),Set-ExcelRow, andSet-ExcelColumn. Both-Calculatesites (Close-ExcelPackage,Export-Excel) register the aliases first.NETWORKDAYS.INTL,WORKDAY.INTL, andISO.CEILINGare deliberately excluded — Excel stores them unprefixed despite what some references claim.Also fixed in passing
Set-ExcelRange -Value '=SUM(1,2)'(a value starting with=is documented to be treated as a formula) actually wrote an empty formula: the code set$PSBoundParameters["Formula"]but then read the never-assigned$Formulavariable. Now it works, with an explicit-Formulastill taking precedence if both are supplied.Performance
Compiled once at module load (~1 ms,
RegexOptions.Compileddeliberately avoided), with a cheap pre-test so formulas containing no affected function pay ~4 µs each: exports without affected formulas are unchanged (10k rows benchmark: within noise of master), and a pathological 10k-rows-of-CONCAT export adds ~1 s — those cells produced broken files before.Scope notes
$cell.Formula = ..., as in the issue's original snippet) bypass the module and are unaffected — routing them throughSet-ExcelRange -Formulagets the fix.Add-ConditionalFormatting,Add-ExcelDataValidationRule, and tableTotalsRowFormulastill write raw formula text (same pre-existing behavior); they could adoptExpand-XlFnFormulaas a follow-up if desired.Testing
__tests__/XlFnFormula.tests.ps1) — EPPlus-only, no Excel/COM required, so they run on the Linux/macOS CI. They cover the issue's exact formula, string/sheet-name/structured-ref protection, double-prefix idempotency, UDF names,_xlwsfunctions, case preservation, all four cmdlet paths,-Calculatestill computing prefixed functions, and theNoXlFnopt-out.#NAME?and clean formula display; legacy-function files are byte-for-byte unaffected; a control file written with$env:NoXlFnset still reproduces the original bug, proving the rewrite is what fixes it.🤖 Generated with Claude Code