@pixerate/editor 0.23.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 +96 -1
- package/THIRD_PARTY_NOTICES.md +51 -0
- package/dist/agent/index.cjs +9 -8
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +3 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +1210 -17
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +318 -1
- package/dist/canvas/index.d.ts +318 -1
- package/dist/canvas/index.js +1199 -6
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-QBJ22YZR.js → chunk-3CN2WD3X.js} +1354 -206
- 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-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-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-XDMXXC4Z.cjs → chunk-MA2F5KHF.cjs} +350 -227
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-RIKBJLS2.cjs → chunk-MCSJFWAO.cjs} +1356 -208
- 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-K3R5NG2D.js → chunk-U5KWZU4X.js} +251 -128
- 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.d.cts +5 -353
- package/dist/commands/index.d.ts +5 -353
- 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-pePCJLT1.d.cts → controller-BhLLAoi8.d.cts} +45 -4
- package/dist/{controller-pePCJLT1.d.ts → controller-BhLLAoi8.d.ts} +45 -4
- 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/image-editor/index.cjs +1831 -30
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +590 -3
- package/dist/image-editor/index.d.ts +590 -3
- package/dist/image-editor/index.js +1807 -6
- 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 +162 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +174 -37
- 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-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
- 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 +37 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +188 -17
- package/dist/spreadsheet/index.d.ts +188 -17
- package/dist/spreadsheet/index.js +43 -9
- 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.ts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/package.json +16 -1
- package/dist/chunk-4SUNJZWB.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-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-K3R5NG2D.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QBJ22YZR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RIKBJLS2.cjs.map +0 -1
- package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
|
@@ -982,209 +982,1343 @@ function parseCriteria(criterion) {
|
|
|
982
982
|
};
|
|
983
983
|
}
|
|
984
984
|
|
|
985
|
-
// src/spreadsheet/
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
let
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
if (
|
|
1035
|
-
|
|
1036
|
-
};
|
|
1037
|
-
for (let i = 0; i < format.length; i++) {
|
|
1038
|
-
const char = format[i];
|
|
1039
|
-
if (char === '"') {
|
|
1040
|
-
const end = format.indexOf('"', i + 1);
|
|
1041
|
-
const stop = end === -1 ? format.length : end;
|
|
1042
|
-
push(true, format.slice(i + 1, stop));
|
|
1043
|
-
i = stop;
|
|
1044
|
-
} else if (char === "\\" && i + 1 < format.length) {
|
|
1045
|
-
push(true, format[++i]);
|
|
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);
|
|
994
|
+
}
|
|
995
|
+
return { digits, exp: Number(s.slice(e + 1)) };
|
|
996
|
+
}
|
|
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;
|
|
1046
1036
|
} else {
|
|
1047
|
-
|
|
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("");
|
|
1258
|
+
} else {
|
|
1259
|
+
out[cur] += c;
|
|
1048
1260
|
}
|
|
1049
1261
|
}
|
|
1050
1262
|
return out;
|
|
1051
1263
|
}
|
|
1052
|
-
var
|
|
1053
|
-
function
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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);
|
|
1350
|
+
continue;
|
|
1351
|
+
}
|
|
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;
|
|
1077
1370
|
continue;
|
|
1078
1371
|
}
|
|
1079
|
-
|
|
1080
|
-
const
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
pattern += char;
|
|
1085
|
-
} else if (pattern) {
|
|
1086
|
-
suffix += char;
|
|
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;
|
|
1087
1377
|
} else {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
const percents = (prefix + suffix).split("%").length - 1;
|
|
1093
|
-
value = Math.abs(value) * Math.pow(100, percents);
|
|
1094
|
-
if (!pattern) return `${sign}${prefix}${suffix}`;
|
|
1095
|
-
const expMatch = /^([^Ee]*)[Ee]([+-])(0+)$/.exec(pattern);
|
|
1096
|
-
if (expMatch) {
|
|
1097
|
-
const [, mantissa, expSign, expDigits] = expMatch;
|
|
1098
|
-
const decimals = (mantissa.split(".")[1] ?? "").length;
|
|
1099
|
-
const [m, e] = value.toExponential(decimals).split("e");
|
|
1100
|
-
const exponent = Number(e);
|
|
1101
|
-
const expText = String(Math.abs(exponent)).padStart(expDigits.length, "0");
|
|
1102
|
-
const shownSign = exponent < 0 ? "-" : expSign === "+" ? "+" : "";
|
|
1103
|
-
return `${sign}${prefix}${m}E${shownSign}${expText}${suffix}`;
|
|
1104
|
-
}
|
|
1105
|
-
const [intPattern, decPattern = ""] = pattern.split(".");
|
|
1106
|
-
const maxDecimals = (decPattern.match(/[0#?]/g) ?? []).length;
|
|
1107
|
-
const minDecimals = (decPattern.match(/0/g) ?? []).length;
|
|
1108
|
-
const minInt = (intPattern.match(/0/g) ?? []).length;
|
|
1109
|
-
const grouping = intPattern.includes(",");
|
|
1110
|
-
const rounded = roundHalfAwayFromZero(value, maxDecimals);
|
|
1111
|
-
let [intPart, decPart = ""] = rounded.toFixed(maxDecimals).split(".");
|
|
1112
|
-
while (decPart.length > minDecimals && decPart.endsWith("0")) decPart = decPart.slice(0, -1);
|
|
1113
|
-
if (intPart === "0" && minInt === 0) intPart = "";
|
|
1114
|
-
intPart = intPart.padStart(minInt, "0");
|
|
1115
|
-
if (grouping) intPart = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1116
|
-
const showSign = sign && /[1-9]/.test(intPart + decPart) ? sign : "";
|
|
1117
|
-
return `${showSign}${prefix}${intPart}${decPart ? `.${decPart}` : ""}${suffix}`;
|
|
1118
|
-
}
|
|
1119
|
-
function splitSections(format) {
|
|
1120
|
-
const sections = [""];
|
|
1121
|
-
let quoted = false;
|
|
1122
|
-
for (let i = 0; i < format.length; i++) {
|
|
1123
|
-
const char = format[i];
|
|
1124
|
-
if (char === '"') quoted = !quoted;
|
|
1125
|
-
if (char === "\\" && i + 1 < format.length) {
|
|
1126
|
-
sections[sections.length - 1] += char + format[++i];
|
|
1378
|
+
pushLit(toks, "[");
|
|
1379
|
+
i += 1;
|
|
1380
|
+
}
|
|
1127
1381
|
continue;
|
|
1128
1382
|
}
|
|
1129
|
-
if (
|
|
1130
|
-
else
|
|
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
|
+
}
|
|
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);
|
|
1428
|
+
}
|
|
1429
|
+
i += 1;
|
|
1131
1430
|
}
|
|
1132
|
-
return
|
|
1431
|
+
return raw;
|
|
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)));
|
|
1133
1609
|
}
|
|
1610
|
+
|
|
1611
|
+
// src/spreadsheet/numfmt/render.ts
|
|
1612
|
+
var MAX_DATE_SERIAL = 2958465;
|
|
1134
1613
|
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
1135
|
-
var
|
|
1136
|
-
var
|
|
1137
|
-
function
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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);
|
|
1142
1697
|
continue;
|
|
1143
1698
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1699
|
+
if (t.t !== "date") {
|
|
1700
|
+
out.push(literalText(t));
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
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";
|
|
1188
2322
|
}
|
|
1189
2323
|
|
|
1190
2324
|
// src/spreadsheet/formula/functions/helpers.ts
|
|
@@ -2197,15 +3331,19 @@ var TEXT_FUNCTIONS = [
|
|
|
2197
3331
|
{
|
|
2198
3332
|
name: "TEXT",
|
|
2199
3333
|
category: "text",
|
|
2200
|
-
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".',
|
|
2201
3335
|
args: [arg("value", "The number or date to format."), arg("format_text", "The format code.", "text")],
|
|
2202
3336
|
evaluate: (ctx) => {
|
|
2203
3337
|
const value = ctx.scalar(0);
|
|
2204
3338
|
if (isFormulaError(value)) return value;
|
|
2205
3339
|
const format = ctx.text(1);
|
|
2206
3340
|
if (failed(format)) return format;
|
|
2207
|
-
const result =
|
|
2208
|
-
|
|
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;
|
|
2209
3347
|
}
|
|
2210
3348
|
}
|
|
2211
3349
|
];
|
|
@@ -2270,15 +3408,15 @@ function tableLookup(ctx, vertical) {
|
|
|
2270
3408
|
if (failed(table)) return table;
|
|
2271
3409
|
const index = ctx.integer(2);
|
|
2272
3410
|
if (failed(index)) return index;
|
|
2273
|
-
const
|
|
2274
|
-
if (failed(
|
|
3411
|
+
const approximate2 = ctx.boolean(3, true);
|
|
3412
|
+
if (failed(approximate2)) return approximate2;
|
|
2275
3413
|
const span = vertical ? table.cols : table.rows;
|
|
2276
3414
|
if (index < 1) return ctx.argError(2, "#VALUE!", "must be 1 or more.");
|
|
2277
3415
|
if (index > span) {
|
|
2278
3416
|
return ctx.argError(2, "#REF!", `is larger than the number of ${vertical ? "columns" : "rows"} in the table (${span}).`);
|
|
2279
3417
|
}
|
|
2280
3418
|
const keys = vertical ? sliceRange(table, 0, table.rows, 0, 1) : sliceRange(table, 0, 1, 0, table.cols);
|
|
2281
|
-
const position = lookupIndex(keys, target,
|
|
3419
|
+
const position = lookupIndex(keys, target, approximate2 ? "sorted-asc" : "wildcard");
|
|
2282
3420
|
if (position === -1) return notFound(ctx, target);
|
|
2283
3421
|
return vertical ? rangeGet(table, position, index - 1) : rangeGet(table, index - 1, position);
|
|
2284
3422
|
}
|
|
@@ -3044,17 +4182,17 @@ function layoutOf(def) {
|
|
|
3044
4182
|
return { fixed: def.args.slice(0, groupStart), group: def.args.slice(groupStart), groupStart };
|
|
3045
4183
|
}
|
|
3046
4184
|
function getFormulaArgumentSpecIndex(def, argIndex) {
|
|
3047
|
-
const { fixed, group, groupStart } = layoutOf(def);
|
|
3048
|
-
if (argIndex <
|
|
4185
|
+
const { fixed: fixed2, group, groupStart } = layoutOf(def);
|
|
4186
|
+
if (argIndex < fixed2.length) return argIndex;
|
|
3049
4187
|
if (group.length === 0) return -1;
|
|
3050
4188
|
return groupStart + (argIndex - groupStart) % group.length;
|
|
3051
4189
|
}
|
|
3052
4190
|
function checkFormulaArgumentCount(def, count) {
|
|
3053
|
-
const { fixed, group } = layoutOf(def);
|
|
3054
|
-
const requiredFixed =
|
|
4191
|
+
const { fixed: fixed2, group } = layoutOf(def);
|
|
4192
|
+
const requiredFixed = fixed2.filter((arg2) => !arg2.optional).length;
|
|
3055
4193
|
const groupRequired = group.length > 0 && !group[0].optional;
|
|
3056
4194
|
const min2 = requiredFixed + (groupRequired ? group.length : 0);
|
|
3057
|
-
const max2 = group.length > 0 ? Infinity :
|
|
4195
|
+
const max2 = group.length > 0 ? Infinity : fixed2.length;
|
|
3058
4196
|
const given = `${count} ${count === 1 ? "was" : "were"} given`;
|
|
3059
4197
|
const plural = (n) => `${n} argument${n === 1 ? "" : "s"}`;
|
|
3060
4198
|
if (count < min2) {
|
|
@@ -3065,8 +4203,8 @@ function checkFormulaArgumentCount(def, count) {
|
|
|
3065
4203
|
const expected = min2 === max2 ? plural(max2) : `at most ${plural(max2)}`;
|
|
3066
4204
|
return max2 === 0 ? `${def.name} takes no arguments, but ${given}.` : `${def.name} takes ${expected}, but ${given}.`;
|
|
3067
4205
|
}
|
|
3068
|
-
if (group.length > 1 && count >
|
|
3069
|
-
const extra = (count -
|
|
4206
|
+
if (group.length > 1 && count > fixed2.length) {
|
|
4207
|
+
const extra = (count - fixed2.length) % group.length;
|
|
3070
4208
|
if (extra !== 0 && !(def.allowTrailingArgument && extra === 1)) {
|
|
3071
4209
|
const names = group.map((arg2) => arg2.name).join(", ");
|
|
3072
4210
|
return `${def.name} expects its repeating arguments (${names}) in complete groups.`;
|
|
@@ -3077,7 +4215,7 @@ function checkFormulaArgumentCount(def, count) {
|
|
|
3077
4215
|
function getFormulaSignatureParts(nameOrDef, activeArgIndex) {
|
|
3078
4216
|
const def = typeof nameOrDef === "string" ? getFormulaFunction(nameOrDef) : nameOrDef;
|
|
3079
4217
|
if (!def) return [];
|
|
3080
|
-
const { fixed, group, groupStart } = layoutOf(def);
|
|
4218
|
+
const { fixed: fixed2, group, groupStart } = layoutOf(def);
|
|
3081
4219
|
let activeSpec = -1;
|
|
3082
4220
|
let activeRepeat = 0;
|
|
3083
4221
|
if (activeArgIndex !== void 0 && activeArgIndex !== null && activeArgIndex >= 0) {
|
|
@@ -3091,7 +4229,7 @@ function getFormulaSignatureParts(nameOrDef, activeArgIndex) {
|
|
|
3091
4229
|
if (parts.length > 1) parts.push({ text: ", ", specIndex: null, active: false });
|
|
3092
4230
|
parts.push({ text, specIndex, active: specIndex === activeSpec && repeat === activeRepeat });
|
|
3093
4231
|
};
|
|
3094
|
-
|
|
4232
|
+
fixed2.forEach((arg2, i) => pushArg(arg2.optional ? `[${arg2.name}]` : arg2.name, i));
|
|
3095
4233
|
if (group.length > 0) {
|
|
3096
4234
|
const groupOptional = !!group[0].optional;
|
|
3097
4235
|
group.forEach((arg2, i) => {
|
|
@@ -3595,6 +4733,8 @@ export {
|
|
|
3595
4733
|
parseNumberText,
|
|
3596
4734
|
FORMULA_ERRORS,
|
|
3597
4735
|
isFormulaError,
|
|
4736
|
+
isBlank,
|
|
4737
|
+
toText,
|
|
3598
4738
|
numberToText,
|
|
3599
4739
|
numericValue,
|
|
3600
4740
|
compareNumbers,
|
|
@@ -3605,8 +4745,16 @@ export {
|
|
|
3605
4745
|
parseIsoDate,
|
|
3606
4746
|
wildcardToRegExp,
|
|
3607
4747
|
parseCriteria,
|
|
3608
|
-
|
|
3609
|
-
|
|
4748
|
+
formatGeneralFit,
|
|
4749
|
+
builtinNumberFormat,
|
|
4750
|
+
builtinNumberFormatId,
|
|
4751
|
+
MAX_NUMBER_FORMAT_CODE_CHARS,
|
|
4752
|
+
NumberFormat,
|
|
4753
|
+
clearNumberFormatCache,
|
|
4754
|
+
dateValueToSerial,
|
|
4755
|
+
formatNumberFormatValue,
|
|
4756
|
+
formatTextFunction,
|
|
4757
|
+
numberFormatColorToCss,
|
|
3610
4758
|
registerFormulaFunction,
|
|
3611
4759
|
unregisterFormulaFunction,
|
|
3612
4760
|
getFormulaFunction,
|
|
@@ -3620,4 +4768,4 @@ export {
|
|
|
3620
4768
|
MAX_FORMULA_ARRAY_SIZE,
|
|
3621
4769
|
FormulaEvaluator
|
|
3622
4770
|
};
|
|
3623
|
-
//# sourceMappingURL=chunk-
|
|
4771
|
+
//# sourceMappingURL=chunk-3CN2WD3X.js.map
|