@cj-tech-master/excelts 9.3.1 → 9.4.0
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/browser/index.d.ts +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/modules/excel/cell.d.ts +18 -0
- package/dist/browser/modules/excel/cell.js +21 -0
- package/dist/browser/modules/excel/utils/cell-format.js +85 -13
- package/dist/browser/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/browser/modules/excel/workbook.browser.js +49 -0
- package/dist/browser/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/browser/modules/formula/compile/binder.js +48 -6
- package/dist/browser/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/browser/modules/formula/compile/bound-ast.js +1 -0
- package/dist/browser/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/browser/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/browser/modules/formula/functions/_shared.js +47 -0
- package/dist/browser/modules/formula/functions/conditional.js +103 -22
- package/dist/browser/modules/formula/functions/date.js +105 -23
- package/dist/browser/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/browser/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/browser/modules/formula/functions/engineering.js +103 -151
- package/dist/browser/modules/formula/functions/financial.js +210 -184
- package/dist/browser/modules/formula/functions/lookup.js +224 -157
- package/dist/browser/modules/formula/functions/math.d.ts +26 -0
- package/dist/browser/modules/formula/functions/math.js +249 -69
- package/dist/browser/modules/formula/functions/statistical.js +221 -171
- package/dist/browser/modules/formula/functions/text.js +112 -52
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/browser/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/browser/modules/formula/materialize/types.d.ts +15 -0
- package/dist/browser/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/browser/modules/formula/runtime/evaluator.js +582 -162
- package/dist/browser/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/browser/modules/formula/runtime/function-registry.js +59 -13
- package/dist/browser/modules/formula/runtime/values.d.ts +13 -0
- package/dist/browser/modules/formula/runtime/values.js +20 -2
- package/dist/browser/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/browser/modules/formula/syntax/ast.js +1 -0
- package/dist/browser/modules/formula/syntax/parser.js +29 -7
- package/dist/browser/modules/formula/syntax/token-types.d.ts +4 -0
- package/dist/browser/modules/formula/syntax/token-types.js +9 -0
- package/dist/browser/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/modules/excel/cell.js +21 -0
- package/dist/cjs/modules/excel/utils/cell-format.js +85 -13
- package/dist/cjs/modules/excel/workbook.browser.js +49 -0
- package/dist/cjs/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/cjs/modules/formula/compile/binder.js +48 -6
- package/dist/cjs/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/cjs/modules/formula/functions/_shared.js +48 -0
- package/dist/cjs/modules/formula/functions/conditional.js +103 -22
- package/dist/cjs/modules/formula/functions/date.js +104 -22
- package/dist/cjs/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/cjs/modules/formula/functions/engineering.js +109 -157
- package/dist/cjs/modules/formula/functions/financial.js +209 -183
- package/dist/cjs/modules/formula/functions/lookup.js +224 -157
- package/dist/cjs/modules/formula/functions/math.js +254 -70
- package/dist/cjs/modules/formula/functions/statistical.js +222 -172
- package/dist/cjs/modules/formula/functions/text.js +112 -52
- package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/cjs/modules/formula/runtime/evaluator.js +581 -161
- package/dist/cjs/modules/formula/runtime/function-registry.js +57 -11
- package/dist/cjs/modules/formula/runtime/values.js +21 -2
- package/dist/cjs/modules/formula/syntax/parser.js +29 -7
- package/dist/cjs/modules/formula/syntax/token-types.js +9 -0
- package/dist/cjs/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/esm/index.js +2 -0
- package/dist/esm/modules/excel/cell.js +21 -0
- package/dist/esm/modules/excel/utils/cell-format.js +85 -13
- package/dist/esm/modules/excel/workbook.browser.js +49 -0
- package/dist/esm/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/esm/modules/formula/compile/binder.js +48 -6
- package/dist/esm/modules/formula/compile/bound-ast.js +1 -0
- package/dist/esm/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/esm/modules/formula/functions/_shared.js +47 -0
- package/dist/esm/modules/formula/functions/conditional.js +103 -22
- package/dist/esm/modules/formula/functions/date.js +105 -23
- package/dist/esm/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/esm/modules/formula/functions/engineering.js +103 -151
- package/dist/esm/modules/formula/functions/financial.js +210 -184
- package/dist/esm/modules/formula/functions/lookup.js +224 -157
- package/dist/esm/modules/formula/functions/math.js +249 -69
- package/dist/esm/modules/formula/functions/statistical.js +221 -171
- package/dist/esm/modules/formula/functions/text.js +112 -52
- package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/esm/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/esm/modules/formula/runtime/evaluator.js +582 -162
- package/dist/esm/modules/formula/runtime/function-registry.js +59 -13
- package/dist/esm/modules/formula/runtime/values.js +20 -2
- package/dist/esm/modules/formula/syntax/ast.js +1 -0
- package/dist/esm/modules/formula/syntax/parser.js +29 -7
- package/dist/esm/modules/formula/syntax/token-types.js +9 -0
- package/dist/esm/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/iife/excelts.iife.js +1502 -1379
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +26 -26
- package/dist/types/index.d.ts +1 -0
- package/dist/types/modules/excel/cell.d.ts +18 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/types/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/types/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/types/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/types/modules/formula/functions/math.d.ts +26 -0
- package/dist/types/modules/formula/materialize/types.d.ts +15 -0
- package/dist/types/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/types/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/types/modules/formula/runtime/values.d.ts +13 -0
- package/dist/types/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/types/modules/formula/syntax/token-types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -38,6 +38,11 @@ export declare function registerFunction(desc: FunctionDescriptor): void;
|
|
|
38
38
|
* `_XLFN._XLWS.` prefixed variants by stripping the prefix before lookup
|
|
39
39
|
* (a no-op for plain names, so plain lookups also go through a single
|
|
40
40
|
* Map.get call — avoiding the double-lookup pattern used previously).
|
|
41
|
+
*
|
|
42
|
+
* Fast-path: the overwhelming majority of lookups use plain names
|
|
43
|
+
* (`SUM`, `IF`, `VLOOKUP`, …). Checking the prefix sentinel byte up
|
|
44
|
+
* front lets those callers skip the `slice`/`startsWith` machinery in
|
|
45
|
+
* `stripFunctionPrefix` entirely.
|
|
41
46
|
*/
|
|
42
47
|
export declare function lookupFunction(name: string): FunctionDescriptor | undefined;
|
|
43
48
|
/**
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* are handled directly by the evaluator's special-form dispatch.
|
|
10
10
|
*/
|
|
11
11
|
import { stripFunctionPrefix } from "../syntax/token-types.js";
|
|
12
|
-
import { RVKind, BLANK, ERRORS, rvBoolean, rvNumber, rvString, topLeft } from "./values.js";
|
|
12
|
+
import { RVKind, BLANK, ERRORS, rvBoolean, rvNumber, rvString, toNumberRV, toStringRV, topLeft } from "./values.js";
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// Registry
|
|
15
15
|
// ============================================================================
|
|
@@ -34,8 +34,17 @@ export function registerFunction(desc) {
|
|
|
34
34
|
* `_XLFN._XLWS.` prefixed variants by stripping the prefix before lookup
|
|
35
35
|
* (a no-op for plain names, so plain lookups also go through a single
|
|
36
36
|
* Map.get call — avoiding the double-lookup pattern used previously).
|
|
37
|
+
*
|
|
38
|
+
* Fast-path: the overwhelming majority of lookups use plain names
|
|
39
|
+
* (`SUM`, `IF`, `VLOOKUP`, …). Checking the prefix sentinel byte up
|
|
40
|
+
* front lets those callers skip the `slice`/`startsWith` machinery in
|
|
41
|
+
* `stripFunctionPrefix` entirely.
|
|
37
42
|
*/
|
|
38
43
|
export function lookupFunction(name) {
|
|
44
|
+
// Plain names don't start with `_` — skip the prefix-strip call.
|
|
45
|
+
if (name.length === 0 || name.charCodeAt(0) !== 95 /* `_` */) {
|
|
46
|
+
return registryMap.get(name);
|
|
47
|
+
}
|
|
39
48
|
return registryMap.get(stripFunctionPrefix(name));
|
|
40
49
|
}
|
|
41
50
|
/**
|
|
@@ -105,20 +114,26 @@ function registerNativeInformationAndLogical() {
|
|
|
105
114
|
if (v.kind === RVKind.Error) {
|
|
106
115
|
return v;
|
|
107
116
|
}
|
|
108
|
-
|
|
117
|
+
// Excel coerces numeric strings and booleans for ISEVEN / ISODD
|
|
118
|
+
// (e.g. `ISEVEN("3")` → FALSE, `ISODD(TRUE)` → TRUE). Only genuine
|
|
119
|
+
// non-numeric text falls through to #VALUE!. Previously we rejected
|
|
120
|
+
// every non-Number kind outright.
|
|
121
|
+
const n = toNumberRV(v);
|
|
122
|
+
if (n.kind === RVKind.Error) {
|
|
109
123
|
return ERRORS.VALUE;
|
|
110
124
|
}
|
|
111
|
-
return rvBoolean(Math.floor(Math.abs(
|
|
125
|
+
return rvBoolean(Math.floor(Math.abs(n.value)) % 2 === 0);
|
|
112
126
|
});
|
|
113
127
|
defineEager("ISODD", 1, 1, args => {
|
|
114
128
|
const v = scalar(args);
|
|
115
129
|
if (v.kind === RVKind.Error) {
|
|
116
130
|
return v;
|
|
117
131
|
}
|
|
118
|
-
|
|
132
|
+
const n = toNumberRV(v);
|
|
133
|
+
if (n.kind === RVKind.Error) {
|
|
119
134
|
return ERRORS.VALUE;
|
|
120
135
|
}
|
|
121
|
-
return rvBoolean(Math.floor(Math.abs(
|
|
136
|
+
return rvBoolean(Math.floor(Math.abs(n.value)) % 2 === 1);
|
|
122
137
|
});
|
|
123
138
|
defineEager("N", 1, 1, args => {
|
|
124
139
|
const v = scalar(args);
|
|
@@ -169,6 +184,11 @@ function registerNativeInformationAndLogical() {
|
|
|
169
184
|
return map[v.code] !== undefined ? rvNumber(map[v.code]) : ERRORS.NA;
|
|
170
185
|
});
|
|
171
186
|
defineEager("NA", 0, 0, () => ERRORS.NA);
|
|
187
|
+
// TRUE() and FALSE() — Excel accepts both the literal and the
|
|
188
|
+
// zero-arg function form. The tokenizer routes `TRUE(` to a Function
|
|
189
|
+
// token, so we need to register these so the call binds.
|
|
190
|
+
defineEager("TRUE", 0, 0, () => rvBoolean(true));
|
|
191
|
+
defineEager("FALSE", 0, 0, () => rvBoolean(false));
|
|
172
192
|
// ── Stubs — limited implementations for functions that need runtime context ──
|
|
173
193
|
// INFO returns a handful of environment-describing strings. We implement
|
|
174
194
|
// the subset that's meaningful in a headless engine: `"release"` (engine
|
|
@@ -180,7 +200,10 @@ function registerNativeInformationAndLogical() {
|
|
|
180
200
|
if (args.length === 0) {
|
|
181
201
|
return ERRORS.NA;
|
|
182
202
|
}
|
|
183
|
-
|
|
203
|
+
// Implicit intersection — without topLeft, passing an array would
|
|
204
|
+
// route through the default `.value = ""` branch and silently
|
|
205
|
+
// surface #VALUE! instead of using the first cell.
|
|
206
|
+
const t = topLeft(args[0]);
|
|
184
207
|
if (t.kind === RVKind.Error) {
|
|
185
208
|
return t;
|
|
186
209
|
}
|
|
@@ -239,7 +262,10 @@ function registerNativeInformationAndLogical() {
|
|
|
239
262
|
if (url.kind === RVKind.Error) {
|
|
240
263
|
return url;
|
|
241
264
|
}
|
|
242
|
-
|
|
265
|
+
// Previously `String(url)` stringified a RuntimeValue object to
|
|
266
|
+
// the literal `"[object Object]"`. Route through `toStringRV`
|
|
267
|
+
// so numbers / booleans / blanks produce the expected text.
|
|
268
|
+
return rvString(toStringRV(url));
|
|
243
269
|
}
|
|
244
270
|
if (display.kind === RVKind.String) {
|
|
245
271
|
return display;
|
|
@@ -264,6 +290,21 @@ function registerNativeInformationAndLogical() {
|
|
|
264
290
|
if (v.kind === RVKind.Number) {
|
|
265
291
|
return rvBoolean(v.value === 0);
|
|
266
292
|
}
|
|
293
|
+
// Excel accepts "TRUE" / "FALSE" strings (case-insensitive) and
|
|
294
|
+
// Blank cells (treated as FALSE). Previously any non-boolean,
|
|
295
|
+
// non-numeric kind fell through to #VALUE!.
|
|
296
|
+
if (v.kind === RVKind.Blank) {
|
|
297
|
+
return rvBoolean(true);
|
|
298
|
+
}
|
|
299
|
+
if (v.kind === RVKind.String) {
|
|
300
|
+
const upper = v.value.toUpperCase();
|
|
301
|
+
if (upper === "TRUE") {
|
|
302
|
+
return rvBoolean(false);
|
|
303
|
+
}
|
|
304
|
+
if (upper === "FALSE") {
|
|
305
|
+
return rvBoolean(true);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
267
308
|
return ERRORS.VALUE;
|
|
268
309
|
});
|
|
269
310
|
defineEager("AND", 1, 255, args => boolAggregate(args, true, (cur, val) => cur && val));
|
|
@@ -379,6 +420,11 @@ function registerNativeTextFunctions() {
|
|
|
379
420
|
defineEager("PROPER", 1, 1, fnPROPER);
|
|
380
421
|
defineEager("SUBSTITUTE", 3, 4, fnSUBSTITUTE);
|
|
381
422
|
defineEager("REPLACE", 4, 4, fnREPLACE);
|
|
423
|
+
// REPLACEB is REPLACE's double-byte alias. In non-DBCS locales Excel
|
|
424
|
+
// treats them identically, so aliasing to the same implementation
|
|
425
|
+
// matches behaviour without duplicating logic (matches the existing
|
|
426
|
+
// LEFTB / RIGHTB / MIDB / LENB / FINDB / SEARCHB wiring below).
|
|
427
|
+
defineEager("REPLACEB", 4, 4, fnREPLACE);
|
|
382
428
|
defineEager("FIND", 2, 3, fnFIND);
|
|
383
429
|
defineEager("FINDB", 2, 3, fnFIND);
|
|
384
430
|
defineEager("SEARCH", 2, 3, fnSEARCH);
|
|
@@ -674,7 +720,7 @@ function registerNativeStatisticalFunctions() {
|
|
|
674
720
|
// ============================================================================
|
|
675
721
|
// Native Math Functions
|
|
676
722
|
// ============================================================================
|
|
677
|
-
import { fnSUM, fnAVERAGE, fnMIN, fnMAX, fnCOUNT, fnCOUNTA, fnCOUNTBLANK, fnPRODUCT, fnSUMPRODUCT, fnABS, fnCEILING, fnFLOOR, fnINT, fnMOD, fnPOWER, fnROUND, fnROUNDDOWN, fnROUNDUP, fnSQRT, fnSQRTPI, fnLN, fnLOG, fnLOG10, fnEXP, fnPI, fnRAND, fnRANDBETWEEN, fnSIGN, fnTRUNC, fnSUMSQ, fnGCD, fnLCM, fnEVEN, fnODD, fnMROUND, fnQUOTIENT, fnBASE, fnDECIMAL, fnROMAN, fnARABIC, fnDEGREES, fnRADIANS, fnSUMX2MY2, fnSUMX2PY2, fnSUMXMY2, fnMULTINOMIAL, fnFACT as fnMathFACT, fnFACTDOUBLE as fnMathFACTDOUBLE, fnCOMBIN as fnMathCOMBIN, fnCOMBINA as fnMathCOMBINA, fnPERMUT as fnMathPERMUT, fnSIN, fnCOS, fnTAN, fnASIN, fnACOS, fnATAN, fnATAN2, fnSINH, fnCOSH, fnTANH, fnASINH, fnACOSH, fnATANH, fnSEC, fnCSC, fnCOT, fnSECH, fnCSCH, fnCOTH, fnACOT, fnACOTH, fnMMULT, fnMDETERM, fnMINVERSE, fnMUNIT, fnSERIESSUM } from "../functions/math.js";
|
|
723
|
+
import { fnSUM, fnAVERAGE, fnMIN, fnMAX, fnCOUNT, fnCOUNTA, fnCOUNTBLANK, fnPRODUCT, fnSUMPRODUCT, fnABS, fnCEILING, fnCEILING_MATH, fnCEILING_PRECISE, fnFLOOR, fnFLOOR_MATH, fnFLOOR_PRECISE, fnINT, fnMOD, fnPOWER, fnROUND, fnROUNDDOWN, fnROUNDUP, fnSQRT, fnSQRTPI, fnLN, fnLOG, fnLOG10, fnEXP, fnPI, fnRAND, fnRANDBETWEEN, fnSIGN, fnTRUNC, fnSUMSQ, fnGCD, fnLCM, fnEVEN, fnODD, fnMROUND, fnQUOTIENT, fnBASE, fnDECIMAL, fnROMAN, fnARABIC, fnDEGREES, fnRADIANS, fnSUMX2MY2, fnSUMX2PY2, fnSUMXMY2, fnMULTINOMIAL, fnFACT as fnMathFACT, fnFACTDOUBLE as fnMathFACTDOUBLE, fnCOMBIN as fnMathCOMBIN, fnCOMBINA as fnMathCOMBINA, fnPERMUT as fnMathPERMUT, fnSIN, fnCOS, fnTAN, fnASIN, fnACOS, fnATAN, fnATAN2, fnSINH, fnCOSH, fnTANH, fnASINH, fnACOSH, fnATANH, fnSEC, fnCSC, fnCOT, fnSECH, fnCSCH, fnCOTH, fnACOT, fnACOTH, fnMMULT, fnMDETERM, fnMINVERSE, fnMUNIT, fnSERIESSUM } from "../functions/math.js";
|
|
678
724
|
function registerNativeMathFunctions() {
|
|
679
725
|
defineEager("SUM", 1, 255, fnSUM);
|
|
680
726
|
defineEager("AVERAGE", 1, 255, fnAVERAGE);
|
|
@@ -692,12 +738,12 @@ function registerNativeMathFunctions() {
|
|
|
692
738
|
defineEager("SERIESSUM", 4, 4, fnSERIESSUM);
|
|
693
739
|
defineEager("ABS", 1, 1, fnABS);
|
|
694
740
|
defineEager("CEILING", 2, 2, fnCEILING);
|
|
695
|
-
defineEager("CEILING.MATH", 1, 3,
|
|
696
|
-
defineEager("CEILING.PRECISE", 1, 2,
|
|
697
|
-
defineEager("ISO.CEILING", 1, 2,
|
|
741
|
+
defineEager("CEILING.MATH", 1, 3, fnCEILING_MATH);
|
|
742
|
+
defineEager("CEILING.PRECISE", 1, 2, fnCEILING_PRECISE);
|
|
743
|
+
defineEager("ISO.CEILING", 1, 2, fnCEILING_PRECISE);
|
|
698
744
|
defineEager("FLOOR", 2, 2, fnFLOOR);
|
|
699
|
-
defineEager("FLOOR.MATH", 1, 3,
|
|
700
|
-
defineEager("FLOOR.PRECISE", 1, 2,
|
|
745
|
+
defineEager("FLOOR.MATH", 1, 3, fnFLOOR_MATH);
|
|
746
|
+
defineEager("FLOOR.PRECISE", 1, 2, fnFLOOR_PRECISE);
|
|
701
747
|
defineEager("INT", 1, 1, fnINT);
|
|
702
748
|
defineEager("MOD", 2, 2, fnMOD);
|
|
703
749
|
defineEager("POWER", 2, 2, fnPOWER);
|
|
@@ -156,6 +156,19 @@ export declare function rvString(value: string): StringValue;
|
|
|
156
156
|
export declare function rvBoolean(value: boolean): BooleanValue;
|
|
157
157
|
export declare function rvError(code: ErrorCode): ErrorValue;
|
|
158
158
|
export declare function rvArray(rows: ScalarValue[][], originRow?: number, originCol?: number, subtotalMask?: readonly (readonly boolean[])[], hiddenRowMask?: readonly boolean[]): ArrayValue;
|
|
159
|
+
/**
|
|
160
|
+
* Fast-path rectangular ArrayValue constructor.
|
|
161
|
+
*
|
|
162
|
+
* Callers that have already produced strictly-rectangular `rows` (every
|
|
163
|
+
* row is the same length — the length they explicitly `new Array(width)`
|
|
164
|
+
* allocated) can skip the two-pass width-scan + padding loop in
|
|
165
|
+
* `rvArray`. Examples: `buildRangeArray`, `broadcastBinaryOp`,
|
|
166
|
+
* `evaluateArrayLiteral`, `TRANSPOSE` — they all know `width` up front.
|
|
167
|
+
*
|
|
168
|
+
* Rows MUST be rectangular; passing ragged data will silently surface as
|
|
169
|
+
* `undefined` cells downstream.
|
|
170
|
+
*/
|
|
171
|
+
export declare function rvArrayRect(rows: ScalarValue[][], height: number, width: number, originRow?: number, originCol?: number, subtotalMask?: readonly (readonly boolean[])[], hiddenRowMask?: readonly boolean[]): ArrayValue;
|
|
159
172
|
export declare function rvRef(sheet: string, top: number, left: number, bottom: number, right: number): ReferenceValue;
|
|
160
173
|
export declare function rvCellRef(sheet: string, row: number, col: number): ReferenceValue;
|
|
161
174
|
export declare function rvLambda(params: string[], body: BoundExpr, closureBindings?: ReadonlyMap<string, RuntimeValue>): LambdaValue;
|
|
@@ -112,10 +112,28 @@ export function rvArray(rows, originRow, originCol, subtotalMask, hiddenRowMask)
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
return buildArrayValue(normalisedRows, height, width, originRow, originCol, subtotalMask, hiddenRowMask);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Fast-path rectangular ArrayValue constructor.
|
|
119
|
+
*
|
|
120
|
+
* Callers that have already produced strictly-rectangular `rows` (every
|
|
121
|
+
* row is the same length — the length they explicitly `new Array(width)`
|
|
122
|
+
* allocated) can skip the two-pass width-scan + padding loop in
|
|
123
|
+
* `rvArray`. Examples: `buildRangeArray`, `broadcastBinaryOp`,
|
|
124
|
+
* `evaluateArrayLiteral`, `TRANSPOSE` — they all know `width` up front.
|
|
125
|
+
*
|
|
126
|
+
* Rows MUST be rectangular; passing ragged data will silently surface as
|
|
127
|
+
* `undefined` cells downstream.
|
|
128
|
+
*/
|
|
129
|
+
export function rvArrayRect(rows, height, width, originRow, originCol, subtotalMask, hiddenRowMask) {
|
|
130
|
+
return buildArrayValue(rows, height, width, originRow, originCol, subtotalMask, hiddenRowMask);
|
|
131
|
+
}
|
|
132
|
+
function buildArrayValue(rows, height, width, originRow, originCol, subtotalMask, hiddenRowMask) {
|
|
115
133
|
return originRow !== undefined
|
|
116
134
|
? {
|
|
117
135
|
kind: RVKind.Array,
|
|
118
|
-
rows
|
|
136
|
+
rows,
|
|
119
137
|
height,
|
|
120
138
|
width,
|
|
121
139
|
originRow,
|
|
@@ -125,7 +143,7 @@ export function rvArray(rows, originRow, originCol, subtotalMask, hiddenRowMask)
|
|
|
125
143
|
}
|
|
126
144
|
: {
|
|
127
145
|
kind: RVKind.Array,
|
|
128
|
-
rows
|
|
146
|
+
rows,
|
|
129
147
|
height,
|
|
130
148
|
width,
|
|
131
149
|
...(subtotalMask ? { subtotalMask } : {}),
|
|
@@ -20,7 +20,8 @@ export declare const enum NodeType {
|
|
|
20
20
|
ColRangeRef = 13,
|
|
21
21
|
RowRangeRef = 14,
|
|
22
22
|
StructuredRef = 15,
|
|
23
|
-
Missing = 16
|
|
23
|
+
Missing = 16,
|
|
24
|
+
UnionRef = 17
|
|
24
25
|
}
|
|
25
26
|
export interface NumberNode {
|
|
26
27
|
type: NodeType.Number;
|
|
@@ -126,4 +127,15 @@ export interface StructuredRefNode {
|
|
|
126
127
|
export interface MissingNode {
|
|
127
128
|
type: NodeType.Missing;
|
|
128
129
|
}
|
|
129
|
-
|
|
130
|
+
/**
|
|
131
|
+
* A reference union produced by the comma operator inside
|
|
132
|
+
* parentheses — `(A1:B2, D4:E5)`. Each member is itself a
|
|
133
|
+
* reference-producing sub-expression. INDEX's `area_num` parameter
|
|
134
|
+
* selects which element of the union to read.
|
|
135
|
+
*/
|
|
136
|
+
export interface UnionRefNode {
|
|
137
|
+
type: NodeType.UnionRef;
|
|
138
|
+
/** The union members, in syntactic order. */
|
|
139
|
+
areas: AstNode[];
|
|
140
|
+
}
|
|
141
|
+
export type AstNode = NumberNode | StringNode | BooleanNode | ErrorNode | CellRefNode | RangeRefNode | BinaryOpNode | UnaryOpNode | FunctionCallNode | ArrayNode | PercentNode | NameNode | ColRangeRefNode | RowRangeRefNode | StructuredRefNode | MissingNode | UnionRefNode;
|
|
@@ -25,4 +25,5 @@ export var NodeType;
|
|
|
25
25
|
NodeType[NodeType["RowRangeRef"] = 14] = "RowRangeRef";
|
|
26
26
|
NodeType[NodeType["StructuredRef"] = 15] = "StructuredRef";
|
|
27
27
|
NodeType[NodeType["Missing"] = 16] = "Missing";
|
|
28
|
+
NodeType[NodeType["UnionRef"] = 17] = "UnionRef";
|
|
28
29
|
})(NodeType || (NodeType = {}));
|
|
@@ -22,9 +22,15 @@ function prefixBindingPower(op) {
|
|
|
22
22
|
switch (op) {
|
|
23
23
|
case "+":
|
|
24
24
|
case "-":
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
25
|
+
// Excel's unary `-` binds TIGHTER than `^` — unique among
|
|
26
|
+
// spreadsheets and most programming languages. Microsoft's
|
|
27
|
+
// published precedence table places "Negation (as in –1)" at
|
|
28
|
+
// rank 1 (highest) and "Exponentiation" at rank 4.
|
|
29
|
+
// =-2^2 → (-2)^2 → 4 (NOT -(2^2) = -4)
|
|
30
|
+
// =-2^3 → (-2)^3 → -8
|
|
31
|
+
// Previously we used 55 which routed via `^` (60/61) and produced
|
|
32
|
+
// `-(2^3)` — matching most languages but not Excel.
|
|
33
|
+
return 70;
|
|
28
34
|
default:
|
|
29
35
|
return 0;
|
|
30
36
|
}
|
|
@@ -49,7 +55,11 @@ function infixBindingPower(op) {
|
|
|
49
55
|
case "/":
|
|
50
56
|
return [40, 41];
|
|
51
57
|
case "^":
|
|
52
|
-
|
|
58
|
+
// Excel is unusual: `^` is LEFT-associative (not right-associative
|
|
59
|
+
// like the math convention). `=2^3^2` evaluates to `(2^3)^2 = 64`,
|
|
60
|
+
// not `2^(3^2) = 512`. Using right-associative precedence silently
|
|
61
|
+
// diverged from Excel for any stacked exponent.
|
|
62
|
+
return [60, 61];
|
|
53
63
|
// Intersection operator — whitespace between two refs. In Excel
|
|
54
64
|
// precedence this sits between `:` (range, already handled at the
|
|
55
65
|
// tokenizer level) and unary +/-. Left-associative, binds tighter
|
|
@@ -246,12 +256,24 @@ class Parser {
|
|
|
246
256
|
this.next();
|
|
247
257
|
return { type: NodeType.Error, value: t.value };
|
|
248
258
|
}
|
|
249
|
-
// Parenthesized expression
|
|
259
|
+
// Parenthesized expression — also the syntactic entry point for
|
|
260
|
+
// reference unions: `(A1:B2, D4:E5)` is a multi-area reference
|
|
261
|
+
// that `INDEX(..., area_num)` can index into. A single expression
|
|
262
|
+
// inside parens is just an expression group (no UnionRef wrapper).
|
|
250
263
|
if (t.type === TokenType.OpenParen) {
|
|
251
264
|
this.next();
|
|
252
|
-
const
|
|
265
|
+
const first = this.parseExpr(0);
|
|
266
|
+
if (this.peek()?.type === TokenType.Comma) {
|
|
267
|
+
const areas = [first];
|
|
268
|
+
while (this.peek()?.type === TokenType.Comma) {
|
|
269
|
+
this.next(); // consume ','
|
|
270
|
+
areas.push(this.parseExpr(0));
|
|
271
|
+
}
|
|
272
|
+
this.expect(TokenType.CloseParen);
|
|
273
|
+
return { type: NodeType.UnionRef, areas };
|
|
274
|
+
}
|
|
253
275
|
this.expect(TokenType.CloseParen);
|
|
254
|
-
return
|
|
276
|
+
return first;
|
|
255
277
|
}
|
|
256
278
|
// Array constant: {1,2;3,4}
|
|
257
279
|
if (t.type === TokenType.OpenBrace) {
|
|
@@ -148,6 +148,10 @@ export interface IntersectToken {
|
|
|
148
148
|
* The input may or may not already be uppercased — this helper does not
|
|
149
149
|
* alter case; callers that compare against an uppercase table should
|
|
150
150
|
* uppercase first (or compare case-insensitively).
|
|
151
|
+
*
|
|
152
|
+
* Fast path: plain names (99%+ of call sites) start with a letter, so
|
|
153
|
+
* checking the first code unit before the `startsWith` machinery lets
|
|
154
|
+
* those lookups skip the allocation.
|
|
151
155
|
*/
|
|
152
156
|
export declare function stripFunctionPrefix(name: string): string;
|
|
153
157
|
export type Token = NumberToken | StringToken | BooleanToken | ErrorToken | CellRefToken | RangeToken | SheetRefToken | FunctionToken | OperatorToken | OpenParenToken | CloseParenToken | CommaToken | ColonToken | PercentToken | OpenBraceToken | CloseBraceToken | SemicolonToken | UnaryPrefixToken | NameToken | ColRangeToken | RowRangeToken | StructuredRefToken | AtSignToken | IntersectToken;
|
|
@@ -47,8 +47,17 @@ export var TokenType;
|
|
|
47
47
|
* The input may or may not already be uppercased — this helper does not
|
|
48
48
|
* alter case; callers that compare against an uppercase table should
|
|
49
49
|
* uppercase first (or compare case-insensitively).
|
|
50
|
+
*
|
|
51
|
+
* Fast path: plain names (99%+ of call sites) start with a letter, so
|
|
52
|
+
* checking the first code unit before the `startsWith` machinery lets
|
|
53
|
+
* those lookups skip the allocation.
|
|
50
54
|
*/
|
|
51
55
|
export function stripFunctionPrefix(name) {
|
|
56
|
+
// `_` is code unit 95 — ASCII letters are 65..90 / 97..122. The XLFN
|
|
57
|
+
// prefix is the only legitimate name shape that begins with `_`.
|
|
58
|
+
if (name.length === 0 || name.charCodeAt(0) !== 95) {
|
|
59
|
+
return name;
|
|
60
|
+
}
|
|
52
61
|
if (name.startsWith("_XLFN._XLWS.")) {
|
|
53
62
|
return name.slice(12);
|
|
54
63
|
}
|
|
@@ -442,26 +442,56 @@ export function tokenize(formula) {
|
|
|
442
442
|
// String literals
|
|
443
443
|
if (ch === '"') {
|
|
444
444
|
i++; // skip opening quote
|
|
445
|
-
|
|
445
|
+
// Fast path: walk forward looking for a closing quote. In the common
|
|
446
|
+
// case (no escaped quotes) we emit a single `slice` rather than
|
|
447
|
+
// growing a string byte by byte. The slow path falls back to the
|
|
448
|
+
// explicit escape-aware concat when we actually see `""`.
|
|
449
|
+
const start = i;
|
|
446
450
|
let closed = false;
|
|
451
|
+
let firstEscape = -1;
|
|
447
452
|
while (i < len) {
|
|
448
453
|
if (formula[i] === '"') {
|
|
449
454
|
if (i + 1 < len && formula[i + 1] === '"') {
|
|
450
|
-
|
|
451
|
-
str += '"';
|
|
452
|
-
i += 2;
|
|
453
|
-
}
|
|
454
|
-
else {
|
|
455
|
-
i++; // skip closing quote
|
|
456
|
-
closed = true;
|
|
455
|
+
firstEscape = i;
|
|
457
456
|
break;
|
|
458
457
|
}
|
|
458
|
+
closed = true;
|
|
459
|
+
break;
|
|
459
460
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
461
|
+
i++;
|
|
462
|
+
}
|
|
463
|
+
let str;
|
|
464
|
+
if (firstEscape !== -1) {
|
|
465
|
+
// At least one escaped quote — use the classic byte-by-byte loop
|
|
466
|
+
// starting from the first escape so the prefix can still come
|
|
467
|
+
// from `slice`.
|
|
468
|
+
str = formula.slice(start, firstEscape);
|
|
469
|
+
i = firstEscape;
|
|
470
|
+
while (i < len) {
|
|
471
|
+
if (formula[i] === '"') {
|
|
472
|
+
if (i + 1 < len && formula[i + 1] === '"') {
|
|
473
|
+
str += '"';
|
|
474
|
+
i += 2;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
i++;
|
|
478
|
+
closed = true;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
str += formula[i];
|
|
484
|
+
i++;
|
|
485
|
+
}
|
|
463
486
|
}
|
|
464
487
|
}
|
|
488
|
+
else if (closed) {
|
|
489
|
+
str = formula.slice(start, i);
|
|
490
|
+
i++; // consume closing quote
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
str = formula.slice(start, i);
|
|
494
|
+
}
|
|
465
495
|
if (!closed) {
|
|
466
496
|
// Unterminated string literal — reject at tokenize time so we
|
|
467
497
|
// never hand the parser a truncated value that could alias to a
|
|
@@ -723,21 +753,48 @@ export function tokenize(formula) {
|
|
|
723
753
|
// Quoted sheet name: 'Sheet Name'! or 3D ref 'Sheet1:Sheet3'!
|
|
724
754
|
if (ch === "'") {
|
|
725
755
|
i++; // skip opening quote
|
|
726
|
-
|
|
756
|
+
// Fast path: scan to the first `'` — most sheet names don't contain
|
|
757
|
+
// an escaped quote, so we can slice the prefix verbatim instead of
|
|
758
|
+
// growing a string byte-by-byte.
|
|
759
|
+
const start = i;
|
|
760
|
+
let firstEscape = -1;
|
|
727
761
|
while (i < len) {
|
|
728
762
|
if (formula[i] === "'") {
|
|
729
763
|
if (i + 1 < len && formula[i + 1] === "'") {
|
|
730
|
-
|
|
731
|
-
|
|
764
|
+
firstEscape = i;
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
i++;
|
|
770
|
+
}
|
|
771
|
+
let sheetName;
|
|
772
|
+
if (firstEscape !== -1) {
|
|
773
|
+
sheetName = formula.slice(start, firstEscape);
|
|
774
|
+
i = firstEscape;
|
|
775
|
+
// Slow path from the first escape: consume `''` pairs and
|
|
776
|
+
// literal characters until the terminating single `'`.
|
|
777
|
+
while (i < len) {
|
|
778
|
+
if (formula[i] === "'") {
|
|
779
|
+
if (i + 1 < len && formula[i + 1] === "'") {
|
|
780
|
+
sheetName += "'";
|
|
781
|
+
i += 2;
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
i++;
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
732
787
|
}
|
|
733
788
|
else {
|
|
734
|
-
|
|
735
|
-
|
|
789
|
+
sheetName += formula[i];
|
|
790
|
+
i++;
|
|
736
791
|
}
|
|
737
792
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
sheetName = formula.slice(start, i);
|
|
796
|
+
if (i < len && formula[i] === "'") {
|
|
797
|
+
i++; // consume closing quote
|
|
741
798
|
}
|
|
742
799
|
}
|
|
743
800
|
// Expect ! after
|
package/dist/cjs/index.js
CHANGED
|
@@ -17,8 +17,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
17
17
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.
|
|
21
|
-
exports.MarkdownParseError = exports.MarkdownError = exports.MaxItemsExceededError = exports.ImageError = exports.TableError = exports.PivotTableError = exports.WorksheetNameError = exports.XmlParseError = exports.InvalidValueTypeError = exports.MergeConflictError = exports.RowOutOfBoundsError = exports.ColumnOutOfBoundsError = exports.InvalidAddressError = exports.ExcelStreamStateError = exports.ExcelNotSupportedError = exports.ExcelDownloadError = exports.ExcelFileError = exports.isExcelError = exports.ExcelError = exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = void 0;
|
|
20
|
+
exports.pdf = exports.uint8ArrayToString = exports.stringToUint8Array = exports.toUint8Array = exports.concatUint8Arrays = exports.getRootCause = exports.getErrorChain = exports.errorToJSON = exports.toError = exports.BaseError = exports.getSupportedFormats = exports.DateFormatter = exports.DateParser = exports.xmlDecode = exports.xmlEncode = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.excelToDate = exports.dateToExcel = exports.isDateDisplayFormat = exports.formatCellValue = exports.getCellDisplayText = exports.encodeRange = exports.decodeRange = exports.encodeCell = exports.decodeCell = exports.encodeRow = exports.decodeRow = exports.encodeCol = exports.decodeCol = exports.DefinedNames = exports.createCsvFormatterStream = exports.createCsvParserStream = exports.CsvFormatterStream = exports.CsvParserStream = exports.createTextWatermarkImage = exports.WorksheetReader = exports.WorksheetWriter = exports.WorkbookReader = exports.WorkbookWriter = exports.FormCheckbox = exports.DataValidations = exports.Table = exports.Image = exports.Range = exports.Cell = exports.Column = exports.Row = exports.Worksheet = exports.Workbook = void 0;
|
|
21
|
+
exports.MarkdownParseError = exports.MarkdownError = exports.MaxItemsExceededError = exports.ImageError = exports.TableError = exports.PivotTableError = exports.WorksheetNameError = exports.XmlParseError = exports.InvalidValueTypeError = exports.MergeConflictError = exports.RowOutOfBoundsError = exports.ColumnOutOfBoundsError = exports.InvalidAddressError = exports.ExcelStreamStateError = exports.ExcelNotSupportedError = exports.ExcelDownloadError = exports.ExcelFileError = exports.isExcelError = exports.ExcelError = exports.isPdfError = exports.PdfStructureError = exports.PdfFontError = exports.PdfRenderError = exports.PdfError = exports.PageSizes = exports.excelToPdf = void 0;
|
|
22
22
|
var workbook_1 = require("./modules/excel/workbook.js");
|
|
23
23
|
Object.defineProperty(exports, "Workbook", { enumerable: true, get: function () { return workbook_1.Workbook; } });
|
|
24
24
|
var worksheet_1 = require("./modules/excel/worksheet.js");
|
|
@@ -91,6 +91,11 @@ Object.defineProperty(exports, "decodeCell", { enumerable: true, get: function (
|
|
|
91
91
|
Object.defineProperty(exports, "encodeCell", { enumerable: true, get: function () { return address_1.encodeCell; } });
|
|
92
92
|
Object.defineProperty(exports, "decodeRange", { enumerable: true, get: function () { return address_1.decodeRange; } });
|
|
93
93
|
Object.defineProperty(exports, "encodeRange", { enumerable: true, get: function () { return address_1.encodeRange; } });
|
|
94
|
+
// Cell display-text helpers (apply numFmt to produce an Excel-style string)
|
|
95
|
+
var cell_format_1 = require("./modules/excel/utils/cell-format.js");
|
|
96
|
+
Object.defineProperty(exports, "getCellDisplayText", { enumerable: true, get: function () { return cell_format_1.getCellDisplayText; } });
|
|
97
|
+
Object.defineProperty(exports, "formatCellValue", { enumerable: true, get: function () { return cell_format_1.formatCellValue; } });
|
|
98
|
+
Object.defineProperty(exports, "isDateDisplayFormat", { enumerable: true, get: function () { return cell_format_1.isDateDisplayFormat; } });
|
|
94
99
|
// Date conversion (Excel serial dates <-> JS Date)
|
|
95
100
|
var utils_base_1 = require("./utils/utils.base.js");
|
|
96
101
|
Object.defineProperty(exports, "dateToExcel", { enumerable: true, get: function () { return utils_base_1.dateToExcel; } });
|
|
@@ -4,6 +4,7 @@ exports.Cell = void 0;
|
|
|
4
4
|
const enums_1 = require("./enums.js");
|
|
5
5
|
const errors_1 = require("./errors.js");
|
|
6
6
|
const note_1 = require("./note.js");
|
|
7
|
+
const cell_format_1 = require("./utils/cell-format.js");
|
|
7
8
|
const col_cache_1 = require("./utils/col-cache.js");
|
|
8
9
|
const copy_style_1 = require("./utils/copy-style.js");
|
|
9
10
|
const shared_formula_1 = require("./utils/shared-formula.js");
|
|
@@ -274,6 +275,26 @@ class Cell {
|
|
|
274
275
|
get text() {
|
|
275
276
|
return this._value.toString();
|
|
276
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* The cell's display text — the value formatted the way Excel would render
|
|
280
|
+
* it, applying the cell's `numFmt`. For a Date cell with `numFmt` `"mm-dd-yy"`,
|
|
281
|
+
* this returns e.g. `"04-12-19"` rather than the JS `Date.prototype.toString()`
|
|
282
|
+
* output you'd get from `cell.text`.
|
|
283
|
+
*
|
|
284
|
+
* Handles primitive values, dates, and formula results. For rich text,
|
|
285
|
+
* hyperlinks, errors, and other complex types, falls back to `cell.text`.
|
|
286
|
+
*
|
|
287
|
+
* Note: numFmt codes that are locale-dependent in Excel (e.g. built-in
|
|
288
|
+
* numFmtId 14 renders as `dd.mm.yyyy` under German locale but is stored
|
|
289
|
+
* as `mm-dd-yy`) are applied literally — excelts does not perform
|
|
290
|
+
* Excel's locale-based format substitution. If you need a specific date
|
|
291
|
+
* style across cells regardless of per-cell numFmts, call the exported
|
|
292
|
+
* {@link getCellDisplayText} helper with a `dateFormat` argument, or use
|
|
293
|
+
* `worksheet.toJSON({ dateFormat })`.
|
|
294
|
+
*/
|
|
295
|
+
get displayText() {
|
|
296
|
+
return (0, cell_format_1.getCellDisplayText)(this);
|
|
297
|
+
}
|
|
277
298
|
get html() {
|
|
278
299
|
return (0, under_dash_1.escapeHtml)(this.text);
|
|
279
300
|
}
|