@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.
- package/dist/cjs/autofilter-condition.js +6 -2
- package/dist/cjs/excel-parser/helpers.d.ts +9 -1
- package/dist/cjs/excel-parser/helpers.d.ts.map +1 -1
- package/dist/cjs/excel-parser/helpers.js +70 -16
- package/dist/cjs/excel-parser/interface.d.ts +9 -1
- package/dist/cjs/excel-parser/interface.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/conditional-formatting.js +61 -6
- package/dist/cjs/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/data-validations.js +13 -2
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts +8 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/index.js +159 -26
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts +17 -2
- package/dist/cjs/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
- package/dist/cjs/excel-parser/plugins/xlsx/styles.js +25 -14
- package/dist/cjs/excel-parser/types.d.ts +5 -1
- package/dist/cjs/excel-parser/types.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.d.ts +1 -0
- package/dist/cjs/excel-parser/workbook.d.ts.map +1 -1
- package/dist/cjs/excel-parser/workbook.js +4 -0
- package/dist/cjs/excel-parser/worker.js +3 -0
- package/dist/cjs/exporter/xlsx/chart-builder.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/chart-builder.js +167 -53
- package/dist/cjs/exporter/xlsx/future-functions.d.ts +6 -7
- package/dist/cjs/exporter/xlsx/future-functions.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/future-functions.js +5 -261
- package/dist/cjs/exporter/xlsx/style-builder.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/style-builder.js +62 -19
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts +10 -2
- package/dist/cjs/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/cjs/exporter/xlsx/xlsx-export.js +409 -160
- package/dist/cjs/helpers/excel-formula-prefixes.d.ts +51 -0
- package/dist/cjs/helpers/excel-formula-prefixes.d.ts.map +1 -0
- package/dist/cjs/helpers/excel-formula-prefixes.js +548 -0
- package/dist/cjs/helpers.d.ts +2 -0
- package/dist/cjs/helpers.d.ts.map +1 -1
- package/dist/cjs/helpers.js +21 -12
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/autofilter-condition.js +6 -2
- package/dist/esm/excel-parser/helpers.d.ts +9 -1
- package/dist/esm/excel-parser/helpers.d.ts.map +1 -1
- package/dist/esm/excel-parser/helpers.js +65 -3
- package/dist/esm/excel-parser/interface.d.ts +9 -1
- package/dist/esm/excel-parser/interface.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/conditional-formatting.js +42 -1
- package/dist/esm/excel-parser/plugins/xlsx/data-validations.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/data-validations.js +11 -2
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts +8 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/index.js +109 -4
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts +17 -2
- package/dist/esm/excel-parser/plugins/xlsx/styles.d.ts.map +1 -1
- package/dist/esm/excel-parser/plugins/xlsx/styles.js +20 -8
- package/dist/esm/excel-parser/types.d.ts +5 -1
- package/dist/esm/excel-parser/types.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.d.ts +1 -0
- package/dist/esm/excel-parser/workbook.d.ts.map +1 -1
- package/dist/esm/excel-parser/workbook.js +3 -0
- package/dist/esm/excel-parser/worker.js +3 -0
- package/dist/esm/exporter/xlsx/chart-builder.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/chart-builder.js +80 -0
- package/dist/esm/exporter/xlsx/future-functions.d.ts +6 -7
- package/dist/esm/exporter/xlsx/future-functions.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/future-functions.js +5 -261
- package/dist/esm/exporter/xlsx/style-builder.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/style-builder.js +56 -18
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts +10 -2
- package/dist/esm/exporter/xlsx/xlsx-export.d.ts.map +1 -1
- package/dist/esm/exporter/xlsx/xlsx-export.js +343 -108
- package/dist/esm/helpers/excel-formula-prefixes.d.ts +51 -0
- package/dist/esm/helpers/excel-formula-prefixes.d.ts.map +1 -0
- package/dist/esm/helpers/excel-formula-prefixes.js +531 -0
- package/dist/esm/helpers.d.ts +2 -0
- package/dist/esm/helpers.d.ts.map +1 -1
- package/dist/esm/helpers.js +23 -12
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- 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
|
+
}
|
package/dist/cjs/helpers.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/cjs/helpers.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
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
|
|
119
|
+
return excelColumnWidthToPixels(chars);
|
|
114
120
|
};
|
|
121
|
+
/** Rendered pixels → stored OOXML column width (padded character units). */
|
|
115
122
|
export var columnPixelToChars = function (pixel) {
|
|
116
|
-
|
|
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
|