@pixerate/editor 0.21.0 → 0.23.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/README.md +100 -13
- package/THIRD_PARTY_NOTICES.md +88 -0
- package/dist/agent/index.cjs +86 -111
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +69 -211
- package/dist/agent/index.d.ts +69 -211
- package/dist/agent/index.js +77 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
- package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
- package/dist/canvas/index.cjs +2112 -60
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +712 -181
- package/dist/canvas/index.d.ts +712 -181
- package/dist/canvas/index.js +2105 -53
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-26BVFKHU.cjs +453 -0
- package/dist/chunk-26BVFKHU.cjs.map +1 -0
- package/dist/chunk-2H7ZHPEJ.js +762 -0
- package/dist/chunk-2H7ZHPEJ.js.map +1 -0
- package/dist/chunk-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
- package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
- package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.map +1 -0
- package/dist/chunk-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
- package/dist/chunk-GVSMRPHB.cjs.map +1 -0
- package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
- package/dist/{chunk-GVW4P7BG.js → chunk-K3R5NG2D.js} +448 -54
- package/dist/chunk-K3R5NG2D.js.map +1 -0
- package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
- package/dist/chunk-MJZBNWG7.js.map +1 -0
- package/dist/{chunk-A7DKIA72.js → chunk-QBJ22YZR.js} +1059 -93
- package/dist/chunk-QBJ22YZR.js.map +1 -0
- package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
- package/dist/chunk-QGHGJ56O.js.map +1 -0
- package/dist/{chunk-JYRIOFEN.cjs → chunk-RIKBJLS2.cjs} +1064 -98
- package/dist/chunk-RIKBJLS2.cjs.map +1 -0
- package/dist/{chunk-U6NDQFOK.cjs → chunk-XDMXXC4Z.cjs} +569 -175
- package/dist/chunk-XDMXXC4Z.cjs.map +1 -0
- package/dist/commands/index.cjs +860 -0
- package/dist/commands/index.cjs.map +1 -0
- package/dist/commands/index.d.cts +592 -0
- package/dist/commands/index.d.ts +592 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/{controller-OUdxPY-X.d.ts → controller-pePCJLT1.d.cts} +216 -11
- package/dist/{controller-OUdxPY-X.d.cts → controller-pePCJLT1.d.ts} +216 -11
- package/dist/extensions/index.cjs +14 -4
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +3 -3
- package/dist/extensions/index.d.ts +3 -3
- package/dist/extensions/index.js +13 -3
- package/dist/history/index.cjs +12 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +211 -7
- package/dist/history/index.d.ts +211 -7
- package/dist/history/index.js +13 -3
- package/dist/image-editor/index.cjs +418 -13
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +154 -3
- package/dist/image-editor/index.d.ts +154 -3
- package/dist/image-editor/index.js +417 -12
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
- package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
- package/dist/index.cjs +275 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +296 -47
- package/dist/index.d.ts +296 -47
- package/dist/index.js +266 -74
- package/dist/index.js.map +1 -1
- package/dist/registry-D5-R90p0.d.cts +209 -0
- package/dist/registry-D5-R90p0.d.ts +209 -0
- package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
- package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
- package/dist/serializers/index.cjs +3 -3
- package/dist/serializers/index.d.cts +1 -1
- package/dist/serializers/index.d.ts +1 -1
- package/dist/serializers/index.js +2 -2
- package/dist/spreadsheet/index.cjs +23 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +97 -10
- package/dist/spreadsheet/index.d.ts +97 -10
- package/dist/spreadsheet/index.js +22 -2
- package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
- package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
- package/dist/{types-DMe0vFB1.d.cts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-DMe0vFB1.d.ts → types-DX4Mh4SL.d.ts} +1 -1
- package/dist/types-GT5a5YAX.d.cts +86 -0
- package/dist/types-GT5a5YAX.d.ts +86 -0
- package/package.json +8 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-3TYMNULT.js.map +0 -1
- package/dist/chunk-4QPLEUL2.cjs.map +0 -1
- package/dist/chunk-A7DKIA72.js.map +0 -1
- package/dist/chunk-CYFJCKC4.js.map +0 -1
- package/dist/chunk-GVW4P7BG.js.map +0 -1
- package/dist/chunk-JYRIOFEN.cjs.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-TVRC4KVT.cjs.map +0 -1
- package/dist/chunk-U6NDQFOK.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
- /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
|
@@ -53,7 +53,7 @@ function tokenizeFormula(formula) {
|
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
55
|
if (char === "#") {
|
|
56
|
-
const match = /^#(?:REF!|DIV\/0!|VALUE!|NAME\?|N\/A|NUM!|CYCLE!|ERROR!)/.exec(input.slice(i));
|
|
56
|
+
const match = /^#(?:REF!|DIV\/0!|VALUE!|NAME\?|N\/A|NUM!|CYCLE!|ERROR!|SPILL!|CALC!)/.exec(input.slice(i));
|
|
57
57
|
if (!match) {
|
|
58
58
|
throw new Error(`Unexpected character '#' at position ${i + offset}`);
|
|
59
59
|
}
|
|
@@ -104,7 +104,7 @@ function tokenizeFormula(formula) {
|
|
|
104
104
|
i += 2;
|
|
105
105
|
continue;
|
|
106
106
|
}
|
|
107
|
-
if (["+", "-", "*", "/", "^", "&", "=", "<", ">"].includes(char)) {
|
|
107
|
+
if (["+", "-", "*", "/", "^", "&", "=", "<", ">", "@"].includes(char)) {
|
|
108
108
|
tokens.push({ type: "OPERATOR", value: char, position: i + offset });
|
|
109
109
|
i++;
|
|
110
110
|
continue;
|
|
@@ -144,6 +144,11 @@ function tokenizeFormula(formula) {
|
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
146
|
if (/^\$?[A-Za-z]+\$?[0-9]+$/.test(rawWord)) {
|
|
147
|
+
if (input[i] === "#") {
|
|
148
|
+
i++;
|
|
149
|
+
tokens.push({ type: "SPILL_REF", value: upper.replace(/\$/g, ""), position: start + offset });
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
147
152
|
tokens.push({
|
|
148
153
|
type: "CELL_REF",
|
|
149
154
|
value: upper.replace(/\$/g, ""),
|
|
@@ -265,7 +270,7 @@ var FormulaParser = (_class = class {
|
|
|
265
270
|
return expr;
|
|
266
271
|
}
|
|
267
272
|
unary() {
|
|
268
|
-
if (this.matchOperator("-", "+")) {
|
|
273
|
+
if (this.matchOperator("-", "+", "@")) {
|
|
269
274
|
const op = this.previous().value;
|
|
270
275
|
const argument = this.unary();
|
|
271
276
|
return { type: "UnaryOp", op, argument };
|
|
@@ -304,6 +309,11 @@ var FormulaParser = (_class = class {
|
|
|
304
309
|
this.advance();
|
|
305
310
|
return parseRange(token.value);
|
|
306
311
|
}
|
|
312
|
+
if (token.type === "SPILL_REF") {
|
|
313
|
+
this.advance();
|
|
314
|
+
const parsed = parseCellReference(token.value);
|
|
315
|
+
return { type: "SpillRef", ref: token.value, col: parsed.col, row: parsed.row };
|
|
316
|
+
}
|
|
307
317
|
if (token.type === "PROPERTY_REF") {
|
|
308
318
|
this.advance();
|
|
309
319
|
return {
|
|
@@ -329,7 +339,7 @@ var FormulaParser = (_class = class {
|
|
|
329
339
|
args
|
|
330
340
|
};
|
|
331
341
|
}
|
|
332
|
-
return { type: "
|
|
342
|
+
return { type: "Name", name };
|
|
333
343
|
}
|
|
334
344
|
if (this.match("LPAREN")) {
|
|
335
345
|
const expr = this.expression();
|
|
@@ -391,7 +401,9 @@ var FORMULA_ERRORS = /* @__PURE__ */ new Set([
|
|
|
391
401
|
"#N/A",
|
|
392
402
|
"#NUM!",
|
|
393
403
|
"#CYCLE!",
|
|
394
|
-
"#ERROR!"
|
|
404
|
+
"#ERROR!",
|
|
405
|
+
"#SPILL!",
|
|
406
|
+
"#CALC!"
|
|
395
407
|
]);
|
|
396
408
|
function isFormulaError(value) {
|
|
397
409
|
return typeof value === "string" && FORMULA_ERRORS.has(value);
|
|
@@ -402,9 +414,7 @@ function toNumber(value) {
|
|
|
402
414
|
if (isBlank(value)) return 0;
|
|
403
415
|
if (typeof value === "boolean") return value ? 1 : 0;
|
|
404
416
|
if (typeof value === "number") return value;
|
|
405
|
-
|
|
406
|
-
const num = trimmed === "" ? NaN : Number(trimmed);
|
|
407
|
-
return isNaN(num) ? "#VALUE!" : num;
|
|
417
|
+
return _nullishCoalesce(parseNumberText(String(value)), () => ( "#VALUE!"));
|
|
408
418
|
}
|
|
409
419
|
function toBoolean(value) {
|
|
410
420
|
if (isFormulaError(value)) return value;
|
|
@@ -416,7 +426,26 @@ function toBoolean(value) {
|
|
|
416
426
|
if (upper === "FALSE") return false;
|
|
417
427
|
return "#VALUE!";
|
|
418
428
|
}
|
|
419
|
-
var toText = (value) => isBlank(value) ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : String(value);
|
|
429
|
+
var toText = (value) => isBlank(value) ? "" : typeof value === "boolean" ? value ? "TRUE" : "FALSE" : typeof value === "number" ? numberToText(value) : String(value);
|
|
430
|
+
function numberToText(n) {
|
|
431
|
+
if (n === 0 || !Number.isFinite(n)) return "0";
|
|
432
|
+
const a = Math.abs(n);
|
|
433
|
+
if ((a < 1e-9 || a >= 1e15) && Math.round(a) !== a || a >= 1e15) return sciText(n, 15);
|
|
434
|
+
let mag = Math.floor(Math.log10(a));
|
|
435
|
+
if (a >= Math.pow(10, mag + 1)) mag++;
|
|
436
|
+
const decimals = Math.min(Math.max(14 - mag, 0), 30);
|
|
437
|
+
return trimDecimal(n.toFixed(decimals));
|
|
438
|
+
}
|
|
439
|
+
function trimDecimal(s) {
|
|
440
|
+
if (!s.includes(".")) return s;
|
|
441
|
+
const t = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
442
|
+
return t === "-0" ? "0" : t;
|
|
443
|
+
}
|
|
444
|
+
function sciText(n, sig) {
|
|
445
|
+
const [mantissa, exp] = n.toExponential(Math.max(sig - 1, 0)).split("e");
|
|
446
|
+
const e = Number(exp);
|
|
447
|
+
return `${trimDecimal(mantissa)}E${e < 0 ? "-" : "+"}${String(Math.abs(e)).padStart(2, "0")}`;
|
|
448
|
+
}
|
|
420
449
|
function numericValue(value) {
|
|
421
450
|
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
422
451
|
if (typeof value !== "string" || FORMULA_ERRORS.has(value)) return null;
|
|
@@ -429,17 +458,68 @@ function normalizeValue(value) {
|
|
|
429
458
|
const num = typeof value === "string" ? numericValue(value) : null;
|
|
430
459
|
return num === null ? value : num;
|
|
431
460
|
}
|
|
461
|
+
var ERROR_ORDER = ["#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A", "#SPILL!", "#CALC!", "#CYCLE!", "#ERROR!"];
|
|
462
|
+
function valueRank(value) {
|
|
463
|
+
if (isBlank(value)) return 4;
|
|
464
|
+
if (typeof value === "number") return 0;
|
|
465
|
+
if (typeof value === "boolean") return 2;
|
|
466
|
+
if (typeof value === "string" && FORMULA_ERRORS.has(value)) return 3;
|
|
467
|
+
return 1;
|
|
468
|
+
}
|
|
469
|
+
function compareNumbers(a, b) {
|
|
470
|
+
if (a === b) return 0;
|
|
471
|
+
if (Math.abs(a - b) > Math.max(Math.abs(a), Math.abs(b)) * 1e-13) return a < b ? -1 : 1;
|
|
472
|
+
const round15 = (x2) => !Number.isFinite(x2) || x2 === 0 ? x2 : Number(x2.toExponential(14));
|
|
473
|
+
const x = round15(a);
|
|
474
|
+
const y = round15(b);
|
|
475
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
476
|
+
}
|
|
477
|
+
function compareText(a, b) {
|
|
478
|
+
const x = a.toLowerCase();
|
|
479
|
+
const y = b.toLowerCase();
|
|
480
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
481
|
+
}
|
|
432
482
|
function compareValues(left, right) {
|
|
433
|
-
const
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
483
|
+
const lRank = valueRank(left);
|
|
484
|
+
const rRank = valueRank(right);
|
|
485
|
+
if (lRank === 4 || rRank === 4) {
|
|
486
|
+
if (lRank === rRank) return 0;
|
|
487
|
+
const blankFirst = lRank === 4;
|
|
488
|
+
const other = blankFirst ? right : left;
|
|
489
|
+
const otherRank = blankFirst ? rRank : lRank;
|
|
490
|
+
let cmp;
|
|
491
|
+
if (otherRank === 0) cmp = compareNumbers(0, other);
|
|
492
|
+
else if (otherRank === 1) cmp = String(other) === "" ? 0 : -1;
|
|
493
|
+
else if (otherRank === 2) cmp = other ? -1 : 0;
|
|
494
|
+
else return lRank - rRank;
|
|
495
|
+
return blankFirst ? cmp : -cmp;
|
|
496
|
+
}
|
|
497
|
+
if (lRank === 1 && rRank === 1) {
|
|
498
|
+
const a = String(left);
|
|
499
|
+
const b = String(right);
|
|
500
|
+
const ta = isoDateTime(a);
|
|
501
|
+
const tb = ta === null ? null : isoDateTime(b);
|
|
502
|
+
if (ta !== null && tb !== null) return Math.sign(ta - tb);
|
|
503
|
+
return compareText(a, b);
|
|
504
|
+
}
|
|
505
|
+
if (lRank === 0 && rRank === 1 || lRank === 1 && rRank === 0) {
|
|
506
|
+
const date = parseIsoDate(lRank === 1 ? left : right);
|
|
507
|
+
if (date) {
|
|
508
|
+
const serial = dateToSerial(date);
|
|
509
|
+
return lRank === 0 ? compareNumbers(left, serial) : compareNumbers(serial, right);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (lRank !== rRank) return lRank - rRank;
|
|
513
|
+
if (lRank === 0) return compareNumbers(left, right);
|
|
514
|
+
if (lRank === 2) return Number(left) - Number(right);
|
|
515
|
+
return Math.sign(ERROR_ORDER.indexOf(left) - ERROR_ORDER.indexOf(right));
|
|
516
|
+
}
|
|
517
|
+
function sortCompareValues(left, right) {
|
|
518
|
+
const lBlank = isBlank(left);
|
|
519
|
+
const rBlank = isBlank(right);
|
|
520
|
+
if (lBlank || rBlank) return lBlank === rBlank ? 0 : lBlank ? 1 : -1;
|
|
521
|
+
if (isFormulaError(left) && isFormulaError(right)) return 0;
|
|
522
|
+
return compareValues(left, right);
|
|
443
523
|
}
|
|
444
524
|
function roundHalfAwayFromZero(value, decimals) {
|
|
445
525
|
const factor = Math.pow(10, decimals);
|
|
@@ -505,6 +585,20 @@ function toDate(value) {
|
|
|
505
585
|
}
|
|
506
586
|
return null;
|
|
507
587
|
}
|
|
588
|
+
var ISO_DATE = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/;
|
|
589
|
+
function parseIsoDate(value) {
|
|
590
|
+
if (typeof value !== "string" || value.length < 8 || value.length > 19) return null;
|
|
591
|
+
const iso = ISO_DATE.exec(value.trim());
|
|
592
|
+
if (!iso) return null;
|
|
593
|
+
const [, y, m, d, hh = "0", mm = "0", ss = "0"] = iso;
|
|
594
|
+
const date = new Date(Number(y), Number(m) - 1, Number(d), Number(hh), Number(mm), Number(ss));
|
|
595
|
+
return date.getMonth() === Number(m) - 1 && date.getDate() === Number(d) ? date : null;
|
|
596
|
+
}
|
|
597
|
+
var isoDateHasTime = (value) => /[ T]\d/.test(value.trim());
|
|
598
|
+
function isoDateTime(value) {
|
|
599
|
+
if (value.length < 8 || value.charCodeAt(4) !== 45) return null;
|
|
600
|
+
return _nullishCoalesce(_optionalChain([parseIsoDate, 'call', _2 => _2(value), 'optionalAccess', _3 => _3.getTime, 'call', _4 => _4()]), () => ( null));
|
|
601
|
+
}
|
|
508
602
|
function dateToSerial(date) {
|
|
509
603
|
const utc = Date.UTC(
|
|
510
604
|
date.getFullYear(),
|
|
@@ -534,6 +628,282 @@ var daysBetween = (start, end) => Math.round(
|
|
|
534
628
|
(Date.UTC(end.getFullYear(), end.getMonth(), end.getDate()) - Date.UTC(start.getFullYear(), start.getMonth(), start.getDate())) / MS_PER_DAY
|
|
535
629
|
);
|
|
536
630
|
|
|
631
|
+
// src/spreadsheet/formula/parse-input.ts
|
|
632
|
+
var MONTH_NAMES = [
|
|
633
|
+
"January",
|
|
634
|
+
"February",
|
|
635
|
+
"March",
|
|
636
|
+
"April",
|
|
637
|
+
"May",
|
|
638
|
+
"June",
|
|
639
|
+
"July",
|
|
640
|
+
"August",
|
|
641
|
+
"September",
|
|
642
|
+
"October",
|
|
643
|
+
"November",
|
|
644
|
+
"December"
|
|
645
|
+
];
|
|
646
|
+
function daysFromCivil(year, month, day) {
|
|
647
|
+
const y = month <= 2 ? year - 1 : year;
|
|
648
|
+
const era = Math.floor((y >= 0 ? y : y - 399) / 400);
|
|
649
|
+
const yoe = y - era * 400;
|
|
650
|
+
const doy = Math.floor((153 * (month > 2 ? month - 3 : month + 9) + 2) / 5) + day - 1;
|
|
651
|
+
const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
|
|
652
|
+
return era * 146097 + doe - 719468;
|
|
653
|
+
}
|
|
654
|
+
var isLeapYear = (y) => y % 4 === 0 && y % 100 !== 0 || y % 400 === 0;
|
|
655
|
+
function daysInMonth(year, month) {
|
|
656
|
+
if (month === 2) return isLeapYear(year) ? 29 : 28;
|
|
657
|
+
if ([4, 6, 9, 11].includes(month)) return 30;
|
|
658
|
+
return month >= 1 && month <= 12 ? 31 : 0;
|
|
659
|
+
}
|
|
660
|
+
var daysInMonth1900 = (year, month) => year === 1900 && month === 2 ? 29 : daysInMonth(year, month);
|
|
661
|
+
var UNIX_1899_12_30 = -25569;
|
|
662
|
+
var MAX_SERIAL_1900 = 2958465;
|
|
663
|
+
function serialFromYmd(year, month, day) {
|
|
664
|
+
if (![year, month, day].every(Number.isSafeInteger)) return null;
|
|
665
|
+
let y = year >= 0 && year < 1900 ? year + 1900 : year;
|
|
666
|
+
const m0 = month - 1;
|
|
667
|
+
y += Math.floor(m0 / 12);
|
|
668
|
+
const m = (m0 % 12 + 12) % 12 + 1;
|
|
669
|
+
if (y < 1900 || y > 9999) return null;
|
|
670
|
+
const first = daysFromCivil(y, m, 1) - UNIX_1899_12_30 - (y === 1900 && m <= 2 ? 1 : 0);
|
|
671
|
+
const serial = first + day - 1;
|
|
672
|
+
return serial >= 0 && serial <= MAX_SERIAL_1900 ? serial : null;
|
|
673
|
+
}
|
|
674
|
+
var timeFraction = (h, m, s) => (h * 3600 + m * 60 + s) / 86400;
|
|
675
|
+
var currentYear = () => (/* @__PURE__ */ new Date()).getFullYear();
|
|
676
|
+
function parseCellInput(input) {
|
|
677
|
+
if (input === "") return { value: "", type: "empty" };
|
|
678
|
+
if (input.startsWith("'")) return { value: input.slice(1), type: "text" };
|
|
679
|
+
const t = input.trim();
|
|
680
|
+
const upper = t.toUpperCase();
|
|
681
|
+
if (upper === "TRUE") return { value: true, type: "boolean" };
|
|
682
|
+
if (upper === "FALSE") return { value: false, type: "boolean" };
|
|
683
|
+
if (FORMULA_ERRORS.has(upper)) return { value: upper, type: "error" };
|
|
684
|
+
const numeric = parseNumeric(t);
|
|
685
|
+
if (numeric) {
|
|
686
|
+
if (numeric.kind === "date") {
|
|
687
|
+
const iso = isoText(numeric.date);
|
|
688
|
+
if (iso !== null) {
|
|
689
|
+
return { value: iso, type: "date", numberFormat: numeric.format, formatKind: "date", cellFormat: "date" };
|
|
690
|
+
}
|
|
691
|
+
} else {
|
|
692
|
+
const formatKind = numeric.kind === "time" ? "time" : formatKindOf(numeric.format);
|
|
693
|
+
return {
|
|
694
|
+
value: normalizeZero(numeric.value),
|
|
695
|
+
type: "number",
|
|
696
|
+
...numeric.format ? { numberFormat: numeric.format } : {},
|
|
697
|
+
...formatKind ? { formatKind } : {},
|
|
698
|
+
...formatKind && formatKind !== "time" ? { cellFormat: formatKind } : {}
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return { value: input, type: "text" };
|
|
703
|
+
}
|
|
704
|
+
function parseNumberText(text) {
|
|
705
|
+
const numeric = parseNumeric(text.trim());
|
|
706
|
+
if (!numeric) return null;
|
|
707
|
+
if (numeric.kind !== "date") return normalizeZero(numeric.value);
|
|
708
|
+
const { year, month, day, time = 0 } = numeric.date;
|
|
709
|
+
const serial = serialFromYmd(year, month, day);
|
|
710
|
+
return serial === null ? null : serial + time;
|
|
711
|
+
}
|
|
712
|
+
var normalizeZero = (n) => n === 0 ? 0 : n;
|
|
713
|
+
function formatKindOf(format) {
|
|
714
|
+
if (!format) return void 0;
|
|
715
|
+
if (format.includes("$")) return "currency";
|
|
716
|
+
if (format.includes("%")) return "percentage";
|
|
717
|
+
return "number";
|
|
718
|
+
}
|
|
719
|
+
var pad2 = (n, width = 2) => String(n).padStart(width, "0");
|
|
720
|
+
function isoText({ year, month, day, time }) {
|
|
721
|
+
if (day > daysInMonth(year, month)) return null;
|
|
722
|
+
const date = `${pad2(year, 4)}-${pad2(month)}-${pad2(day)}`;
|
|
723
|
+
if (time === void 0) return date;
|
|
724
|
+
let seconds = Math.round(time * 86400);
|
|
725
|
+
if (seconds >= 86400) seconds = 86399;
|
|
726
|
+
return `${date} ${pad2(Math.floor(seconds / 3600))}:${pad2(Math.floor(seconds / 60) % 60)}:${pad2(seconds % 60)}`;
|
|
727
|
+
}
|
|
728
|
+
function parseNumeric(t) {
|
|
729
|
+
if (t === "") return null;
|
|
730
|
+
if (t.startsWith("(") && t.endsWith(")") && t.length >= 2) {
|
|
731
|
+
const inner = parseNumeric(t.slice(1, -1).trim());
|
|
732
|
+
if (!inner || inner.kind === "date") return null;
|
|
733
|
+
return { ...inner, value: -inner.value };
|
|
734
|
+
}
|
|
735
|
+
const plain = parsePlain(t);
|
|
736
|
+
if (plain) return plain;
|
|
737
|
+
const fraction = parseFraction(t);
|
|
738
|
+
if (fraction !== null) return { kind: "number", value: fraction, format: "# ?/?" };
|
|
739
|
+
return parseDateTime(t);
|
|
740
|
+
}
|
|
741
|
+
var startsWithDigitOrDot = (s) => /^[0-9.]/.test(s);
|
|
742
|
+
function parsePlain(t) {
|
|
743
|
+
let s = t;
|
|
744
|
+
let neg = false;
|
|
745
|
+
let currency = false;
|
|
746
|
+
let percent = false;
|
|
747
|
+
if (s.startsWith("-")) {
|
|
748
|
+
neg = true;
|
|
749
|
+
s = s.slice(1).trimStart();
|
|
750
|
+
} else if (s.startsWith("+")) {
|
|
751
|
+
s = s.slice(1).trimStart();
|
|
752
|
+
}
|
|
753
|
+
if (s.startsWith("$")) {
|
|
754
|
+
currency = true;
|
|
755
|
+
s = s.slice(1).trimStart();
|
|
756
|
+
if (!neg && s.startsWith("-")) {
|
|
757
|
+
neg = true;
|
|
758
|
+
s = s.slice(1);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (s.endsWith("%")) {
|
|
762
|
+
percent = true;
|
|
763
|
+
s = s.slice(0, -1).trimEnd();
|
|
764
|
+
}
|
|
765
|
+
if (s === "") return null;
|
|
766
|
+
const hasSep = s.includes(",");
|
|
767
|
+
let digits;
|
|
768
|
+
if (hasSep) {
|
|
769
|
+
const dot = s.indexOf(".");
|
|
770
|
+
const int = dot === -1 ? s : s.slice(0, dot);
|
|
771
|
+
const frac = dot === -1 ? null : s.slice(dot + 1);
|
|
772
|
+
const groups = int.split(",");
|
|
773
|
+
const first = groups[0];
|
|
774
|
+
if (first === "" || first.length > 3 || groups.slice(1).some((g) => g.length !== 3)) return null;
|
|
775
|
+
digits = groups.join("") + (frac !== null ? `.${frac}` : "");
|
|
776
|
+
} else {
|
|
777
|
+
digits = s;
|
|
778
|
+
}
|
|
779
|
+
if (!/^[0-9.eE+-]+$/.test(digits) || !startsWithDigitOrDot(digits)) return null;
|
|
780
|
+
if (!/^(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(digits)) return null;
|
|
781
|
+
let n = Number(digits);
|
|
782
|
+
if (!Number.isFinite(n)) return null;
|
|
783
|
+
const sci = /[eE]/.test(digits);
|
|
784
|
+
if (percent) n /= 100;
|
|
785
|
+
if (neg) n = -n;
|
|
786
|
+
const hasDot = digits.includes(".");
|
|
787
|
+
const format = currency ? hasDot ? '"$"#,##0.00' : '"$"#,##0' : percent ? hasDot ? "0.00%" : "0%" : sci ? "0.00E+00" : hasSep ? hasDot ? "#,##0.00" : "#,##0" : void 0;
|
|
788
|
+
return { kind: "number", value: n, ...format ? { format } : {} };
|
|
789
|
+
}
|
|
790
|
+
var parseDecimal = (s) => /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(s) ? Number(s) : null;
|
|
791
|
+
function parseFraction(t) {
|
|
792
|
+
const space = t.indexOf(" ");
|
|
793
|
+
if (space === -1) return null;
|
|
794
|
+
const whole = t.slice(0, space);
|
|
795
|
+
const frac = t.slice(space + 1).trim();
|
|
796
|
+
const neg = whole.startsWith("-");
|
|
797
|
+
const w = parseDecimal(whole.replace(/^-+/, ""));
|
|
798
|
+
const slash = frac.indexOf("/");
|
|
799
|
+
if (w === null || slash === -1) return null;
|
|
800
|
+
const a = parseDecimal(frac.slice(0, slash));
|
|
801
|
+
const b = parseDecimal(frac.slice(slash + 1));
|
|
802
|
+
if (a === null || b === null || b === 0 || !Number.isFinite(w)) return null;
|
|
803
|
+
const v = w + a / b;
|
|
804
|
+
return neg ? -v : v;
|
|
805
|
+
}
|
|
806
|
+
function monthFromName(s) {
|
|
807
|
+
const l = s.toLowerCase();
|
|
808
|
+
if (l.length < 3) return null;
|
|
809
|
+
const index = MONTH_NAMES.findIndex((m) => m.toLowerCase().startsWith(l));
|
|
810
|
+
return index === -1 ? null : index + 1;
|
|
811
|
+
}
|
|
812
|
+
function parseTime(input) {
|
|
813
|
+
const lower = input.trim().toLowerCase();
|
|
814
|
+
let body = lower;
|
|
815
|
+
let pm = null;
|
|
816
|
+
for (const [suffix, isPm] of [["am", false], ["a", false], ["pm", true], ["p", true]]) {
|
|
817
|
+
if (lower.endsWith(suffix)) {
|
|
818
|
+
body = lower.slice(0, -suffix.length).trim();
|
|
819
|
+
pm = isPm;
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const parts = body.split(":");
|
|
824
|
+
if (parts.length === 1 && pm === null) return null;
|
|
825
|
+
if (parts.length > 3 || parts.some((p) => p === "")) return null;
|
|
826
|
+
const num = (p) => p === void 0 ? 0 : parseDecimal(p);
|
|
827
|
+
let h = num(parts[0]);
|
|
828
|
+
const m = num(parts[1]);
|
|
829
|
+
const s = num(parts[2]);
|
|
830
|
+
if (h === null || m === null || s === null) return null;
|
|
831
|
+
if (m >= 60 || s >= 60 || h < 0 || m < 0 || s < 0) return null;
|
|
832
|
+
if (pm !== null) {
|
|
833
|
+
if (h > 12) return null;
|
|
834
|
+
if (h === 12) h = 0;
|
|
835
|
+
if (pm) h += 12;
|
|
836
|
+
} else if (h >= 1e4) {
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
return { fraction: timeFraction(h, m, s), seconds: parts.length === 3, ampm: pm !== null };
|
|
840
|
+
}
|
|
841
|
+
var expandYear = (text, y) => text.length <= 2 ? y < 30 ? 2e3 + y : 1900 + y : y;
|
|
842
|
+
function validYmd(year, month, day) {
|
|
843
|
+
if (month < 1 || month > 12 || day < 1 || year < 1900 || year > 9999) return null;
|
|
844
|
+
if (day > daysInMonth1900(year, month)) return null;
|
|
845
|
+
return { year, month, day };
|
|
846
|
+
}
|
|
847
|
+
function parseDate(input) {
|
|
848
|
+
const t = input.trim().replace(/,+$/, "");
|
|
849
|
+
const num = (s) => /^[+-]?\d+$/.test(s.trim()) ? Number(s.trim()) : null;
|
|
850
|
+
const withFormat = (date, format) => date ? { date, format } : null;
|
|
851
|
+
const parts = t.split(/[/.-]/);
|
|
852
|
+
if (parts.length === 3) {
|
|
853
|
+
const [a, b, c] = parts.map((p) => p.trim());
|
|
854
|
+
if (a.length === 4) {
|
|
855
|
+
const [y2, m2, d2] = [num(a), num(b), num(c)];
|
|
856
|
+
if (y2 !== null && m2 !== null && d2 !== null) return withFormat(validYmd(y2, m2, d2), "yyyy-mm-dd");
|
|
857
|
+
}
|
|
858
|
+
{
|
|
859
|
+
const [m2, d2, y2] = [num(a), num(b), num(c)];
|
|
860
|
+
if (m2 !== null && d2 !== null && y2 !== null) return withFormat(validYmd(expandYear(c, y2), m2, d2), "m/d/yyyy");
|
|
861
|
+
}
|
|
862
|
+
const [d, m, y] = [num(a), monthFromName(b), num(c)];
|
|
863
|
+
if (d !== null && m !== null && y !== null) return withFormat(validYmd(expandYear(c, y), m, d), "d-mmm-yy");
|
|
864
|
+
}
|
|
865
|
+
if (parts.length === 2) {
|
|
866
|
+
const [a, b] = parts.map((p) => p.trim());
|
|
867
|
+
const [x, y] = [num(a), num(b)];
|
|
868
|
+
if (x !== null && y !== null) {
|
|
869
|
+
if (x >= 1 && x <= 12 && y >= 1 && y <= 31) return withFormat(validYmd(currentYear(), x, y), "d-mmm");
|
|
870
|
+
if (x >= 1 && x <= 12 && b.length === 4) return withFormat(validYmd(y, x, 1), "mmm-yy");
|
|
871
|
+
}
|
|
872
|
+
const dayA = num(a);
|
|
873
|
+
const monthB = monthFromName(b);
|
|
874
|
+
if (dayA !== null && monthB !== null) return withFormat(validYmd(currentYear(), monthB, dayA), "d-mmm");
|
|
875
|
+
const monthA = monthFromName(a);
|
|
876
|
+
const yearB = num(b);
|
|
877
|
+
if (monthA !== null && yearB !== null) return withFormat(validYmd(expandYear(b, yearB), monthA, 1), "mmm-yy");
|
|
878
|
+
}
|
|
879
|
+
const words = t.split(/[ ,]/).filter((w) => w !== "");
|
|
880
|
+
if (words.length === 3) {
|
|
881
|
+
const [w0, w1, w2] = words;
|
|
882
|
+
let [m, d, y] = [monthFromName(w0), num(w1), num(w2)];
|
|
883
|
+
if (m !== null && d !== null && y !== null) return withFormat(validYmd(expandYear(w2, y), m, d), "d-mmm-yy");
|
|
884
|
+
[d, m, y] = [num(w0), monthFromName(w1), num(w2)];
|
|
885
|
+
if (d !== null && m !== null && y !== null) return withFormat(validYmd(expandYear(w2, y), m, d), "d-mmm-yy");
|
|
886
|
+
}
|
|
887
|
+
return null;
|
|
888
|
+
}
|
|
889
|
+
function parseDateTime(t) {
|
|
890
|
+
const time = parseTime(t);
|
|
891
|
+
if (time) {
|
|
892
|
+
const format = time.fraction >= 1 ? "[h]:mm:ss" : time.seconds ? time.ampm ? "h:mm:ss AM/PM" : "h:mm:ss" : time.ampm ? "h:mm AM/PM" : "h:mm";
|
|
893
|
+
return { kind: "time", value: time.fraction, format };
|
|
894
|
+
}
|
|
895
|
+
const date = parseDate(t);
|
|
896
|
+
if (date) return { kind: "date", date: date.date, format: date.format };
|
|
897
|
+
for (let i = t.indexOf(" "); i !== -1; i = t.indexOf(" ", i + 1)) {
|
|
898
|
+
const d = parseDate(t.slice(0, i));
|
|
899
|
+
const tm = d ? parseTime(t.slice(i + 1)) : null;
|
|
900
|
+
if (d && tm && tm.fraction < 1) {
|
|
901
|
+
return { kind: "date", date: { ...d.date, time: tm.fraction }, format: "m/d/yyyy h:mm" };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
|
|
537
907
|
// src/spreadsheet/formula/criteria.ts
|
|
538
908
|
function wildcardToRegExp(pattern) {
|
|
539
909
|
let source = "";
|
|
@@ -570,12 +940,17 @@ function parseCriteria(criterion) {
|
|
|
570
940
|
if (op === "<>") return (value) => !isBlank(value);
|
|
571
941
|
return () => false;
|
|
572
942
|
}
|
|
573
|
-
const
|
|
943
|
+
const parsed = parseCellInput(operand.trim());
|
|
944
|
+
const operandNumber = _nullishCoalesce(numericValue(operand), () => ( (parsed.type === "number" ? parsed.value : null)));
|
|
945
|
+
const operandDate = parsed.type === "date" ? parsed.value : null;
|
|
946
|
+
const isDateValue = (value) => typeof value === "number" || parseIsoDate(value) !== null;
|
|
574
947
|
const operandUpper = operand.trim().toUpperCase();
|
|
575
948
|
const operandBoolean = operandUpper === "TRUE" ? true : operandUpper === "FALSE" ? false : null;
|
|
576
949
|
if (op === "=" || op === "<>") {
|
|
577
950
|
let equals;
|
|
578
|
-
if (
|
|
951
|
+
if (operandDate !== null) {
|
|
952
|
+
equals = (value) => isDateValue(value) && compareValues(value, operandDate) === 0;
|
|
953
|
+
} else if (operandNumber !== null) {
|
|
579
954
|
equals = (value) => numericValue(value) === operandNumber;
|
|
580
955
|
} else if (operandBoolean !== null) {
|
|
581
956
|
equals = (value) => value === operandBoolean || typeof value === "string" && value.trim().toUpperCase() === operandUpper;
|
|
@@ -588,6 +963,9 @@ function parseCriteria(criterion) {
|
|
|
588
963
|
return op === "=" ? (value) => !isFormulaError(value) && equals(value) : (value) => !isFormulaError(value) && !equals(value);
|
|
589
964
|
}
|
|
590
965
|
const test = (cmp) => op === "<" ? cmp < 0 : op === "<=" ? cmp <= 0 : op === ">" ? cmp > 0 : cmp >= 0;
|
|
966
|
+
if (operandDate !== null) {
|
|
967
|
+
return (value) => isDateValue(value) && test(compareValues(value, operandDate));
|
|
968
|
+
}
|
|
591
969
|
if (operandNumber !== null) {
|
|
592
970
|
return (value) => {
|
|
593
971
|
const num = numericValue(value);
|
|
@@ -605,6 +983,42 @@ function parseCriteria(criterion) {
|
|
|
605
983
|
}
|
|
606
984
|
|
|
607
985
|
// src/spreadsheet/formula/format.ts
|
|
986
|
+
function formatCellDisplay(cell) {
|
|
987
|
+
if (!cell) return "";
|
|
988
|
+
if (cell.error) return cell.error;
|
|
989
|
+
const { value, numberFormat } = cell;
|
|
990
|
+
if (numberFormat && value !== "" && value !== null && value !== void 0 && typeof value !== "boolean") {
|
|
991
|
+
if (/^\[h\]/i.test(numberFormat) && typeof value === "number") return formatElapsedHours(value);
|
|
992
|
+
if (numberFormat === "# ?/?" && typeof value === "number") return formatFraction(value);
|
|
993
|
+
const formatted = formatValueWithCode(value, numberFormat);
|
|
994
|
+
if (formatted !== null) return formatted;
|
|
995
|
+
}
|
|
996
|
+
return toText(value);
|
|
997
|
+
}
|
|
998
|
+
function formatFraction(value) {
|
|
999
|
+
const sign = value < 0 ? "-" : "";
|
|
1000
|
+
const abs = Math.abs(value);
|
|
1001
|
+
let whole = Math.floor(abs);
|
|
1002
|
+
const frac = abs - whole;
|
|
1003
|
+
let best = { num: 0, den: 1, err: frac };
|
|
1004
|
+
for (let den = 2; den <= 9; den++) {
|
|
1005
|
+
const num = Math.round(frac * den);
|
|
1006
|
+
const err = Math.abs(frac - num / den);
|
|
1007
|
+
if (err < best.err - 1e-12) best = { num, den, err };
|
|
1008
|
+
}
|
|
1009
|
+
if (best.num === best.den) {
|
|
1010
|
+
whole += 1;
|
|
1011
|
+
best = { num: 0, den: 1, err: 0 };
|
|
1012
|
+
}
|
|
1013
|
+
if (best.num === 0) return `${sign}${whole}`;
|
|
1014
|
+
return `${sign}${whole === 0 ? "" : `${whole} `}${best.num}/${best.den}`;
|
|
1015
|
+
}
|
|
1016
|
+
function formatElapsedHours(days) {
|
|
1017
|
+
const total = Math.round(Math.abs(days) * 86400);
|
|
1018
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
1019
|
+
const text = `${Math.floor(total / 3600)}:${p(Math.floor(total / 60) % 60)}:${p(total % 60)}`;
|
|
1020
|
+
return days < 0 ? `-${text}` : text;
|
|
1021
|
+
}
|
|
608
1022
|
function formatValueWithCode(value, format) {
|
|
609
1023
|
if (isDateFormat(format)) {
|
|
610
1024
|
const date = toDate(value);
|
|
@@ -719,7 +1133,7 @@ function splitSections(format) {
|
|
|
719
1133
|
}
|
|
720
1134
|
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
721
1135
|
var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
722
|
-
var
|
|
1136
|
+
var pad3 = (n) => String(n).padStart(2, "0");
|
|
723
1137
|
function formatDate(date, format) {
|
|
724
1138
|
const parts = [];
|
|
725
1139
|
for (const token of tokenize(format)) {
|
|
@@ -746,30 +1160,30 @@ function formatDate(date, format) {
|
|
|
746
1160
|
return code[0] === code[0].toLowerCase() ? text.toLowerCase() : text;
|
|
747
1161
|
}
|
|
748
1162
|
if (lower.startsWith("y")) {
|
|
749
|
-
return lower.length <= 2 ?
|
|
1163
|
+
return lower.length <= 2 ? pad3(date.getFullYear() % 100) : String(date.getFullYear());
|
|
750
1164
|
}
|
|
751
1165
|
if (lower.startsWith("d")) {
|
|
752
1166
|
if (lower.length >= 4) return DAYS[date.getDay()];
|
|
753
1167
|
if (lower.length === 3) return DAYS[date.getDay()].slice(0, 3);
|
|
754
|
-
return lower.length === 2 ?
|
|
1168
|
+
return lower.length === 2 ? pad3(date.getDate()) : String(date.getDate());
|
|
755
1169
|
}
|
|
756
1170
|
if (lower.startsWith("h")) {
|
|
757
1171
|
let hours = date.getHours();
|
|
758
1172
|
if (twelveHour) hours = hours % 12 || 12;
|
|
759
|
-
return lower.length >= 2 ?
|
|
1173
|
+
return lower.length >= 2 ? pad3(hours) : String(hours);
|
|
760
1174
|
}
|
|
761
1175
|
if (lower.startsWith("s")) {
|
|
762
|
-
return lower.length >= 2 ?
|
|
1176
|
+
return lower.length >= 2 ? pad3(date.getSeconds()) : String(date.getSeconds());
|
|
763
1177
|
}
|
|
764
1178
|
const position = codes.findIndex((c) => c.i === index);
|
|
765
|
-
const prev = _nullishCoalesce(_optionalChain([codes, 'access',
|
|
766
|
-
const next = _nullishCoalesce(_optionalChain([codes, 'access',
|
|
1179
|
+
const prev = _nullishCoalesce(_optionalChain([codes, 'access', _5 => _5[position - 1], 'optionalAccess', _6 => _6.text, 'access', _7 => _7.toLowerCase, 'call', _8 => _8()]), () => ( ""));
|
|
1180
|
+
const next = _nullishCoalesce(_optionalChain([codes, 'access', _9 => _9[position + 1], 'optionalAccess', _10 => _10.text, 'access', _11 => _11.toLowerCase, 'call', _12 => _12()]), () => ( ""));
|
|
767
1181
|
if (lower.length <= 2 && (prev.startsWith("h") || next.startsWith("s"))) {
|
|
768
|
-
return lower.length === 2 ?
|
|
1182
|
+
return lower.length === 2 ? pad3(date.getMinutes()) : String(date.getMinutes());
|
|
769
1183
|
}
|
|
770
1184
|
if (lower.length >= 4) return MONTHS[date.getMonth()];
|
|
771
1185
|
if (lower.length === 3) return MONTHS[date.getMonth()].slice(0, 3);
|
|
772
|
-
return lower.length === 2 ?
|
|
1186
|
+
return lower.length === 2 ? pad3(date.getMonth() + 1) : String(date.getMonth() + 1);
|
|
773
1187
|
}).join("");
|
|
774
1188
|
}
|
|
775
1189
|
|
|
@@ -1606,11 +2020,11 @@ var TEXT_FUNCTIONS = [
|
|
|
1606
2020
|
{
|
|
1607
2021
|
name: "TRIM",
|
|
1608
2022
|
category: "text",
|
|
1609
|
-
description: "Removes leading and trailing spaces.",
|
|
2023
|
+
description: "Removes leading and trailing spaces and collapses runs of inner spaces to one, like Excel.",
|
|
1610
2024
|
args: [TEXT],
|
|
1611
2025
|
evaluate: (ctx) => {
|
|
1612
2026
|
const text = ctx.text(0);
|
|
1613
|
-
return failed(text) ? text : text.trim();
|
|
2027
|
+
return failed(text) ? text : text.trim().replace(/ {2,}/g, " ");
|
|
1614
2028
|
}
|
|
1615
2029
|
},
|
|
1616
2030
|
{
|
|
@@ -1775,7 +2189,7 @@ var TEXT_FUNCTIONS = [
|
|
|
1775
2189
|
if (isFormulaError(value)) return value;
|
|
1776
2190
|
if (typeof value === "number") return value;
|
|
1777
2191
|
if (isBlank(value)) return 0;
|
|
1778
|
-
const num = parseNumericText(toText(value));
|
|
2192
|
+
const num = _nullishCoalesce(parseNumericText(toText(value)), () => ( parseNumberText(toText(value))));
|
|
1779
2193
|
if (num === null) return ctx.argError(0, "#VALUE!", `("${toText(value)}") can't be converted to a number.`);
|
|
1780
2194
|
return num;
|
|
1781
2195
|
}
|
|
@@ -2241,6 +2655,335 @@ var INFORMATION_FUNCTIONS = [
|
|
|
2241
2655
|
}
|
|
2242
2656
|
];
|
|
2243
2657
|
|
|
2658
|
+
// src/spreadsheet/formula/functions/array.ts
|
|
2659
|
+
var MAX_ARRAY_CELLS = 1e6;
|
|
2660
|
+
function rangeArg(ctx, index) {
|
|
2661
|
+
const value = ctx.value(index);
|
|
2662
|
+
if (Array.isArray(value)) return toFormulaRange(value);
|
|
2663
|
+
if (isFormulaError(value)) return value;
|
|
2664
|
+
return { rows: 1, cols: 1, values: [value] };
|
|
2665
|
+
}
|
|
2666
|
+
function fromRows(rows, cols) {
|
|
2667
|
+
const out = [];
|
|
2668
|
+
for (let c = 0; c < cols; c++) {
|
|
2669
|
+
for (const row of rows) out.push(row[c]);
|
|
2670
|
+
}
|
|
2671
|
+
return withRangeShape(out, rows.length, cols);
|
|
2672
|
+
}
|
|
2673
|
+
var rowOf = (range, r) => {
|
|
2674
|
+
const row = [];
|
|
2675
|
+
for (let c = 0; c < range.cols; c++) row.push(rangeGet(range, r, c));
|
|
2676
|
+
return row;
|
|
2677
|
+
};
|
|
2678
|
+
var colOf = (range, c) => {
|
|
2679
|
+
const col = [];
|
|
2680
|
+
for (let r = 0; r < range.rows; r++) col.push(rangeGet(range, r, c));
|
|
2681
|
+
return col;
|
|
2682
|
+
};
|
|
2683
|
+
var linesOf = (range, byCol) => byCol ? Array.from({ length: range.cols }, (_, c) => colOf(range, c)) : Array.from({ length: range.rows }, (_, r) => rowOf(range, r));
|
|
2684
|
+
function fromLines(lines, byCol, width) {
|
|
2685
|
+
if (!byCol) return fromRows(lines, width);
|
|
2686
|
+
const out = [];
|
|
2687
|
+
for (const line of lines) for (const v of line) out.push(v);
|
|
2688
|
+
return withRangeShape(out, width, lines.length);
|
|
2689
|
+
}
|
|
2690
|
+
function compareForSort(a, b, order) {
|
|
2691
|
+
const blankA = isBlank(a);
|
|
2692
|
+
const blankB = isBlank(b);
|
|
2693
|
+
if (blankA || blankB) return blankA === blankB ? 0 : blankA ? 1 : -1;
|
|
2694
|
+
return compareValues(normalizeValue(a), normalizeValue(b)) * order;
|
|
2695
|
+
}
|
|
2696
|
+
var sortOrder = (ctx, index) => {
|
|
2697
|
+
const order = ctx.integer(index, 1);
|
|
2698
|
+
if (failed(order)) return order;
|
|
2699
|
+
return order === 1 || order === -1 ? order : ctx.argError(index, "#VALUE!", "must be 1 (ascending) or -1 (descending).");
|
|
2700
|
+
};
|
|
2701
|
+
function includeFlag(value) {
|
|
2702
|
+
if (isFormulaError(value)) return value;
|
|
2703
|
+
const flag = toBoolean(value);
|
|
2704
|
+
return isFormulaError(flag) ? "#VALUE!" : flag;
|
|
2705
|
+
}
|
|
2706
|
+
var ARRAY_FUNCTIONS = [
|
|
2707
|
+
{
|
|
2708
|
+
name: "FILTER",
|
|
2709
|
+
category: "lookup",
|
|
2710
|
+
description: "Returns the rows (or columns) of an array whose include value is TRUE. The result spills.",
|
|
2711
|
+
args: [
|
|
2712
|
+
arg("array", "The range or array to filter.", "range"),
|
|
2713
|
+
arg("include", "TRUE/FALSE for each row (a column as tall as array) or each column (a row as wide as array)."),
|
|
2714
|
+
optional("if_empty", "Returned when nothing matches (default #CALC!).")
|
|
2715
|
+
],
|
|
2716
|
+
evaluate: (ctx) => {
|
|
2717
|
+
const range = rangeArg(ctx, 0);
|
|
2718
|
+
if (failed(range)) return range;
|
|
2719
|
+
const include = rangeArg(ctx, 1);
|
|
2720
|
+
if (failed(include)) return include;
|
|
2721
|
+
let byCol;
|
|
2722
|
+
if (include.cols === 1 && include.rows === range.rows) byCol = false;
|
|
2723
|
+
else if (include.rows === 1 && include.cols === range.cols) byCol = true;
|
|
2724
|
+
else {
|
|
2725
|
+
return ctx.argError(1, "#VALUE!", "must be a single column as tall as argument 1 (or a single row as wide).");
|
|
2726
|
+
}
|
|
2727
|
+
const lines = linesOf(range, byCol);
|
|
2728
|
+
const kept = [];
|
|
2729
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2730
|
+
const flag = includeFlag(include.values[i]);
|
|
2731
|
+
if (typeof flag === "string") {
|
|
2732
|
+
return flag === "#VALUE!" ? ctx.argError(1, "#VALUE!", `has a value that isn't TRUE or FALSE (${include.values[i]}).`) : flag;
|
|
2733
|
+
}
|
|
2734
|
+
if (flag) kept.push(lines[i]);
|
|
2735
|
+
}
|
|
2736
|
+
if (kept.length === 0) {
|
|
2737
|
+
return ctx.has(2) ? ctx.value(2) : ctx.error("#CALC!", "FILTER found no matching values.");
|
|
2738
|
+
}
|
|
2739
|
+
return fromLines(kept, byCol, byCol ? range.rows : range.cols);
|
|
2740
|
+
}
|
|
2741
|
+
},
|
|
2742
|
+
{
|
|
2743
|
+
name: "SORT",
|
|
2744
|
+
category: "lookup",
|
|
2745
|
+
description: "Sorts the rows (or columns) of an array by one of its columns (or rows). The result spills.",
|
|
2746
|
+
args: [
|
|
2747
|
+
arg("array", "The range or array to sort.", "range"),
|
|
2748
|
+
optional("sort_index", "The column (or row, with by_col) to sort by; 1 = first (default).", "number"),
|
|
2749
|
+
optional("sort_order", "1 = ascending (default), -1 = descending.", "number"),
|
|
2750
|
+
optional("by_col", "TRUE sorts columns by a row; FALSE (default) sorts rows by a column.", "logical")
|
|
2751
|
+
],
|
|
2752
|
+
evaluate: (ctx) => {
|
|
2753
|
+
const range = rangeArg(ctx, 0);
|
|
2754
|
+
if (failed(range)) return range;
|
|
2755
|
+
const index = ctx.integer(1, 1);
|
|
2756
|
+
if (failed(index)) return index;
|
|
2757
|
+
const order = sortOrder(ctx, 2);
|
|
2758
|
+
if (failed(order)) return order;
|
|
2759
|
+
const byCol = ctx.boolean(3, false);
|
|
2760
|
+
if (failed(byCol)) return byCol;
|
|
2761
|
+
const width = byCol ? range.rows : range.cols;
|
|
2762
|
+
if (index < 1 || index > width) {
|
|
2763
|
+
return ctx.argError(1, "#VALUE!", `must be between 1 and ${width}.`);
|
|
2764
|
+
}
|
|
2765
|
+
const lines = linesOf(range, byCol).map((line, position) => ({ line, position })).sort((a, b) => compareForSort(a.line[index - 1], b.line[index - 1], order) || a.position - b.position).map((entry) => entry.line);
|
|
2766
|
+
return fromLines(lines, byCol, width);
|
|
2767
|
+
}
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
name: "SORTBY",
|
|
2771
|
+
category: "lookup",
|
|
2772
|
+
description: "Sorts an array by the values in other ranges (one value per row or column). The result spills.",
|
|
2773
|
+
args: [
|
|
2774
|
+
arg("array", "The range or array to sort.", "range"),
|
|
2775
|
+
arg("by_array", "A column as tall as array (or a row as wide) to sort by.", "range"),
|
|
2776
|
+
optional("sort_order", "1 = ascending (default), -1 = descending.", "number"),
|
|
2777
|
+
arg("by_array", "Another range to sort by, for ties.", "range", { repeating: true, optional: true }),
|
|
2778
|
+
arg("sort_order", "Its order: 1 = ascending (default), -1 = descending.", "number", { repeating: true, optional: true })
|
|
2779
|
+
],
|
|
2780
|
+
allowTrailingArgument: true,
|
|
2781
|
+
evaluate: (ctx) => {
|
|
2782
|
+
const range = rangeArg(ctx, 0);
|
|
2783
|
+
if (failed(range)) return range;
|
|
2784
|
+
const keys = [];
|
|
2785
|
+
let byCol = null;
|
|
2786
|
+
for (let i = 1; i < ctx.args.length; i += 2) {
|
|
2787
|
+
const by = rangeArg(ctx, i);
|
|
2788
|
+
if (failed(by)) return by;
|
|
2789
|
+
let keyByCol;
|
|
2790
|
+
if (by.cols === 1 && by.rows === range.rows) keyByCol = false;
|
|
2791
|
+
else if (by.rows === 1 && by.cols === range.cols) keyByCol = true;
|
|
2792
|
+
else {
|
|
2793
|
+
return ctx.argError(i, "#VALUE!", "must be a single column as tall as argument 1 (or a single row as wide).");
|
|
2794
|
+
}
|
|
2795
|
+
if (byCol !== null && byCol !== keyByCol) {
|
|
2796
|
+
return ctx.argError(i, "#VALUE!", "must have the same orientation as the other sort ranges.");
|
|
2797
|
+
}
|
|
2798
|
+
byCol = keyByCol;
|
|
2799
|
+
const order = sortOrder(ctx, i + 1);
|
|
2800
|
+
if (failed(order)) return order;
|
|
2801
|
+
keys.push({ values: by.values, order });
|
|
2802
|
+
}
|
|
2803
|
+
const sortByCol = _nullishCoalesce(byCol, () => ( false));
|
|
2804
|
+
const lines = linesOf(range, sortByCol).map((line, position) => ({ line, position })).sort((a, b) => {
|
|
2805
|
+
for (const key of keys) {
|
|
2806
|
+
const result = compareForSort(key.values[a.position], key.values[b.position], key.order);
|
|
2807
|
+
if (result !== 0) return result;
|
|
2808
|
+
}
|
|
2809
|
+
return a.position - b.position;
|
|
2810
|
+
}).map((entry) => entry.line);
|
|
2811
|
+
return fromLines(lines, sortByCol, sortByCol ? range.rows : range.cols);
|
|
2812
|
+
}
|
|
2813
|
+
},
|
|
2814
|
+
{
|
|
2815
|
+
name: "UNIQUE",
|
|
2816
|
+
category: "lookup",
|
|
2817
|
+
description: "Returns the distinct rows (or columns) of an array, in order of first appearance. The result spills.",
|
|
2818
|
+
args: [
|
|
2819
|
+
arg("array", "The range or array.", "range"),
|
|
2820
|
+
optional("by_col", "TRUE compares columns; FALSE (default) compares rows.", "logical"),
|
|
2821
|
+
optional("exactly_once", "TRUE returns only the rows that appear exactly once (default FALSE).", "logical")
|
|
2822
|
+
],
|
|
2823
|
+
evaluate: (ctx) => {
|
|
2824
|
+
const range = rangeArg(ctx, 0);
|
|
2825
|
+
if (failed(range)) return range;
|
|
2826
|
+
const byCol = ctx.boolean(1, false);
|
|
2827
|
+
if (failed(byCol)) return byCol;
|
|
2828
|
+
const exactlyOnce = ctx.boolean(2, false);
|
|
2829
|
+
if (failed(exactlyOnce)) return exactlyOnce;
|
|
2830
|
+
const keyOf = (line) => JSON.stringify(
|
|
2831
|
+
line.map((v) => {
|
|
2832
|
+
const n = normalizeValue(v);
|
|
2833
|
+
return typeof n === "string" ? `s:${n.toLowerCase()}` : isBlank(n) ? "blank" : `${typeof n}:${String(n)}`;
|
|
2834
|
+
})
|
|
2835
|
+
);
|
|
2836
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2837
|
+
for (const line of linesOf(range, byCol)) {
|
|
2838
|
+
const key = keyOf(line);
|
|
2839
|
+
const entry = counts.get(key);
|
|
2840
|
+
if (entry) entry.count++;
|
|
2841
|
+
else counts.set(key, { line, count: 1 });
|
|
2842
|
+
}
|
|
2843
|
+
const lines = Array.from(counts.values()).filter((entry) => !exactlyOnce || entry.count === 1).map((entry) => entry.line);
|
|
2844
|
+
if (lines.length === 0) return ctx.error("#CALC!", "UNIQUE found no values that appear exactly once.");
|
|
2845
|
+
return fromLines(lines, byCol, byCol ? range.rows : range.cols);
|
|
2846
|
+
}
|
|
2847
|
+
},
|
|
2848
|
+
{
|
|
2849
|
+
name: "SEQUENCE",
|
|
2850
|
+
category: "math",
|
|
2851
|
+
description: "Returns a grid of sequential numbers, filled row by row. The result spills.",
|
|
2852
|
+
args: [
|
|
2853
|
+
arg("rows", "The number of rows.", "number"),
|
|
2854
|
+
optional("columns", "The number of columns (default 1).", "number"),
|
|
2855
|
+
optional("start", "The first number (default 1).", "number"),
|
|
2856
|
+
optional("step", "The increment (default 1).", "number")
|
|
2857
|
+
],
|
|
2858
|
+
evaluate: (ctx) => {
|
|
2859
|
+
const rows = ctx.integer(0);
|
|
2860
|
+
if (failed(rows)) return rows;
|
|
2861
|
+
const cols = ctx.integer(1, 1);
|
|
2862
|
+
if (failed(cols)) return cols;
|
|
2863
|
+
const start = ctx.number(2, 1);
|
|
2864
|
+
if (failed(start)) return start;
|
|
2865
|
+
const step = ctx.number(3, 1);
|
|
2866
|
+
if (failed(step)) return step;
|
|
2867
|
+
if (rows < 1 || cols < 1) return ctx.error("#CALC!", "SEQUENCE needs at least one row and one column.");
|
|
2868
|
+
if (rows * cols > MAX_ARRAY_CELLS) return ctx.error("#NUM!", `SEQUENCE can return at most ${MAX_ARRAY_CELLS} values.`);
|
|
2869
|
+
const out = [];
|
|
2870
|
+
for (let c = 0; c < cols; c++) {
|
|
2871
|
+
for (let r = 0; r < rows; r++) out.push(start + step * (r * cols + c));
|
|
2872
|
+
}
|
|
2873
|
+
return rows * cols === 1 ? out[0] : withRangeShape(out, rows, cols);
|
|
2874
|
+
}
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
name: "TRANSPOSE",
|
|
2878
|
+
category: "lookup",
|
|
2879
|
+
description: "Swaps the rows and columns of an array. The result spills.",
|
|
2880
|
+
args: [arg("array", "The range or array to transpose.", "range")],
|
|
2881
|
+
evaluate: (ctx) => {
|
|
2882
|
+
const range = rangeArg(ctx, 0);
|
|
2883
|
+
if (failed(range)) return range;
|
|
2884
|
+
if (range.rows * range.cols === 1) return range.values[0];
|
|
2885
|
+
const out = [];
|
|
2886
|
+
for (let r = 0; r < range.rows; r++) {
|
|
2887
|
+
for (let c = 0; c < range.cols; c++) out.push(rangeGet(range, r, c));
|
|
2888
|
+
}
|
|
2889
|
+
return withRangeShape(out, range.cols, range.rows);
|
|
2890
|
+
}
|
|
2891
|
+
},
|
|
2892
|
+
{
|
|
2893
|
+
name: "RANDARRAY",
|
|
2894
|
+
category: "math",
|
|
2895
|
+
description: "Returns a grid of random numbers. Changes on every recalculation. The result spills.",
|
|
2896
|
+
args: [
|
|
2897
|
+
optional("rows", "The number of rows (default 1).", "number"),
|
|
2898
|
+
optional("columns", "The number of columns (default 1).", "number"),
|
|
2899
|
+
optional("min", "The smallest number (default 0).", "number"),
|
|
2900
|
+
optional("max", "The largest number (default 1).", "number"),
|
|
2901
|
+
optional("integer", "TRUE returns whole numbers; FALSE (default) decimals.", "logical")
|
|
2902
|
+
],
|
|
2903
|
+
volatile: true,
|
|
2904
|
+
evaluate: (ctx) => {
|
|
2905
|
+
const rows = ctx.integer(0, 1);
|
|
2906
|
+
if (failed(rows)) return rows;
|
|
2907
|
+
const cols = ctx.integer(1, 1);
|
|
2908
|
+
if (failed(cols)) return cols;
|
|
2909
|
+
const min2 = ctx.number(2, 0);
|
|
2910
|
+
if (failed(min2)) return min2;
|
|
2911
|
+
const max2 = ctx.number(3, 1);
|
|
2912
|
+
if (failed(max2)) return max2;
|
|
2913
|
+
const integer = ctx.boolean(4, false);
|
|
2914
|
+
if (failed(integer)) return integer;
|
|
2915
|
+
if (rows < 1 || cols < 1) return ctx.error("#CALC!", "RANDARRAY needs at least one row and one column.");
|
|
2916
|
+
if (rows * cols > MAX_ARRAY_CELLS) return ctx.error("#NUM!", `RANDARRAY can return at most ${MAX_ARRAY_CELLS} values.`);
|
|
2917
|
+
if (min2 > max2) return ctx.argError(2, "#VALUE!", "can't be larger than max.");
|
|
2918
|
+
if (integer && (!Number.isInteger(min2) || !Number.isInteger(max2))) {
|
|
2919
|
+
return ctx.error("#VALUE!", "RANDARRAY needs whole min and max values when integer is TRUE.");
|
|
2920
|
+
}
|
|
2921
|
+
const next = () => integer ? min2 + Math.floor(Math.random() * (max2 - min2 + 1)) : min2 + Math.random() * (max2 - min2);
|
|
2922
|
+
const out = [];
|
|
2923
|
+
for (let i = 0; i < rows * cols; i++) out.push(next());
|
|
2924
|
+
return rows * cols === 1 ? out[0] : withRangeShape(out, rows, cols);
|
|
2925
|
+
}
|
|
2926
|
+
},
|
|
2927
|
+
{
|
|
2928
|
+
// Evaluated by the evaluator itself (it defines names); listed here for
|
|
2929
|
+
// autocomplete, signatures and argument-count checks.
|
|
2930
|
+
name: "LET",
|
|
2931
|
+
category: "logical",
|
|
2932
|
+
description: "Names the results of calculations so a formula can reuse them: LET(x, A1*2, x+x). The last argument is the calculation that uses the names.",
|
|
2933
|
+
args: [
|
|
2934
|
+
repeating("name", "A name to define (letters, not a cell reference)."),
|
|
2935
|
+
repeating("name_value", "The value of the name.")
|
|
2936
|
+
],
|
|
2937
|
+
allowTrailingArgument: true,
|
|
2938
|
+
evaluate: (ctx) => ctx.error("#ERROR!", "LET must be evaluated by the formula evaluator.")
|
|
2939
|
+
}
|
|
2940
|
+
];
|
|
2941
|
+
var ELEMENTWISE_BUILTINS = {
|
|
2942
|
+
ABS: true,
|
|
2943
|
+
ROUND: true,
|
|
2944
|
+
ROUNDUP: true,
|
|
2945
|
+
ROUNDDOWN: true,
|
|
2946
|
+
INT: true,
|
|
2947
|
+
MOD: true,
|
|
2948
|
+
POWER: true,
|
|
2949
|
+
SQRT: true,
|
|
2950
|
+
SIGN: true,
|
|
2951
|
+
CEILING: true,
|
|
2952
|
+
FLOOR: true,
|
|
2953
|
+
LEN: true,
|
|
2954
|
+
UPPER: true,
|
|
2955
|
+
LOWER: true,
|
|
2956
|
+
TRIM: true,
|
|
2957
|
+
PROPER: true,
|
|
2958
|
+
LEFT: true,
|
|
2959
|
+
RIGHT: true,
|
|
2960
|
+
MID: true,
|
|
2961
|
+
FIND: true,
|
|
2962
|
+
SEARCH: true,
|
|
2963
|
+
SUBSTITUTE: true,
|
|
2964
|
+
REPLACE: true,
|
|
2965
|
+
REPT: true,
|
|
2966
|
+
EXACT: true,
|
|
2967
|
+
VALUE: true,
|
|
2968
|
+
TEXT: true,
|
|
2969
|
+
YEAR: true,
|
|
2970
|
+
MONTH: true,
|
|
2971
|
+
DAY: true,
|
|
2972
|
+
WEEKDAY: true,
|
|
2973
|
+
EDATE: true,
|
|
2974
|
+
EOMONTH: true,
|
|
2975
|
+
DATE: true,
|
|
2976
|
+
NOT: true,
|
|
2977
|
+
ISBLANK: true,
|
|
2978
|
+
ISNUMBER: true,
|
|
2979
|
+
ISTEXT: true,
|
|
2980
|
+
ISERROR: true,
|
|
2981
|
+
ISNA: true,
|
|
2982
|
+
IF: [0],
|
|
2983
|
+
IFERROR: [0],
|
|
2984
|
+
IFNA: [0]
|
|
2985
|
+
};
|
|
2986
|
+
|
|
2244
2987
|
// src/spreadsheet/formula/functions/index.ts
|
|
2245
2988
|
var BUILTIN_FORMULA_FUNCTIONS = [
|
|
2246
2989
|
...MATH_FUNCTIONS,
|
|
@@ -2250,8 +2993,12 @@ var BUILTIN_FORMULA_FUNCTIONS = [
|
|
|
2250
2993
|
...TEXT_FUNCTIONS,
|
|
2251
2994
|
...LOOKUP_FUNCTIONS,
|
|
2252
2995
|
...DATE_FUNCTIONS,
|
|
2253
|
-
...INFORMATION_FUNCTIONS
|
|
2254
|
-
|
|
2996
|
+
...INFORMATION_FUNCTIONS,
|
|
2997
|
+
...ARRAY_FUNCTIONS
|
|
2998
|
+
].map((def) => {
|
|
2999
|
+
const elementwise = ELEMENTWISE_BUILTINS[def.name];
|
|
3000
|
+
return elementwise && def.elementwise === void 0 ? { ...def, elementwise } : def;
|
|
3001
|
+
});
|
|
2255
3002
|
|
|
2256
3003
|
// src/spreadsheet/formula/registry.ts
|
|
2257
3004
|
var registry = /* @__PURE__ */ new Map();
|
|
@@ -2373,16 +3120,43 @@ var DEFAULT_MESSAGES = {
|
|
|
2373
3120
|
"#N/A": "A value is not available.",
|
|
2374
3121
|
"#NUM!": "The formula produced an invalid number.",
|
|
2375
3122
|
"#CYCLE!": "This formula is part of a circular reference.",
|
|
2376
|
-
"#ERROR!": "The formula could not be evaluated."
|
|
3123
|
+
"#ERROR!": "The formula could not be evaluated.",
|
|
3124
|
+
"#SPILL!": "The formula's array result can't spill into the cells it needs.",
|
|
3125
|
+
"#CALC!": "The formula produced an empty array."
|
|
2377
3126
|
};
|
|
2378
3127
|
function describeFormulaError(code) {
|
|
2379
3128
|
return _nullishCoalesce(DEFAULT_MESSAGES[code], () => ( "The formula could not be evaluated."));
|
|
2380
3129
|
}
|
|
2381
3130
|
var quote = (value) => typeof value === "string" ? `"${value}"` : String(value);
|
|
3131
|
+
var MAX_FORMULA_ARRAY_SIZE = 1e6;
|
|
3132
|
+
function shapeOf(value) {
|
|
3133
|
+
if (!Array.isArray(value)) return { rows: 1, cols: 1 };
|
|
3134
|
+
const range = toFormulaRange(value);
|
|
3135
|
+
return { rows: range.rows, cols: range.cols };
|
|
3136
|
+
}
|
|
3137
|
+
function broadcastGet(value, row, col) {
|
|
3138
|
+
if (!Array.isArray(value)) return value;
|
|
3139
|
+
const range = toFormulaRange(value);
|
|
3140
|
+
const r = range.rows === 1 ? 0 : row;
|
|
3141
|
+
const c = range.cols === 1 ? 0 : col;
|
|
3142
|
+
if (r >= range.rows || c >= range.cols) return "#N/A";
|
|
3143
|
+
return range.values[c * range.rows + r];
|
|
3144
|
+
}
|
|
3145
|
+
function buildArray(rows, cols, fn) {
|
|
3146
|
+
const out = [];
|
|
3147
|
+
for (let c = 0; c < cols; c++) {
|
|
3148
|
+
for (let r = 0; r < rows; r++) out.push(fn(r, c));
|
|
3149
|
+
}
|
|
3150
|
+
return withRangeShape(out, rows, cols);
|
|
3151
|
+
}
|
|
3152
|
+
var isMultiArray = (value) => Array.isArray(value) && value.length > 1;
|
|
3153
|
+
var firstValue = (value) => Array.isArray(value) ? value.length ? value[0] : "" : value;
|
|
2382
3154
|
var FormulaEvaluator = (_class2 = class {
|
|
2383
3155
|
|
|
2384
3156
|
__init3() {this.diagnostics = []}
|
|
2385
|
-
|
|
3157
|
+
// LET variables, innermost scope last.
|
|
3158
|
+
__init4() {this.scopes = []}
|
|
3159
|
+
constructor(context = {}) {;_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);
|
|
2386
3160
|
this.context = context;
|
|
2387
3161
|
}
|
|
2388
3162
|
/**
|
|
@@ -2395,25 +3169,39 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2395
3169
|
}
|
|
2396
3170
|
/**
|
|
2397
3171
|
* Evaluates a formula and explains errors. Never throws: malformed formulas
|
|
2398
|
-
* return `#ERROR!` with the parser's message. A result
|
|
2399
|
-
* (
|
|
3172
|
+
* return `#ERROR!` with the parser's message. A result with several values
|
|
3173
|
+
* (a range or a dynamic array) is returned in `array`, with its top-left
|
|
3174
|
+
* value as `value`; a one-value array is just that value.
|
|
2400
3175
|
*/
|
|
2401
3176
|
evaluateWithDiagnostics(astOrFormula) {
|
|
2402
3177
|
const saved = this.diagnostics;
|
|
3178
|
+
const savedScopes = this.scopes;
|
|
2403
3179
|
this.diagnostics = [];
|
|
3180
|
+
this.scopes = [];
|
|
2404
3181
|
try {
|
|
2405
3182
|
let value = this.evaluate(astOrFormula);
|
|
2406
|
-
|
|
3183
|
+
let array;
|
|
3184
|
+
if (Array.isArray(value)) {
|
|
3185
|
+
if (value.length > 1) array = toFormulaRange(value);
|
|
3186
|
+
value = firstValue(value);
|
|
3187
|
+
}
|
|
2407
3188
|
if (isFormulaError(value)) {
|
|
2408
3189
|
const code = value;
|
|
2409
3190
|
const diagnostic = this.diagnostics.find((d) => d.code === code);
|
|
2410
|
-
|
|
3191
|
+
const result = {
|
|
3192
|
+
value,
|
|
3193
|
+
error: code,
|
|
3194
|
+
message: _nullishCoalesce(_optionalChain([diagnostic, 'optionalAccess', _13 => _13.message]), () => ( describeFormulaError(code)))
|
|
3195
|
+
};
|
|
3196
|
+
if (array) result.array = array;
|
|
3197
|
+
return result;
|
|
2411
3198
|
}
|
|
2412
|
-
return { value };
|
|
3199
|
+
return array ? { value, array } : { value };
|
|
2413
3200
|
} catch (err) {
|
|
2414
|
-
return { value: "#ERROR!", error: "#ERROR!", message: _optionalChain([err, 'optionalAccess',
|
|
3201
|
+
return { value: "#ERROR!", error: "#ERROR!", message: _optionalChain([err, 'optionalAccess', _14 => _14.message]) || "Formula error" };
|
|
2415
3202
|
} finally {
|
|
2416
3203
|
this.diagnostics = saved;
|
|
3204
|
+
this.scopes = savedScopes;
|
|
2417
3205
|
}
|
|
2418
3206
|
}
|
|
2419
3207
|
/** Records why an error happened and returns the error code. */
|
|
@@ -2442,6 +3230,14 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2442
3230
|
}
|
|
2443
3231
|
case "Range":
|
|
2444
3232
|
return this.evaluateRange(node);
|
|
3233
|
+
case "SpillRef":
|
|
3234
|
+
return this.evaluateSpillRef(node);
|
|
3235
|
+
case "Name": {
|
|
3236
|
+
for (let i = this.scopes.length - 1; i >= 0; i--) {
|
|
3237
|
+
if (this.scopes[i].has(node.name)) return this.scopes[i].get(node.name);
|
|
3238
|
+
}
|
|
3239
|
+
return this.fail("#NAME?", "This formula contains an unknown name.");
|
|
3240
|
+
}
|
|
2445
3241
|
case "PropertyRef": {
|
|
2446
3242
|
if (!this.context.resolveProperty) {
|
|
2447
3243
|
throw new Error(`Cannot resolve property [${node.property}]: no property resolver provided`);
|
|
@@ -2449,65 +3245,38 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2449
3245
|
return this.context.resolveProperty(node.property);
|
|
2450
3246
|
}
|
|
2451
3247
|
case "UnaryOp": {
|
|
3248
|
+
if (node.op === "@") return this.implicitIntersection(node.argument);
|
|
2452
3249
|
const raw = this.evaluateNode(node.argument);
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
return
|
|
3250
|
+
if (isMultiArray(raw)) {
|
|
3251
|
+
const { rows, cols } = shapeOf(raw);
|
|
3252
|
+
return buildArray(rows, cols, (r, c) => this.unary(node.op, broadcastGet(raw, r, c)));
|
|
2456
3253
|
}
|
|
2457
|
-
|
|
2458
|
-
if (node.op === "+") return val;
|
|
2459
|
-
throw new Error(`Unsupported unary operator: ${node.op}`);
|
|
3254
|
+
return this.unary(node.op, firstValue(raw));
|
|
2460
3255
|
}
|
|
2461
3256
|
case "BinaryOp": {
|
|
2462
3257
|
const left = this.evaluateNode(node.left);
|
|
2463
3258
|
if (isFormulaError(left)) return left;
|
|
2464
3259
|
const right = this.evaluateNode(node.right);
|
|
2465
3260
|
if (isFormulaError(right)) return right;
|
|
2466
|
-
if (
|
|
2467
|
-
const a =
|
|
2468
|
-
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
result = x - y;
|
|
2480
|
-
break;
|
|
2481
|
-
case "*":
|
|
2482
|
-
result = x * y;
|
|
2483
|
-
break;
|
|
2484
|
-
case "/":
|
|
2485
|
-
if (y === 0) return this.fail("#DIV/0!", "The formula divides by zero.");
|
|
2486
|
-
result = x / y;
|
|
2487
|
-
break;
|
|
2488
|
-
default:
|
|
2489
|
-
result = Math.pow(x, y);
|
|
2490
|
-
}
|
|
2491
|
-
return Number.isFinite(result) ? result : this.fail("#NUM!", "The result is too large or not a number.");
|
|
2492
|
-
}
|
|
2493
|
-
switch (node.op) {
|
|
2494
|
-
case "&":
|
|
2495
|
-
return toText(left) + toText(right);
|
|
2496
|
-
case "=":
|
|
2497
|
-
return compareValues(left, right) === 0;
|
|
2498
|
-
case "<>":
|
|
2499
|
-
return compareValues(left, right) !== 0;
|
|
2500
|
-
case "<":
|
|
2501
|
-
return compareValues(left, right) < 0;
|
|
2502
|
-
case "<=":
|
|
2503
|
-
return compareValues(left, right) <= 0;
|
|
2504
|
-
case ">":
|
|
2505
|
-
return compareValues(left, right) > 0;
|
|
2506
|
-
case ">=":
|
|
2507
|
-
return compareValues(left, right) >= 0;
|
|
2508
|
-
default:
|
|
2509
|
-
throw new Error(`Unsupported binary operator: ${node.op}`);
|
|
3261
|
+
if (isMultiArray(left) || isMultiArray(right)) {
|
|
3262
|
+
const a = shapeOf(left);
|
|
3263
|
+
const b = shapeOf(right);
|
|
3264
|
+
const rows = Math.max(a.rows, b.rows);
|
|
3265
|
+
const cols = Math.max(a.cols, b.cols);
|
|
3266
|
+
if (rows * cols > MAX_FORMULA_ARRAY_SIZE) return this.fail("#NUM!", "The array result is too large.");
|
|
3267
|
+
return buildArray(rows, cols, (r2, c) => {
|
|
3268
|
+
const l2 = broadcastGet(left, r2, c);
|
|
3269
|
+
if (isFormulaError(l2)) return l2;
|
|
3270
|
+
const rv = broadcastGet(right, r2, c);
|
|
3271
|
+
if (isFormulaError(rv)) return rv;
|
|
3272
|
+
return this.binary(node.op, l2, rv);
|
|
3273
|
+
});
|
|
2510
3274
|
}
|
|
3275
|
+
const l = firstValue(left);
|
|
3276
|
+
if (isFormulaError(l)) return l;
|
|
3277
|
+
const r = firstValue(right);
|
|
3278
|
+
if (isFormulaError(r)) return r;
|
|
3279
|
+
return this.binary(node.op, l, r);
|
|
2511
3280
|
}
|
|
2512
3281
|
case "FunctionCall":
|
|
2513
3282
|
return this.callFunction(node);
|
|
@@ -2515,6 +3284,134 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2515
3284
|
throw new Error(`Unknown AST node: ${node.type}`);
|
|
2516
3285
|
}
|
|
2517
3286
|
}
|
|
3287
|
+
unary(op, raw) {
|
|
3288
|
+
const val = toNumber(raw);
|
|
3289
|
+
if (isFormulaError(val)) {
|
|
3290
|
+
return isFormulaError(raw) ? raw : this.fail("#VALUE!", `The ${op} operator can't use text (${quote(raw)}).`);
|
|
3291
|
+
}
|
|
3292
|
+
if (op === "-") return -val;
|
|
3293
|
+
if (op === "+") return val;
|
|
3294
|
+
throw new Error(`Unsupported unary operator: ${op}`);
|
|
3295
|
+
}
|
|
3296
|
+
/** A binary operator on two single values. */
|
|
3297
|
+
binary(op, left, right) {
|
|
3298
|
+
if (["+", "-", "*", "/", "^"].includes(op)) {
|
|
3299
|
+
if (op === "+" || op === "-") {
|
|
3300
|
+
const leftDate = parseIsoDate(left);
|
|
3301
|
+
const rightDate = parseIsoDate(right);
|
|
3302
|
+
if (leftDate || rightDate) return this.dateArithmetic(op, left, right, leftDate, rightDate);
|
|
3303
|
+
}
|
|
3304
|
+
const a = toNumber(left);
|
|
3305
|
+
if (isFormulaError(a)) return this.fail("#VALUE!", `The ${op} operator can't use text (${quote(left)}).`);
|
|
3306
|
+
const b = toNumber(right);
|
|
3307
|
+
if (isFormulaError(b)) return this.fail("#VALUE!", `The ${op} operator can't use text (${quote(right)}).`);
|
|
3308
|
+
const x = a;
|
|
3309
|
+
const y = b;
|
|
3310
|
+
let result;
|
|
3311
|
+
switch (op) {
|
|
3312
|
+
case "+":
|
|
3313
|
+
result = x + y;
|
|
3314
|
+
break;
|
|
3315
|
+
case "-":
|
|
3316
|
+
result = x - y;
|
|
3317
|
+
break;
|
|
3318
|
+
case "*":
|
|
3319
|
+
result = x * y;
|
|
3320
|
+
break;
|
|
3321
|
+
case "/":
|
|
3322
|
+
if (y === 0) return this.fail("#DIV/0!", "The formula divides by zero.");
|
|
3323
|
+
result = x / y;
|
|
3324
|
+
break;
|
|
3325
|
+
default:
|
|
3326
|
+
result = Math.pow(x, y);
|
|
3327
|
+
}
|
|
3328
|
+
return Number.isFinite(result) ? result : this.fail("#NUM!", "The result is too large or not a number.");
|
|
3329
|
+
}
|
|
3330
|
+
switch (op) {
|
|
3331
|
+
case "&":
|
|
3332
|
+
return toText(left) + toText(right);
|
|
3333
|
+
case "=":
|
|
3334
|
+
return compareValues(left, right) === 0;
|
|
3335
|
+
case "<>":
|
|
3336
|
+
return compareValues(left, right) !== 0;
|
|
3337
|
+
case "<":
|
|
3338
|
+
return compareValues(left, right) < 0;
|
|
3339
|
+
case "<=":
|
|
3340
|
+
return compareValues(left, right) <= 0;
|
|
3341
|
+
case ">":
|
|
3342
|
+
return compareValues(left, right) > 0;
|
|
3343
|
+
case ">=":
|
|
3344
|
+
return compareValues(left, right) >= 0;
|
|
3345
|
+
default:
|
|
3346
|
+
throw new Error(`Unsupported binary operator: ${op}`);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3350
|
+
* Dates are ISO text: date +/- days is a date, date - date is a number of
|
|
3351
|
+
* days (fractional when times are involved).
|
|
3352
|
+
*/
|
|
3353
|
+
dateArithmetic(op, left, right, leftDate, rightDate) {
|
|
3354
|
+
if (leftDate && rightDate) {
|
|
3355
|
+
if (op === "+") return this.fail("#VALUE!", "Two dates can't be added; subtract them to count the days between.");
|
|
3356
|
+
return cleanFloat(dateToSerial(leftDate) - dateToSerial(rightDate));
|
|
3357
|
+
}
|
|
3358
|
+
if (!leftDate && op === "-") {
|
|
3359
|
+
return this.fail("#VALUE!", `A date (${quote(right)}) can't be subtracted from a number.`);
|
|
3360
|
+
}
|
|
3361
|
+
const date = _nullishCoalesce(leftDate, () => ( rightDate));
|
|
3362
|
+
const dateText = String(leftDate ? left : right);
|
|
3363
|
+
const other = leftDate ? right : left;
|
|
3364
|
+
const days = toNumber(other);
|
|
3365
|
+
if (isFormulaError(days)) return this.fail("#VALUE!", `The ${op} operator can't use text (${quote(other)}).`);
|
|
3366
|
+
const n = days;
|
|
3367
|
+
const result = serialToDate(dateToSerial(date) + (op === "+" ? n : -n));
|
|
3368
|
+
if (!result || result.getFullYear() < 1 || result.getFullYear() > 9999) {
|
|
3369
|
+
return this.fail("#NUM!", "The resulting date is out of range.");
|
|
3370
|
+
}
|
|
3371
|
+
return isoDateHasTime(dateText) || !Number.isInteger(n) ? formatIsoDateTime(result) : formatIsoDate(result);
|
|
3372
|
+
}
|
|
3373
|
+
/**
|
|
3374
|
+
* `@` (implicit intersection): a range reference reads the cell in the
|
|
3375
|
+
* formula's row (one column) or column (one row); arrays give their
|
|
3376
|
+
* top-left value.
|
|
3377
|
+
*/
|
|
3378
|
+
implicitIntersection(argument) {
|
|
3379
|
+
const value = this.evaluateNode(argument);
|
|
3380
|
+
if (!Array.isArray(value)) return value;
|
|
3381
|
+
if (value.length <= 1) return firstValue(value);
|
|
3382
|
+
const cell = _optionalChain([this, 'access', _15 => _15.context, 'access', _16 => _16.getFormulaCell, 'optionalCall', _17 => _17()]);
|
|
3383
|
+
if (argument.type === "Range" && cell) {
|
|
3384
|
+
const range = toFormulaRange(value);
|
|
3385
|
+
const top = Math.min(argument.startRow, argument.endRow);
|
|
3386
|
+
const left = Math.min(colNameToIndex(argument.startCol), colNameToIndex(argument.endCol));
|
|
3387
|
+
if (range.cols === 1) {
|
|
3388
|
+
const r = cell.row - top;
|
|
3389
|
+
if (r >= 0 && r < range.rows) return range.values[r];
|
|
3390
|
+
} else if (range.rows === 1) {
|
|
3391
|
+
const c = colNameToIndex(cell.col) - left;
|
|
3392
|
+
if (c >= 0 && c < range.cols) return range.values[c];
|
|
3393
|
+
}
|
|
3394
|
+
return this.fail("#VALUE!", `The @ operator found no cell of ${argument.raw} in the formula's row or column.`);
|
|
3395
|
+
}
|
|
3396
|
+
return value[0];
|
|
3397
|
+
}
|
|
3398
|
+
evaluateSpillRef(node) {
|
|
3399
|
+
if (!this.context.resolveSpill) {
|
|
3400
|
+
throw new Error(`Cannot resolve spill range ${node.ref}#: no spill resolver provided`);
|
|
3401
|
+
}
|
|
3402
|
+
const spill = this.context.resolveSpill(node.col, node.row);
|
|
3403
|
+
if (typeof spill === "string" || !spill) {
|
|
3404
|
+
const code = isFormulaError(spill) ? spill : "#REF!";
|
|
3405
|
+
return this.fail(
|
|
3406
|
+
code,
|
|
3407
|
+
code === "#REF!" ? `${node.ref}# refers to ${node.ref}, which doesn't hold a spilled array.` : `The spill range ${node.ref}# isn't available (${code}).`
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3410
|
+
const values = withRangeShape(spill.values.slice(), spill.rows, spill.cols);
|
|
3411
|
+
const error = values.find(isFormulaError);
|
|
3412
|
+
if (error) this.fail(error, `The spill range ${node.ref}# contains an error (${error}).`);
|
|
3413
|
+
return values;
|
|
3414
|
+
}
|
|
2518
3415
|
evaluateRange(node) {
|
|
2519
3416
|
if (!this.context.resolveRange) {
|
|
2520
3417
|
throw new Error(`Cannot resolve range ${node.raw}: no range resolver provided`);
|
|
@@ -2532,15 +3429,23 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2532
3429
|
if (!def) return this.fail("#NAME?", `Unknown function "${node.name.toUpperCase()}".`);
|
|
2533
3430
|
const countError = checkFormulaArgumentCount(def, node.args.length);
|
|
2534
3431
|
if (countError) return this.fail("#ERROR!", countError);
|
|
2535
|
-
|
|
3432
|
+
if (def.name === "LET") return this.evaluateLet(node);
|
|
3433
|
+
const args = this.lazyArguments(node.args);
|
|
3434
|
+
if (def.elementwise) {
|
|
3435
|
+
const lifted = this.liftOverArrays(def, args);
|
|
3436
|
+
if (lifted !== void 0) return lifted;
|
|
3437
|
+
}
|
|
3438
|
+
return this.finishCall(def, def.evaluate(this.createContext(def, args)));
|
|
3439
|
+
}
|
|
3440
|
+
finishCall(def, result) {
|
|
2536
3441
|
if (typeof result === "number" && !Number.isFinite(result)) {
|
|
2537
3442
|
return this.fail("#NUM!", `${def.name} produced an invalid number.`);
|
|
2538
3443
|
}
|
|
2539
3444
|
return result === void 0 || result === null ? "" : result;
|
|
2540
3445
|
}
|
|
2541
|
-
|
|
3446
|
+
lazyArguments(nodes) {
|
|
2542
3447
|
const cache = /* @__PURE__ */ new Map();
|
|
2543
|
-
|
|
3448
|
+
return nodes.map((node, index) => ({
|
|
2544
3449
|
index,
|
|
2545
3450
|
node,
|
|
2546
3451
|
value: () => {
|
|
@@ -2548,6 +3453,56 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2548
3453
|
return cache.get(index);
|
|
2549
3454
|
}
|
|
2550
3455
|
}));
|
|
3456
|
+
}
|
|
3457
|
+
/**
|
|
3458
|
+
* Applies a scalar function element by element when it receives arrays
|
|
3459
|
+
* (`=LEN(A1:A3)`, `=IF(A1:A3>1, "y", "n")`). Arguments typed `range` are
|
|
3460
|
+
* passed whole. Returns undefined when no trigger argument is a
|
|
3461
|
+
* multi-value array.
|
|
3462
|
+
*/
|
|
3463
|
+
liftOverArrays(def, args) {
|
|
3464
|
+
const liftable = (index) => _optionalChain([def, 'access', _18 => _18.args, 'access', _19 => _19[getFormulaArgumentSpecIndex(def, index)], 'optionalAccess', _20 => _20.type]) !== "range";
|
|
3465
|
+
const triggers = def.elementwise === true ? args.map((a) => a.index) : def.elementwise;
|
|
3466
|
+
if (!triggers.some((i) => i < args.length && liftable(i) && isMultiArray(args[i].value()))) return void 0;
|
|
3467
|
+
let rows = 1;
|
|
3468
|
+
let cols = 1;
|
|
3469
|
+
for (const a of args) {
|
|
3470
|
+
if (!liftable(a.index)) continue;
|
|
3471
|
+
const shape = shapeOf(a.value());
|
|
3472
|
+
rows = Math.max(rows, shape.rows);
|
|
3473
|
+
cols = Math.max(cols, shape.cols);
|
|
3474
|
+
}
|
|
3475
|
+
if (rows * cols > MAX_FORMULA_ARRAY_SIZE) return this.fail("#NUM!", "The array result is too large.");
|
|
3476
|
+
return buildArray(rows, cols, (r, c) => {
|
|
3477
|
+
const elementArgs = args.map((a) => {
|
|
3478
|
+
if (!liftable(a.index)) return a;
|
|
3479
|
+
const element = broadcastGet(a.value(), r, c);
|
|
3480
|
+
return { index: a.index, node: a.node, value: () => element };
|
|
3481
|
+
});
|
|
3482
|
+
return firstValue(this.finishCall(def, def.evaluate(this.createContext(def, elementArgs))));
|
|
3483
|
+
});
|
|
3484
|
+
}
|
|
3485
|
+
/** LET(name1, value1, [name2, value2, ...], calculation). */
|
|
3486
|
+
evaluateLet(node) {
|
|
3487
|
+
if (node.args.length % 2 === 0) {
|
|
3488
|
+
return this.fail("#ERROR!", "LET needs name/value pairs followed by a calculation.");
|
|
3489
|
+
}
|
|
3490
|
+
const scope = /* @__PURE__ */ new Map();
|
|
3491
|
+
this.scopes.push(scope);
|
|
3492
|
+
try {
|
|
3493
|
+
for (let i = 0; i < node.args.length - 1; i += 2) {
|
|
3494
|
+
const nameNode = node.args[i];
|
|
3495
|
+
if (nameNode.type !== "Name") {
|
|
3496
|
+
return this.fail("#ERROR!", `Argument ${i + 1} of LET must be a name (names can't look like cell references).`);
|
|
3497
|
+
}
|
|
3498
|
+
scope.set(nameNode.name, this.evaluateNode(node.args[i + 1]));
|
|
3499
|
+
}
|
|
3500
|
+
return this.evaluateNode(node.args[node.args.length - 1]);
|
|
3501
|
+
} finally {
|
|
3502
|
+
this.scopes.pop();
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
createContext(def, args) {
|
|
2551
3506
|
const has = (index) => index >= 0 && index < args.length;
|
|
2552
3507
|
const value = (index) => has(index) ? args[index].value() : "";
|
|
2553
3508
|
const argError = (index, code, reason) => this.fail(code, `Argument ${index + 1} of ${def.name} ${reason}`);
|
|
@@ -2653,5 +3608,16 @@ var FormulaEvaluator = (_class2 = class {
|
|
|
2653
3608
|
|
|
2654
3609
|
|
|
2655
3610
|
|
|
2656
|
-
|
|
2657
|
-
|
|
3611
|
+
|
|
3612
|
+
|
|
3613
|
+
|
|
3614
|
+
|
|
3615
|
+
|
|
3616
|
+
|
|
3617
|
+
|
|
3618
|
+
|
|
3619
|
+
|
|
3620
|
+
|
|
3621
|
+
|
|
3622
|
+
exports.tokenizeFormula = tokenizeFormula; exports.WHOLE_COLUMN_END_ROW = WHOLE_COLUMN_END_ROW; exports.parseCellReference = parseCellReference; exports.parseRange = parseRange; exports.FormulaParser = FormulaParser; exports.parseFormula = parseFormula; exports.serialFromYmd = serialFromYmd; exports.parseCellInput = parseCellInput; exports.parseNumberText = parseNumberText; exports.FORMULA_ERRORS = FORMULA_ERRORS; exports.isFormulaError = isFormulaError; exports.numberToText = numberToText; exports.numericValue = numericValue; exports.compareNumbers = compareNumbers; exports.compareValues = compareValues; exports.sortCompareValues = sortCompareValues; exports.colNameToIndex = colNameToIndex; exports.indexToColName = indexToColName; exports.parseIsoDate = parseIsoDate; exports.wildcardToRegExp = wildcardToRegExp; exports.parseCriteria = parseCriteria; exports.formatCellDisplay = formatCellDisplay; exports.formatValueWithCode = formatValueWithCode; exports.registerFormulaFunction = registerFormulaFunction; exports.unregisterFormulaFunction = unregisterFormulaFunction; exports.getFormulaFunction = getFormulaFunction; exports.listFormulaFunctions = listFormulaFunctions; exports.searchFormulaFunctions = searchFormulaFunctions; exports.getFormulaArgumentSpecIndex = getFormulaArgumentSpecIndex; exports.checkFormulaArgumentCount = checkFormulaArgumentCount; exports.getFormulaSignatureParts = getFormulaSignatureParts; exports.getFormulaSignature = getFormulaSignature; exports.describeFormulaError = describeFormulaError; exports.MAX_FORMULA_ARRAY_SIZE = MAX_FORMULA_ARRAY_SIZE; exports.FormulaEvaluator = FormulaEvaluator;
|
|
3623
|
+
//# sourceMappingURL=chunk-RIKBJLS2.cjs.map
|