@pixerate/editor 0.22.0 → 0.24.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 +175 -2
- package/THIRD_PARTY_NOTICES.md +139 -0
- package/dist/agent/index.cjs +87 -111
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +70 -211
- package/dist/agent/index.d.ts +70 -211
- package/dist/agent/index.js +78 -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 +2075 -86
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +608 -186
- package/dist/canvas/index.d.ts +608 -186
- package/dist/canvas/index.js +2060 -71
- 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-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
- package/dist/chunk-3CN2WD3X.js.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
- package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
- package/dist/chunk-5S3Z4PPF.js.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/chunk-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
- package/dist/chunk-D27YFC46.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/chunk-FODNLX5N.js +749 -0
- package/dist/chunk-FODNLX5N.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
- package/dist/chunk-ITBSKI26.js.map +1 -0
- package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
- package/dist/chunk-MCSJFWAO.cjs.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-OLZJXL4R.cjs +749 -0
- package/dist/chunk-OLZJXL4R.cjs.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PGNZKFQS.js +685 -0
- package/dist/chunk-PGNZKFQS.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
- package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
- package/dist/chunk-U5KWZU4X.js.map +1 -0
- package/dist/chunk-XNORY7M6.cjs +685 -0
- package/dist/chunk-XNORY7M6.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 +244 -0
- package/dist/commands/index.d.ts +244 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
- package/dist/documents-BQjzJNe2.d.ts +76 -0
- package/dist/documents-KRTPhvst.d.cts +76 -0
- package/dist/extensions/index.cjs +53 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +7 -3
- package/dist/extensions/index.d.ts +7 -3
- package/dist/extensions/index.js +52 -4
- package/dist/generation/index.cjs +1373 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +554 -0
- package/dist/generation/index.d.ts +554 -0
- package/dist/generation/index.js +1373 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- 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 +2244 -38
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +742 -4
- package/dist/image-editor/index.d.ts +742 -4
- package/dist/image-editor/index.js +2222 -16
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
- package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
- package/dist/index.cjs +193 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -48
- package/dist/index.d.ts +17 -48
- package/dist/index.js +198 -103
- package/dist/index.js.map +1 -1
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +347 -0
- package/dist/persistence/index.d.ts +347 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/registry-BDe-vlv3.d.ts +171 -0
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/registry-DnzpgY27.d.cts +171 -0
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +4 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +2 -1
- package/dist/serializers/index.d.ts +2 -1
- package/dist/serializers/index.js +3 -2
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +53 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +247 -18
- package/dist/spreadsheet/index.d.ts +247 -18
- package/dist/spreadsheet/index.js +58 -8
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.cts → 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 +23 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-DENSCKFC.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RU52I7QO.js.map +0 -1
- package/dist/chunk-SAZRENEZ.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
|
@@ -414,9 +414,7 @@ function toNumber(value) {
|
|
|
414
414
|
if (isBlank(value)) return 0;
|
|
415
415
|
if (typeof value === "boolean") return value ? 1 : 0;
|
|
416
416
|
if (typeof value === "number") return value;
|
|
417
|
-
|
|
418
|
-
const num = trimmed === "" ? NaN : Number(trimmed);
|
|
419
|
-
return isNaN(num) ? "#VALUE!" : num;
|
|
417
|
+
return parseNumberText(String(value)) ?? "#VALUE!";
|
|
420
418
|
}
|
|
421
419
|
function toBoolean(value) {
|
|
422
420
|
if (isFormulaError(value)) return value;
|
|
@@ -428,7 +426,26 @@ function toBoolean(value) {
|
|
|
428
426
|
if (upper === "FALSE") return false;
|
|
429
427
|
return "#VALUE!";
|
|
430
428
|
}
|
|
431
|
-
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
|
+
}
|
|
432
449
|
function numericValue(value) {
|
|
433
450
|
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
434
451
|
if (typeof value !== "string" || FORMULA_ERRORS.has(value)) return null;
|
|
@@ -441,22 +458,68 @@ function normalizeValue(value) {
|
|
|
441
458
|
const num = typeof value === "string" ? numericValue(value) : null;
|
|
442
459
|
return num === null ? value : num;
|
|
443
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
|
+
}
|
|
444
482
|
function compareValues(left, right) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return
|
|
458
|
-
}
|
|
459
|
-
|
|
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);
|
|
460
523
|
}
|
|
461
524
|
function roundHalfAwayFromZero(value, decimals) {
|
|
462
525
|
const factor = Math.pow(10, decimals);
|
|
@@ -565,6 +628,282 @@ var daysBetween = (start, end) => Math.round(
|
|
|
565
628
|
(Date.UTC(end.getFullYear(), end.getMonth(), end.getDate()) - Date.UTC(start.getFullYear(), start.getMonth(), start.getDate())) / MS_PER_DAY
|
|
566
629
|
);
|
|
567
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
|
+
|
|
568
907
|
// src/spreadsheet/formula/criteria.ts
|
|
569
908
|
function wildcardToRegExp(pattern) {
|
|
570
909
|
let source = "";
|
|
@@ -601,12 +940,17 @@ function parseCriteria(criterion) {
|
|
|
601
940
|
if (op === "<>") return (value) => !isBlank(value);
|
|
602
941
|
return () => false;
|
|
603
942
|
}
|
|
604
|
-
const
|
|
943
|
+
const parsed = parseCellInput(operand.trim());
|
|
944
|
+
const operandNumber = 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;
|
|
605
947
|
const operandUpper = operand.trim().toUpperCase();
|
|
606
948
|
const operandBoolean = operandUpper === "TRUE" ? true : operandUpper === "FALSE" ? false : null;
|
|
607
949
|
if (op === "=" || op === "<>") {
|
|
608
950
|
let equals;
|
|
609
|
-
if (
|
|
951
|
+
if (operandDate !== null) {
|
|
952
|
+
equals = (value) => isDateValue(value) && compareValues(value, operandDate) === 0;
|
|
953
|
+
} else if (operandNumber !== null) {
|
|
610
954
|
equals = (value) => numericValue(value) === operandNumber;
|
|
611
955
|
} else if (operandBoolean !== null) {
|
|
612
956
|
equals = (value) => value === operandBoolean || typeof value === "string" && value.trim().toUpperCase() === operandUpper;
|
|
@@ -619,6 +963,9 @@ function parseCriteria(criterion) {
|
|
|
619
963
|
return op === "=" ? (value) => !isFormulaError(value) && equals(value) : (value) => !isFormulaError(value) && !equals(value);
|
|
620
964
|
}
|
|
621
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
|
+
}
|
|
622
969
|
if (operandNumber !== null) {
|
|
623
970
|
return (value) => {
|
|
624
971
|
const num = numericValue(value);
|
|
@@ -635,173 +982,1343 @@ function parseCriteria(criterion) {
|
|
|
635
982
|
};
|
|
636
983
|
}
|
|
637
984
|
|
|
638
|
-
// src/spreadsheet/
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
985
|
+
// src/spreadsheet/numfmt/decimal.ts
|
|
986
|
+
var MAX_DECIMALS = 400;
|
|
987
|
+
function significantDigits(a, n) {
|
|
988
|
+
const s = a.toExponential(Math.max(0, Math.min(100, n - 1)));
|
|
989
|
+
const e = s.indexOf("e");
|
|
990
|
+
const digits = [];
|
|
991
|
+
for (let i = 0; i < e; i++) {
|
|
992
|
+
const c = s.charCodeAt(i);
|
|
993
|
+
if (c >= 48 && c <= 57) digits.push(c - 48);
|
|
643
994
|
}
|
|
644
|
-
|
|
645
|
-
return num === null ? toText(value) : formatNumber(num, format);
|
|
995
|
+
return { digits, exp: Number(s.slice(e + 1)) };
|
|
646
996
|
}
|
|
647
|
-
function
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
997
|
+
function exactDigits(a) {
|
|
998
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
999
|
+
view.setFloat64(0, a);
|
|
1000
|
+
const hi = view.getUint32(0);
|
|
1001
|
+
const lo = view.getUint32(4);
|
|
1002
|
+
const biased = hi >>> 20 & 2047;
|
|
1003
|
+
let mantissa = BigInt(hi & 1048575) << 32n | BigInt(lo);
|
|
1004
|
+
let exp2;
|
|
1005
|
+
if (biased === 0) {
|
|
1006
|
+
exp2 = -1074;
|
|
1007
|
+
} else {
|
|
1008
|
+
mantissa |= 1n << 52n;
|
|
1009
|
+
exp2 = biased - 1075;
|
|
1010
|
+
}
|
|
1011
|
+
const digits = exp2 >= 0 ? (mantissa << BigInt(exp2)).toString() : (mantissa * 5n ** BigInt(-exp2)).toString();
|
|
1012
|
+
return digits.replace(/0+$/, "");
|
|
1013
|
+
}
|
|
1014
|
+
function isExactTie(a) {
|
|
1015
|
+
const isTie = (ds) => ds.length >= 16 && ds[15] === "5" && /^0*$/.test(ds.slice(16));
|
|
1016
|
+
return isTie(significantDigits(a, 30).digits.join("")) && isTie(exactDigits(a).padEnd(16, "0"));
|
|
1017
|
+
}
|
|
1018
|
+
function decimalDigits(a) {
|
|
1019
|
+
if (!Number.isFinite(a) || a <= 0) return { digits: new Array(15).fill(0), exp: 0 };
|
|
1020
|
+
const { digits: d16, exp } = significantDigits(a, 16);
|
|
1021
|
+
const digits = d16.slice(0, 15);
|
|
1022
|
+
while (digits.length < 15) digits.push(0);
|
|
1023
|
+
const sixteenth = d16[15] ?? 0;
|
|
1024
|
+
if (sixteenth <= 4) return { digits, exp };
|
|
1025
|
+
if (sixteenth === 5) {
|
|
1026
|
+
if (isExactTie(a)) return { digits, exp };
|
|
1027
|
+
const r = significantDigits(a, 15);
|
|
1028
|
+
while (r.digits.length < 15) r.digits.push(0);
|
|
1029
|
+
return r;
|
|
1030
|
+
}
|
|
1031
|
+
let e = exp;
|
|
1032
|
+
let carry = true;
|
|
1033
|
+
for (let i = 14; i >= 0; i--) {
|
|
1034
|
+
if (digits[i] === 9) {
|
|
1035
|
+
digits[i] = 0;
|
|
1036
|
+
} else {
|
|
1037
|
+
digits[i] += 1;
|
|
1038
|
+
carry = false;
|
|
1039
|
+
break;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (carry) {
|
|
1043
|
+
digits.fill(0);
|
|
1044
|
+
digits[0] = 1;
|
|
1045
|
+
e += 1;
|
|
1046
|
+
}
|
|
1047
|
+
return { digits, exp: e };
|
|
1048
|
+
}
|
|
1049
|
+
function fixedFromDigits(digits, exp, decimals) {
|
|
1050
|
+
const decimalsCalc = Math.min(decimals, MAX_DECIMALS);
|
|
1051
|
+
const keep = exp + decimalsCalc + 1;
|
|
1052
|
+
let m;
|
|
1053
|
+
if (keep <= 0) {
|
|
1054
|
+
m = keep === 0 && (digits[0] ?? 0) >= 5 ? [1] : [];
|
|
1055
|
+
} else if (keep >= digits.length) {
|
|
1056
|
+
m = digits.slice();
|
|
1057
|
+
while (m.length < keep) m.push(0);
|
|
1058
|
+
} else {
|
|
1059
|
+
m = digits.slice(0, keep);
|
|
1060
|
+
if ((digits[keep] ?? 0) >= 5) {
|
|
1061
|
+
let carry = true;
|
|
1062
|
+
for (let i = m.length - 1; i >= 0; i--) {
|
|
1063
|
+
if (m[i] === 9) {
|
|
1064
|
+
m[i] = 0;
|
|
1065
|
+
} else {
|
|
1066
|
+
m[i] += 1;
|
|
1067
|
+
carry = false;
|
|
1068
|
+
break;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
if (carry) m.unshift(1);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
let lead = 0;
|
|
1075
|
+
while (lead < m.length && m[lead] === 0) lead++;
|
|
1076
|
+
m = m.slice(lead);
|
|
1077
|
+
const toS = (ds) => ds.join("");
|
|
1078
|
+
let int;
|
|
1079
|
+
let frac;
|
|
1080
|
+
if (m.length <= decimalsCalc) {
|
|
1081
|
+
int = "";
|
|
1082
|
+
frac = "0".repeat(decimalsCalc - m.length) + toS(m);
|
|
1083
|
+
} else {
|
|
1084
|
+
const split = m.length - decimalsCalc;
|
|
1085
|
+
int = toS(m.slice(0, split));
|
|
1086
|
+
frac = toS(m.slice(split));
|
|
1087
|
+
}
|
|
1088
|
+
if (decimals > decimalsCalc) frac += "0".repeat(decimals - decimalsCalc);
|
|
1089
|
+
return [int, frac];
|
|
1090
|
+
}
|
|
1091
|
+
function fixed(a, decimals) {
|
|
1092
|
+
if (!Number.isFinite(a) || a <= 0) return ["", "0".repeat(Math.min(decimals, MAX_DECIMALS * 4))];
|
|
1093
|
+
const { digits, exp } = decimalDigits(a);
|
|
1094
|
+
return fixedFromDigits(digits, exp, decimals);
|
|
1095
|
+
}
|
|
1096
|
+
var charCount = (s) => Array.from(s).length;
|
|
1097
|
+
function formatGeneralFit(n, maxChars) {
|
|
1098
|
+
if (!Number.isFinite(n)) return null;
|
|
1099
|
+
if (n === 0) return maxChars >= 1 ? "0" : null;
|
|
1100
|
+
const sign = n < 0 ? "-" : "";
|
|
1101
|
+
const { digits, exp: x } = decimalDigits(Math.abs(n));
|
|
1102
|
+
const intLen = sign.length + Math.max(x, 0) + 1;
|
|
1103
|
+
if (x >= -4 && intLen <= maxChars) {
|
|
1104
|
+
const avail = maxChars - intLen;
|
|
1105
|
+
let decimals = avail >= 2 ? avail - 1 : 0;
|
|
1106
|
+
for (; ; ) {
|
|
1107
|
+
const [i, f0] = fixedFromDigits(digits, x, decimals);
|
|
1108
|
+
const f = f0.replace(/0+$/, "");
|
|
1109
|
+
if (i === "" && f === "") break;
|
|
1110
|
+
let s = sign + (i === "" ? "0" : i);
|
|
1111
|
+
if (f !== "") s += `.${f}`;
|
|
1112
|
+
if (charCount(s) <= maxChars) return s;
|
|
1113
|
+
if (decimals === 0) break;
|
|
1114
|
+
decimals -= 1;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
for (let md = Math.min(maxChars, 14); md >= 0; md--) {
|
|
1118
|
+
let e = x;
|
|
1119
|
+
let [mi, mf] = fixedFromDigits(digits, 0, md);
|
|
1120
|
+
if (mi.length > 1) {
|
|
1121
|
+
e += 1;
|
|
1122
|
+
[mi, mf] = fixedFromDigits(digits, -1, md);
|
|
1123
|
+
}
|
|
1124
|
+
mf = mf.replace(/0+$/, "");
|
|
1125
|
+
let s = sign + (mi === "" ? "0" : mi);
|
|
1126
|
+
if (mf !== "") s += `.${mf}`;
|
|
1127
|
+
s += `E${e < 0 ? "-" : "+"}${String(Math.abs(e)).padStart(2, "0")}`;
|
|
1128
|
+
if (charCount(s) <= maxChars) return s;
|
|
1129
|
+
}
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// src/spreadsheet/numfmt/builtin.ts
|
|
1134
|
+
var BUILTINS = [
|
|
1135
|
+
[0, "General"],
|
|
1136
|
+
[1, "0"],
|
|
1137
|
+
[2, "0.00"],
|
|
1138
|
+
[3, "#,##0"],
|
|
1139
|
+
[4, "#,##0.00"],
|
|
1140
|
+
[5, '"$"#,##0_);\\("$"#,##0\\)'],
|
|
1141
|
+
[6, '"$"#,##0_);[Red]\\("$"#,##0\\)'],
|
|
1142
|
+
[7, '"$"#,##0.00_);\\("$"#,##0.00\\)'],
|
|
1143
|
+
[8, '"$"#,##0.00_);[Red]\\("$"#,##0.00\\)'],
|
|
1144
|
+
[9, "0%"],
|
|
1145
|
+
[10, "0.00%"],
|
|
1146
|
+
[11, "0.00E+00"],
|
|
1147
|
+
[12, "# ?/?"],
|
|
1148
|
+
[13, "# ??/??"],
|
|
1149
|
+
[14, "m/d/yyyy"],
|
|
1150
|
+
[15, "d-mmm-yy"],
|
|
1151
|
+
[16, "d-mmm"],
|
|
1152
|
+
[17, "mmm-yy"],
|
|
1153
|
+
[18, "h:mm AM/PM"],
|
|
1154
|
+
[19, "h:mm:ss AM/PM"],
|
|
1155
|
+
[20, "h:mm"],
|
|
1156
|
+
[21, "h:mm:ss"],
|
|
1157
|
+
[22, "m/d/yyyy h:mm"],
|
|
1158
|
+
[37, "#,##0 ;(#,##0)"],
|
|
1159
|
+
[38, "#,##0 ;[Red](#,##0)"],
|
|
1160
|
+
[39, "#,##0.00;(#,##0.00)"],
|
|
1161
|
+
[40, "#,##0.00;[Red](#,##0.00)"],
|
|
1162
|
+
[41, '_(* #,##0_);_(* \\(#,##0\\);_(* "-"_);_(@_)'],
|
|
1163
|
+
[42, '_("$"* #,##0_);_("$"* \\(#,##0\\);_("$"* "-"_);_(@_)'],
|
|
1164
|
+
[43, '_(* #,##0.00_);_(* \\(#,##0.00\\);_(* "-"??_);_(@_)'],
|
|
1165
|
+
[44, '_("$"* #,##0.00_);_("$"* \\(#,##0.00\\);_("$"* "-"??_);_(@_)'],
|
|
1166
|
+
[45, "mm:ss"],
|
|
1167
|
+
[46, "[h]:mm:ss"],
|
|
1168
|
+
[47, "mmss.0"],
|
|
1169
|
+
[48, "##0.0E+0"],
|
|
1170
|
+
[49, "@"]
|
|
1171
|
+
];
|
|
1172
|
+
function builtinNumberFormat(id) {
|
|
1173
|
+
return BUILTINS.find(([i]) => i === id)?.[1];
|
|
1174
|
+
}
|
|
1175
|
+
function builtinNumberFormatId(code) {
|
|
1176
|
+
const exact = BUILTINS.find(([, c]) => c === code);
|
|
1177
|
+
if (exact) return exact[0];
|
|
1178
|
+
const norm = (s) => s.replace(/\\/g, "").toLowerCase();
|
|
1179
|
+
const n = norm(code);
|
|
1180
|
+
return BUILTINS.find(([, c]) => norm(c) === n)?.[0];
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// src/spreadsheet/numfmt/parse.ts
|
|
1184
|
+
var MAX_SECTIONS = 4;
|
|
1185
|
+
var isCalendarPart = (d) => d.k === "year" || d.k === "month" || d.k === "day";
|
|
1186
|
+
function literalText(tok) {
|
|
1187
|
+
switch (tok.t) {
|
|
1188
|
+
case "lit":
|
|
1189
|
+
case "denom":
|
|
1190
|
+
return tok.s;
|
|
1191
|
+
case "digit":
|
|
1192
|
+
return tok.c;
|
|
1193
|
+
case "point":
|
|
1194
|
+
return ".";
|
|
1195
|
+
case "comma":
|
|
1196
|
+
return ",";
|
|
1197
|
+
case "percent":
|
|
1198
|
+
return "%";
|
|
1199
|
+
case "exp":
|
|
1200
|
+
return `${tok.upper ? "E" : "e"}${tok.plus ? "+" : "-"}`;
|
|
1201
|
+
case "slash":
|
|
1202
|
+
return "/";
|
|
1203
|
+
default:
|
|
1204
|
+
return "";
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function testCond(c, v) {
|
|
1208
|
+
switch (c.op) {
|
|
1209
|
+
case "=":
|
|
1210
|
+
return v === c.value;
|
|
1211
|
+
case "<>":
|
|
1212
|
+
return v !== c.value;
|
|
1213
|
+
case "<":
|
|
1214
|
+
return v < c.value;
|
|
1215
|
+
case "<=":
|
|
1216
|
+
return v <= c.value;
|
|
1217
|
+
case ">":
|
|
1218
|
+
return v > c.value;
|
|
1219
|
+
case ">=":
|
|
1220
|
+
return v >= c.value;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
var isDigitTok = (t, c) => t?.t === "digit" && (c === void 0 || t.c === c);
|
|
1224
|
+
function splitSections(code) {
|
|
1225
|
+
const cs = Array.from(code);
|
|
1226
|
+
const lastClose = cs.lastIndexOf("]");
|
|
1227
|
+
const out = [""];
|
|
1228
|
+
let quoted = false;
|
|
1229
|
+
let escape = false;
|
|
1230
|
+
let i = 0;
|
|
1231
|
+
while (i < cs.length) {
|
|
1232
|
+
const c = cs[i];
|
|
1233
|
+
i += 1;
|
|
1234
|
+
const cur = out.length - 1;
|
|
1235
|
+
if (escape) {
|
|
1236
|
+
out[cur] += c;
|
|
1237
|
+
escape = false;
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (c === '"') {
|
|
1241
|
+
quoted = !quoted;
|
|
1242
|
+
out[cur] += c;
|
|
1243
|
+
} else if ((c === "\\" || c === "_" || c === "*") && !quoted) {
|
|
1244
|
+
escape = true;
|
|
1245
|
+
out[cur] += c;
|
|
1246
|
+
} else if (c === "[" && !quoted && lastClose >= i) {
|
|
1247
|
+
let s = c;
|
|
1248
|
+
while (i < cs.length) {
|
|
1249
|
+
const d = cs[i];
|
|
1250
|
+
i += 1;
|
|
1251
|
+
s += d;
|
|
1252
|
+
if (d === "]") break;
|
|
1253
|
+
}
|
|
1254
|
+
out[cur] += s;
|
|
1255
|
+
} else if (c === ";" && !quoted) {
|
|
1256
|
+
if (out.length >= MAX_SECTIONS) break;
|
|
1257
|
+
out.push("");
|
|
663
1258
|
} else {
|
|
664
|
-
|
|
1259
|
+
out[cur] += c;
|
|
665
1260
|
}
|
|
666
1261
|
}
|
|
667
1262
|
return out;
|
|
668
1263
|
}
|
|
669
|
-
var
|
|
670
|
-
function
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
1264
|
+
var lowerAscii = (c) => c >= "A" && c <= "Z" ? c.toLowerCase() : c;
|
|
1265
|
+
function startsWithCi(cs, i, pat) {
|
|
1266
|
+
for (let k = 0; k < pat.length; k++) {
|
|
1267
|
+
const c = cs[i + k];
|
|
1268
|
+
if (c === void 0 || lowerAscii(c) !== pat[k]) return false;
|
|
1269
|
+
}
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
function pushLit(toks, s) {
|
|
1273
|
+
if (s === "") return;
|
|
1274
|
+
const last = toks[toks.length - 1];
|
|
1275
|
+
if (last?.t === "lit") last.s += s;
|
|
1276
|
+
else toks.push({ t: "lit", s });
|
|
1277
|
+
}
|
|
1278
|
+
var CURRENCY_CHARS = /* @__PURE__ */ new Set(["$", "\u20AC", "\xA3", "\xA5", "\u20B9", "\u20A9", "\u20BD", "\xA2", "\u20AA", "\u20AB", "\u0E3F"]);
|
|
1279
|
+
var isCurrencyChar = (c) => CURRENCY_CHARS.has(c);
|
|
1280
|
+
var NAMED_COLORS = /* @__PURE__ */ new Set(["black", "blue", "cyan", "green", "magenta", "red", "white", "yellow"]);
|
|
1281
|
+
function parseColor(lc) {
|
|
1282
|
+
if (NAMED_COLORS.has(lc)) return lc;
|
|
1283
|
+
if (!lc.startsWith("color")) return void 0;
|
|
1284
|
+
const rest = lc.slice(5).trim();
|
|
1285
|
+
if (!/^\+?\d{1,3}$/.test(rest)) return void 0;
|
|
1286
|
+
const n = Number(rest);
|
|
1287
|
+
return n >= 1 && n <= 56 ? `color${n}` : void 0;
|
|
1288
|
+
}
|
|
1289
|
+
function parseCond(s) {
|
|
1290
|
+
const t = s.trim();
|
|
1291
|
+
let op;
|
|
1292
|
+
let rest;
|
|
1293
|
+
if (t.startsWith("<=")) [op, rest] = ["<=", t.slice(2)];
|
|
1294
|
+
else if (t.startsWith(">=")) [op, rest] = [">=", t.slice(2)];
|
|
1295
|
+
else if (t.startsWith("<>")) [op, rest] = ["<>", t.slice(2)];
|
|
1296
|
+
else if (t.startsWith("<")) [op, rest] = ["<", t.slice(1)];
|
|
1297
|
+
else if (t.startsWith(">")) [op, rest] = [">", t.slice(1)];
|
|
1298
|
+
else if (t.startsWith("=")) [op, rest] = ["=", t.slice(1)];
|
|
1299
|
+
else return void 0;
|
|
1300
|
+
const r = rest.trim();
|
|
1301
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i.test(r)) return void 0;
|
|
1302
|
+
const value = Number(r);
|
|
1303
|
+
return Number.isFinite(value) ? { op, value } : void 0;
|
|
1304
|
+
}
|
|
1305
|
+
function bracket(raw, content) {
|
|
1306
|
+
const lc = content.trim().toLowerCase();
|
|
1307
|
+
const color = parseColor(lc);
|
|
1308
|
+
if (color) {
|
|
1309
|
+
raw.color = color;
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
if (lc.startsWith("<") || lc.startsWith(">") || lc.startsWith("=")) {
|
|
1313
|
+
const c = parseCond(lc);
|
|
1314
|
+
if (c) raw.cond = c;
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
if (content.startsWith("$")) {
|
|
1318
|
+
const sym = content.slice(1).split("-")[0];
|
|
1319
|
+
if (sym !== "") {
|
|
1320
|
+
raw.currency = true;
|
|
1321
|
+
pushLit(raw.toks, sym);
|
|
1322
|
+
}
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
const first = lc[0];
|
|
1326
|
+
if ((first === "h" || first === "m" || first === "s") && Array.from(lc).every((c) => c === first)) {
|
|
1327
|
+
const n = Math.min(Array.from(lc).length, 255);
|
|
1328
|
+
raw.toks.push({ t: "date", d: { k: first === "h" ? "eh" : first === "m" ? "em" : "es", n } });
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function tokenize(src) {
|
|
1332
|
+
const cs = Array.from(src);
|
|
1333
|
+
const raw = { toks: [], currency: false };
|
|
1334
|
+
const toks = raw.toks;
|
|
1335
|
+
const lastClose = cs.lastIndexOf("]");
|
|
1336
|
+
let i = 0;
|
|
1337
|
+
while (i < cs.length) {
|
|
1338
|
+
const c = cs[i];
|
|
1339
|
+
if (c === '"') {
|
|
1340
|
+
let s = "";
|
|
1341
|
+
i += 1;
|
|
1342
|
+
while (i < cs.length) {
|
|
1343
|
+
const d = cs[i];
|
|
1344
|
+
i += 1;
|
|
1345
|
+
if (d === '"') break;
|
|
1346
|
+
s += d;
|
|
1347
|
+
}
|
|
1348
|
+
if (Array.from(s).some(isCurrencyChar)) raw.currency = true;
|
|
1349
|
+
pushLit(toks, s);
|
|
694
1350
|
continue;
|
|
695
1351
|
}
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
1352
|
+
if (c === "\\") {
|
|
1353
|
+
const d = cs[i + 1];
|
|
1354
|
+
if (d !== void 0) {
|
|
1355
|
+
if (isCurrencyChar(d)) raw.currency = true;
|
|
1356
|
+
pushLit(toks, d);
|
|
1357
|
+
}
|
|
1358
|
+
i += 2;
|
|
1359
|
+
continue;
|
|
1360
|
+
}
|
|
1361
|
+
if (c === "_") {
|
|
1362
|
+
if (cs[i + 1] !== void 0) pushLit(toks, " ");
|
|
1363
|
+
i += 2;
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
if (c === "*") {
|
|
1367
|
+
const d = cs[i + 1];
|
|
1368
|
+
if (d !== void 0) toks.push({ t: "fill", c: d });
|
|
1369
|
+
i += 2;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (c === "[") {
|
|
1373
|
+
const close = lastClose > i ? cs.indexOf("]", i + 1) : -1;
|
|
1374
|
+
if (close !== -1) {
|
|
1375
|
+
bracket(raw, cs.slice(i + 1, close).join(""));
|
|
1376
|
+
i = close + 1;
|
|
704
1377
|
} else {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const grouping = intPattern.includes(",");
|
|
727
|
-
const rounded = roundHalfAwayFromZero(value, maxDecimals);
|
|
728
|
-
let [intPart, decPart = ""] = rounded.toFixed(maxDecimals).split(".");
|
|
729
|
-
while (decPart.length > minDecimals && decPart.endsWith("0")) decPart = decPart.slice(0, -1);
|
|
730
|
-
if (intPart === "0" && minInt === 0) intPart = "";
|
|
731
|
-
intPart = intPart.padStart(minInt, "0");
|
|
732
|
-
if (grouping) intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
733
|
-
const showSign = sign && /[1-9]/.test(intPart + decPart) ? sign : "";
|
|
734
|
-
return `${showSign}${prefix}${intPart}${decPart ? `.${decPart}` : ""}${suffix}`;
|
|
735
|
-
}
|
|
736
|
-
function splitSections(format) {
|
|
737
|
-
const sections = [""];
|
|
738
|
-
let quoted = false;
|
|
739
|
-
for (let i = 0; i < format.length; i++) {
|
|
740
|
-
const char = format[i];
|
|
741
|
-
if (char === '"') quoted = !quoted;
|
|
742
|
-
if (char === "\\" && i + 1 < format.length) {
|
|
743
|
-
sections[sections.length - 1] += char + format[++i];
|
|
1378
|
+
pushLit(toks, "[");
|
|
1379
|
+
i += 1;
|
|
1380
|
+
}
|
|
1381
|
+
continue;
|
|
1382
|
+
}
|
|
1383
|
+
if (c === "0" || c === "#" || c === "?") toks.push({ t: "digit", c });
|
|
1384
|
+
else if (c === ".") toks.push({ t: "point" });
|
|
1385
|
+
else if (c === ",") toks.push({ t: "comma" });
|
|
1386
|
+
else if (c === "%") toks.push({ t: "percent" });
|
|
1387
|
+
else if (c === "@") toks.push({ t: "at" });
|
|
1388
|
+
else if (c === "/") {
|
|
1389
|
+
toks.push({ t: "slash" });
|
|
1390
|
+
i += 1;
|
|
1391
|
+
if (cs[i] !== void 0 && cs[i] >= "1" && cs[i] <= "9") {
|
|
1392
|
+
let s = "";
|
|
1393
|
+
while (i < cs.length && cs[i] >= "0" && cs[i] <= "9") {
|
|
1394
|
+
s += cs[i];
|
|
1395
|
+
i += 1;
|
|
1396
|
+
}
|
|
1397
|
+
toks.push({ t: "denom", s });
|
|
1398
|
+
}
|
|
744
1399
|
continue;
|
|
1400
|
+
} else if ((c === "e" || c === "E") && (cs[i + 1] === "+" || cs[i + 1] === "-")) {
|
|
1401
|
+
toks.push({ t: "exp", upper: c === "E", plus: cs[i + 1] === "+" });
|
|
1402
|
+
i += 2;
|
|
1403
|
+
continue;
|
|
1404
|
+
} else if ((c === "G" || c === "g") && startsWithCi(cs, i, "general")) {
|
|
1405
|
+
toks.push({ t: "general" });
|
|
1406
|
+
i += 7;
|
|
1407
|
+
continue;
|
|
1408
|
+
} else if ((c === "A" || c === "a") && startsWithCi(cs, i, "am/pm")) {
|
|
1409
|
+
toks.push({ t: "date", d: { k: "ampm" } });
|
|
1410
|
+
i += 5;
|
|
1411
|
+
continue;
|
|
1412
|
+
} else if ((c === "A" || c === "a") && startsWithCi(cs, i, "a/p")) {
|
|
1413
|
+
toks.push({ t: "date", d: { k: "ap", a: c, p: cs[i + 2] ?? "P" } });
|
|
1414
|
+
i += 3;
|
|
1415
|
+
continue;
|
|
1416
|
+
} else if ("yYmMdDhHsSeE".includes(c)) {
|
|
1417
|
+
const lc = lowerAscii(c);
|
|
1418
|
+
let n = 0;
|
|
1419
|
+
while (cs[i + n] !== void 0 && lowerAscii(cs[i + n]) === lc) n += 1;
|
|
1420
|
+
const n8 = Math.min(n, 255);
|
|
1421
|
+
const d = lc === "y" ? { k: "year", n: n8 } : lc === "e" ? { k: "year", n: 4 } : lc === "m" ? { k: "month", n: n8 } : lc === "d" ? { k: "day", n: n8 } : lc === "h" ? { k: "hour", n: n8 } : { k: "second", n: n8 };
|
|
1422
|
+
toks.push({ t: "date", d });
|
|
1423
|
+
i += Math.max(n, 1);
|
|
1424
|
+
continue;
|
|
1425
|
+
} else {
|
|
1426
|
+
if (isCurrencyChar(c)) raw.currency = true;
|
|
1427
|
+
pushLit(toks, c);
|
|
745
1428
|
}
|
|
746
|
-
|
|
747
|
-
else sections[sections.length - 1] += char;
|
|
1429
|
+
i += 1;
|
|
748
1430
|
}
|
|
749
|
-
return
|
|
1431
|
+
return raw;
|
|
750
1432
|
}
|
|
1433
|
+
var datePartOf = (t) => t?.t === "date" ? t.d : void 0;
|
|
1434
|
+
function analyze(raw) {
|
|
1435
|
+
const toks = raw.toks;
|
|
1436
|
+
const isDate = toks.some((t) => t.t === "date");
|
|
1437
|
+
const hasGeneral = toks.some((t) => t.t === "general");
|
|
1438
|
+
const sec = {
|
|
1439
|
+
toks: [],
|
|
1440
|
+
color: raw.color,
|
|
1441
|
+
cond: raw.cond,
|
|
1442
|
+
kind: "number",
|
|
1443
|
+
thousands: false,
|
|
1444
|
+
scale: 0,
|
|
1445
|
+
percent: 0,
|
|
1446
|
+
hasAt: toks.some((t) => t.t === "at"),
|
|
1447
|
+
hasDigits: false,
|
|
1448
|
+
exp: false,
|
|
1449
|
+
fraction: false,
|
|
1450
|
+
currency: raw.currency,
|
|
1451
|
+
fill: toks.some((t) => t.t === "fill")
|
|
1452
|
+
};
|
|
1453
|
+
if (isDate) {
|
|
1454
|
+
sec.kind = "date";
|
|
1455
|
+
const prevPart = [];
|
|
1456
|
+
let last;
|
|
1457
|
+
for (let i2 = 0; i2 < toks.length; i2++) {
|
|
1458
|
+
prevPart.push(last);
|
|
1459
|
+
const d = datePartOf(toks[i2]);
|
|
1460
|
+
if (d) last = d;
|
|
1461
|
+
}
|
|
1462
|
+
const nextPart = new Array(toks.length);
|
|
1463
|
+
let next;
|
|
1464
|
+
for (let i2 = toks.length - 1; i2 >= 0; i2--) {
|
|
1465
|
+
nextPart[i2] = next;
|
|
1466
|
+
const d = datePartOf(toks[i2]);
|
|
1467
|
+
if (d) next = d;
|
|
1468
|
+
}
|
|
1469
|
+
for (let i2 = 0; i2 < toks.length; i2++) {
|
|
1470
|
+
const t = toks[i2];
|
|
1471
|
+
if (t.t !== "date" || t.d.k !== "month" || t.d.n > 2) continue;
|
|
1472
|
+
const prev = prevPart[i2];
|
|
1473
|
+
const nxt = nextPart[i2];
|
|
1474
|
+
const afterH = prev?.k === "hour" || prev?.k === "eh";
|
|
1475
|
+
const beforeS = nxt?.k === "second" || nxt?.k === "es";
|
|
1476
|
+
if (afterH || beforeS) toks[i2] = { t: "date", d: { k: "minute", n: t.d.n } };
|
|
1477
|
+
}
|
|
1478
|
+
const out2 = [];
|
|
1479
|
+
let i = 0;
|
|
1480
|
+
while (i < toks.length) {
|
|
1481
|
+
const t = toks[i];
|
|
1482
|
+
if (t.t === "point" && isDigitTok(toks[i + 1], "0")) {
|
|
1483
|
+
let n = 0;
|
|
1484
|
+
while (isDigitTok(toks[i + 1 + n], "0")) n += 1;
|
|
1485
|
+
out2.push({ t: "date", d: { k: "subsec", n: Math.min(n, 255) } });
|
|
1486
|
+
i += 1 + n;
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (t.t === "date" || t.t === "fill") out2.push(t);
|
|
1490
|
+
else pushLit(out2, literalText(t));
|
|
1491
|
+
i += 1;
|
|
1492
|
+
}
|
|
1493
|
+
sec.toks = out2;
|
|
1494
|
+
return sec;
|
|
1495
|
+
}
|
|
1496
|
+
if (hasGeneral) {
|
|
1497
|
+
sec.kind = "general";
|
|
1498
|
+
const out2 = [];
|
|
1499
|
+
for (const t of toks) {
|
|
1500
|
+
if (t.t === "general" || t.t === "fill") out2.push(t);
|
|
1501
|
+
else pushLit(out2, literalText(t));
|
|
1502
|
+
}
|
|
1503
|
+
sec.toks = out2;
|
|
1504
|
+
return sec;
|
|
1505
|
+
}
|
|
1506
|
+
sec.hasDigits = toks.some((t) => t.t === "digit");
|
|
1507
|
+
if (sec.hasAt && !sec.hasDigits) {
|
|
1508
|
+
sec.kind = "text";
|
|
1509
|
+
const out2 = [];
|
|
1510
|
+
for (const t of toks) {
|
|
1511
|
+
if (t.t === "at" || t.t === "fill") out2.push(t);
|
|
1512
|
+
else pushLit(out2, literalText(t));
|
|
1513
|
+
}
|
|
1514
|
+
sec.toks = out2;
|
|
1515
|
+
return sec;
|
|
1516
|
+
}
|
|
1517
|
+
let seenPoint = false;
|
|
1518
|
+
let seenExp = false;
|
|
1519
|
+
let seenDigit = false;
|
|
1520
|
+
let fractionSlash = false;
|
|
1521
|
+
const digitAfterCommas = new Array(toks.length).fill(false);
|
|
1522
|
+
const firstNonLitAfter = new Array(toks.length);
|
|
1523
|
+
let after = false;
|
|
1524
|
+
let nonLit;
|
|
1525
|
+
for (let j = toks.length - 1; j >= 0; j--) {
|
|
1526
|
+
digitAfterCommas[j] = after;
|
|
1527
|
+
firstNonLitAfter[j] = nonLit;
|
|
1528
|
+
const t = toks[j];
|
|
1529
|
+
if (t.t !== "comma") after = t.t === "digit";
|
|
1530
|
+
if (t.t !== "lit") nonLit = t;
|
|
1531
|
+
}
|
|
1532
|
+
let lastExp = -1;
|
|
1533
|
+
for (let j = toks.length - 1; j >= 0; j--) {
|
|
1534
|
+
if (toks[j].t === "exp") {
|
|
1535
|
+
lastExp = j;
|
|
1536
|
+
break;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
const firstDigit = toks.findIndex((t) => t.t === "digit");
|
|
1540
|
+
const scientific = firstDigit !== -1 && toks.slice(firstDigit).some((t) => t.t === "exp");
|
|
1541
|
+
for (let i = 0; i < toks.length; i++) {
|
|
1542
|
+
const t = toks[i];
|
|
1543
|
+
let replace;
|
|
1544
|
+
switch (t.t) {
|
|
1545
|
+
case "digit":
|
|
1546
|
+
seenDigit = true;
|
|
1547
|
+
break;
|
|
1548
|
+
case "point":
|
|
1549
|
+
if (seenPoint || seenExp) replace = { t: "lit", s: "." };
|
|
1550
|
+
else seenPoint = true;
|
|
1551
|
+
break;
|
|
1552
|
+
case "percent":
|
|
1553
|
+
sec.percent += 1;
|
|
1554
|
+
break;
|
|
1555
|
+
case "exp":
|
|
1556
|
+
if (seenExp || !seenDigit) replace = { t: "lit", s: literalText(t) };
|
|
1557
|
+
else {
|
|
1558
|
+
seenExp = true;
|
|
1559
|
+
sec.exp = true;
|
|
1560
|
+
}
|
|
1561
|
+
break;
|
|
1562
|
+
case "comma": {
|
|
1563
|
+
const nextDigit = digitAfterCommas[i];
|
|
1564
|
+
if (seenDigit && nextDigit && !seenPoint && !seenExp) {
|
|
1565
|
+
sec.thousands = true;
|
|
1566
|
+
replace = { t: "lit", s: "" };
|
|
1567
|
+
} else if (seenDigit && scientific) {
|
|
1568
|
+
replace = { t: "lit", s: "" };
|
|
1569
|
+
} else if (seenDigit && !nextDigit) {
|
|
1570
|
+
sec.scale += 1;
|
|
1571
|
+
replace = { t: "lit", s: "" };
|
|
1572
|
+
} else {
|
|
1573
|
+
replace = { t: "lit", s: "," };
|
|
1574
|
+
}
|
|
1575
|
+
break;
|
|
1576
|
+
}
|
|
1577
|
+
case "slash": {
|
|
1578
|
+
const nextOk = toks[i + 1]?.t === "denom" || firstNonLitAfter[i]?.t === "digit";
|
|
1579
|
+
const hasExp = seenExp || lastExp > i;
|
|
1580
|
+
if (!fractionSlash && seenDigit && nextOk && !seenPoint && !hasExp) {
|
|
1581
|
+
fractionSlash = true;
|
|
1582
|
+
sec.fraction = true;
|
|
1583
|
+
} else {
|
|
1584
|
+
replace = { t: "lit", s: "/" };
|
|
1585
|
+
}
|
|
1586
|
+
break;
|
|
1587
|
+
}
|
|
1588
|
+
case "denom":
|
|
1589
|
+
if (!(toks[i - 1]?.t === "slash" && sec.fraction)) replace = { t: "lit", s: t.s };
|
|
1590
|
+
break;
|
|
1591
|
+
default:
|
|
1592
|
+
break;
|
|
1593
|
+
}
|
|
1594
|
+
if (replace) toks[i] = replace;
|
|
1595
|
+
}
|
|
1596
|
+
if (sec.fraction) {
|
|
1597
|
+
for (let i = 0; i < toks.length; i++) if (toks[i].t === "point") toks[i] = { t: "lit", s: "." };
|
|
1598
|
+
}
|
|
1599
|
+
const out = [];
|
|
1600
|
+
for (const t of toks) {
|
|
1601
|
+
if (t.t === "lit") pushLit(out, t.s);
|
|
1602
|
+
else out.push(t);
|
|
1603
|
+
}
|
|
1604
|
+
sec.toks = out;
|
|
1605
|
+
return sec;
|
|
1606
|
+
}
|
|
1607
|
+
function parseSections(code) {
|
|
1608
|
+
return splitSections(code).map((s) => analyze(tokenize(s)));
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// src/spreadsheet/numfmt/render.ts
|
|
1612
|
+
var MAX_DATE_SERIAL = 2958465;
|
|
751
1613
|
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
752
|
-
var
|
|
753
|
-
var
|
|
754
|
-
function
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1614
|
+
var WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1615
|
+
var UNIX_1899_12_302 = -25569;
|
|
1616
|
+
function civilFromDays(z0) {
|
|
1617
|
+
const z = z0 + 719468;
|
|
1618
|
+
const era = Math.floor(z / 146097);
|
|
1619
|
+
const doe = z - era * 146097;
|
|
1620
|
+
const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365);
|
|
1621
|
+
const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100));
|
|
1622
|
+
const mp = Math.floor((5 * doy + 2) / 153);
|
|
1623
|
+
const d = doy - Math.floor((153 * mp + 2) / 5) + 1;
|
|
1624
|
+
const m = mp < 10 ? mp + 3 : mp - 9;
|
|
1625
|
+
return { y: yoe + era * 400 + (m <= 2 ? 1 : 0), m, d };
|
|
1626
|
+
}
|
|
1627
|
+
function dateFromSerial(day) {
|
|
1628
|
+
const weekday = ((day + 6) % 7 + 7) % 7;
|
|
1629
|
+
if (day === 60) return { year: 1900, month: 2, day: 29, weekday };
|
|
1630
|
+
if (day === 0) return { year: 1900, month: 1, day: 0, weekday };
|
|
1631
|
+
const real = day < 60 ? day + 1 : day;
|
|
1632
|
+
const { y, m, d } = civilFromDays(real + UNIX_1899_12_302);
|
|
1633
|
+
return { year: y, month: m, day: d, weekday };
|
|
1634
|
+
}
|
|
1635
|
+
var Out = class {
|
|
1636
|
+
text = "";
|
|
1637
|
+
fill;
|
|
1638
|
+
/** True when every digit shown is zero (a number section that rounded to zero). */
|
|
1639
|
+
zero = false;
|
|
1640
|
+
push(s) {
|
|
1641
|
+
this.text += s;
|
|
1642
|
+
}
|
|
1643
|
+
setFill(c) {
|
|
1644
|
+
if (!this.fill) this.fill = { char: c, index: this.text.length };
|
|
1645
|
+
}
|
|
1646
|
+
prepend(s) {
|
|
1647
|
+
this.text = s + this.text;
|
|
1648
|
+
if (this.fill) this.fill.index += s.length;
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
function renderText(sec, text) {
|
|
1652
|
+
const out = new Out();
|
|
1653
|
+
for (const t of sec.toks) {
|
|
1654
|
+
if (t.t === "at") out.push(text);
|
|
1655
|
+
else if (t.t === "fill") out.setFill(t.c);
|
|
1656
|
+
else out.push(literalText(t));
|
|
1657
|
+
}
|
|
1658
|
+
return out;
|
|
1659
|
+
}
|
|
1660
|
+
function renderGeneral(sec, v, width) {
|
|
1661
|
+
const out = new Out();
|
|
1662
|
+
for (const t of sec.toks) {
|
|
1663
|
+
if (t.t === "general") out.push(formatGeneralFit(v, width) ?? "#".repeat(Math.min(Math.max(width, 1), 11)));
|
|
1664
|
+
else if (t.t === "fill") out.setFill(t.c);
|
|
1665
|
+
else out.push(literalText(t));
|
|
1666
|
+
}
|
|
1667
|
+
return out;
|
|
1668
|
+
}
|
|
1669
|
+
var padNum = (n, w) => w >= 2 ? String(n).padStart(Math.min(w, 20), "0") : String(n);
|
|
1670
|
+
function renderDate(sec, v) {
|
|
1671
|
+
if (!Number.isFinite(v) || v < 0 || v >= MAX_DATE_SERIAL + 1) return null;
|
|
1672
|
+
let subDigits = 0;
|
|
1673
|
+
for (const t of sec.toks) if (t.t === "date" && t.d.k === "subsec") subDigits = Math.max(subDigits, t.d.n);
|
|
1674
|
+
subDigits = Math.min(subDigits, 3);
|
|
1675
|
+
const unit = 10 ** (3 - subDigits);
|
|
1676
|
+
let day = Math.floor(v);
|
|
1677
|
+
let ms = Math.round((v - Math.floor(v)) * 864e5 / unit) * unit;
|
|
1678
|
+
if (ms >= 864e5) {
|
|
1679
|
+
ms -= 864e5;
|
|
1680
|
+
day += 1;
|
|
1681
|
+
}
|
|
1682
|
+
const totalMs = day * 864e5 + ms;
|
|
1683
|
+
const needsDate = sec.toks.some((t) => t.t === "date" && isCalendarPart(t.d));
|
|
1684
|
+
if (needsDate && day > MAX_DATE_SERIAL) return null;
|
|
1685
|
+
const dt = needsDate ? dateFromSerial(day) : dateFromSerial(0);
|
|
1686
|
+
const ampm = sec.toks.some((t) => t.t === "date" && (t.d.k === "ampm" || t.d.k === "ap"));
|
|
1687
|
+
const hour = Math.floor(ms / 36e5);
|
|
1688
|
+
const minute = Math.floor(ms / 6e4) % 60;
|
|
1689
|
+
const second = Math.floor(ms / 1e3) % 60;
|
|
1690
|
+
const milli = ms % 1e3;
|
|
1691
|
+
const monthName = MONTHS[dt.month - 1] ?? "";
|
|
1692
|
+
const dayName = WEEKDAYS[dt.weekday] ?? "";
|
|
1693
|
+
const out = new Out();
|
|
1694
|
+
for (const t of sec.toks) {
|
|
1695
|
+
if (t.t === "fill") {
|
|
1696
|
+
out.setFill(t.c);
|
|
1697
|
+
continue;
|
|
1698
|
+
}
|
|
1699
|
+
if (t.t !== "date") {
|
|
1700
|
+
out.push(literalText(t));
|
|
759
1701
|
continue;
|
|
760
1702
|
}
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
}
|
|
1703
|
+
const d = t.d;
|
|
1704
|
+
let s;
|
|
1705
|
+
switch (d.k) {
|
|
1706
|
+
case "year":
|
|
1707
|
+
s = d.n <= 2 ? String((dt.year % 100 + 100) % 100).padStart(2, "0") : String(dt.year).padStart(4, "0");
|
|
1708
|
+
break;
|
|
1709
|
+
case "month":
|
|
1710
|
+
s = d.n <= 2 ? padNum(dt.month, d.n) : d.n === 3 ? monthName.slice(0, 3) : d.n === 5 ? monthName.slice(0, 1) : monthName;
|
|
1711
|
+
break;
|
|
1712
|
+
case "day":
|
|
1713
|
+
s = d.n <= 2 ? padNum(dt.day, d.n) : d.n === 3 ? dayName.slice(0, 3) : dayName;
|
|
1714
|
+
break;
|
|
1715
|
+
case "hour":
|
|
1716
|
+
s = padNum(ampm ? hour % 12 || 12 : hour, d.n);
|
|
1717
|
+
break;
|
|
1718
|
+
case "minute":
|
|
1719
|
+
s = padNum(minute, d.n);
|
|
1720
|
+
break;
|
|
1721
|
+
case "second":
|
|
1722
|
+
s = padNum(second, d.n);
|
|
1723
|
+
break;
|
|
1724
|
+
case "subsec": {
|
|
1725
|
+
s = `.${String(milli).padStart(3, "0")}`.slice(0, 1 + Math.min(d.n, 3));
|
|
1726
|
+
if (d.n > 3) s += "0".repeat(d.n - 3);
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1729
|
+
case "eh":
|
|
1730
|
+
s = padNum(Math.floor(totalMs / 36e5), d.n);
|
|
1731
|
+
break;
|
|
1732
|
+
case "em":
|
|
1733
|
+
s = padNum(Math.floor(totalMs / 6e4), d.n);
|
|
1734
|
+
break;
|
|
1735
|
+
case "es":
|
|
1736
|
+
s = padNum(Math.floor(totalMs / 1e3), d.n);
|
|
1737
|
+
break;
|
|
1738
|
+
case "ampm":
|
|
1739
|
+
s = hour >= 12 ? "PM" : "AM";
|
|
1740
|
+
break;
|
|
1741
|
+
case "ap":
|
|
1742
|
+
s = hour >= 12 ? d.p : d.a;
|
|
1743
|
+
break;
|
|
1744
|
+
}
|
|
1745
|
+
out.push(s);
|
|
1746
|
+
}
|
|
1747
|
+
return out;
|
|
1748
|
+
}
|
|
1749
|
+
var digitChar = (toks, i) => {
|
|
1750
|
+
const t = toks[i];
|
|
1751
|
+
return t?.t === "digit" ? t.c : "0";
|
|
1752
|
+
};
|
|
1753
|
+
function fillInt(toks, idxs, digits, thousands, forceZero, pieces) {
|
|
1754
|
+
let rem = digits.length;
|
|
1755
|
+
let pos = 0;
|
|
1756
|
+
const count = idxs.length;
|
|
1757
|
+
for (let k = 0; k < count; k++) {
|
|
1758
|
+
const ti = idxs[count - 1 - k];
|
|
1759
|
+
const leftmost = k + 1 === count;
|
|
1760
|
+
const ph = digitChar(toks, ti);
|
|
1761
|
+
const rev = [];
|
|
1762
|
+
const pushDigit = (c) => {
|
|
1763
|
+
if (thousands && pos > 0 && pos % 3 === 0) rev.push(",");
|
|
1764
|
+
rev.push(c);
|
|
1765
|
+
pos += 1;
|
|
1766
|
+
};
|
|
1767
|
+
const take = leftmost ? rem : Math.min(rem, 1);
|
|
1768
|
+
if (take === 0) {
|
|
1769
|
+
if (ph === "0") pushDigit("0");
|
|
1770
|
+
else if (forceZero && k === 0) pushDigit("0");
|
|
1771
|
+
else if (ph === "?") {
|
|
1772
|
+
if (thousands && pos > 0 && pos % 3 === 0) rev.push(" ");
|
|
1773
|
+
rev.push(" ");
|
|
1774
|
+
pos += 1;
|
|
1775
|
+
}
|
|
1776
|
+
} else {
|
|
1777
|
+
for (let n = 0; n < take; n++) {
|
|
1778
|
+
rem -= 1;
|
|
1779
|
+
pushDigit(digits[rem] ?? "0");
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
pieces[ti] = rev.reverse().join("");
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function withCommas(digits, thousands) {
|
|
1786
|
+
if (!thousands) return digits;
|
|
1787
|
+
const n = digits.length;
|
|
1788
|
+
let s = "";
|
|
1789
|
+
for (let i = 0; i < n; i++) {
|
|
1790
|
+
if (i > 0 && (n - i) % 3 === 0) s += ",";
|
|
1791
|
+
s += digits[i];
|
|
1792
|
+
}
|
|
1793
|
+
return s;
|
|
1794
|
+
}
|
|
1795
|
+
function fillFrac(toks, idxs, frac, pieces) {
|
|
1796
|
+
let lastSig = -1;
|
|
1797
|
+
for (let i = frac.length - 1; i >= 0; i--) {
|
|
1798
|
+
if (frac[i] !== "0") {
|
|
1799
|
+
lastSig = i;
|
|
1800
|
+
break;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
idxs.forEach((ti, i) => {
|
|
1804
|
+
const ph = digitChar(toks, ti);
|
|
1805
|
+
pieces[ti] = i <= lastSig ? frac[i] ?? "0" : ph === "0" ? "0" : ph === "?" ? " " : "";
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
function digitIndices(toks, start, end) {
|
|
1809
|
+
const out = [];
|
|
1810
|
+
for (let i = start; i < end; i++) if (toks[i]?.t === "digit") out.push(i);
|
|
1811
|
+
return out;
|
|
1812
|
+
}
|
|
1813
|
+
function renderNumber(sec, v) {
|
|
1814
|
+
let x = Math.abs(v);
|
|
1815
|
+
if (sec.percent > 0 && !sec.exp) x *= 100 ** Math.min(sec.percent, 20);
|
|
1816
|
+
if (sec.scale > 0) x /= 1e3 ** Math.min(sec.scale, 110);
|
|
1817
|
+
if (!Number.isFinite(x)) x = Number.MAX_VALUE;
|
|
1818
|
+
const toks = sec.toks;
|
|
1819
|
+
const n = toks.length;
|
|
1820
|
+
const pieces = new Array(n).fill("");
|
|
1821
|
+
const before = new Array(n + 1).fill("");
|
|
1822
|
+
const lits = new Array(n);
|
|
1823
|
+
let zero = false;
|
|
1824
|
+
if (sec.fraction) {
|
|
1825
|
+
renderFraction(sec, x, pieces, lits);
|
|
1826
|
+
} else {
|
|
1827
|
+
const pointIdx = toks.findIndex((t) => t.t === "point");
|
|
1828
|
+
const expIdx = toks.findIndex((t) => t.t === "exp");
|
|
1829
|
+
const point = pointIdx === -1 ? void 0 : pointIdx;
|
|
1830
|
+
const exp = expIdx === -1 ? void 0 : expIdx;
|
|
1831
|
+
const intEnd = point ?? exp ?? n;
|
|
1832
|
+
const fracEnd = exp ?? n;
|
|
1833
|
+
const intIdx = digitIndices(toks, 0, intEnd);
|
|
1834
|
+
const fracIdx = point === void 0 ? [] : digitIndices(toks, point + 1, fracEnd);
|
|
1835
|
+
let intS;
|
|
1836
|
+
let fracS;
|
|
1837
|
+
if (exp !== void 0) {
|
|
1838
|
+
const expDigitIdx = digitIndices(toks, exp + 1, n);
|
|
1839
|
+
const step = Math.max(intIdx.length, 1);
|
|
1840
|
+
let e;
|
|
1841
|
+
if (x === 0) {
|
|
1842
|
+
e = 0;
|
|
1843
|
+
intS = "0".repeat(intIdx.length);
|
|
1844
|
+
fracS = "0".repeat(fracIdx.length);
|
|
1845
|
+
} else {
|
|
1846
|
+
const { digits, exp: xe } = decimalDigits(x);
|
|
1847
|
+
e = xe - (xe % step + step) % step;
|
|
1848
|
+
[intS, fracS] = fixedFromDigits(digits, xe - e, fracIdx.length);
|
|
1849
|
+
if (intS.length > step) {
|
|
1850
|
+
e += step;
|
|
1851
|
+
[intS, fracS] = fixedFromDigits(digits, xe - e, fracIdx.length);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
const et = toks[exp];
|
|
1855
|
+
const upper = et.t === "exp" ? et.upper : true;
|
|
1856
|
+
const plus = et.t === "exp" ? et.plus : true;
|
|
1857
|
+
if (intS === "" && /^0*$/.test(fracS)) e = 0;
|
|
1858
|
+
const head = upper ? "E" : "e";
|
|
1859
|
+
const sign = e < 0 ? "-" : plus ? "+" : "";
|
|
1860
|
+
const expDigits = String(Math.abs(e));
|
|
1861
|
+
if (expDigitIdx.length > 0) {
|
|
1862
|
+
pieces[exp] = head;
|
|
1863
|
+
before[expDigitIdx[0]] = sign;
|
|
1864
|
+
fillInt(toks, expDigitIdx, expDigits, false, false, pieces);
|
|
1865
|
+
} else {
|
|
1866
|
+
pieces[exp] = `${head}${sign}${expDigits}`;
|
|
1867
|
+
}
|
|
1868
|
+
} else {
|
|
1869
|
+
[intS, fracS] = fixed(x, fracIdx.length);
|
|
1870
|
+
}
|
|
1871
|
+
zero = /^0*$/.test(intS + fracS);
|
|
1872
|
+
fillInt(toks, intIdx, intS, sec.thousands, false, pieces);
|
|
1873
|
+
const anchor = point ?? exp;
|
|
1874
|
+
if (intIdx.length === 0 && intS !== "" && anchor !== void 0) before[anchor] = withCommas(intS, sec.thousands);
|
|
1875
|
+
fillFrac(toks, fracIdx, fracS, pieces);
|
|
1876
|
+
if (point !== void 0) pieces[point] = ".";
|
|
1877
|
+
}
|
|
1878
|
+
const out = new Out();
|
|
1879
|
+
for (let i = 0; i < n; i++) {
|
|
1880
|
+
out.push(before[i]);
|
|
1881
|
+
const t = toks[i];
|
|
1882
|
+
if (t.t === "lit") out.push(lits[i] ?? t.s);
|
|
1883
|
+
else if (t.t === "percent") out.push("%");
|
|
1884
|
+
else if (t.t === "fill") out.setFill(t.c);
|
|
1885
|
+
else if (t.t === "at" || t.t === "general" || t.t === "date" || t.t === "comma") continue;
|
|
1886
|
+
else out.push(pieces[i]);
|
|
1887
|
+
}
|
|
1888
|
+
out.zero = zero;
|
|
1889
|
+
return out;
|
|
1890
|
+
}
|
|
1891
|
+
function approximate(x, maxDen) {
|
|
1892
|
+
if (Number.isNaN(x) || x <= 0 || !Number.isFinite(x)) return [0, 1];
|
|
1893
|
+
if (x >= 1e15) return [Math.round(x), 1];
|
|
1894
|
+
let [p0, q0, p1, q1] = [0, 1, 1, 0];
|
|
1895
|
+
let r = x;
|
|
1896
|
+
for (let iter = 0; iter < 64; iter++) {
|
|
1897
|
+
const aF = Math.floor(r);
|
|
1898
|
+
if (aF > 1e15) break;
|
|
1899
|
+
const q2 = aF * q1 + q0;
|
|
1900
|
+
if (q2 > maxDen) break;
|
|
1901
|
+
const p2 = aF * p1 + p0;
|
|
1902
|
+
[p0, q0, p1, q1] = [p1, q1, p2, q2];
|
|
1903
|
+
const frac = r - aF;
|
|
1904
|
+
if (frac < 1e-12) break;
|
|
1905
|
+
r = 1 / frac;
|
|
1906
|
+
}
|
|
1907
|
+
if (q1 === 0) return [Math.round(x), 1];
|
|
1908
|
+
const k = Math.floor((maxDen - q0) / q1);
|
|
1909
|
+
const pb = p0 + k * p1;
|
|
1910
|
+
const qb = q0 + k * q1;
|
|
1911
|
+
const errA = Math.abs(p1 / q1 - x);
|
|
1912
|
+
const errB = qb > 0 ? Math.abs(pb / qb - x) : Infinity;
|
|
1913
|
+
return errB < errA ? [pb, qb] : [p1, q1];
|
|
1914
|
+
}
|
|
1915
|
+
function shownWidth(t) {
|
|
1916
|
+
if (t.t === "lit" || t.t === "denom") return Array.from(t.s).length;
|
|
1917
|
+
return t.t === "digit" || t.t === "slash" ? 1 : 0;
|
|
1918
|
+
}
|
|
1919
|
+
function blankRange(toks, start, end, spaces, pieces, lits) {
|
|
1920
|
+
for (let i = start; i < end && i < toks.length; i++) {
|
|
1921
|
+
const t = toks[i];
|
|
1922
|
+
const s = spaces ? " ".repeat(shownWidth(t)) : "";
|
|
1923
|
+
if (t.t === "lit") lits[i] = s;
|
|
1924
|
+
else if (t.t === "digit" || t.t === "slash" || t.t === "denom") pieces[i] = s;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
function renderFraction(sec, x, pieces, lits) {
|
|
1928
|
+
const toks = sec.toks;
|
|
1929
|
+
const isDigit = (i) => toks[i]?.t === "digit";
|
|
1930
|
+
const slash = toks.findIndex((t) => t.t === "slash");
|
|
1931
|
+
if (slash === -1) return;
|
|
1932
|
+
let numLast = -1;
|
|
1933
|
+
for (let i = slash - 1; i >= 0; i--) {
|
|
1934
|
+
if (isDigit(i)) {
|
|
1935
|
+
numLast = i;
|
|
1936
|
+
break;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
if (numLast === -1) return;
|
|
1940
|
+
let numStart = numLast;
|
|
1941
|
+
while (numStart > 0 && isDigit(numStart - 1)) numStart -= 1;
|
|
1942
|
+
const wholeIdx = digitIndices(toks, 0, numStart);
|
|
1943
|
+
const numIdx = digitIndices(toks, numStart, numLast + 1);
|
|
1944
|
+
const sepStart = wholeIdx.length > 0 ? wholeIdx[wholeIdx.length - 1] + 1 : numStart;
|
|
1945
|
+
let fixedDen;
|
|
1946
|
+
let denStart;
|
|
1947
|
+
const afterSlash = toks[slash + 1];
|
|
1948
|
+
if (afterSlash?.t === "denom") {
|
|
1949
|
+
const parsed = Number(afterSlash.s);
|
|
1950
|
+
fixedDen = Math.min(Math.max(Number.isFinite(parsed) ? parsed : 1e9, 1), 1e9);
|
|
1951
|
+
denStart = slash + 1;
|
|
1952
|
+
} else {
|
|
1953
|
+
denStart = slash + 1;
|
|
1954
|
+
while (toks[denStart]?.t === "lit") denStart += 1;
|
|
1955
|
+
}
|
|
1956
|
+
let denEnd = denStart;
|
|
1957
|
+
if (fixedDen !== void 0) denEnd += 1;
|
|
1958
|
+
else while (isDigit(denEnd)) denEnd += 1;
|
|
1959
|
+
const denIdx = digitIndices(toks, denStart, denEnd);
|
|
1960
|
+
const hasQ = (idx) => idx.some((i) => isDigitTok2(toks[i], "?"));
|
|
1961
|
+
const hasWhole = wholeIdx.length > 0;
|
|
1962
|
+
let whole = hasWhole ? Math.floor(x) : 0;
|
|
1963
|
+
const f = hasWhole ? x - Math.floor(x) : x;
|
|
1964
|
+
let num;
|
|
1965
|
+
let den;
|
|
1966
|
+
if (fixedDen !== void 0) {
|
|
1967
|
+
num = Math.round(f * fixedDen);
|
|
1968
|
+
den = fixedDen;
|
|
1969
|
+
} else {
|
|
1970
|
+
const digits = Math.min(Math.max(denIdx.length, 1), 7);
|
|
1971
|
+
[num, den] = approximate(f, 10 ** digits - 1);
|
|
1972
|
+
}
|
|
1973
|
+
if (hasWhole && num >= den && den > 0) {
|
|
1974
|
+
whole += Math.floor(num / den);
|
|
1975
|
+
num %= den;
|
|
1976
|
+
}
|
|
1977
|
+
const wholeS = whole >= 1 ? fixed(whole, 0)[0] : "";
|
|
1978
|
+
const numZeroPh = numIdx.some((i) => isDigitTok2(toks[i], "0"));
|
|
1979
|
+
if (hasWhole && num === 0 && !numZeroPh) {
|
|
1980
|
+
fillInt(toks, wholeIdx, wholeS, sec.thousands, true, pieces);
|
|
1981
|
+
const spaces = hasQ(wholeIdx) || hasQ(numIdx) || hasQ(denIdx);
|
|
1982
|
+
blankRange(toks, sepStart, denEnd, spaces, pieces, lits);
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
if (hasWhole) {
|
|
1986
|
+
const unitsHash = isDigitTok2(toks[wholeIdx[wholeIdx.length - 1]], "#");
|
|
1987
|
+
const force = wholeS === "" && num === 0 && !unitsHash;
|
|
1988
|
+
fillInt(toks, wholeIdx, wholeS, sec.thousands, force, pieces);
|
|
1989
|
+
const wholeShown = wholeIdx.some((i) => /[0-9]/.test(pieces[i] ?? ""));
|
|
1990
|
+
if (!wholeShown) {
|
|
1991
|
+
const spaces = hasQ(wholeIdx) || hasQ(numIdx);
|
|
1992
|
+
blankRange(toks, sepStart, numStart, spaces, pieces, lits);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
fillInt(toks, numIdx, fixed(num, 0)[0] || "0", false, true, pieces);
|
|
1996
|
+
pieces[slash] = "/";
|
|
1997
|
+
const denS = fixed(den, 0)[0] || "0";
|
|
1998
|
+
if (fixedDen !== void 0) {
|
|
1999
|
+
pieces[denStart] = denS;
|
|
2000
|
+
} else {
|
|
2001
|
+
const zeros = denIdx.filter((i) => isDigitTok2(toks[i], "0")).length;
|
|
2002
|
+
const padded = "0".repeat(Math.max(zeros - denS.length, 0)) + denS;
|
|
2003
|
+
const count = denIdx.length;
|
|
2004
|
+
denIdx.forEach((ti, k) => {
|
|
2005
|
+
let s = k + 1 === count ? padded.slice(k) : padded[k] ?? "";
|
|
2006
|
+
if (s === "") s = isDigitTok2(toks[ti], "?") ? " " : isDigitTok2(toks[ti], "0") ? "0" : "";
|
|
2007
|
+
pieces[ti] = s;
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
function isDigitTok2(t, c) {
|
|
2012
|
+
return t?.t === "digit" && t.c === c;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
// src/spreadsheet/numfmt/index.ts
|
|
2016
|
+
var GENERAL_WIDTH = 11;
|
|
2017
|
+
var OVERFLOW = "########";
|
|
2018
|
+
var MAX_NUMBER_FORMAT_CODE_CHARS = 255;
|
|
2019
|
+
function tooLong(code) {
|
|
2020
|
+
return code.length > MAX_NUMBER_FORMAT_CODE_CHARS && Array.from(code).length > MAX_NUMBER_FORMAT_CODE_CHARS;
|
|
2021
|
+
}
|
|
2022
|
+
var SPECIAL_CODES = ["00000", "00000-0000", "[<=9999999]###-####;(###) ###-####", "000-00-0000"];
|
|
2023
|
+
var NumberFormat = class _NumberFormat {
|
|
2024
|
+
code;
|
|
2025
|
+
general;
|
|
2026
|
+
sections;
|
|
2027
|
+
/** Sections that apply to numbers (a trailing text section is excluded). */
|
|
2028
|
+
numSections;
|
|
2029
|
+
/** Index of the section that applies to text, if any. */
|
|
2030
|
+
textSection;
|
|
2031
|
+
constructor(code, uncapped) {
|
|
2032
|
+
this.code = code;
|
|
2033
|
+
const trimmed = code.trim();
|
|
2034
|
+
if (trimmed === "" || trimmed.toLowerCase() === "general" || trimmed === "checkbox" || !uncapped && tooLong(code)) {
|
|
2035
|
+
this.general = true;
|
|
2036
|
+
this.sections = [];
|
|
2037
|
+
this.numSections = 0;
|
|
2038
|
+
this.textSection = void 0;
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
this.general = false;
|
|
2042
|
+
this.sections = parseSections(code);
|
|
2043
|
+
const len = this.sections.length;
|
|
2044
|
+
const lastIsText = this.sections[len - 1]?.kind === "text";
|
|
2045
|
+
this.textSection = len >= 4 ? 3 : lastIsText ? len - 1 : void 0;
|
|
2046
|
+
this.numSections = len >= 4 ? 3 : lastIsText ? len - 1 : len;
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Parses a format code. Never throws: unknown or malformed parts degrade to
|
|
2050
|
+
* literal text, and a code longer than 255 characters degrades to General.
|
|
2051
|
+
*/
|
|
2052
|
+
static parse(code) {
|
|
2053
|
+
return new _NumberFormat(code, false);
|
|
2054
|
+
}
|
|
2055
|
+
/** `parse` without the 255-character cap (for tests that reach renderer edge cases). */
|
|
2056
|
+
static parseUncapped(code) {
|
|
2057
|
+
return new _NumberFormat(code, true);
|
|
2058
|
+
}
|
|
2059
|
+
/** Like `parse`, but remembers recently parsed codes (bounded), so formatting many values parses a code once. */
|
|
2060
|
+
static parseCached(code) {
|
|
2061
|
+
if (code.length > CACHE_MAX_CODE_LENGTH) return _NumberFormat.parse(code);
|
|
2062
|
+
let fmt = parseCache.get(code);
|
|
2063
|
+
if (!fmt) {
|
|
2064
|
+
fmt = _NumberFormat.parse(code);
|
|
2065
|
+
if (parseCache.size >= CACHE_CAPACITY) parseCache.clear();
|
|
2066
|
+
parseCache.set(code, fmt);
|
|
2067
|
+
}
|
|
2068
|
+
return fmt;
|
|
2069
|
+
}
|
|
2070
|
+
static general() {
|
|
2071
|
+
return _NumberFormat.parse("General");
|
|
2072
|
+
}
|
|
2073
|
+
isGeneral() {
|
|
2074
|
+
return this.general;
|
|
2075
|
+
}
|
|
2076
|
+
numberSections() {
|
|
2077
|
+
return this.sections.slice(0, this.numSections);
|
|
2078
|
+
}
|
|
2079
|
+
/** True when the code contains date or time tokens. */
|
|
2080
|
+
isDate() {
|
|
2081
|
+
return this.numberSections().some((s) => s.kind === "date");
|
|
2082
|
+
}
|
|
2083
|
+
isPercent() {
|
|
2084
|
+
return this.numberSections().some((s) => s.kind === "number" && s.percent > 0);
|
|
2085
|
+
}
|
|
2086
|
+
/** True for a pure text format such as `@`. */
|
|
2087
|
+
isText() {
|
|
2088
|
+
return this.numSections === 0 && this.textSection !== void 0;
|
|
2089
|
+
}
|
|
2090
|
+
/** Best guess at the format category. */
|
|
2091
|
+
kind() {
|
|
2092
|
+
if (this.general) return "general";
|
|
2093
|
+
if (this.isText()) return "text";
|
|
2094
|
+
if (SPECIAL_CODES.includes(this.code)) return "special";
|
|
2095
|
+
const s = this.sections[0];
|
|
2096
|
+
if (!s) return "custom";
|
|
2097
|
+
if (s.kind === "general") return "general";
|
|
2098
|
+
if (s.kind === "text") return "text";
|
|
2099
|
+
if (s.kind === "date") {
|
|
2100
|
+
const calendar = s.toks.some((t) => t.t === "date" && (t.d.k === "year" || t.d.k === "month" || t.d.k === "day"));
|
|
2101
|
+
return calendar ? "date" : "time";
|
|
2102
|
+
}
|
|
2103
|
+
if (s.fraction) return "fraction";
|
|
2104
|
+
if (s.exp) return "scientific";
|
|
2105
|
+
if (s.percent > 0) return "percentage";
|
|
2106
|
+
if (s.fill && s.hasDigits) return "accounting";
|
|
2107
|
+
if (s.currency && s.hasDigits) return "currency";
|
|
2108
|
+
if (s.hasDigits) return "number";
|
|
2109
|
+
return "custom";
|
|
2110
|
+
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Picks the number section for `v`, and whether the minus sign is dropped
|
|
2113
|
+
* (a negative section shows the absolute value). Null means General.
|
|
2114
|
+
*/
|
|
2115
|
+
select(v) {
|
|
2116
|
+
const secs = this.numberSections();
|
|
2117
|
+
const k = secs.length;
|
|
2118
|
+
const s0 = secs[0];
|
|
2119
|
+
if (!s0) return null;
|
|
2120
|
+
const c0 = s0.cond;
|
|
2121
|
+
const c1 = secs[1]?.cond;
|
|
2122
|
+
if (c0 || c1) {
|
|
2123
|
+
let idx;
|
|
2124
|
+
if (c0 && testCond(c0, v)) idx = 0;
|
|
2125
|
+
else if (c1 && testCond(c1, v)) idx = 1;
|
|
2126
|
+
else if (c0 && c1) idx = 2;
|
|
2127
|
+
else if (!c0) idx = 0;
|
|
2128
|
+
else idx = 1;
|
|
2129
|
+
const sec = secs[idx];
|
|
2130
|
+
return sec ? { sec, dropSign: k >= 2 } : null;
|
|
2131
|
+
}
|
|
2132
|
+
if (v < 0 && k >= 2) return { sec: secs[1], dropSign: true };
|
|
2133
|
+
if (v === 0 && k >= 3) return { sec: secs[2], dropSign: false };
|
|
2134
|
+
return { sec: s0, dropSign: false };
|
|
2135
|
+
}
|
|
2136
|
+
/**
|
|
2137
|
+
* Formats a number. `overflow` is true when the value can't be shown (a
|
|
2138
|
+
* date out of range, painted as `########`).
|
|
2139
|
+
*/
|
|
2140
|
+
formatNumber(n) {
|
|
2141
|
+
const general = () => ({
|
|
2142
|
+
text: formatGeneralFit(n, GENERAL_WIDTH) ?? OVERFLOW,
|
|
2143
|
+
numeric: true,
|
|
2144
|
+
overflow: false
|
|
2145
|
+
});
|
|
2146
|
+
if (!Number.isFinite(n)) return { text: "#NUM!", numeric: false, overflow: true };
|
|
2147
|
+
if (this.general) return general();
|
|
2148
|
+
const picked = this.select(n);
|
|
2149
|
+
if (!picked) return general();
|
|
2150
|
+
const { sec, dropSign } = picked;
|
|
2151
|
+
const minus = n < 0 && !dropSign;
|
|
2152
|
+
let out;
|
|
2153
|
+
switch (sec.kind) {
|
|
2154
|
+
case "text":
|
|
2155
|
+
out = renderText(sec, formatGeneralFit(n, GENERAL_WIDTH) ?? "");
|
|
2156
|
+
break;
|
|
2157
|
+
case "general":
|
|
2158
|
+
out = renderGeneral(sec, Math.abs(n), minus ? GENERAL_WIDTH - 1 : GENERAL_WIDTH);
|
|
2159
|
+
break;
|
|
2160
|
+
case "date":
|
|
2161
|
+
out = minus ? null : renderDate(sec, Math.abs(n));
|
|
2162
|
+
break;
|
|
2163
|
+
default:
|
|
2164
|
+
out = renderNumber(sec, Math.abs(n));
|
|
2165
|
+
}
|
|
2166
|
+
if (!out) return withColor({ text: OVERFLOW, numeric: true, overflow: true }, sec.color);
|
|
2167
|
+
if (minus && sec.kind !== "text" && !out.zero) out.prepend("-");
|
|
2168
|
+
return withColor({ text: out.text, numeric: true, overflow: false, ...out.fill ? { fill: out.fill } : {} }, sec.color);
|
|
2169
|
+
}
|
|
2170
|
+
/** Formats text through the text section (`@`), or returns it unchanged. */
|
|
2171
|
+
formatText(text) {
|
|
2172
|
+
const sec = this.textSection === void 0 ? void 0 : this.sections[this.textSection];
|
|
2173
|
+
if (!sec) return { text, numeric: false };
|
|
2174
|
+
const out = renderText(sec, text);
|
|
2175
|
+
return withColor({ text: out.text, numeric: false, ...out.fill ? { fill: out.fill } : {} }, sec.color);
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Formats a cell value: numbers through the number sections, ISO date text
|
|
2179
|
+
* (`2026-03-04`, `2026-03-04 18:00:00`) and `Date`s as serial numbers, other
|
|
2180
|
+
* text through the text section; booleans show TRUE/FALSE and error values
|
|
2181
|
+
* themselves.
|
|
2182
|
+
*/
|
|
2183
|
+
format(value) {
|
|
2184
|
+
if (value === "" || value === null || value === void 0) return { text: "", numeric: false };
|
|
2185
|
+
if (typeof value === "boolean") return { text: value ? "TRUE" : "FALSE", numeric: false };
|
|
2186
|
+
if (typeof value === "number") {
|
|
2187
|
+
const { overflow: _overflow, ...rest } = this.formatNumber(value);
|
|
2188
|
+
return rest;
|
|
2189
|
+
}
|
|
2190
|
+
if (typeof value === "string" && FORMULA_ERRORS.has(value)) return { text: value, numeric: false };
|
|
2191
|
+
const serial = dateValueToSerial(value);
|
|
2192
|
+
if (serial !== null) {
|
|
2193
|
+
const { overflow: _overflow, ...rest } = this.formatNumber(serial);
|
|
2194
|
+
return rest;
|
|
2195
|
+
}
|
|
2196
|
+
return this.formatText(String(value));
|
|
2197
|
+
}
|
|
2198
|
+
};
|
|
2199
|
+
function withColor(value, color) {
|
|
2200
|
+
if (color) value.color = color;
|
|
2201
|
+
return value;
|
|
2202
|
+
}
|
|
2203
|
+
var CACHE_CAPACITY = 64;
|
|
2204
|
+
var CACHE_MAX_CODE_LENGTH = 256;
|
|
2205
|
+
var parseCache = /* @__PURE__ */ new Map();
|
|
2206
|
+
function clearNumberFormatCache() {
|
|
2207
|
+
parseCache.clear();
|
|
2208
|
+
}
|
|
2209
|
+
var ISO_DATE_TIME = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}(?:\.\d+)?))?)?$/;
|
|
2210
|
+
function dateValueToSerial(value) {
|
|
2211
|
+
if (value instanceof Date) {
|
|
2212
|
+
if (isNaN(value.getTime())) return null;
|
|
2213
|
+
const serial2 = serialFromYmd(value.getFullYear(), value.getMonth() + 1, value.getDate());
|
|
2214
|
+
return serial2 === null ? null : serial2 + timeFraction(value.getHours(), value.getMinutes(), value.getSeconds() + value.getMilliseconds() / 1e3);
|
|
2215
|
+
}
|
|
2216
|
+
if (typeof value !== "string" || value.length < 8 || value.length > 30) return null;
|
|
2217
|
+
const m = ISO_DATE_TIME.exec(value.trim());
|
|
2218
|
+
if (!m) return null;
|
|
2219
|
+
const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
2220
|
+
if (mo < 1 || mo > 12 || d < 1 || d > daysInMonth(y, mo)) return null;
|
|
2221
|
+
const serial = serialFromYmd(y, mo, d);
|
|
2222
|
+
if (serial === null) return null;
|
|
2223
|
+
if (m[4] === void 0) return serial;
|
|
2224
|
+
const [hh, mm, ss] = [Number(m[4]), Number(m[5]), Number(m[6] ?? 0)];
|
|
2225
|
+
if (hh > 23 || mm > 59 || ss >= 60) return null;
|
|
2226
|
+
return serial + timeFraction(hh, mm, ss);
|
|
2227
|
+
}
|
|
2228
|
+
function formatNumberFormatValue(value, code) {
|
|
2229
|
+
return NumberFormat.parseCached(code).format(value);
|
|
2230
|
+
}
|
|
2231
|
+
function formatTextFunction(value, code) {
|
|
2232
|
+
if (typeof value === "string" && FORMULA_ERRORS.has(value)) return { error: value };
|
|
2233
|
+
if (code === "") return { text: "" };
|
|
2234
|
+
if (tooLong(code)) return { error: "#VALUE!" };
|
|
2235
|
+
const fmt = NumberFormat.parseCached(code);
|
|
2236
|
+
let n;
|
|
2237
|
+
if (value === "" || value === null || value === void 0) n = 0;
|
|
2238
|
+
else if (typeof value === "number") n = value;
|
|
2239
|
+
else if (typeof value === "boolean") return { text: fmt.formatText(value ? "TRUE" : "FALSE").text };
|
|
2240
|
+
else {
|
|
2241
|
+
const serial = dateValueToSerial(value);
|
|
2242
|
+
const parsed = serial ?? (typeof value === "string" ? parseNumberText(value) : null);
|
|
2243
|
+
if (parsed === null) return { text: fmt.formatText(String(value)).text };
|
|
2244
|
+
n = parsed;
|
|
2245
|
+
}
|
|
2246
|
+
if (!Number.isFinite(n)) return { error: "#NUM!" };
|
|
2247
|
+
const f = fmt.formatNumber(n);
|
|
2248
|
+
return f.overflow ? { error: "#VALUE!" } : { text: f.text };
|
|
2249
|
+
}
|
|
2250
|
+
var INDEXED_COLORS = [
|
|
2251
|
+
"#000000",
|
|
2252
|
+
"#FFFFFF",
|
|
2253
|
+
"#FF0000",
|
|
2254
|
+
"#00FF00",
|
|
2255
|
+
"#0000FF",
|
|
2256
|
+
"#FFFF00",
|
|
2257
|
+
"#FF00FF",
|
|
2258
|
+
"#00FFFF",
|
|
2259
|
+
"#800000",
|
|
2260
|
+
"#008000",
|
|
2261
|
+
"#000080",
|
|
2262
|
+
"#808000",
|
|
2263
|
+
"#800080",
|
|
2264
|
+
"#008080",
|
|
2265
|
+
"#C0C0C0",
|
|
2266
|
+
"#808080",
|
|
2267
|
+
"#9999FF",
|
|
2268
|
+
"#993366",
|
|
2269
|
+
"#FFFFCC",
|
|
2270
|
+
"#CCFFFF",
|
|
2271
|
+
"#660066",
|
|
2272
|
+
"#FF8080",
|
|
2273
|
+
"#0066CC",
|
|
2274
|
+
"#CCCCFF",
|
|
2275
|
+
"#000080",
|
|
2276
|
+
"#FF00FF",
|
|
2277
|
+
"#FFFF00",
|
|
2278
|
+
"#00FFFF",
|
|
2279
|
+
"#800080",
|
|
2280
|
+
"#800000",
|
|
2281
|
+
"#008080",
|
|
2282
|
+
"#0000FF",
|
|
2283
|
+
"#00CCFF",
|
|
2284
|
+
"#CCFFFF",
|
|
2285
|
+
"#CCFFCC",
|
|
2286
|
+
"#FFFF99",
|
|
2287
|
+
"#99CCFF",
|
|
2288
|
+
"#FF99CC",
|
|
2289
|
+
"#CC99FF",
|
|
2290
|
+
"#FFCC99",
|
|
2291
|
+
"#3366FF",
|
|
2292
|
+
"#33CCCC",
|
|
2293
|
+
"#99CC00",
|
|
2294
|
+
"#FFCC00",
|
|
2295
|
+
"#FF9900",
|
|
2296
|
+
"#FF6600",
|
|
2297
|
+
"#666699",
|
|
2298
|
+
"#969696",
|
|
2299
|
+
"#003366",
|
|
2300
|
+
"#339966",
|
|
2301
|
+
"#003300",
|
|
2302
|
+
"#333300",
|
|
2303
|
+
"#993300",
|
|
2304
|
+
"#993366",
|
|
2305
|
+
"#333399",
|
|
2306
|
+
"#333333"
|
|
2307
|
+
];
|
|
2308
|
+
var NAMED_CSS = {
|
|
2309
|
+
black: "#000000",
|
|
2310
|
+
blue: "#0000FF",
|
|
2311
|
+
cyan: "#00FFFF",
|
|
2312
|
+
green: "#00FF00",
|
|
2313
|
+
magenta: "#FF00FF",
|
|
2314
|
+
red: "#FF0000",
|
|
2315
|
+
white: "#FFFFFF",
|
|
2316
|
+
yellow: "#FFFF00"
|
|
2317
|
+
};
|
|
2318
|
+
function numberFormatColorToCss(color) {
|
|
2319
|
+
const named = NAMED_CSS[color];
|
|
2320
|
+
if (named) return named;
|
|
2321
|
+
return INDEXED_COLORS[Number(color.slice(5)) - 1] ?? "#000000";
|
|
805
2322
|
}
|
|
806
2323
|
|
|
807
2324
|
// src/spreadsheet/formula/functions/helpers.ts
|
|
@@ -1806,7 +3323,7 @@ var TEXT_FUNCTIONS = [
|
|
|
1806
3323
|
if (isFormulaError(value)) return value;
|
|
1807
3324
|
if (typeof value === "number") return value;
|
|
1808
3325
|
if (isBlank(value)) return 0;
|
|
1809
|
-
const num = parseNumericText(toText(value));
|
|
3326
|
+
const num = parseNumericText(toText(value)) ?? parseNumberText(toText(value));
|
|
1810
3327
|
if (num === null) return ctx.argError(0, "#VALUE!", `("${toText(value)}") can't be converted to a number.`);
|
|
1811
3328
|
return num;
|
|
1812
3329
|
}
|
|
@@ -1814,15 +3331,19 @@ var TEXT_FUNCTIONS = [
|
|
|
1814
3331
|
{
|
|
1815
3332
|
name: "TEXT",
|
|
1816
3333
|
category: "text",
|
|
1817
|
-
description: 'Formats a number or date as text using
|
|
3334
|
+
description: 'Formats a number or date as text using an Excel format code such as "0.00", "#,##0", "0%", "# ?/?" or "yyyy-mm-dd".',
|
|
1818
3335
|
args: [arg("value", "The number or date to format."), arg("format_text", "The format code.", "text")],
|
|
1819
3336
|
evaluate: (ctx) => {
|
|
1820
3337
|
const value = ctx.scalar(0);
|
|
1821
3338
|
if (isFormulaError(value)) return value;
|
|
1822
3339
|
const format = ctx.text(1);
|
|
1823
3340
|
if (failed(format)) return format;
|
|
1824
|
-
const result =
|
|
1825
|
-
|
|
3341
|
+
const result = formatTextFunction(value, format);
|
|
3342
|
+
if ("text" in result) return result.text;
|
|
3343
|
+
if (result.error === "#VALUE!") {
|
|
3344
|
+
return Array.from(format).length > 255 ? ctx.argError(1, "#VALUE!", "is longer than 255 characters.") : ctx.argError(0, "#VALUE!", `is outside the date range, so it can't use the format "${format}".`);
|
|
3345
|
+
}
|
|
3346
|
+
return result.error;
|
|
1826
3347
|
}
|
|
1827
3348
|
}
|
|
1828
3349
|
];
|
|
@@ -1887,15 +3408,15 @@ function tableLookup(ctx, vertical) {
|
|
|
1887
3408
|
if (failed(table)) return table;
|
|
1888
3409
|
const index = ctx.integer(2);
|
|
1889
3410
|
if (failed(index)) return index;
|
|
1890
|
-
const
|
|
1891
|
-
if (failed(
|
|
3411
|
+
const approximate2 = ctx.boolean(3, true);
|
|
3412
|
+
if (failed(approximate2)) return approximate2;
|
|
1892
3413
|
const span = vertical ? table.cols : table.rows;
|
|
1893
3414
|
if (index < 1) return ctx.argError(2, "#VALUE!", "must be 1 or more.");
|
|
1894
3415
|
if (index > span) {
|
|
1895
3416
|
return ctx.argError(2, "#REF!", `is larger than the number of ${vertical ? "columns" : "rows"} in the table (${span}).`);
|
|
1896
3417
|
}
|
|
1897
3418
|
const keys = vertical ? sliceRange(table, 0, table.rows, 0, 1) : sliceRange(table, 0, 1, 0, table.cols);
|
|
1898
|
-
const position = lookupIndex(keys, target,
|
|
3419
|
+
const position = lookupIndex(keys, target, approximate2 ? "sorted-asc" : "wildcard");
|
|
1899
3420
|
if (position === -1) return notFound(ctx, target);
|
|
1900
3421
|
return vertical ? rangeGet(table, position, index - 1) : rangeGet(table, index - 1, position);
|
|
1901
3422
|
}
|
|
@@ -2661,17 +4182,17 @@ function layoutOf(def) {
|
|
|
2661
4182
|
return { fixed: def.args.slice(0, groupStart), group: def.args.slice(groupStart), groupStart };
|
|
2662
4183
|
}
|
|
2663
4184
|
function getFormulaArgumentSpecIndex(def, argIndex) {
|
|
2664
|
-
const { fixed, group, groupStart } = layoutOf(def);
|
|
2665
|
-
if (argIndex <
|
|
4185
|
+
const { fixed: fixed2, group, groupStart } = layoutOf(def);
|
|
4186
|
+
if (argIndex < fixed2.length) return argIndex;
|
|
2666
4187
|
if (group.length === 0) return -1;
|
|
2667
4188
|
return groupStart + (argIndex - groupStart) % group.length;
|
|
2668
4189
|
}
|
|
2669
4190
|
function checkFormulaArgumentCount(def, count) {
|
|
2670
|
-
const { fixed, group } = layoutOf(def);
|
|
2671
|
-
const requiredFixed =
|
|
4191
|
+
const { fixed: fixed2, group } = layoutOf(def);
|
|
4192
|
+
const requiredFixed = fixed2.filter((arg2) => !arg2.optional).length;
|
|
2672
4193
|
const groupRequired = group.length > 0 && !group[0].optional;
|
|
2673
4194
|
const min2 = requiredFixed + (groupRequired ? group.length : 0);
|
|
2674
|
-
const max2 = group.length > 0 ? Infinity :
|
|
4195
|
+
const max2 = group.length > 0 ? Infinity : fixed2.length;
|
|
2675
4196
|
const given = `${count} ${count === 1 ? "was" : "were"} given`;
|
|
2676
4197
|
const plural = (n) => `${n} argument${n === 1 ? "" : "s"}`;
|
|
2677
4198
|
if (count < min2) {
|
|
@@ -2682,8 +4203,8 @@ function checkFormulaArgumentCount(def, count) {
|
|
|
2682
4203
|
const expected = min2 === max2 ? plural(max2) : `at most ${plural(max2)}`;
|
|
2683
4204
|
return max2 === 0 ? `${def.name} takes no arguments, but ${given}.` : `${def.name} takes ${expected}, but ${given}.`;
|
|
2684
4205
|
}
|
|
2685
|
-
if (group.length > 1 && count >
|
|
2686
|
-
const extra = (count -
|
|
4206
|
+
if (group.length > 1 && count > fixed2.length) {
|
|
4207
|
+
const extra = (count - fixed2.length) % group.length;
|
|
2687
4208
|
if (extra !== 0 && !(def.allowTrailingArgument && extra === 1)) {
|
|
2688
4209
|
const names = group.map((arg2) => arg2.name).join(", ");
|
|
2689
4210
|
return `${def.name} expects its repeating arguments (${names}) in complete groups.`;
|
|
@@ -2694,7 +4215,7 @@ function checkFormulaArgumentCount(def, count) {
|
|
|
2694
4215
|
function getFormulaSignatureParts(nameOrDef, activeArgIndex) {
|
|
2695
4216
|
const def = typeof nameOrDef === "string" ? getFormulaFunction(nameOrDef) : nameOrDef;
|
|
2696
4217
|
if (!def) return [];
|
|
2697
|
-
const { fixed, group, groupStart } = layoutOf(def);
|
|
4218
|
+
const { fixed: fixed2, group, groupStart } = layoutOf(def);
|
|
2698
4219
|
let activeSpec = -1;
|
|
2699
4220
|
let activeRepeat = 0;
|
|
2700
4221
|
if (activeArgIndex !== void 0 && activeArgIndex !== null && activeArgIndex >= 0) {
|
|
@@ -2708,7 +4229,7 @@ function getFormulaSignatureParts(nameOrDef, activeArgIndex) {
|
|
|
2708
4229
|
if (parts.length > 1) parts.push({ text: ", ", specIndex: null, active: false });
|
|
2709
4230
|
parts.push({ text, specIndex, active: specIndex === activeSpec && repeat === activeRepeat });
|
|
2710
4231
|
};
|
|
2711
|
-
|
|
4232
|
+
fixed2.forEach((arg2, i) => pushArg(arg2.optional ? `[${arg2.name}]` : arg2.name, i));
|
|
2712
4233
|
if (group.length > 0) {
|
|
2713
4234
|
const groupOptional = !!group[0].optional;
|
|
2714
4235
|
group.forEach((arg2, i) => {
|
|
@@ -3207,13 +4728,33 @@ export {
|
|
|
3207
4728
|
parseRange,
|
|
3208
4729
|
FormulaParser,
|
|
3209
4730
|
parseFormula,
|
|
4731
|
+
serialFromYmd,
|
|
4732
|
+
parseCellInput,
|
|
4733
|
+
parseNumberText,
|
|
3210
4734
|
FORMULA_ERRORS,
|
|
3211
4735
|
isFormulaError,
|
|
4736
|
+
isBlank,
|
|
4737
|
+
toText,
|
|
4738
|
+
numberToText,
|
|
4739
|
+
numericValue,
|
|
4740
|
+
compareNumbers,
|
|
4741
|
+
compareValues,
|
|
4742
|
+
sortCompareValues,
|
|
3212
4743
|
colNameToIndex,
|
|
3213
4744
|
indexToColName,
|
|
4745
|
+
parseIsoDate,
|
|
3214
4746
|
wildcardToRegExp,
|
|
3215
4747
|
parseCriteria,
|
|
3216
|
-
|
|
4748
|
+
formatGeneralFit,
|
|
4749
|
+
builtinNumberFormat,
|
|
4750
|
+
builtinNumberFormatId,
|
|
4751
|
+
MAX_NUMBER_FORMAT_CODE_CHARS,
|
|
4752
|
+
NumberFormat,
|
|
4753
|
+
clearNumberFormatCache,
|
|
4754
|
+
dateValueToSerial,
|
|
4755
|
+
formatNumberFormatValue,
|
|
4756
|
+
formatTextFunction,
|
|
4757
|
+
numberFormatColorToCss,
|
|
3217
4758
|
registerFormulaFunction,
|
|
3218
4759
|
unregisterFormulaFunction,
|
|
3219
4760
|
getFormulaFunction,
|
|
@@ -3227,4 +4768,4 @@ export {
|
|
|
3227
4768
|
MAX_FORMULA_ARRAY_SIZE,
|
|
3228
4769
|
FormulaEvaluator
|
|
3229
4770
|
};
|
|
3230
|
-
//# sourceMappingURL=chunk-
|
|
4771
|
+
//# sourceMappingURL=chunk-3CN2WD3X.js.map
|