@rowsncolumns/toolkit 24.5.3 → 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 (79) 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 +409 -160
  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/helpers.d.ts +2 -0
  37. package/dist/cjs/helpers.d.ts.map +1 -1
  38. package/dist/cjs/helpers.js +21 -12
  39. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  40. package/dist/esm/autofilter-condition.js +6 -2
  41. package/dist/esm/excel-parser/helpers.d.ts +9 -1
  42. package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
  43. package/dist/esm/excel-parser/helpers.js +65 -3
  44. package/dist/esm/excel-parser/interface.d.ts +9 -1
  45. package/dist/esm/excel-parser/interface.d.ts.map +1 -1
  46. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -1
  47. package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.js +42 -1
  48. package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -1
  49. package/dist/esm/excel-parser/plugins/xlsx/data-validations.js +11 -2
  50. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +8 -1
  51. package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
  52. package/dist/esm/excel-parser/plugins/xlsx/index.js +109 -4
  53. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts +17 -2
  54. package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
  55. package/dist/esm/excel-parser/plugins/xlsx/styles.js +20 -8
  56. package/dist/esm/excel-parser/types.d.ts +5 -1
  57. package/dist/esm/excel-parser/types.d.ts.map +1 -1
  58. package/dist/esm/excel-parser/workbook.d.ts +1 -0
  59. package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
  60. package/dist/esm/excel-parser/workbook.js +3 -0
  61. package/dist/esm/excel-parser/worker.js +3 -0
  62. package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
  63. package/dist/esm/exporter/xlsx/chart-builder.js +80 -0
  64. package/dist/esm/exporter/xlsx/future-functions.d.ts +6 -7
  65. package/dist/esm/exporter/xlsx/future-functions.d.ts.map +1 -1
  66. package/dist/esm/exporter/xlsx/future-functions.js +5 -261
  67. package/dist/esm/exporter/xlsx/style-builder.d.ts.map +1 -1
  68. package/dist/esm/exporter/xlsx/style-builder.js +56 -18
  69. package/dist/esm/exporter/xlsx/xlsx-export.d.ts +10 -2
  70. package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
  71. package/dist/esm/exporter/xlsx/xlsx-export.js +343 -108
  72. package/dist/esm/helpers/excel-formula-prefixes.d.ts +51 -0
  73. package/dist/esm/helpers/excel-formula-prefixes.d.ts.map +1 -0
  74. package/dist/esm/helpers/excel-formula-prefixes.js +531 -0
  75. package/dist/esm/helpers.d.ts +2 -0
  76. package/dist/esm/helpers.d.ts.map +1 -1
  77. package/dist/esm/helpers.js +23 -12
  78. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  79. 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,548 @@
