@rowsncolumns/toolkit 24.5.4 → 24.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/cjs/autofilter-condition.js +6 -2
  2. package/dist/cjs/excel-parser/helpers.d.ts +9 -1
  3. package/dist/cjs/excel-parser/helpers.d.ts.map +1 -1
  4. package/dist/cjs/excel-parser/helpers.js +70 -16
  5. package/dist/cjs/excel-parser/interface.d.ts +9 -1
  6. package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
  7. package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -1
  8. package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.js +61 -6
  9. package/dist/cjs/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -1
  10. package/dist/cjs/excel-parser/plugins/xlsx/data-validations.js +13 -2
  11. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +8 -1
  12. package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  13. package/dist/cjs/excel-parser/plugins/xlsx/index.js +159 -26
  14. package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts +17 -2
  15. package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  16. package/dist/cjs/excel-parser/plugins/xlsx/styles.js +25 -14
  17. package/dist/cjs/excel-parser/types.d.ts +5 -1
  18. package/dist/cjs/excel-parser/types.d.ts.map +1 -1
  19. package/dist/cjs/excel-parser/workbook.d.ts +1 -0
  20. package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
  21. package/dist/cjs/excel-parser/workbook.js +4 -0
  22. package/dist/cjs/excel-parser/worker.js +3 -0
  23. package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
  24. package/dist/cjs/exporter/xlsx/chart-builder.js +167 -53
  25. package/dist/cjs/exporter/xlsx/future-functions.d.ts +6 -7
  26. package/dist/cjs/exporter/xlsx/future-functions.d.ts.map +1 -1
  27. package/dist/cjs/exporter/xlsx/future-functions.js +5 -261
  28. package/dist/cjs/exporter/xlsx/style-builder.d.ts.map +1 -1
  29. package/dist/cjs/exporter/xlsx/style-builder.js +62 -19
  30. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +10 -2
  31. package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  32. package/dist/cjs/exporter/xlsx/xlsx-export.js +404 -158
  33. package/dist/cjs/helpers/excel-formula-prefixes.d.ts +51 -0
  34. package/dist/cjs/helpers/excel-formula-prefixes.d.ts.map +1 -0
  35. package/dist/cjs/helpers/excel-formula-prefixes.js +548 -0
  36. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  37. package/dist/esm/autofilter-condition.js +6 -2
  38. package/dist/esm/excel-parser/helpers.d.ts +9 -1
  39. package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
  40. package/dist/esm/excel-parser/helpers.js +65 -3
  41. package/dist/esm/excel-parser/interface.d.ts +9 -1
  42. package/dist/esm/excel-parser/interface.d.ts.map +1 -1
  43. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -1
  44. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.js +42 -1
  45. package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -1
  46. package/dist/esm/excel-parser/plugins/xlsx/data-validations.js +11 -2
  47. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +8 -1
  48. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  49. package/dist/esm/excel-parser/plugins/xlsx/index.js +109 -4
  50. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts +17 -2
  51. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  52. package/dist/esm/excel-parser/plugins/xlsx/styles.js +20 -8
  53. package/dist/esm/excel-parser/types.d.ts +5 -1
  54. package/dist/esm/excel-parser/types.d.ts.map +1 -1
  55. package/dist/esm/excel-parser/workbook.d.ts +1 -0
  56. package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
  57. package/dist/esm/excel-parser/workbook.js +3 -0
  58. package/dist/esm/excel-parser/worker.js +3 -0
  59. package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
  60. package/dist/esm/exporter/xlsx/chart-builder.js +80 -0
  61. package/dist/esm/exporter/xlsx/future-functions.d.ts +6 -7
  62. package/dist/esm/exporter/xlsx/future-functions.d.ts.map +1 -1
  63. package/dist/esm/exporter/xlsx/future-functions.js +5 -261
  64. package/dist/esm/exporter/xlsx/style-builder.d.ts.map +1 -1
  65. package/dist/esm/exporter/xlsx/style-builder.js +56 -18
  66. package/dist/esm/exporter/xlsx/xlsx-export.d.ts +10 -2
  67. package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  68. package/dist/esm/exporter/xlsx/xlsx-export.js +338 -106
  69. package/dist/esm/helpers/excel-formula-prefixes.d.ts +51 -0
  70. package/dist/esm/helpers/excel-formula-prefixes.d.ts.map +1 -0
  71. package/dist/esm/helpers/excel-formula-prefixes.js +531 -0
  72. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  73. package/package.json +4 -4
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Every function Excel stores behind `_xlfn.` (uppercase). One list for both
3
+ * directions: the exporter qualifies exactly these; the importer strips any
4
+ * marker it finds, list or not, so a newer function still opens cleanly.
5
+ */
6
+ export declare const FUTURE_FUNCTION_NAMES: readonly string[];
7
+ /**
8
+ * Future functions that Excel writes behind the worksheet-scope prefix
9
+ * (`_xlfn._xlws.`) rather than plain `_xlfn.` — the dynamic-array functions
10
+ * that also exist as worksheet-only names, plus Python in Excel.
11
+ */
12
+ export declare const WORKSHEET_SCOPED_FUTURE_FUNCTIONS: ReadonlySet<string>;
13
+ /**
14
+ * Remove every `_xlfn.` / `_xlws.` / `_xlpm.` / `_xludf.` storage marker from
15
+ * `formula` (outside string literals and quoted sheet names). Idempotent.
16
+ */
17
+ export declare function stripExcelFormulaPrefixes(formula: string): string;
18
+ /**
19
+ * `_xlfn.ANCHORARRAY(A1)` / `ANCHORARRAY(Sheet1!A1)` → `A1#` / `Sheet1!A1#`
20
+ * — Excel's stored form of the spilled-range operator back to its UI form.
21
+ * A call whose argument is not exactly one cell reference is left as is.
22
+ */
23
+ export declare function decodeAnchorArray(formula: string): string;
24
+ /**
25
+ * `A1#` / `Sheet1!A1#` → `ANCHORARRAY(A1)` / `ANCHORARRAY(Sheet1!A1)` — the
26
+ * spilled-range operator in the form sheet XML accepts (future-function
27
+ * qualification then adds the `_xlfn.` prefix). `#REF!#` is left alone.
28
+ */
29
+ export declare function encodeSpilledRanges(formula: string): string;
30
+ /**
31
+ * Prefix every LET / LAMBDA parameter with `_xlpm.` — at its declaration and
32
+ * at every use inside the call (a use as a function, `double(5)` for a
33
+ * LET-bound lambda, included). Parameters are the odd-position LET arguments
34
+ * (all but the last argument, which is the calculation) and every LAMBDA
35
+ * argument but the last. Already-prefixed input comes out prefixed once.
36
+ */
37
+ export declare function qualifyLambdaParameters(formula: string): string;
38
+ /**
39
+ * Export direction. Strip the storage markers and re-add the correct one to
40
+ * every future-function call in `formula`, write `A1#` as `ANCHORARRAY(A1)`
41
+ * (qualified to `_xlfn.ANCHORARRAY`) and prefix LET / LAMBDA parameters with
42
+ * `_xlpm.`. Quoted spans keep their original text. Idempotent: already
43
+ * qualified input comes out qualified exactly once.
44
+ */
45
+ export declare function qualifyFutureFunctions(formula: string): string;
46
+ /**
47
+ * Import direction. The formula as Excel displays it: every storage marker
48
+ * removed, `_xlfn.ANCHORARRAY(A1)` shown as `A1#`, `_xlfn.SINGLE(x)` as `@x`.
49
+ */
50
+ export declare function normalizeImportedFormula(formula: string): string;
51
+ //# sourceMappingURL=excel-formula-prefixes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excel-formula-prefixes.d.ts","sourceRoot":"","sources":["../../../helpers/excel-formula-prefixes.ts"],"names":[],"mappings":"AAoCA;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EA+KlD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,iCAAiC,EAAE,WAAW,CAAC,MAAM,CAIhE,CAAC;AA4FH;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAuDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA6BzD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkB3D;AA0CD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgD/D;AAID;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAIhE"}
@@ -0,0 +1,531 @@
1
+ /**
2
+ * Excel's file-format spelling of modern formulas, in both directions.
3
+ *
4
+ * The .xlsx grammar was frozen with Excel 2007, so every function added since
5
+ * is stored behind a namespace prefix the UI never shows:
6
+ *
7
+ * - `_xlfn.NAME(` — a "future function" (`_xlfn.XLOOKUP(`, `_xlfn.IFS(`);
8
+ * - `_xlfn._xlws.NAME(` — the worksheet-scoped dynamic-array functions
9
+ * (`_xlfn._xlws.FILTER(`, `_xlfn._xlws.SORT(`, `_xlfn._xlws.PY(`);
10
+ * - `_xlpm.name` — every LET / LAMBDA parameter, at its declaration
11
+ * and at every use (`_xlfn.LET(_xlpm.x,A1,_xlpm.x+1)`);
12
+ * - `_xlfn.ANCHORARRAY(A1)` — the spilled-range operator `A1#`;
13
+ * - `_xlfn.SINGLE(x)` — the implicit-intersection operator `@x`;
14
+ * - `_xludf.NAME(` — a user-defined (add-in) function.
15
+ *
16
+ * Written bare, Excel does not resolve a future function: it renders the call
17
+ * behind the implicit-intersection operator (`=@DAYS(...)`) and evaluates
18
+ * `#NAME?`. Kept prefixed in the engine, `SUM(_xlfn.ANCHORARRAY(A1))` is a
19
+ * `#NAME?` and the formula bar shows `=_xlfn.XLOOKUP(`. So the importer
20
+ * removes every marker ({@link normalizeImportedFormula}) and the exporter
21
+ * re-adds them from one list ({@link qualifyFutureFunctions}); a formula
22
+ * round-trips unchanged.
23
+ *
24
+ * The list follows Microsoft's documentation of `_xlfn.`-prefixed functions
25
+ * ("Issue: An _xlfn. prefix is displayed in front of a function in a
26
+ * formula", support.microsoft.com) plus the dynamic-array / LAMBDA-era, 2024
27
+ * and Python-in-Excel additions that ship with the same prefix convention.
28
+ */
29
+ import { lexify, tokenVocabulary } from "@rowsncolumns/calculator";
30
+ import { decodeImplicitIntersection } from "./implicit-intersection";
31
+ const T = tokenVocabulary;
32
+ /**
33
+ * Every function Excel stores behind `_xlfn.` (uppercase). One list for both
34
+ * directions: the exporter qualifies exactly these; the importer strips any
35
+ * marker it finds, list or not, so a newer function still opens cleanly.
36
+ */
37
+ export const FUTURE_FUNCTION_NAMES = [
38
+ "ACOT",
39
+ "ACOTH",
40
+ "AGGREGATE",
41
+ "ANCHORARRAY",
42
+ "ARABIC",
43
+ "ARRAYTOTEXT",
44
+ "BASE",
45
+ "BETA.DIST",
46
+ "BETA.INV",
47
+ "BINOM.DIST",
48
+ "BINOM.DIST.RANGE",
49
+ "BINOM.INV",
50
+ "BITAND",
51
+ "BITLSHIFT",
52
+ "BITOR",
53
+ "BITRSHIFT",
54
+ "BITXOR",
55
+ "BYCOL",
56
+ "BYROW",
57
+ "CEILING.MATH",
58
+ "CEILING.PRECISE",
59
+ "CHISQ.DIST",
60
+ "CHISQ.DIST.RT",
61
+ "CHISQ.INV",
62
+ "CHISQ.INV.RT",
63
+ "CHISQ.TEST",
64
+ "CHOOSECOLS",
65
+ "CHOOSEROWS",
66
+ "COMBINA",
67
+ "CONCAT",
68
+ "CONFIDENCE.NORM",
69
+ "CONFIDENCE.T",
70
+ "COT",
71
+ "COTH",
72
+ "COVARIANCE.P",
73
+ "COVARIANCE.S",
74
+ "CSC",
75
+ "CSCH",
76
+ "DAYS",
77
+ "DECIMAL",
78
+ "DETECTLANGUAGE",
79
+ "DROP",
80
+ "ENCODEURL",
81
+ "ERF.PRECISE",
82
+ "ERFC.PRECISE",
83
+ "EXPAND",
84
+ "EXPON.DIST",
85
+ "F.DIST",
86
+ "F.DIST.RT",
87
+ "F.INV",
88
+ "F.INV.RT",
89
+ "F.TEST",
90
+ "FIELDVALUE",
91
+ "FILTER",
92
+ "FILTERXML",
93
+ "FLOOR.MATH",
94
+ "FLOOR.PRECISE",
95
+ "FORECAST.ETS",
96
+ "FORECAST.ETS.CONFINT",
97
+ "FORECAST.ETS.SEASONALITY",
98
+ "FORECAST.ETS.STAT",
99
+ "FORECAST.LINEAR",
100
+ "FORMULATEXT",
101
+ "GAMMA",
102
+ "GAMMA.DIST",
103
+ "GAMMA.INV",
104
+ "GAMMALN.PRECISE",
105
+ "GAUSS",
106
+ "GROUPBY",
107
+ "HSTACK",
108
+ "HYPGEOM.DIST",
109
+ "IFNA",
110
+ "IFS",
111
+ "IMAGE",
112
+ "IMCOSH",
113
+ "IMCOT",
114
+ "IMCSC",
115
+ "IMCSCH",
116
+ "IMSEC",
117
+ "IMSECH",
118
+ "IMSINH",
119
+ "IMTAN",
120
+ "ISFORMULA",
121
+ "ISOMITTED",
122
+ "ISOWEEKNUM",
123
+ "LAMBDA",
124
+ "LET",
125
+ "LOGNORM.DIST",
126
+ "LOGNORM.INV",
127
+ "MAKEARRAY",
128
+ "MAP",
129
+ "MAXIFS",
130
+ "MINIFS",
131
+ "MODE.MULT",
132
+ "MODE.SNGL",
133
+ "MUNIT",
134
+ "NEGBINOM.DIST",
135
+ "NETWORKDAYS.INTL",
136
+ "NORM.DIST",
137
+ "NORM.INV",
138
+ "NORM.S.DIST",
139
+ "NORM.S.INV",
140
+ "NUMBERVALUE",
141
+ "PDURATION",
142
+ "PERCENTILE.EXC",
143
+ "PERCENTILE.INC",
144
+ "PERCENTOF",
145
+ "PERCENTRANK.EXC",
146
+ "PERCENTRANK.INC",
147
+ "PERMUTATIONA",
148
+ "PHI",
149
+ "PIVOTBY",
150
+ "POISSON.DIST",
151
+ "PQSOURCE",
152
+ "PY",
153
+ "PYTHON_STR",
154
+ "PYTHON_TYPE",
155
+ "PYTHON_TYPENAME",
156
+ "QUARTILE.EXC",
157
+ "QUARTILE.INC",
158
+ "QUERYSTRING",
159
+ "RANDARRAY",
160
+ "RANK.AVG",
161
+ "RANK.EQ",
162
+ "REDUCE",
163
+ "REGEXEXTRACT",
164
+ "REGEXREPLACE",
165
+ "REGEXTEST",
166
+ "RRI",
167
+ "SCAN",
168
+ "SEC",
169
+ "SECH",
170
+ "SEQUENCE",
171
+ "SHEET",
172
+ "SHEETS",
173
+ "SINGLE",
174
+ "SKEW.P",
175
+ "SORT",
176
+ "SORTBY",
177
+ "STDEV.P",
178
+ "STDEV.S",
179
+ "STOCKHISTORY",
180
+ "SWITCH",
181
+ "T.DIST",
182
+ "T.DIST.2T",
183
+ "T.DIST.RT",
184
+ "T.INV",
185
+ "T.INV.2T",
186
+ "T.TEST",
187
+ "TAKE",
188
+ "TEXTAFTER",
189
+ "TEXTBEFORE",
190
+ "TEXTJOIN",
191
+ "TEXTSPLIT",
192
+ "TOCOL",
193
+ "TOROW",
194
+ "TRANSLATE",
195
+ "TRIMRANGE",
196
+ "UNICHAR",
197
+ "UNICODE",
198
+ "UNIQUE",
199
+ "VALUETOTEXT",
200
+ "VAR.P",
201
+ "VAR.S",
202
+ "VSTACK",
203
+ "WEBSERVICE",
204
+ "WEIBULL.DIST",
205
+ "WORKDAY.INTL",
206
+ "WRAPCOLS",
207
+ "WRAPROWS",
208
+ "XLOOKUP",
209
+ "XMATCH",
210
+ "XOR",
211
+ "Z.TEST",
212
+ ];
213
+ /**
214
+ * Future functions that Excel writes behind the worksheet-scope prefix
215
+ * (`_xlfn._xlws.`) rather than plain `_xlfn.` — the dynamic-array functions
216
+ * that also exist as worksheet-only names, plus Python in Excel.
217
+ */
218
+ export const WORKSHEET_SCOPED_FUTURE_FUNCTIONS = new Set([
219
+ "FILTER",
220
+ "SORT",
221
+ "PY",
222
+ ]);
223
+ const QUALIFIED_BY_NAME = new Map(FUTURE_FUNCTION_NAMES.map((name) => [
224
+ name,
225
+ WORKSHEET_SCOPED_FUTURE_FUNCTIONS.has(name)
226
+ ? `_xlfn._xlws.${name}`
227
+ : `_xlfn.${name}`,
228
+ ]));
229
+ // Longest names first so `T.DIST.2T` wins over `T.DIST`. The match must be
230
+ // followed by `(` — a bare name (e.g. the defined name `DAYS`) is not a call.
231
+ const FUTURE_FUNCTION_REGEX = new RegExp("(" +
232
+ [...FUTURE_FUNCTION_NAMES]
233
+ .sort((a, b) => b.length - a.length)
234
+ .map((name) => name.replace(/\./g, "\\."))
235
+ .join("|") +
236
+ ")(\\s*\\()", "gi");
237
+ // `\b` is not enough of a left boundary: it would match the `DAYS` in
238
+ // `_xlfn.DAYS` and in `MYDAYS`. The preceding character is checked by offset
239
+ // instead of being consumed by the pattern, so nested calls
240
+ // (`SORT(FILTER(...))`) still match after the enclosing one.
241
+ const FUTURE_FUNCTION_LEFT_BOUNDARY = /[A-Za-z0-9_.$]/;
242
+ // A storage marker starts an identifier: nothing identifier-like may precede
243
+ // it (`MY_xlfn.X` is one name). `_xlfn._xlws.FILTER` strips in two passes.
244
+ const STORAGE_PREFIX_REGEX = /(?<![A-Za-z0-9_])_xl(?:fn|ws|pm|udf)\./gi;
245
+ /**
246
+ * Run `transform` over every unquoted span of `formula`, copying string
247
+ * literals (`"…"`) and quoted sheet names (`'…'`) verbatim. A doubled quote
248
+ * inside a span is an escaped quote; an unterminated literal consumes the
249
+ * rest of the formula.
250
+ */
251
+ function mapUnquotedSegments(formula, transform) {
252
+ let result = "";
253
+ let segmentStart = 0;
254
+ let index = 0;
255
+ while (index < formula.length) {
256
+ const char = formula[index];
257
+ if (char !== '"' && char !== "'") {
258
+ index++;
259
+ continue;
260
+ }
261
+ result += transform(formula.slice(segmentStart, index));
262
+ let end = index + 1;
263
+ while (end < formula.length) {
264
+ if (formula[end] === char) {
265
+ if (formula[end + 1] === char) {
266
+ end += 2;
267
+ continue;
268
+ }
269
+ break;
270
+ }
271
+ end++;
272
+ }
273
+ const literalEnd = end < formula.length ? end + 1 : formula.length;
274
+ result += formula.slice(index, literalEnd);
275
+ index = literalEnd;
276
+ segmentStart = literalEnd;
277
+ }
278
+ return result + transform(formula.slice(segmentStart));
279
+ }
280
+ const stripSegment = (segment) => segment.replace(STORAGE_PREFIX_REGEX, "");
281
+ function qualifySegment(segment) {
282
+ const stripped = stripSegment(segment);
283
+ return stripped.replace(FUTURE_FUNCTION_REGEX, (match, name, open, offset) => {
284
+ if (offset > 0 &&
285
+ FUTURE_FUNCTION_LEFT_BOUNDARY.test(stripped[offset - 1])) {
286
+ return match;
287
+ }
288
+ const qualified = QUALIFIED_BY_NAME.get(name.toUpperCase());
289
+ return qualified ? qualified + open : match;
290
+ });
291
+ }
292
+ /**
293
+ * Remove every `_xlfn.` / `_xlws.` / `_xlpm.` / `_xludf.` storage marker from
294
+ * `formula` (outside string literals and quoted sheet names). Idempotent.
295
+ */
296
+ export function stripExcelFormulaPrefixes(formula) {
297
+ return mapUnquotedSegments(formula, stripSegment);
298
+ }
299
+ // ---------------------------------------------------------------- token helpers
300
+ const tokenName = (token) => token?.tokenType?.name;
301
+ const tokenEnd = (token) => (token.endOffset ?? token.startOffset) + 1;
302
+ /** Index of the `)` closing the call / group that opens at `start`, or -1. */
303
+ const closeParenOf = (tokens, start) => {
304
+ let depth = 0;
305
+ for (let i = start; i < tokens.length; i++) {
306
+ const name = tokenName(tokens[i]);
307
+ if (name === T.Function.name || name === T.OpenParen.name)
308
+ depth++;
309
+ else if (name === T.CloseParen.name)
310
+ depth--;
311
+ if (depth === 0)
312
+ return i;
313
+ }
314
+ return -1;
315
+ };
316
+ const isSheetPrefix = (token) => {
317
+ const name = tokenName(token);
318
+ return (name === T.Sheet.name ||
319
+ name === T.SheetQuoted.name ||
320
+ name === T.SheetRange.name);
321
+ };
322
+ /** Apply non-overlapping `[start, end)` replacements to `source`. */
323
+ const applyReplacements = (source, replacements) => {
324
+ const sorted = [...replacements].sort((a, b) => a.start - b.start);
325
+ let out = "";
326
+ let cursor = 0;
327
+ for (const r of sorted) {
328
+ if (r.start < cursor)
329
+ continue;
330
+ out += source.slice(cursor, r.start) + r.text;
331
+ cursor = r.end;
332
+ }
333
+ return out + source.slice(cursor);
334
+ };
335
+ const safeLexify = (formula) => {
336
+ try {
337
+ return lexify(formula).tokens;
338
+ }
339
+ catch {
340
+ return undefined;
341
+ }
342
+ };
343
+ // ---------------------------------------------------------------- spilled-range operator
344
+ const ANCHORARRAY_CALL = /^(?:_xlfn\.)?ANCHORARRAY\($/i;
345
+ /**
346
+ * `_xlfn.ANCHORARRAY(A1)` / `ANCHORARRAY(Sheet1!A1)` → `A1#` / `Sheet1!A1#`
347
+ * — Excel's stored form of the spilled-range operator back to its UI form.
348
+ * A call whose argument is not exactly one cell reference is left as is.
349
+ */
350
+ export function decodeAnchorArray(formula) {
351
+ const tokens = safeLexify(formula);
352
+ if (!tokens)
353
+ return formula;
354
+ const replacements = [];
355
+ for (let i = 0; i < tokens.length; i++) {
356
+ const token = tokens[i];
357
+ if (tokenName(token) !== T.Function.name ||
358
+ !ANCHORARRAY_CALL.test(token.image)) {
359
+ continue;
360
+ }
361
+ const close = closeParenOf(tokens, i);
362
+ if (close === -1)
363
+ continue;
364
+ const inner = tokens.slice(i + 1, close);
365
+ const cell = inner[inner.length - 1];
366
+ const isCell = cell !== undefined &&
367
+ tokenName(cell) === T.Cell.name &&
368
+ !cell.image.endsWith("#") &&
369
+ (inner.length === 1 || (inner.length === 2 && isSheetPrefix(inner[0])));
370
+ if (!isCell)
371
+ continue;
372
+ replacements.push({
373
+ start: token.startOffset,
374
+ end: tokenEnd(tokens[close]),
375
+ text: `${formula.slice(inner[0].startOffset, tokenEnd(cell))}#`,
376
+ });
377
+ }
378
+ return replacements.length ? applyReplacements(formula, replacements) : formula;
379
+ }
380
+ /**
381
+ * `A1#` / `Sheet1!A1#` → `ANCHORARRAY(A1)` / `ANCHORARRAY(Sheet1!A1)` — the
382
+ * spilled-range operator in the form sheet XML accepts (future-function
383
+ * qualification then adds the `_xlfn.` prefix). `#REF!#` is left alone.
384
+ */
385
+ export function encodeSpilledRanges(formula) {
386
+ const tokens = safeLexify(formula);
387
+ if (!tokens)
388
+ return formula;
389
+ const replacements = [];
390
+ for (let i = 0; i < tokens.length; i++) {
391
+ const token = tokens[i];
392
+ if (tokenName(token) !== T.Cell.name || !token.image.endsWith("#")) {
393
+ continue;
394
+ }
395
+ const first = isSheetPrefix(tokens[i - 1]) ? tokens[i - 1] : token;
396
+ const ref = formula.slice(first.startOffset, tokenEnd(token) - 1);
397
+ replacements.push({
398
+ start: first.startOffset,
399
+ end: tokenEnd(token),
400
+ text: `ANCHORARRAY(${ref})`,
401
+ });
402
+ }
403
+ return replacements.length ? applyReplacements(formula, replacements) : formula;
404
+ }
405
+ // ---------------------------------------------------------------- LET / LAMBDA parameters
406
+ const LET_CALL = /^(?:_xlfn\.)?LET\($/i;
407
+ const LAMBDA_CALL = /^(?:_xlfn\.)?LAMBDA\($/i;
408
+ const PARAMETER_MARKER = /^_xlpm\./i;
409
+ /**
410
+ * A bare identifier at `index`: a `Name`, or a one-to-three-letter word the
411
+ * lexer reads as a whole-column reference (`x`, `acc`) when it is not part of
412
+ * an `A:A` column range.
413
+ */
414
+ const isIdentifier = (tokens, index) => {
415
+ const name = tokenName(tokens[index]);
416
+ if (name === T.Name.name)
417
+ return true;
418
+ if (name !== T.Column.name)
419
+ return false;
420
+ if (/[$\d]/.test(tokens[index].image))
421
+ return false;
422
+ return (tokenName(tokens[index - 1]) !== T.Colon.name &&
423
+ tokenName(tokens[index + 1]) !== T.Colon.name);
424
+ };
425
+ /** `a, b, c` → the token index ranges of the top-level arguments of a call. */
426
+ const topLevelArguments = (tokens, open, close) => {
427
+ const args = [];
428
+ let depth = 0;
429
+ let start = open + 1;
430
+ for (let i = open + 1; i < close; i++) {
431
+ const name = tokenName(tokens[i]);
432
+ if (name === T.Function.name || name === T.OpenParen.name)
433
+ depth++;
434
+ else if (name === T.CloseParen.name)
435
+ depth--;
436
+ else if (depth === 0 && (name === T.Comma.name || name === T.Semicolon.name)) {
437
+ args.push([start, i - 1]);
438
+ start = i + 1;
439
+ }
440
+ }
441
+ if (start <= close - 1)
442
+ args.push([start, close - 1]);
443
+ return args;
444
+ };
445
+ /**
446
+ * Prefix every LET / LAMBDA parameter with `_xlpm.` — at its declaration and
447
+ * at every use inside the call (a use as a function, `double(5)` for a
448
+ * LET-bound lambda, included). Parameters are the odd-position LET arguments
449
+ * (all but the last argument, which is the calculation) and every LAMBDA
450
+ * argument but the last. Already-prefixed input comes out prefixed once.
451
+ */
452
+ export function qualifyLambdaParameters(formula) {
453
+ const tokens = safeLexify(formula);
454
+ if (!tokens)
455
+ return formula;
456
+ const replacements = new Map();
457
+ for (let i = 0; i < tokens.length; i++) {
458
+ const token = tokens[i];
459
+ if (tokenName(token) !== T.Function.name)
460
+ continue;
461
+ const isLet = LET_CALL.test(token.image);
462
+ const isLambda = !isLet && LAMBDA_CALL.test(token.image);
463
+ if (!isLet && !isLambda)
464
+ continue;
465
+ const close = closeParenOf(tokens, i);
466
+ if (close === -1)
467
+ continue;
468
+ const args = topLevelArguments(tokens, i, close);
469
+ const params = new Set();
470
+ args.forEach(([start, end], index) => {
471
+ const isLast = index === args.length - 1;
472
+ const isParameter = isLambda ? !isLast : index % 2 === 0 && !isLast;
473
+ if (!isParameter || start !== end)
474
+ return;
475
+ const arg = tokens[start];
476
+ if (!isIdentifier(tokens, start))
477
+ return;
478
+ params.add(arg.image.replace(PARAMETER_MARKER, "").toUpperCase());
479
+ });
480
+ if (!params.size)
481
+ continue;
482
+ for (let j = i + 1; j < close; j++) {
483
+ const inner = tokens[j];
484
+ let bare;
485
+ if (isIdentifier(tokens, j)) {
486
+ bare = inner.image;
487
+ }
488
+ else if (tokenName(inner) === T.Function.name) {
489
+ bare = inner.image.slice(0, -1);
490
+ }
491
+ else {
492
+ continue;
493
+ }
494
+ const stripped = bare.replace(PARAMETER_MARKER, "");
495
+ if (!params.has(stripped.toUpperCase()))
496
+ continue;
497
+ // Structured-reference and sheet-prefixed names are never parameters.
498
+ const previous = tokens[j - 1];
499
+ if (isSheetPrefix(previous))
500
+ continue;
501
+ replacements.set(inner.startOffset, {
502
+ start: inner.startOffset,
503
+ end: inner.startOffset + bare.length,
504
+ text: `_xlpm.${stripped}`,
505
+ });
506
+ }
507
+ }
508
+ return replacements.size
509
+ ? applyReplacements(formula, [...replacements.values()])
510
+ : formula;
511
+ }
512
+ // ---------------------------------------------------------------- public directions
513
+ /**
514
+ * Export direction. Strip the storage markers and re-add the correct one to
515
+ * every future-function call in `formula`, write `A1#` as `ANCHORARRAY(A1)`
516
+ * (qualified to `_xlfn.ANCHORARRAY`) and prefix LET / LAMBDA parameters with
517
+ * `_xlpm.`. Quoted spans keep their original text. Idempotent: already
518
+ * qualified input comes out qualified exactly once.
519
+ */
520
+ export function qualifyFutureFunctions(formula) {
521
+ // The qualify pass strips every marker (including `_xlpm.`) before re-adding
522
+ // `_xlfn.`, so the parameter pass must run last.
523
+ return qualifyLambdaParameters(mapUnquotedSegments(encodeSpilledRanges(formula), qualifySegment));
524
+ }
525
+ /**
526
+ * Import direction. The formula as Excel displays it: every storage marker
527
+ * removed, `_xlfn.ANCHORARRAY(A1)` shown as `A1#`, `_xlfn.SINGLE(x)` as `@x`.
528
+ */
529
+ export function normalizeImportedFormula(formula) {
530
+ return decodeImplicitIntersection(decodeAnchorArray(stripExcelFormulaPrefixes(formula)));
531
+ }
@@ -1 +1 @@
1
- {"root":["../../autofilter-condition.ts","../../csv-export.ts","../../csv-import-worker.ts","../../csv-import.ts","../../excel-export.ts","../../excel-import-session.ts","../../excel-import-worker.ts","../../helpers.ts","../../index.ts","../../indexed-colors.ts","../../ods-export.ts","../../parse.ts","../../power-query.ts","../../print-settings.ts","../../server.ts","../../theme1.ts","../../types.ts","../../vitest.config.ts","../../xlsx.d.ts","../../csv-parser/helpers.ts","../../csv-parser/types.ts","../../csv-parser/worker.ts","../../excel-parser/builtin-numfmts.ts","../../excel-parser/cfb-decryptor.ts","../../excel-parser/cfb-encryptor.ts","../../excel-parser/dv-cell-stamp.ts","../../excel-parser/helpers.ts","../../excel-parser/interface.ts","../../excel-parser/registry.ts","../../excel-parser/style-registry.ts","../../excel-parser/theme-default.ts","../../excel-parser/types.ts","../../excel-parser/utils.ts","../../excel-parser/workbook.ts","../../excel-parser/worker.ts","../../excel-parser/zip-parser-streaming.ts","../../excel-parser/zip-parser.ts","../../excel-parser/plugins/base/index.ts","../../excel-parser/plugins/ods/conditional-formatting.ts","../../excel-parser/plugins/ods/data-validations.ts","../../excel-parser/plugins/ods/formula.ts","../../excel-parser/plugins/ods/index.ts","../../excel-parser/plugins/ods/sparklines.ts","../../excel-parser/plugins/ods/styles.ts","../../excel-parser/plugins/ods/types.ts","../../excel-parser/plugins/xls/index.ts","../../excel-parser/plugins/xlsb/biff-record-reader.ts","../../excel-parser/plugins/xlsb/index.ts","../../excel-parser/plugins/xlsb/rgce-parser.ts","../../excel-parser/plugins/xlsm/index.ts","../../excel-parser/plugins/xlsx/chartex.ts","../../excel-parser/plugins/xlsx/charts.ts","../../excel-parser/plugins/xlsx/conditional-formatting.ts","../../excel-parser/plugins/xlsx/data-validations.ts","../../excel-parser/plugins/xlsx/drawing.ts","../../excel-parser/plugins/xlsx/form-controls.ts","../../excel-parser/plugins/xlsx/index.ts","../../excel-parser/plugins/xlsx/numfmt.ts","../../excel-parser/plugins/xlsx/pivots.ts","../../excel-parser/plugins/xlsx/relationships.ts","../../excel-parser/plugins/xlsx/sheet-views.ts","../../excel-parser/plugins/xlsx/styles.ts","../../excel-parser/plugins/xlsx/theme.ts","../../excel-parser/plugins/xlsx/threaded-comments.ts","../../excel-parser/plugins/xlsx/value-type-detection.ts","../../exporter/invalid-data-validation.ts","../../exporter/ods/ods-export.ts","../../exporter/ods/sparkline-builder.ts","../../exporter/xlsx/chart-builder.ts","../../exporter/xlsx/chart-templates.ts","../../exporter/xlsx/chartex-builder.ts","../../exporter/xlsx/form-controls-builder.ts","../../exporter/xlsx/future-functions.ts","../../exporter/xlsx/pivot-builder.ts","../../exporter/xlsx/power-query-builder.ts","../../exporter/xlsx/slicer-builder.ts","../../exporter/xlsx/sparkline-builder.ts","../../exporter/xlsx/style-builder.ts","../../exporter/xlsx/threaded-comments-builder.ts","../../exporter/xlsx/xlsx-export.ts","../../helpers/implicit-intersection.ts","../../helpers/theme-xml-generator.ts"],"version":"5.8.3"}
1
+ {"root":["../../autofilter-condition.ts","../../csv-export.ts","../../csv-import-worker.ts","../../csv-import.ts","../../excel-export.ts","../../excel-import-session.ts","../../excel-import-worker.ts","../../helpers.ts","../../index.ts","../../indexed-colors.ts","../../ods-export.ts","../../parse.ts","../../power-query.ts","../../print-settings.ts","../../server.ts","../../theme1.ts","../../types.ts","../../vitest.config.ts","../../xlsx.d.ts","../../csv-parser/helpers.ts","../../csv-parser/types.ts","../../csv-parser/worker.ts","../../excel-parser/builtin-numfmts.ts","../../excel-parser/cfb-decryptor.ts","../../excel-parser/cfb-encryptor.ts","../../excel-parser/dv-cell-stamp.ts","../../excel-parser/helpers.ts","../../excel-parser/interface.ts","../../excel-parser/registry.ts","../../excel-parser/style-registry.ts","../../excel-parser/theme-default.ts","../../excel-parser/types.ts","../../excel-parser/utils.ts","../../excel-parser/workbook.ts","../../excel-parser/worker.ts","../../excel-parser/zip-parser-streaming.ts","../../excel-parser/zip-parser.ts","../../excel-parser/plugins/base/index.ts","../../excel-parser/plugins/ods/conditional-formatting.ts","../../excel-parser/plugins/ods/data-validations.ts","../../excel-parser/plugins/ods/formula.ts","../../excel-parser/plugins/ods/index.ts","../../excel-parser/plugins/ods/sparklines.ts","../../excel-parser/plugins/ods/styles.ts","../../excel-parser/plugins/ods/types.ts","../../excel-parser/plugins/xls/index.ts","../../excel-parser/plugins/xlsb/biff-record-reader.ts","../../excel-parser/plugins/xlsb/index.ts","../../excel-parser/plugins/xlsb/rgce-parser.ts","../../excel-parser/plugins/xlsm/index.ts","../../excel-parser/plugins/xlsx/chartex.ts","../../excel-parser/plugins/xlsx/charts.ts","../../excel-parser/plugins/xlsx/conditional-formatting.ts","../../excel-parser/plugins/xlsx/data-validations.ts","../../excel-parser/plugins/xlsx/drawing.ts","../../excel-parser/plugins/xlsx/form-controls.ts","../../excel-parser/plugins/xlsx/index.ts","../../excel-parser/plugins/xlsx/numfmt.ts","../../excel-parser/plugins/xlsx/pivots.ts","../../excel-parser/plugins/xlsx/relationships.ts","../../excel-parser/plugins/xlsx/sheet-views.ts","../../excel-parser/plugins/xlsx/styles.ts","../../excel-parser/plugins/xlsx/theme.ts","../../excel-parser/plugins/xlsx/threaded-comments.ts","../../excel-parser/plugins/xlsx/value-type-detection.ts","../../exporter/invalid-data-validation.ts","../../exporter/ods/ods-export.ts","../../exporter/ods/sparkline-builder.ts","../../exporter/xlsx/chart-builder.ts","../../exporter/xlsx/chart-templates.ts","../../exporter/xlsx/chartex-builder.ts","../../exporter/xlsx/form-controls-builder.ts","../../exporter/xlsx/future-functions.ts","../../exporter/xlsx/pivot-builder.ts","../../exporter/xlsx/power-query-builder.ts","../../exporter/xlsx/slicer-builder.ts","../../exporter/xlsx/sparkline-builder.ts","../../exporter/xlsx/style-builder.ts","../../exporter/xlsx/threaded-comments-builder.ts","../../exporter/xlsx/xlsx-export.ts","../../helpers/excel-formula-prefixes.ts","../../helpers/implicit-intersection.ts","../../helpers/theme-xml-generator.ts"],"version":"5.8.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowsncolumns/toolkit",
3
- "version": "24.5.4",
3
+ "version": "24.5.5",
4
4
  "author": "Vinay M",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "main": "./dist/cjs/index.js",
@@ -45,7 +45,7 @@
45
45
  "url": "https://github.com/rowsncolumns/spreadsheet/issues"
46
46
  },
47
47
  "devDependencies": {
48
- "@rowsncolumns/utils": ">=9.5.1",
48
+ "@rowsncolumns/utils": ">=9.5.3",
49
49
  "@types/lodash-es": "^4.17.12"
50
50
  },
51
51
  "peerDependencies": {
@@ -56,8 +56,8 @@
56
56
  "@noble/ciphers": "^0.5.3",
57
57
  "@noble/hashes": "^1.4.0",
58
58
  "@rowsncolumns/calculator": ">=7.3.1",
59
- "@rowsncolumns/common-types": ">=5.3.1",
60
- "@rowsncolumns/date-parser": ">=13.0.2",
59
+ "@rowsncolumns/common-types": ">=5.3.2",
60
+ "@rowsncolumns/date-parser": ">=13.0.3",
61
61
  "@zip.js/zip.js": "^2.8.17",
62
62
  "fast-xml-parser": "^5.8.0",
63
63
  "fflate": "^0.8.2",