1
+ import { __read, __spreadArray, __values } from "tslib";
2
+ /**
3
+ * Excel's file-format spelling of modern formulas, in both directions.
4
+ *
5
+ * The .xlsx grammar was frozen with Excel 2007, so every function added since
6
+ * is stored behind a namespace prefix the UI never shows:
7
+ *
8
+ * - `_xlfn.NAME(` — a "future function" (`_xlfn.XLOOKUP(`, `_xlfn.IFS(`);
9
+ * - `_xlfn._xlws.NAME(` — the worksheet-scoped dynamic-array functions
10
+ * (`_xlfn._xlws.FILTER(`, `_xlfn._xlws.SORT(`, `_xlfn._xlws.PY(`);
11
+ * - `_xlpm.name` — every LET / LAMBDA parameter, at its declaration
12
+ * and at every use (`_xlfn.LET(_xlpm.x,A1,_xlpm.x+1)`);
13
+ * - `_xlfn.ANCHORARRAY(A1)` — the spilled-range operator `A1#`;
14
+ * - `_xlfn.SINGLE(x)` — the implicit-intersection operator `@x`;
15
+ * - `_xludf.NAME(` — a user-defined (add-in) function.
16
+ *
17
+ * Written bare, Excel does not resolve a future function: it renders the call
18
+ * behind the implicit-intersection operator (`=@DAYS(...)`) and evaluates
19
+ * `#NAME?`. Kept prefixed in the engine, `SUM(_xlfn.ANCHORARRAY(A1))` is a
20
+ * `#NAME?` and the formula bar shows `=_xlfn.XLOOKUP(`. So the importer
21
+ * removes every marker ({@link normalizeImportedFormula}) and the exporter
22
+ * re-adds them from one list ({@link qualifyFutureFunctions}); a formula
23
+ * round-trips unchanged.
24
+ *
25
+ * The list follows Microsoft's documentation of `_xlfn.`-prefixed functions
26
+ * ("Issue: An _xlfn. prefix is displayed in front of a function in a
27
+ * formula", support.microsoft.com) plus the dynamic-array / LAMBDA-era, 2024
28
+ * and Python-in-Excel additions that ship with the same prefix convention.
29
+ */
30
+ import { lexify, tokenVocabulary } from "@rowsncolumns/calculator";
31
+ import { decodeImplicitIntersection } from "./implicit-intersection";
32
+ var T = tokenVocabulary;
33
+ /**
34
+ * Every function Excel stores behind `_xlfn.` (uppercase). One list for both
35
+ * directions: the exporter qualifies exactly these; the importer strips any
36
+ * marker it finds, list or not, so a newer function still opens cleanly.
37
+ */
38
+ export var FUTURE_FUNCTION_NAMES = [
39
+ "ACOT",
40
+ "ACOTH",
41
+ "AGGREGATE",
42
+ "ANCHORARRAY",
43
+ "ARABIC",
44
+ "ARRAYTOTEXT",
45
+ "BASE",
46
+ "BETA.DIST",
47
+ "BETA.INV",
48
+ "BINOM.DIST",
49
+ "BINOM.DIST.RANGE",
50
+ "BINOM.INV",
51
+ "BITAND",
52
+ "BITLSHIFT",
53
+ "BITOR",
54
+ "BITRSHIFT",
55
+ "BITXOR",
56
+ "BYCOL",
57
+ "BYROW",
58
+ "CEILING.MATH",
59
+ "CEILING.PRECISE",
60
+ "CHISQ.DIST",
61
+ "CHISQ.DIST.RT",
62
+ "CHISQ.INV",
63
+ "CHISQ.INV.RT",
64
+ "CHISQ.TEST",
65
+ "CHOOSECOLS",
66
+ "CHOOSEROWS",
67
+ "COMBINA",
68
+ "CONCAT",
69
+ "CONFIDENCE.NORM",
70
+ "CONFIDENCE.T",
71
+ "COT",
72
+ "COTH",
73
+ "COVARIANCE.P",
74
+ "COVARIANCE.S",
75
+ "CSC",
76
+ "CSCH",
77
+ "DAYS",
78
+ "DECIMAL",
79
+ "DETECTLANGUAGE",
80
+ "DROP",
81
+ "ENCODEURL",
82
+ "ERF.PRECISE",
83
+ "ERFC.PRECISE",
84
+ "EXPAND",
85
+ "EXPON.DIST",
86
+ "F.DIST",
87
+ "F.DIST.RT",
88
+ "F.INV",
89
+ "F.INV.RT",
90
+ "F.TEST",
91
+ "FIELDVALUE",
92
+ "FILTER",
93
+ "FILTERXML",
94
+ "FLOOR.MATH",
95
+ "FLOOR.PRECISE",
96
+ "FORECAST.ETS",
97
+ "FORECAST.ETS.CONFINT",
98
+ "FORECAST.ETS.SEASONALITY",
99
+ "FORECAST.ETS.STAT",
100
+ "FORECAST.LINEAR",
101
+ "FORMULATEXT",
102
+ "GAMMA",
103
+ "GAMMA.DIST",
104
+ "GAMMA.INV",
105
+ "GAMMALN.PRECISE",
106
+ "GAUSS",
107
+ "GROUPBY",
108
+ "HSTACK",
109
+ "HYPGEOM.DIST",
110
+ "IFNA",
111
+ "IFS",
112
+ "IMAGE",
113
+ "IMCOSH",
114
+ "IMCOT",
115
+ "IMCSC",
116
+ "IMCSCH",
117
+ "IMSEC",
118
+ "IMSECH",
119
+ "IMSINH",
120
+ "IMTAN",
121
+ "ISFORMULA",
122
+ "ISOMITTED",
123
+ "ISOWEEKNUM",
124
+ "LAMBDA",
125
+ "LET",
126
+ "LOGNORM.DIST",
127
+ "LOGNORM.INV",
128
+ "MAKEARRAY",
129
+ "MAP",
130
+ "MAXIFS",
131
+ "MINIFS",
132
+ "MODE.MULT",
133
+ "MODE.SNGL",
134
+ "MUNIT",
135
+ "NEGBINOM.DIST",
136
+ "NETWORKDAYS.INTL",
137
+ "NORM.DIST",
138
+ "NORM.INV",
139
+ "NORM.S.DIST",
140
+ "NORM.S.INV",
141
+ "NUMBERVALUE",
142
+ "PDURATION",
143
+ "PERCENTILE.EXC",
144
+ "PERCENTILE.INC",
145
+ "PERCENTOF",
146
+ "PERCENTRANK.EXC",
147
+ "PERCENTRANK.INC",
148
+ "PERMUTATIONA",
149
+ "PHI",
150
+ "PIVOTBY",
151
+ "POISSON.DIST",
152
+ "PQSOURCE",
153
+ "PY",
154
+ "PYTHON_STR",
155
+ "PYTHON_TYPE",
156
+ "PYTHON_TYPENAME",
157
+ "QUARTILE.EXC",
158
+ "QUARTILE.INC",
159
+ "QUERYSTRING",
160
+ "RANDARRAY",
161
+ "RANK.AVG",
162
+ "RANK.EQ",
163
+ "REDUCE",
164
+ "REGEXEXTRACT",
165
+ "REGEXREPLACE",
166
+ "REGEXTEST",
167
+ "RRI",
168
+ "SCAN",
169
+ "SEC",
170
+ "SECH",
171
+ "SEQUENCE",
172
+ "SHEET",
173
+ "SHEETS",
174
+ "SINGLE",
175
+ "SKEW.P",
176
+ "SORT",
177
+ "SORTBY",
178
+ "STDEV.P",
179
+ "STDEV.S",
180
+ "STOCKHISTORY",
181
+ "SWITCH",
182
+ "T.DIST",
183
+ "T.DIST.2T",
184
+ "T.DIST.RT",
185
+ "T.INV",
186
+ "T.INV.2T",
187
+ "T.TEST",
188
+ "TAKE",
189
+ "TEXTAFTER",
190
+ "TEXTBEFORE",
191
+ "TEXTJOIN",
192
+ "TEXTSPLIT",
193
+ "TOCOL",
194
+ "TOROW",
195
+ "TRANSLATE",
196
+ "TRIMRANGE",
197
+ "UNICHAR",
198
+ "UNICODE",
199
+ "UNIQUE",
200
+ "VALUETOTEXT",
201
+ "VAR.P",
202
+ "VAR.S",
203
+ "VSTACK",
204
+ "WEBSERVICE",
205
+ "WEIBULL.DIST",
206
+ "WORKDAY.INTL",
207
+ "WRAPCOLS",
208
+ "WRAPROWS",
209
+ "XLOOKUP",
210
+ "XMATCH",
211
+ "XOR",
212
+ "Z.TEST",
213
+ ];
214
+ /**
215
+ * Future functions that Excel writes behind the worksheet-scope prefix
216
+ * (`_xlfn._xlws.`) rather than plain `_xlfn.` — the dynamic-array functions
217
+ * that also exist as worksheet-only names, plus Python in Excel.
218
+ */
219
+ export var WORKSHEET_SCOPED_FUTURE_FUNCTIONS = new Set([
220
+ "FILTER",
221
+ "SORT",
222
+ "PY",
223
+ ]);
224
+ var QUALIFIED_BY_NAME = new Map(FUTURE_FUNCTION_NAMES.map(function (name) { return [
225
+ name,
226
+ WORKSHEET_SCOPED_FUTURE_FUNCTIONS.has(name)
227
+ ? "_xlfn._xlws.".concat(name)
228
+ : "_xlfn.".concat(name),
229
+ ]; }));
230
+ // Longest names first so `T.DIST.2T` wins over `T.DIST`. The match must be
231
+ // followed by `(` — a bare name (e.g. the defined name `DAYS`) is not a call.
232
+ var FUTURE_FUNCTION_REGEX = new RegExp("(" +
233
+ __spreadArray([], __read(FUTURE_FUNCTION_NAMES), false).sort(function (a, b) { return b.length - a.length; })
234
+ .map(function (name) { return 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
+ var 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
+ var 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
+ var result = "";
253
+ var segmentStart = 0;
254
+ var index = 0;
255
+ while (index < formula.length) {
256
+ var char = formula[index];
257
+ if (char !== '"' && char !== "'") {
258
+ index++;
259
+ continue;
260
+ }
261
+ result += transform(formula.slice(segmentStart, index));
262
+ var 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
+ var 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
+ var stripSegment = function (segment) {
281
+ return segment.replace(STORAGE_PREFIX_REGEX, "");
282
+ };
283
+ function qualifySegment(segment) {
284
+ var stripped = stripSegment(segment);
285
+ return stripped.replace(FUTURE_FUNCTION_REGEX, function (match, name, open, offset) {
286
+ if (offset > 0 &&
287
+ FUTURE_FUNCTION_LEFT_BOUNDARY.test(stripped[offset - 1])) {
288
+ return match;
289
+ }
290
+ var qualified = QUALIFIED_BY_NAME.get(name.toUpperCase());
291
+ return qualified ? qualified + open : match;
292
+ });
293
+ }
294
+ /**
295
+ * Remove every `_xlfn.` / `_xlws.` / `_xlpm.` / `_xludf.` storage marker from
296
+ * `formula` (outside string literals and quoted sheet names). Idempotent.
297
+ */
298
+ export function stripExcelFormulaPrefixes(formula) {
299
+ return mapUnquotedSegments(formula, stripSegment);
300
+ }
301
+ // ---------------------------------------------------------------- token helpers
302
+ var tokenName = function (token) { var _a; return (_a = token === null || token === void 0 ? void 0 : token.tokenType) === null || _a === void 0 ? void 0 : _a.name; };
303
+ var tokenEnd = function (token) { var _a; return ((_a = token.endOffset) !== null && _a !== void 0 ? _a : token.startOffset) + 1; };
304
+ /** Index of the `)` closing the call / group that opens at `start`, or -1. */
305
+ var closeParenOf = function (tokens, start) {
306
+ var depth = 0;
307
+ for (var i = start; i < tokens.length; i++) {
308
+ var name_1 = tokenName(tokens[i]);
309
+ if (name_1 === T.Function.name || name_1 === T.OpenParen.name)
310
+ depth++;
311
+ else if (name_1 === T.CloseParen.name)
312
+ depth--;
313
+ if (depth === 0)
314
+ return i;
315
+ }
316
+ return -1;
317
+ };
318
+ var isSheetPrefix = function (token) {
319
+ var name = tokenName(token);
320
+ return (name === T.Sheet.name ||
321
+ name === T.SheetQuoted.name ||
322
+ name === T.SheetRange.name);
323
+ };
324
+ /** Apply non-overlapping `[start, end)` replacements to `source`. */
325
+ var applyReplacements = function (source, replacements) {
326
+ var e_1, _a;
327
+ var sorted = __spreadArray([], __read(replacements), false).sort(function (a, b) { return a.start - b.start; });
328
+ var out = "";
329
+ var cursor = 0;
330
+ try {
331
+ for (var sorted_1 = __values(sorted), sorted_1_1 = sorted_1.next(); !sorted_1_1.done; sorted_1_1 = sorted_1.next()) {
332
+ var r = sorted_1_1.value;
333
+ if (r.start < cursor)
334
+ continue;
335
+ out += source.slice(cursor, r.start) + r.text;
336
+ cursor = r.end;
337
+ }
338
+ }
339
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
340
+ finally {
341
+ try {
342
+ if (sorted_1_1 && !sorted_1_1.done && (_a = sorted_1.return)) _a.call(sorted_1);
343
+ }
344
+ finally { if (e_1) throw e_1.error; }
345
+ }
346
+ return out + source.slice(cursor);
347
+ };
348
+ var safeLexify = function (formula) {
349
+ try {
350
+ return lexify(formula).tokens;
351
+ }
352
+ catch (_a) {
353
+ return undefined;
354
+ }
355
+ };
356
+ // ---------------------------------------------------------------- spilled-range operator
357
+ var ANCHORARRAY_CALL = /^(?:_xlfn\.)?ANCHORARRAY\($/i;
358
+ /**
359
+ * `_xlfn.ANCHORARRAY(A1)` / `ANCHORARRAY(Sheet1!A1)` → `A1#` / `Sheet1!A1#`
360
+ * — Excel's stored form of the spilled-range operator back to its UI form.
361
+ * A call whose argument is not exactly one cell reference is left as is.
362
+ */
363
+ export function decodeAnchorArray(formula) {
364
+ var tokens = safeLexify(formula);
365
+ if (!tokens)
366
+ return formula;
367
+ var replacements = [];
368
+ for (var i = 0; i < tokens.length; i++) {
369
+ var token = tokens[i];
370
+ if (tokenName(token) !== T.Function.name ||
371
+ !ANCHORARRAY_CALL.test(token.image)) {
372
+ continue;
373
+ }
374
+ var close_1 = closeParenOf(tokens, i);
375
+ if (close_1 === -1)
376
+ continue;
377
+ var inner = tokens.slice(i + 1, close_1);
378
+ var cell = inner[inner.length - 1];
379
+ var isCell = cell !== undefined &&
380
+ tokenName(cell) === T.Cell.name &&
381
+ !cell.image.endsWith("#") &&
382
+ (inner.length === 1 || (inner.length === 2 && isSheetPrefix(inner[0])));
383
+ if (!isCell)
384
+ continue;
385
+ replacements.push({
386
+ start: token.startOffset,
387
+ end: tokenEnd(tokens[close_1]),
388
+ text: "".concat(formula.slice(inner[0].startOffset, tokenEnd(cell)), "#"),
389
+ });
390
+ }
391
+ return replacements.length ? applyReplacements(formula, replacements) : formula;
392
+ }
393
+ /**
394
+ * `A1#` / `Sheet1!A1#` → `ANCHORARRAY(A1)` / `ANCHORARRAY(Sheet1!A1)` — the
395
+ * spilled-range operator in the form sheet XML accepts (future-function
396
+ * qualification then adds the `_xlfn.` prefix). `#REF!#` is left alone.
397
+ */
398
+ export function encodeSpilledRanges(formula) {
399
+ var tokens = safeLexify(formula);
400
+ if (!tokens)
401
+ return formula;
402
+ var replacements = [];
403
+ for (var i = 0; i < tokens.length; i++) {
404
+ var token = tokens[i];
405
+ if (tokenName(token) !== T.Cell.name || !token.image.endsWith("#")) {
406
+ continue;
407
+ }
408
+ var first = isSheetPrefix(tokens[i - 1]) ? tokens[i - 1] : token;
409
+ var ref = formula.slice(first.startOffset, tokenEnd(token) - 1);
410
+ replacements.push({
411
+ start: first.startOffset,
412
+ end: tokenEnd(token),
413
+ text: "ANCHORARRAY(".concat(ref, ")"),
414
+ });
415
+ }
416
+ return replacements.length ? applyReplacements(formula, replacements) : formula;
417
+ }
418
+ // ---------------------------------------------------------------- LET / LAMBDA parameters
419
+ var LET_CALL = /^(?:_xlfn\.)?LET\($/i;
420
+ var LAMBDA_CALL = /^(?:_xlfn\.)?LAMBDA\($/i;
421
+ var PARAMETER_MARKER = /^_xlpm\./i;
422
+ /**
423
+ * A bare identifier at `index`: a `Name`, or a one-to-three-letter word the
424
+ * lexer reads as a whole-column reference (`x`, `acc`) when it is not part of
425
+ * an `A:A` column range.
426
+ */
427
+ var isIdentifier = function (tokens, index) {
428
+ var name = tokenName(tokens[index]);
429
+ if (name === T.Name.name)
430
+ return true;
431
+ if (name !== T.Column.name)
432
+ return false;
433
+ if (/[$\d]/.test(tokens[index].image))
434
+ return false;
435
+ return (tokenName(tokens[index - 1]) !== T.Colon.name &&
436
+ tokenName(tokens[index + 1]) !== T.Colon.name);
437
+ };
438
+ /** `a, b, c` → the token index ranges of the top-level arguments of a call. */
439
+ var topLevelArguments = function (tokens, open, close) {
440
+ var args = [];
441
+ var depth = 0;
442
+ var start = open + 1;
443
+ for (var i = open + 1; i < close; i++) {
444
+ var name_2 = tokenName(tokens[i]);
445
+ if (name_2 === T.Function.name || name_2 === T.OpenParen.name)
446
+ depth++;
447
+ else if (name_2 === T.CloseParen.name)
448
+ depth--;
449
+ else if (depth === 0 && (name_2 === T.Comma.name || name_2 === T.Semicolon.name)) {
450
+ args.push([start, i - 1]);
451
+ start = i + 1;
452
+ }
453
+ }
454
+ if (start <= close - 1)
455
+ args.push([start, close - 1]);
456
+ return args;
457
+ };
458
+ /**
459
+ * Prefix every LET / LAMBDA parameter with `_xlpm.` — at its declaration and
460
+ * at every use inside the call (a use as a function, `double(5)` for a
461
+ * LET-bound lambda, included). Parameters are the odd-position LET arguments
462
+ * (all but the last argument, which is the calculation) and every LAMBDA
463
+ * argument but the last. Already-prefixed input comes out prefixed once.
464
+ */
465
+ export function qualifyLambdaParameters(formula) {
466
+ var tokens = safeLexify(formula);
467
+ if (!tokens)
468
+ return formula;
469
+ var replacements = new Map();
470
+ var _loop_1 = function (i) {
471
+ var token = tokens[i];
472
+ if (tokenName(token) !== T.Function.name)
473
+ return "continue";
474
+ var isLet = LET_CALL.test(token.image);
475
+ var isLambda = !isLet && LAMBDA_CALL.test(token.image);
476
+ if (!isLet && !isLambda)
477
+ return "continue";
478
+ var close_2 = closeParenOf(tokens, i);
479
+ if (close_2 === -1)
480
+ return "continue";
481
+ var args = topLevelArguments(tokens, i, close_2);
482
+ var params = new Set();
483
+ args.forEach(function (_a, index) {
484
+ var _b = __read(_a, 2), start = _b[0], end = _b[1];
485
+ var isLast = index === args.length - 1;
486
+ var isParameter = isLambda ? !isLast : index % 2 === 0 && !isLast;
487
+ if (!isParameter || start !== end)
488
+ return;
489
+ var arg = tokens[start];
490
+ if (!isIdentifier(tokens, start))
491
+ return;
492
+ params.add(arg.image.replace(PARAMETER_MARKER, "").toUpperCase());
493
+ });
494
+ if (!params.size)
495
+ return "continue";
496
+ for (var j = i + 1; j < close_2; j++) {
497
+ var inner = tokens[j];
498
+ var bare = void 0;
499
+ if (isIdentifier(tokens, j)) {
500
+ bare = inner.image;
501
+ }
502
+ else if (tokenName(inner) === T.Function.name) {
503
+ bare = inner.image.slice(0, -1);
504
+ }
505
+ else {
506
+ continue;
507
+ }
508
+ var stripped = bare.replace(PARAMETER_MARKER, "");
509
+ if (!params.has(stripped.toUpperCase()))
510
+ continue;
511
+ // Structured-reference and sheet-prefixed names are never parameters.
512
+ var previous = tokens[j - 1];
513
+ if (isSheetPrefix(previous))
514
+ continue;
515
+ replacements.set(inner.startOffset, {
516
+ start: inner.startOffset,
517
+ end: inner.startOffset + bare.length,
518
+ text: "_xlpm.".concat(stripped),
519
+ });
520
+ }
521
+ };
522
+ for (var i = 0; i < tokens.length; i++) {
523
+ _loop_1(i);
524
+ }
525
+ return replacements.size
526
+ ? applyReplacements(formula, __spreadArray([], __read(replacements.values()), false))
527
+ : formula;
528
+ }
529
+ // ---------------------------------------------------------------- public directions
530
+ /**
531
+ * Export direction. Strip the storage markers and re-add the correct one to
532
+ * every future-function call in `formula`, write `A1#` as `ANCHORARRAY(A1)`
533
+ * (qualified to `_xlfn.ANCHORARRAY`) and prefix LET / LAMBDA parameters with
534
+ * `_xlpm.`. Quoted spans keep their original text. Idempotent: already
535
+ * qualified input comes out qualified exactly once.
536
+ */
537
+ export function qualifyFutureFunctions(formula) {
538
+ // The qualify pass strips every marker (including `_xlpm.`) before re-adding
539
+ // `_xlfn.`, so the parameter pass must run last.
540
+ return qualifyLambdaParameters(mapUnquotedSegments(encodeSpilledRanges(formula), qualifySegment));
541
+ }
542
+ /**
543
+ * Import direction. The formula as Excel displays it: every storage marker
544
+ * removed, `_xlfn.ANCHORARRAY(A1)` shown as `A1#`, `_xlfn.SINGLE(x)` as `@x`.
545
+ */
546
+ export function normalizeImportedFormula(formula) {
547
+ return decodeImplicitIntersection(decodeAnchorArray(stripExcelFormulaPrefixes(formula)));
548
+ }
@@ -8,7 +8,9 @@ export declare const createDownloadableFile: (filename: string, urlOrBlob: Blob
8
8
  export declare const toArrayBuffer: (data: ArrayBuffer | Uint8Array) => ArrayBuffer;
9
9
  export declare const charsToPixel: (chars: number) => number;
10
10
  export declare const pixelToCharacters: (px: number) => number;
11
+ /** Stored OOXML column width (padded character units) → rendered pixels. */
11
12
  export declare const columnCharsToPixel: (chars: number) => number;
13
+ /** Rendered pixels → stored OOXML column width (padded character units). */
12
14
  export declare const columnPixelToChars: (pixel: number) => number;
13
15
  /**
14
16
  * Get range from address reference with minimal processing
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AA+B3E,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU;IACR,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,KACA,MAAM,GAAG,SA8BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,WAAW,IAAI,GAAG,MAAM,8BAazB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,WAAW,GAAG,UAAU,KAAG,WAO9D,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAiC,CAAC;AAC5E,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,WAA+B,CAAC;AAoB5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WACrB,CAAC;AAE5B,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAClB,CAAC;AAE/B;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;;;;cAwBpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAe7C"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAiC3E,eAAO,MAAM,0BAA0B,GACrC,SAAS,MAAM,GAAG,SAAS,EAC3B,YAAY,aAAa,EACzB,iBAAiB,aAAa,EAC9B,UAAU;IACR,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,KACA,MAAM,GAAG,SA8BX,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAiB5B,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,UAAU,MAAM,EAChB,WAAW,IAAI,GAAG,MAAM,8BAazB,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,WAAW,GAAG,UAAU,KAAG,WAO9D,CAAC;AAIF,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,WAAiC,CAAC;AAC5E,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,WAA+B,CAAC;AAyB5E,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WACf,CAAC;AAElC,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,WAG/C,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS;;;;;cAwBpE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAsB/C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAe7C"}
@@ -2,6 +2,7 @@ import { __awaiter, __generator, __read } from "tslib";
2
2
  import { addressToCell, canUseDOM } from "@rowsncolumns/utils";
3
3
  // ---- Formula helpers ----
4
4
  import { alpha2number, number2Alpha } from "@rowsncolumns/utils";
5
+ import { excelColumnWidthToPixels } from "./excel-parser/helpers";
5
6
  var CELL_ADDR_RE = /(?:'[^']+'!?|[^'!\s]+!?)?(\$?[A-Z]+\$?\d+)(?::(\$?[A-Z]+\$?\d+))?/g;
6
7
  var parseAddr = function (addr) {
7
8
  var m = /^(\$?)([A-Z]+)(\$?)(\d+)$/.exec(addr);
@@ -98,22 +99,30 @@ export var pixelToCharacters = function (px) { return Math.floor((72 / 96) * px)
98
99
  // export const pixelToDimension = (chWidth: number) =>
99
100
  // Math.ceil((33.25 / 265) * chWidth);
100
101
  // // const dimensionToPixel = (chWidth: number) => (72 / 30) * chWidth;
101
- // Excel column-width / pixel conversion. Excel's rendered column box
102
- // includes a 5-pixel padding around the MDW (max-digit-width, 7 px for
103
- // Calibri 11), so the default width 8.43 renders at 64 px (8.43 × 7 + 5).
104
- // We keep the +5 padding form so a round-trip pixels → width → pixels is
105
- // an identity for the values Excel itself produces.
102
+ // Excel column-width / pixel conversion (ECMA-376 §18.3.1.13).
106
103
  //
107
- // pixels = floor(widthChars × 7 + 5)
108
- // widthChars = (pixels − 5) / 7
109
- //
110
- // The "pure" ECMA-376 §18.3.1.13 formula omits the 5px padding and gives
111
- // 59 for default-width columns instead of Excel's actual 64.
104
+ // OOXML `<col width>` / `<sheetFormatPr defaultColWidth>` are stored in
105
+ // *padded* character units — Excel writes `px / MDW` (the default 64 px
106
+ // column is stored as 9.140625, not 8.43) and renders it back as
107
+ // `Truncate(((256 × width + Truncate(128 / MDW)) / 256) × MDW)`, which the
108
+ // importer implements in `excelColumnWidthToPixels`. The exporter must be
109
+ // that function's inverse so export → import is a fixed point: storing
110
+ // `trunc(px × 256 / MDW) / 256` (Excel's own 1/256-character quantisation)
111
+ // makes `excelColumnWidthToPixels(columnPixelToChars(px)) === px` for every
112
+ // integer pixel width. Subtracting the 5 px cell padding here (the previous
113
+ // `(px − 5) / 7`) shrank every column by 5 px per export/import cycle once
114
+ // the importer stopped adding it. MDW is 7 px for the Calibri 11 /
115
+ // Aptos Narrow 12 defaults at 96 dpi.
116
+ var EXCEL_MAX_DIGIT_WIDTH_PX = 7;
117
+ /** Stored OOXML column width (padded character units) → rendered pixels. */
112
118
  export var columnCharsToPixel = function (chars) {
113
- return Math.floor(chars * 7 + 5);
119
+ return excelColumnWidthToPixels(chars);
114
120
  };
121
+ /** Rendered pixels → stored OOXML column width (padded character units). */
115
122
  export var columnPixelToChars = function (pixel) {
116
- return Math.max(0, (pixel - 5) / 7);
123
+ if (!Number.isFinite(pixel) || pixel <= 0)
124
+ return 0;
125
+ return Math.trunc((pixel * 256) / EXCEL_MAX_DIGIT_WIDTH_PX) / 256;
117
126
  };
118
127
  /**
119
128
  * Get range from address reference with minimal processing