@bilig/formula 0.1.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/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/addressing.d.ts +66 -0
- package/dist/addressing.js +179 -0
- package/dist/addressing.js.map +1 -0
- package/dist/ast.d.ts +74 -0
- package/dist/ast.js +2 -0
- package/dist/ast.js.map +1 -0
- package/dist/binder.d.ts +13 -0
- package/dist/binder.js +1700 -0
- package/dist/binder.js.map +1 -0
- package/dist/builtin-capabilities.d.ts +19 -0
- package/dist/builtin-capabilities.js +861 -0
- package/dist/builtin-capabilities.js.map +1 -0
- package/dist/builtins/complex.d.ts +10 -0
- package/dist/builtins/complex.js +407 -0
- package/dist/builtins/complex.js.map +1 -0
- package/dist/builtins/convert.d.ts +3 -0
- package/dist/builtins/convert.js +362 -0
- package/dist/builtins/convert.js.map +1 -0
- package/dist/builtins/datetime.d.ts +23 -0
- package/dist/builtins/datetime.js +1096 -0
- package/dist/builtins/datetime.js.map +1 -0
- package/dist/builtins/distribution-builtins.d.ts +16 -0
- package/dist/builtins/distribution-builtins.js +517 -0
- package/dist/builtins/distribution-builtins.js.map +1 -0
- package/dist/builtins/distributions.d.ts +34 -0
- package/dist/builtins/distributions.js +722 -0
- package/dist/builtins/distributions.js.map +1 -0
- package/dist/builtins/financial-builtins.d.ts +16 -0
- package/dist/builtins/financial-builtins.js +324 -0
- package/dist/builtins/financial-builtins.js.map +1 -0
- package/dist/builtins/financial.d.ts +11 -0
- package/dist/builtins/financial.js +241 -0
- package/dist/builtins/financial.js.map +1 -0
- package/dist/builtins/fixed-income-builtins.d.ts +14 -0
- package/dist/builtins/fixed-income-builtins.js +598 -0
- package/dist/builtins/fixed-income-builtins.js.map +1 -0
- package/dist/builtins/fixed-income.d.ts +42 -0
- package/dist/builtins/fixed-income.js +668 -0
- package/dist/builtins/fixed-income.js.map +1 -0
- package/dist/builtins/formatting.d.ts +8 -0
- package/dist/builtins/formatting.js +53 -0
- package/dist/builtins/formatting.js.map +1 -0
- package/dist/builtins/logical.d.ts +4 -0
- package/dist/builtins/logical.js +258 -0
- package/dist/builtins/logical.js.map +1 -0
- package/dist/builtins/lookup-array-shape-builtins.d.ts +21 -0
- package/dist/builtins/lookup-array-shape-builtins.js +517 -0
- package/dist/builtins/lookup-array-shape-builtins.js.map +1 -0
- package/dist/builtins/lookup-criteria-builtins.d.ts +16 -0
- package/dist/builtins/lookup-criteria-builtins.js +216 -0
- package/dist/builtins/lookup-criteria-builtins.js.map +1 -0
- package/dist/builtins/lookup-database-builtins.d.ts +17 -0
- package/dist/builtins/lookup-database-builtins.js +294 -0
- package/dist/builtins/lookup-database-builtins.js.map +1 -0
- package/dist/builtins/lookup-financial-builtins.d.ts +11 -0
- package/dist/builtins/lookup-financial-builtins.js +291 -0
- package/dist/builtins/lookup-financial-builtins.js.map +1 -0
- package/dist/builtins/lookup-hypothesis-builtins.d.ts +11 -0
- package/dist/builtins/lookup-hypothesis-builtins.js +57 -0
- package/dist/builtins/lookup-hypothesis-builtins.js.map +1 -0
- package/dist/builtins/lookup-matrix-builtins.d.ts +17 -0
- package/dist/builtins/lookup-matrix-builtins.js +218 -0
- package/dist/builtins/lookup-matrix-builtins.js.map +1 -0
- package/dist/builtins/lookup-order-statistics-builtins.d.ts +18 -0
- package/dist/builtins/lookup-order-statistics-builtins.js +575 -0
- package/dist/builtins/lookup-order-statistics-builtins.js.map +1 -0
- package/dist/builtins/lookup-reference-builtins.d.ts +18 -0
- package/dist/builtins/lookup-reference-builtins.js +300 -0
- package/dist/builtins/lookup-reference-builtins.js.map +1 -0
- package/dist/builtins/lookup-regression-builtins.d.ts +12 -0
- package/dist/builtins/lookup-regression-builtins.js +511 -0
- package/dist/builtins/lookup-regression-builtins.js.map +1 -0
- package/dist/builtins/lookup-sort-filter-builtins.d.ts +20 -0
- package/dist/builtins/lookup-sort-filter-builtins.js +382 -0
- package/dist/builtins/lookup-sort-filter-builtins.js.map +1 -0
- package/dist/builtins/lookup.d.ts +13 -0
- package/dist/builtins/lookup.js +867 -0
- package/dist/builtins/lookup.js.map +1 -0
- package/dist/builtins/math-builtins.d.ts +31 -0
- package/dist/builtins/math-builtins.js +420 -0
- package/dist/builtins/math-builtins.js.map +1 -0
- package/dist/builtins/numeric.d.ts +30 -0
- package/dist/builtins/numeric.js +150 -0
- package/dist/builtins/numeric.js.map +1 -0
- package/dist/builtins/placeholder.d.ts +9 -0
- package/dist/builtins/placeholder.js +540 -0
- package/dist/builtins/placeholder.js.map +1 -0
- package/dist/builtins/radix.d.ts +12 -0
- package/dist/builtins/radix.js +220 -0
- package/dist/builtins/radix.js.map +1 -0
- package/dist/builtins/statistical-builtins.d.ts +13 -0
- package/dist/builtins/statistical-builtins.js +240 -0
- package/dist/builtins/statistical-builtins.js.map +1 -0
- package/dist/builtins/statistics.d.ts +8 -0
- package/dist/builtins/statistics.js +74 -0
- package/dist/builtins/statistics.js.map +1 -0
- package/dist/builtins/text.d.ts +5 -0
- package/dist/builtins/text.js +1879 -0
- package/dist/builtins/text.js.map +1 -0
- package/dist/builtins.d.ts +8 -0
- package/dist/builtins.js +695 -0
- package/dist/builtins.js.map +1 -0
- package/dist/compatibility.d.ts +25 -0
- package/dist/compatibility.js +498 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/compiler.d.ts +29 -0
- package/dist/compiler.js +474 -0
- package/dist/compiler.js.map +1 -0
- package/dist/external-function-adapter.d.ts +32 -0
- package/dist/external-function-adapter.js +42 -0
- package/dist/external-function-adapter.js.map +1 -0
- package/dist/generated/formula-inventory.d.ts +6839 -0
- package/dist/generated/formula-inventory.js +7368 -0
- package/dist/generated/formula-inventory.js.map +1 -0
- package/dist/group-pivot-evaluator.d.ts +28 -0
- package/dist/group-pivot-evaluator.js +435 -0
- package/dist/group-pivot-evaluator.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/js-evaluator.d.ts +107 -0
- package/dist/js-evaluator.js +1651 -0
- package/dist/js-evaluator.js.map +1 -0
- package/dist/lexer.d.ts +6 -0
- package/dist/lexer.js +115 -0
- package/dist/lexer.js.map +1 -0
- package/dist/optimizer.d.ts +2 -0
- package/dist/optimizer.js +353 -0
- package/dist/optimizer.js.map +1 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +352 -0
- package/dist/parser.js.map +1 -0
- package/dist/program-arena.d.ts +22 -0
- package/dist/program-arena.js +67 -0
- package/dist/program-arena.js.map +1 -0
- package/dist/runtime-values.d.ts +17 -0
- package/dist/runtime-values.js +11 -0
- package/dist/runtime-values.js.map +1 -0
- package/dist/special-call-rewrites.d.ts +2 -0
- package/dist/special-call-rewrites.js +74 -0
- package/dist/special-call-rewrites.js.map +1 -0
- package/dist/translation.d.ts +28 -0
- package/dist/translation.js +569 -0
- package/dist/translation.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1879 @@
|
|
|
1
|
+
import { ErrorCode, ValueTag } from "@bilig/protocol";
|
|
2
|
+
import { excelSerialToDateParts } from "./datetime.js";
|
|
3
|
+
import { createBlockedBuiltinMap, textPlaceholderBuiltinNames } from "./placeholder.js";
|
|
4
|
+
function error(code) {
|
|
5
|
+
return { tag: ValueTag.Error, code };
|
|
6
|
+
}
|
|
7
|
+
function stringResult(value) {
|
|
8
|
+
return { tag: ValueTag.String, value, stringId: 0 };
|
|
9
|
+
}
|
|
10
|
+
function numberResult(value) {
|
|
11
|
+
return { tag: ValueTag.Number, value };
|
|
12
|
+
}
|
|
13
|
+
function booleanResult(value) {
|
|
14
|
+
return { tag: ValueTag.Boolean, value };
|
|
15
|
+
}
|
|
16
|
+
function firstError(args) {
|
|
17
|
+
return args.find((arg) => arg?.tag === ValueTag.Error);
|
|
18
|
+
}
|
|
19
|
+
function coerceText(value) {
|
|
20
|
+
switch (value.tag) {
|
|
21
|
+
case ValueTag.Empty:
|
|
22
|
+
return "";
|
|
23
|
+
case ValueTag.Number:
|
|
24
|
+
return String(value.value);
|
|
25
|
+
case ValueTag.Boolean:
|
|
26
|
+
return value.value ? "TRUE" : "FALSE";
|
|
27
|
+
case ValueTag.String:
|
|
28
|
+
return value.value;
|
|
29
|
+
case ValueTag.Error:
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const utf8Encoder = new TextEncoder();
|
|
34
|
+
const utf8Decoder = new TextDecoder();
|
|
35
|
+
function utf8Bytes(value) {
|
|
36
|
+
return utf8Encoder.encode(value);
|
|
37
|
+
}
|
|
38
|
+
function utf8Text(bytes) {
|
|
39
|
+
return utf8Decoder.decode(bytes);
|
|
40
|
+
}
|
|
41
|
+
function findSubBytes(haystack, needle, start) {
|
|
42
|
+
if (needle.length === 0) {
|
|
43
|
+
return Math.max(0, Math.min(start, haystack.length));
|
|
44
|
+
}
|
|
45
|
+
for (let index = start; index + needle.length <= haystack.length; index += 1) {
|
|
46
|
+
let match = true;
|
|
47
|
+
for (let offset = 0; offset < needle.length; offset += 1) {
|
|
48
|
+
if (haystack[index + offset] !== needle[offset]) {
|
|
49
|
+
match = false;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (match) {
|
|
54
|
+
return index;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
59
|
+
function leftBytes(text, byteCount) {
|
|
60
|
+
const bytes = utf8Bytes(text);
|
|
61
|
+
const normalizedCount = Math.max(0, Math.min(byteCount, bytes.length));
|
|
62
|
+
return utf8Text(bytes.slice(0, normalizedCount));
|
|
63
|
+
}
|
|
64
|
+
function rightBytes(text, byteCount) {
|
|
65
|
+
const bytes = utf8Bytes(text);
|
|
66
|
+
const normalizedCount = Math.max(0, Math.min(byteCount, bytes.length));
|
|
67
|
+
return utf8Text(bytes.slice(bytes.length - normalizedCount));
|
|
68
|
+
}
|
|
69
|
+
function midBytes(text, start, byteCount) {
|
|
70
|
+
const bytes = utf8Bytes(text);
|
|
71
|
+
if (byteCount <= 0) {
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
const zeroBasedStart = Math.max(0, start - 1);
|
|
75
|
+
const zeroBasedEnd = Math.min(bytes.length, zeroBasedStart + byteCount);
|
|
76
|
+
if (zeroBasedStart >= bytes.length) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
return utf8Text(bytes.slice(zeroBasedStart, zeroBasedEnd));
|
|
80
|
+
}
|
|
81
|
+
function replaceBytes(text, start, byteCount, replacement) {
|
|
82
|
+
const bytes = utf8Bytes(text);
|
|
83
|
+
const replacementBytes = utf8Bytes(replacement);
|
|
84
|
+
const zeroBasedStart = Math.max(0, start - 1);
|
|
85
|
+
if (zeroBasedStart >= bytes.length) {
|
|
86
|
+
return text;
|
|
87
|
+
}
|
|
88
|
+
const zeroBasedEnd = Math.min(bytes.length, zeroBasedStart + Math.max(0, byteCount));
|
|
89
|
+
return utf8Text(new Uint8Array([
|
|
90
|
+
...bytes.slice(0, zeroBasedStart),
|
|
91
|
+
...replacementBytes,
|
|
92
|
+
...bytes.slice(zeroBasedEnd),
|
|
93
|
+
]));
|
|
94
|
+
}
|
|
95
|
+
function bytePositionToCharPosition(text, startByte) {
|
|
96
|
+
if (startByte <= 1) {
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
return utf8Text(utf8Bytes(text).slice(0, startByte - 1)).length + 1;
|
|
100
|
+
}
|
|
101
|
+
function charPositionToBytePosition(text, charPosition) {
|
|
102
|
+
return utf8Bytes(text.slice(0, Math.max(0, charPosition - 1))).length + 1;
|
|
103
|
+
}
|
|
104
|
+
function coerceNumber(value) {
|
|
105
|
+
switch (value.tag) {
|
|
106
|
+
case ValueTag.Number:
|
|
107
|
+
return value.value;
|
|
108
|
+
case ValueTag.Boolean:
|
|
109
|
+
return value.value ? 1 : 0;
|
|
110
|
+
case ValueTag.Empty:
|
|
111
|
+
return 0;
|
|
112
|
+
case ValueTag.String: {
|
|
113
|
+
const trimmed = value.value.trim();
|
|
114
|
+
if (trimmed === "") {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
const parsed = Number(trimmed);
|
|
118
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
119
|
+
}
|
|
120
|
+
case ValueTag.Error:
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function coerceBoolean(value, fallback) {
|
|
125
|
+
if (value.tag === ValueTag.Boolean) {
|
|
126
|
+
return value.value;
|
|
127
|
+
}
|
|
128
|
+
if (value.tag === ValueTag.Empty) {
|
|
129
|
+
return fallback;
|
|
130
|
+
}
|
|
131
|
+
const numeric = coerceNumber(value);
|
|
132
|
+
return numeric === undefined ? error(ErrorCode.Value) : numeric !== 0;
|
|
133
|
+
}
|
|
134
|
+
function coerceInteger(value, defaultValue) {
|
|
135
|
+
if (value === undefined) {
|
|
136
|
+
return defaultValue;
|
|
137
|
+
}
|
|
138
|
+
const numeric = coerceNumber(value);
|
|
139
|
+
if (numeric === undefined || !Number.isInteger(numeric)) {
|
|
140
|
+
return error(ErrorCode.Value);
|
|
141
|
+
}
|
|
142
|
+
return numeric;
|
|
143
|
+
}
|
|
144
|
+
function coercePositiveStart(value, defaultValue) {
|
|
145
|
+
if (value === undefined) {
|
|
146
|
+
return defaultValue;
|
|
147
|
+
}
|
|
148
|
+
const numeric = coerceNumber(value);
|
|
149
|
+
if (numeric === undefined) {
|
|
150
|
+
return error(ErrorCode.Value);
|
|
151
|
+
}
|
|
152
|
+
const truncated = Math.trunc(numeric);
|
|
153
|
+
return truncated >= 1 ? truncated : error(ErrorCode.Value);
|
|
154
|
+
}
|
|
155
|
+
function coerceLength(value, defaultValue) {
|
|
156
|
+
if (value === undefined) {
|
|
157
|
+
return defaultValue;
|
|
158
|
+
}
|
|
159
|
+
const numeric = coerceNumber(value);
|
|
160
|
+
if (numeric === undefined) {
|
|
161
|
+
return error(ErrorCode.Value);
|
|
162
|
+
}
|
|
163
|
+
const truncated = Math.trunc(numeric);
|
|
164
|
+
return truncated >= 0 ? truncated : error(ErrorCode.Value);
|
|
165
|
+
}
|
|
166
|
+
function isErrorValue(value) {
|
|
167
|
+
return typeof value !== "number";
|
|
168
|
+
}
|
|
169
|
+
function coerceNonNegativeInt(value, defaultValue) {
|
|
170
|
+
if (value === undefined) {
|
|
171
|
+
return defaultValue;
|
|
172
|
+
}
|
|
173
|
+
const numeric = coerceNumber(value);
|
|
174
|
+
if (numeric === undefined) {
|
|
175
|
+
return error(ErrorCode.Value);
|
|
176
|
+
}
|
|
177
|
+
const truncated = Math.trunc(numeric);
|
|
178
|
+
return truncated >= 0 ? truncated : error(ErrorCode.Value);
|
|
179
|
+
}
|
|
180
|
+
function replaceSingle(text, start, count, replacement) {
|
|
181
|
+
const index = start - 1;
|
|
182
|
+
if (index >= text.length) {
|
|
183
|
+
return text;
|
|
184
|
+
}
|
|
185
|
+
return text.slice(0, index) + replacement + text.slice(index + count);
|
|
186
|
+
}
|
|
187
|
+
function substituteText(text, oldText, newText, instance) {
|
|
188
|
+
if (oldText === "") {
|
|
189
|
+
return text;
|
|
190
|
+
}
|
|
191
|
+
if (instance === undefined) {
|
|
192
|
+
if (!text.includes(oldText)) {
|
|
193
|
+
return text;
|
|
194
|
+
}
|
|
195
|
+
return text.split(oldText).join(newText);
|
|
196
|
+
}
|
|
197
|
+
let occurrence = 0;
|
|
198
|
+
let searchIndex = 0;
|
|
199
|
+
while (searchIndex <= text.length) {
|
|
200
|
+
const foundAt = text.indexOf(oldText, searchIndex);
|
|
201
|
+
if (foundAt === -1) {
|
|
202
|
+
return text;
|
|
203
|
+
}
|
|
204
|
+
occurrence += 1;
|
|
205
|
+
if (occurrence === instance) {
|
|
206
|
+
return text.slice(0, foundAt) + newText + text.slice(foundAt + oldText.length);
|
|
207
|
+
}
|
|
208
|
+
searchIndex = foundAt + oldText.length;
|
|
209
|
+
}
|
|
210
|
+
return text;
|
|
211
|
+
}
|
|
212
|
+
function regexFlags(caseSensitivity, global = false) {
|
|
213
|
+
return `${global ? "g" : ""}${caseSensitivity === 1 ? "i" : ""}`;
|
|
214
|
+
}
|
|
215
|
+
function compileRegex(pattern, caseSensitivity, global = false) {
|
|
216
|
+
try {
|
|
217
|
+
return new RegExp(pattern, regexFlags(caseSensitivity, global));
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return error(ErrorCode.Value);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function isRegexError(value) {
|
|
224
|
+
return !(value instanceof RegExp);
|
|
225
|
+
}
|
|
226
|
+
function applyReplacementTemplate(template, match, captures) {
|
|
227
|
+
return template.replace(/\$(\$|&|[0-9]{1,2})/g, (_whole, token) => {
|
|
228
|
+
if (token === "$") {
|
|
229
|
+
return "$";
|
|
230
|
+
}
|
|
231
|
+
if (token === "&") {
|
|
232
|
+
return match;
|
|
233
|
+
}
|
|
234
|
+
const index = Number(token);
|
|
235
|
+
if (!Number.isInteger(index) || index <= 0) {
|
|
236
|
+
return "";
|
|
237
|
+
}
|
|
238
|
+
return captures[index - 1] ?? "";
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function valueToTextResult(value, format) {
|
|
242
|
+
if (format !== 0 && format !== 1) {
|
|
243
|
+
return error(ErrorCode.Value);
|
|
244
|
+
}
|
|
245
|
+
switch (value.tag) {
|
|
246
|
+
case ValueTag.Empty:
|
|
247
|
+
return stringResult("");
|
|
248
|
+
case ValueTag.Number:
|
|
249
|
+
return stringResult(String(value.value));
|
|
250
|
+
case ValueTag.Boolean:
|
|
251
|
+
return stringResult(value.value ? "TRUE" : "FALSE");
|
|
252
|
+
case ValueTag.String:
|
|
253
|
+
return stringResult(format === 1 ? JSON.stringify(value.value) : value.value);
|
|
254
|
+
case ValueTag.Error: {
|
|
255
|
+
const label = value.code === ErrorCode.Div0
|
|
256
|
+
? "#DIV/0!"
|
|
257
|
+
: value.code === ErrorCode.Ref
|
|
258
|
+
? "#REF!"
|
|
259
|
+
: value.code === ErrorCode.Value
|
|
260
|
+
? "#VALUE!"
|
|
261
|
+
: value.code === ErrorCode.Name
|
|
262
|
+
? "#NAME?"
|
|
263
|
+
: value.code === ErrorCode.NA
|
|
264
|
+
? "#N/A"
|
|
265
|
+
: value.code === ErrorCode.Cycle
|
|
266
|
+
? "#CYCLE!"
|
|
267
|
+
: value.code === ErrorCode.Spill
|
|
268
|
+
? "#SPILL!"
|
|
269
|
+
: value.code === ErrorCode.Blocked
|
|
270
|
+
? "#BLOCKED!"
|
|
271
|
+
: "#ERROR!";
|
|
272
|
+
return stringResult(label);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const shortMonthNames = [
|
|
277
|
+
"Jan",
|
|
278
|
+
"Feb",
|
|
279
|
+
"Mar",
|
|
280
|
+
"Apr",
|
|
281
|
+
"May",
|
|
282
|
+
"Jun",
|
|
283
|
+
"Jul",
|
|
284
|
+
"Aug",
|
|
285
|
+
"Sep",
|
|
286
|
+
"Oct",
|
|
287
|
+
"Nov",
|
|
288
|
+
"Dec",
|
|
289
|
+
];
|
|
290
|
+
const fullMonthNames = [
|
|
291
|
+
"January",
|
|
292
|
+
"February",
|
|
293
|
+
"March",
|
|
294
|
+
"April",
|
|
295
|
+
"May",
|
|
296
|
+
"June",
|
|
297
|
+
"July",
|
|
298
|
+
"August",
|
|
299
|
+
"September",
|
|
300
|
+
"October",
|
|
301
|
+
"November",
|
|
302
|
+
"December",
|
|
303
|
+
];
|
|
304
|
+
const shortWeekdayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
305
|
+
const fullWeekdayNames = [
|
|
306
|
+
"Sunday",
|
|
307
|
+
"Monday",
|
|
308
|
+
"Tuesday",
|
|
309
|
+
"Wednesday",
|
|
310
|
+
"Thursday",
|
|
311
|
+
"Friday",
|
|
312
|
+
"Saturday",
|
|
313
|
+
];
|
|
314
|
+
function splitFormatSections(format) {
|
|
315
|
+
const sections = [];
|
|
316
|
+
let current = "";
|
|
317
|
+
let inQuotes = false;
|
|
318
|
+
let bracketDepth = 0;
|
|
319
|
+
let escaped = false;
|
|
320
|
+
for (let index = 0; index < format.length; index += 1) {
|
|
321
|
+
const char = format[index];
|
|
322
|
+
if (escaped) {
|
|
323
|
+
current += char;
|
|
324
|
+
escaped = false;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (char === "\\") {
|
|
328
|
+
current += char;
|
|
329
|
+
escaped = true;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (char === '"') {
|
|
333
|
+
current += char;
|
|
334
|
+
inQuotes = !inQuotes;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (!inQuotes && char === "[") {
|
|
338
|
+
bracketDepth += 1;
|
|
339
|
+
current += char;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (!inQuotes && char === "]" && bracketDepth > 0) {
|
|
343
|
+
bracketDepth -= 1;
|
|
344
|
+
current += char;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (!inQuotes && bracketDepth === 0 && char === ";") {
|
|
348
|
+
sections.push(current);
|
|
349
|
+
current = "";
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
current += char;
|
|
353
|
+
}
|
|
354
|
+
sections.push(current);
|
|
355
|
+
return sections;
|
|
356
|
+
}
|
|
357
|
+
function stripFormatDecorations(section) {
|
|
358
|
+
let output = "";
|
|
359
|
+
let inQuotes = false;
|
|
360
|
+
for (let index = 0; index < section.length; index += 1) {
|
|
361
|
+
const char = section[index];
|
|
362
|
+
if (inQuotes) {
|
|
363
|
+
if (char === '"') {
|
|
364
|
+
inQuotes = false;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
output += char;
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (char === '"') {
|
|
372
|
+
inQuotes = true;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (char === "\\") {
|
|
376
|
+
output += section[index + 1] ?? "";
|
|
377
|
+
index += 1;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (char === "_") {
|
|
381
|
+
output += " ";
|
|
382
|
+
index += 1;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (char === "*") {
|
|
386
|
+
index += 1;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (char === "[") {
|
|
390
|
+
const end = section.indexOf("]", index + 1);
|
|
391
|
+
if (end === -1) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
index = end;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
output += char;
|
|
398
|
+
}
|
|
399
|
+
return output;
|
|
400
|
+
}
|
|
401
|
+
function formatThousandsText(integerPart) {
|
|
402
|
+
return integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
403
|
+
}
|
|
404
|
+
function zeroPadText(value, width) {
|
|
405
|
+
return String(Math.trunc(Math.abs(value))).padStart(width, "0");
|
|
406
|
+
}
|
|
407
|
+
function roundToDigits(value, digits) {
|
|
408
|
+
if (!Number.isFinite(value)) {
|
|
409
|
+
return Number.NaN;
|
|
410
|
+
}
|
|
411
|
+
const factor = 10 ** Math.max(0, digits);
|
|
412
|
+
return Math.round((value + Number.EPSILON) * factor) / factor;
|
|
413
|
+
}
|
|
414
|
+
function excelSecondOfDay(serial) {
|
|
415
|
+
if (!Number.isFinite(serial)) {
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
const whole = Math.floor(serial);
|
|
419
|
+
let fraction = serial - whole;
|
|
420
|
+
if (fraction < 0) {
|
|
421
|
+
fraction += 1;
|
|
422
|
+
}
|
|
423
|
+
let seconds = Math.floor(fraction * 86_400 + 1e-9);
|
|
424
|
+
if (seconds >= 86_400) {
|
|
425
|
+
seconds = 0;
|
|
426
|
+
}
|
|
427
|
+
return seconds;
|
|
428
|
+
}
|
|
429
|
+
function excelWeekdayIndex(serial) {
|
|
430
|
+
if (!Number.isFinite(serial)) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
const whole = Math.floor(serial);
|
|
434
|
+
if (whole < 0) {
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
const adjustedWhole = whole < 60 ? whole : whole - 1;
|
|
438
|
+
return ((adjustedWhole % 7) + 7) % 7;
|
|
439
|
+
}
|
|
440
|
+
function isDateTimeFormat(section) {
|
|
441
|
+
const cleaned = stripFormatDecorations(section).toUpperCase();
|
|
442
|
+
return (cleaned.includes("AM/PM") ||
|
|
443
|
+
cleaned.includes("A/P") ||
|
|
444
|
+
/[YDSH]/.test(cleaned) ||
|
|
445
|
+
/(^|[^0#?])M+([^0#?]|$)/.test(cleaned));
|
|
446
|
+
}
|
|
447
|
+
function isTextFormat(section) {
|
|
448
|
+
return stripFormatDecorations(section).includes("@");
|
|
449
|
+
}
|
|
450
|
+
function chooseFormatSection(value, formatText) {
|
|
451
|
+
const sections = splitFormatSections(formatText);
|
|
452
|
+
if (value.tag === ValueTag.String) {
|
|
453
|
+
return { section: sections[3] ?? sections[0] ?? "", autoNegative: false };
|
|
454
|
+
}
|
|
455
|
+
const numeric = coerceNumber(value);
|
|
456
|
+
if (numeric === undefined) {
|
|
457
|
+
return error(ErrorCode.Value);
|
|
458
|
+
}
|
|
459
|
+
if (numeric < 0) {
|
|
460
|
+
if (sections[1] !== undefined) {
|
|
461
|
+
return { section: sections[1], numeric: -numeric, autoNegative: false };
|
|
462
|
+
}
|
|
463
|
+
return { section: sections[0] ?? "", numeric: -numeric, autoNegative: true };
|
|
464
|
+
}
|
|
465
|
+
if (numeric === 0 && sections[2] !== undefined) {
|
|
466
|
+
return { section: sections[2], numeric, autoNegative: false };
|
|
467
|
+
}
|
|
468
|
+
return { section: sections[0] ?? "", numeric, autoNegative: false };
|
|
469
|
+
}
|
|
470
|
+
function formatTextSectionValue(value, section) {
|
|
471
|
+
const cleaned = stripFormatDecorations(section);
|
|
472
|
+
return cleaned.includes("@") ? cleaned.replace(/@/g, value) : cleaned;
|
|
473
|
+
}
|
|
474
|
+
function tokenizeDateTimeFormat(section) {
|
|
475
|
+
const cleaned = stripFormatDecorations(section);
|
|
476
|
+
const tokens = [];
|
|
477
|
+
let index = 0;
|
|
478
|
+
while (index < cleaned.length) {
|
|
479
|
+
const remainder = cleaned.slice(index);
|
|
480
|
+
const upperRemainder = remainder.toUpperCase();
|
|
481
|
+
if (upperRemainder.startsWith("AM/PM")) {
|
|
482
|
+
tokens.push({ kind: "ampm", text: cleaned.slice(index, index + 5) });
|
|
483
|
+
index += 5;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (upperRemainder.startsWith("A/P")) {
|
|
487
|
+
tokens.push({ kind: "ampm", text: cleaned.slice(index, index + 3) });
|
|
488
|
+
index += 3;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const char = cleaned[index];
|
|
492
|
+
const lower = char.toLowerCase();
|
|
493
|
+
if ("ymdhms".includes(lower)) {
|
|
494
|
+
let end = index + 1;
|
|
495
|
+
while (end < cleaned.length && cleaned[end].toLowerCase() === lower) {
|
|
496
|
+
end += 1;
|
|
497
|
+
}
|
|
498
|
+
const tokenText = cleaned.slice(index, end);
|
|
499
|
+
const baseKind = lower === "y"
|
|
500
|
+
? "year"
|
|
501
|
+
: lower === "d"
|
|
502
|
+
? "day"
|
|
503
|
+
: lower === "h"
|
|
504
|
+
? "hour"
|
|
505
|
+
: lower === "s"
|
|
506
|
+
? "second"
|
|
507
|
+
: "month";
|
|
508
|
+
tokens.push({ kind: baseKind, text: tokenText });
|
|
509
|
+
index = end;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
tokens.push({ kind: "literal", text: char });
|
|
513
|
+
index += 1;
|
|
514
|
+
}
|
|
515
|
+
return tokens.map((token, tokenIndex, allTokens) => {
|
|
516
|
+
if (token.kind !== "month") {
|
|
517
|
+
return token;
|
|
518
|
+
}
|
|
519
|
+
const previous = allTokens.slice(0, tokenIndex).findLast((entry) => entry.kind !== "literal");
|
|
520
|
+
const next = allTokens.slice(tokenIndex + 1).find((entry) => entry.kind !== "literal");
|
|
521
|
+
if (previous?.kind === "hour" || previous?.kind === "minute" || next?.kind === "second") {
|
|
522
|
+
return { kind: "minute", text: token.text };
|
|
523
|
+
}
|
|
524
|
+
return token;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function formatAmPmToken(token, hour) {
|
|
528
|
+
const isPm = hour >= 12;
|
|
529
|
+
const upper = token.toUpperCase();
|
|
530
|
+
if (upper === "A/P") {
|
|
531
|
+
const letter = isPm ? "P" : "A";
|
|
532
|
+
return token === token.toLowerCase() ? letter.toLowerCase() : letter;
|
|
533
|
+
}
|
|
534
|
+
if (token === token.toLowerCase()) {
|
|
535
|
+
return isPm ? "pm" : "am";
|
|
536
|
+
}
|
|
537
|
+
return isPm ? "PM" : "AM";
|
|
538
|
+
}
|
|
539
|
+
function formatDateTimeSectionValue(serial, section) {
|
|
540
|
+
const dateParts = excelSerialToDateParts(serial);
|
|
541
|
+
const weekdayIndex = excelWeekdayIndex(serial);
|
|
542
|
+
const secondOfDay = excelSecondOfDay(serial);
|
|
543
|
+
if (!dateParts || weekdayIndex === undefined || secondOfDay === undefined) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
const hour24 = Math.floor(secondOfDay / 3600);
|
|
547
|
+
const minute = Math.floor((secondOfDay % 3600) / 60);
|
|
548
|
+
const second = secondOfDay % 60;
|
|
549
|
+
const tokens = tokenizeDateTimeFormat(section);
|
|
550
|
+
const hasAmPm = tokens.some((token) => token.kind === "ampm");
|
|
551
|
+
return tokens
|
|
552
|
+
.map((token) => {
|
|
553
|
+
switch (token.kind) {
|
|
554
|
+
case "literal":
|
|
555
|
+
return token.text;
|
|
556
|
+
case "year":
|
|
557
|
+
return token.text.length === 2
|
|
558
|
+
? zeroPadText(dateParts.year % 100, 2)
|
|
559
|
+
: String(dateParts.year).padStart(Math.max(4, token.text.length), "0");
|
|
560
|
+
case "month":
|
|
561
|
+
return token.text.length === 1
|
|
562
|
+
? String(dateParts.month)
|
|
563
|
+
: token.text.length === 2
|
|
564
|
+
? zeroPadText(dateParts.month, 2)
|
|
565
|
+
: token.text.length === 3
|
|
566
|
+
? shortMonthNames[dateParts.month - 1]
|
|
567
|
+
: fullMonthNames[dateParts.month - 1];
|
|
568
|
+
case "minute":
|
|
569
|
+
return token.text.length >= 2 ? zeroPadText(minute, 2) : String(minute);
|
|
570
|
+
case "day":
|
|
571
|
+
return token.text.length === 1
|
|
572
|
+
? String(dateParts.day)
|
|
573
|
+
: token.text.length === 2
|
|
574
|
+
? zeroPadText(dateParts.day, 2)
|
|
575
|
+
: token.text.length === 3
|
|
576
|
+
? shortWeekdayNames[weekdayIndex]
|
|
577
|
+
: fullWeekdayNames[weekdayIndex];
|
|
578
|
+
case "hour": {
|
|
579
|
+
const normalizedHour = hasAmPm ? ((hour24 + 11) % 12) + 1 : hour24;
|
|
580
|
+
return token.text.length >= 2 ? zeroPadText(normalizedHour, 2) : String(normalizedHour);
|
|
581
|
+
}
|
|
582
|
+
case "second":
|
|
583
|
+
return token.text.length >= 2 ? zeroPadText(second, 2) : String(second);
|
|
584
|
+
case "ampm":
|
|
585
|
+
return formatAmPmToken(token.text, hour24);
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
.join("");
|
|
589
|
+
}
|
|
590
|
+
function trimOptionalFractionDigits(fraction, minDigits) {
|
|
591
|
+
let trimmed = fraction;
|
|
592
|
+
while (trimmed.length > minDigits && trimmed.endsWith("0")) {
|
|
593
|
+
trimmed = trimmed.slice(0, -1);
|
|
594
|
+
}
|
|
595
|
+
return trimmed;
|
|
596
|
+
}
|
|
597
|
+
function formatScientificSection(value, core) {
|
|
598
|
+
const exponentIndex = core.search(/[Ee][+-]/);
|
|
599
|
+
const mantissaPattern = core.slice(0, exponentIndex);
|
|
600
|
+
const exponentPattern = core.slice(exponentIndex + 2);
|
|
601
|
+
const mantissaParts = mantissaPattern.split(".");
|
|
602
|
+
const fractionPattern = mantissaParts[1] ?? "";
|
|
603
|
+
const maxFractionDigits = (fractionPattern.match(/[0#?]/g) ?? []).length;
|
|
604
|
+
const minFractionDigits = (fractionPattern.match(/0/g) ?? []).length;
|
|
605
|
+
const [mantissaRaw = "0", exponentRaw] = value.toExponential(maxFractionDigits).split("e");
|
|
606
|
+
let [integerPart = "0", fractionPart = ""] = mantissaRaw.split(".");
|
|
607
|
+
fractionPart = trimOptionalFractionDigits(fractionPart, minFractionDigits);
|
|
608
|
+
const exponentValue = Number(exponentRaw ?? 0);
|
|
609
|
+
const exponentText = String(Math.abs(exponentValue)).padStart(exponentPattern.length, "0");
|
|
610
|
+
return `${integerPart}${fractionPart === "" ? "" : `.${fractionPart}`}E${exponentValue < 0 ? "-" : "+"}${exponentText}`;
|
|
611
|
+
}
|
|
612
|
+
function formatNumericSectionValue(value, section, autoNegative) {
|
|
613
|
+
const cleaned = stripFormatDecorations(section);
|
|
614
|
+
if (!/[0#?]/.test(cleaned)) {
|
|
615
|
+
return autoNegative && !cleaned.startsWith("-") ? `-${cleaned}` : cleaned;
|
|
616
|
+
}
|
|
617
|
+
const firstPlaceholder = cleaned.search(/[0#?]/);
|
|
618
|
+
let lastPlaceholder = -1;
|
|
619
|
+
for (let index = cleaned.length - 1; index >= 0; index -= 1) {
|
|
620
|
+
if (/[0#?]/.test(cleaned[index])) {
|
|
621
|
+
lastPlaceholder = index;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const prefix = cleaned.slice(0, firstPlaceholder);
|
|
626
|
+
const core = cleaned.slice(firstPlaceholder, lastPlaceholder + 1);
|
|
627
|
+
const suffix = cleaned.slice(lastPlaceholder + 1);
|
|
628
|
+
const percentCount = (cleaned.match(/%/g) ?? []).length;
|
|
629
|
+
const scaledValue = Math.abs(value) * 100 ** percentCount;
|
|
630
|
+
let numericText = "";
|
|
631
|
+
if (/[Ee][+-]/.test(core)) {
|
|
632
|
+
numericText = formatScientificSection(scaledValue, core);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
const decimalIndex = core.indexOf(".");
|
|
636
|
+
const integerPattern = (decimalIndex === -1 ? core : core.slice(0, decimalIndex)).replaceAll(",", "");
|
|
637
|
+
const fractionPattern = decimalIndex === -1 ? "" : core.slice(decimalIndex + 1);
|
|
638
|
+
const maxFractionDigits = (fractionPattern.match(/[0#?]/g) ?? []).length;
|
|
639
|
+
const minFractionDigits = (fractionPattern.match(/0/g) ?? []).length;
|
|
640
|
+
const minIntegerDigits = (integerPattern.match(/0/g) ?? []).length;
|
|
641
|
+
const roundedValue = roundToDigits(scaledValue, maxFractionDigits);
|
|
642
|
+
const fixed = roundedValue.toFixed(maxFractionDigits);
|
|
643
|
+
let [integerPart = "0", fractionPart = ""] = fixed.split(".");
|
|
644
|
+
if (integerPart.length < minIntegerDigits) {
|
|
645
|
+
integerPart = integerPart.padStart(minIntegerDigits, "0");
|
|
646
|
+
}
|
|
647
|
+
if (core.includes(",")) {
|
|
648
|
+
integerPart = formatThousandsText(integerPart);
|
|
649
|
+
}
|
|
650
|
+
fractionPart = trimOptionalFractionDigits(fractionPart, minFractionDigits);
|
|
651
|
+
numericText = `${integerPart}${fractionPart === "" ? "" : `.${fractionPart}`}`;
|
|
652
|
+
}
|
|
653
|
+
const combined = `${prefix}${numericText}${suffix}`;
|
|
654
|
+
return autoNegative && !combined.startsWith("-") ? `-${combined}` : combined;
|
|
655
|
+
}
|
|
656
|
+
function formatTextBuiltinValue(value, formatText) {
|
|
657
|
+
const chosen = chooseFormatSection(value, formatText);
|
|
658
|
+
if ("tag" in chosen) {
|
|
659
|
+
return chosen;
|
|
660
|
+
}
|
|
661
|
+
const { section, numeric, autoNegative } = chosen;
|
|
662
|
+
if (value.tag === ValueTag.String) {
|
|
663
|
+
const cleaned = stripFormatDecorations(section);
|
|
664
|
+
if (isTextFormat(section) || !/[0#?YMDHS]/i.test(cleaned)) {
|
|
665
|
+
return stringResult(formatTextSectionValue(value.value, section));
|
|
666
|
+
}
|
|
667
|
+
return error(ErrorCode.Value);
|
|
668
|
+
}
|
|
669
|
+
if (numeric === undefined) {
|
|
670
|
+
return error(ErrorCode.Value);
|
|
671
|
+
}
|
|
672
|
+
if (isDateTimeFormat(section)) {
|
|
673
|
+
const formatted = formatDateTimeSectionValue(numeric, section);
|
|
674
|
+
return formatted === undefined ? error(ErrorCode.Value) : stringResult(formatted);
|
|
675
|
+
}
|
|
676
|
+
return stringResult(formatNumericSectionValue(numeric, section, autoNegative));
|
|
677
|
+
}
|
|
678
|
+
function parseNumberValueText(input, decimalSeparator, groupSeparator) {
|
|
679
|
+
const compact = input.replaceAll(/\s+/g, "");
|
|
680
|
+
if (compact === "") {
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
const percentMatch = compact.match(/%+$/);
|
|
684
|
+
const percentCount = percentMatch?.[0].length ?? 0;
|
|
685
|
+
const core = percentCount === 0 ? compact : compact.slice(0, -percentCount);
|
|
686
|
+
if (core.includes("%")) {
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
if (decimalSeparator !== "" && groupSeparator !== "" && decimalSeparator === groupSeparator) {
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
const decimal = decimalSeparator === "" ? "." : decimalSeparator[0];
|
|
693
|
+
const group = groupSeparator === "" ? "" : groupSeparator[0];
|
|
694
|
+
const decimalIndex = decimal === "" ? -1 : core.indexOf(decimal);
|
|
695
|
+
if (decimalIndex !== -1 && core.indexOf(decimal, decimalIndex + 1) !== -1) {
|
|
696
|
+
return undefined;
|
|
697
|
+
}
|
|
698
|
+
let normalized = core;
|
|
699
|
+
if (group !== "") {
|
|
700
|
+
const groupAfterDecimal = decimalIndex === -1 ? -1 : normalized.indexOf(group, decimalIndex + decimal.length);
|
|
701
|
+
if (groupAfterDecimal !== -1) {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
normalized = normalized.replaceAll(group, "");
|
|
705
|
+
}
|
|
706
|
+
if (decimal !== "." && decimal !== "") {
|
|
707
|
+
normalized = normalized.replace(decimal, ".");
|
|
708
|
+
}
|
|
709
|
+
if (normalized === "" || normalized === "." || normalized === "+" || normalized === "-") {
|
|
710
|
+
return undefined;
|
|
711
|
+
}
|
|
712
|
+
const parsed = Number(normalized);
|
|
713
|
+
if (!Number.isFinite(parsed)) {
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
return parsed / 100 ** percentCount;
|
|
717
|
+
}
|
|
718
|
+
const bahtDigitWords = ["ศูนย์", "หนึ่ง", "สอง", "สาม", "สี่", "ห้า", "หก", "เจ็ด", "แปด", "เก้า"];
|
|
719
|
+
const bahtScaleWords = ["", "สิบ", "ร้อย", "พัน", "หมื่น", "แสน"];
|
|
720
|
+
const maxBahtTextSatang = Number.MAX_SAFE_INTEGER;
|
|
721
|
+
function bahtSegmentText(digits) {
|
|
722
|
+
const normalized = digits.replace(/^0+(?=\d)/u, "");
|
|
723
|
+
if (normalized === "" || /^0+$/u.test(normalized)) {
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
let output = "";
|
|
727
|
+
let hasHigherNonZero = false;
|
|
728
|
+
const length = normalized.length;
|
|
729
|
+
for (let index = 0; index < length; index += 1) {
|
|
730
|
+
const digit = normalized.charCodeAt(index) - 48;
|
|
731
|
+
if (digit === 0) {
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
const position = length - index - 1;
|
|
735
|
+
if (position === 0) {
|
|
736
|
+
output += digit === 1 && hasHigherNonZero ? "เอ็ด" : bahtDigitWords[digit];
|
|
737
|
+
}
|
|
738
|
+
else if (position === 1) {
|
|
739
|
+
output += digit === 1 ? "สิบ" : digit === 2 ? "ยี่สิบ" : `${bahtDigitWords[digit]}สิบ`;
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
output += `${bahtDigitWords[digit]}${bahtScaleWords[position]}`;
|
|
743
|
+
}
|
|
744
|
+
hasHigherNonZero = true;
|
|
745
|
+
}
|
|
746
|
+
return output;
|
|
747
|
+
}
|
|
748
|
+
function bahtIntegerText(digits) {
|
|
749
|
+
const normalized = digits.replace(/^0+(?=\d)/u, "");
|
|
750
|
+
if (normalized === "" || /^0+$/u.test(normalized)) {
|
|
751
|
+
return bahtDigitWords[0];
|
|
752
|
+
}
|
|
753
|
+
if (normalized.length > 6) {
|
|
754
|
+
const head = bahtIntegerText(normalized.slice(0, -6));
|
|
755
|
+
const tail = bahtSegmentText(normalized.slice(-6));
|
|
756
|
+
return `${head}ล้าน${tail}`;
|
|
757
|
+
}
|
|
758
|
+
return bahtSegmentText(normalized) || bahtDigitWords[0];
|
|
759
|
+
}
|
|
760
|
+
function bahtTextFromNumber(value) {
|
|
761
|
+
if (!Number.isFinite(value)) {
|
|
762
|
+
return error(ErrorCode.Value);
|
|
763
|
+
}
|
|
764
|
+
const absolute = Math.abs(value);
|
|
765
|
+
const scaled = Math.round(absolute * 100);
|
|
766
|
+
if (!Number.isSafeInteger(scaled) || scaled > maxBahtTextSatang) {
|
|
767
|
+
return error(ErrorCode.Value);
|
|
768
|
+
}
|
|
769
|
+
const baht = Math.trunc(scaled / 100);
|
|
770
|
+
const satang = scaled % 100;
|
|
771
|
+
const prefix = value < 0 ? "ลบ" : "";
|
|
772
|
+
const bahtText = bahtIntegerText(String(baht));
|
|
773
|
+
if (satang === 0) {
|
|
774
|
+
return stringResult(`${prefix}${bahtText}บาทถ้วน`);
|
|
775
|
+
}
|
|
776
|
+
return stringResult(`${prefix}${bahtText}บาท${bahtSegmentText(String(satang))}สตางค์`);
|
|
777
|
+
}
|
|
778
|
+
function createReplaceBuiltin() {
|
|
779
|
+
return (...args) => {
|
|
780
|
+
const existingError = firstError(args);
|
|
781
|
+
if (existingError) {
|
|
782
|
+
return existingError;
|
|
783
|
+
}
|
|
784
|
+
const [textValue, startValue, countValue, replacementValue] = args;
|
|
785
|
+
if (textValue === undefined ||
|
|
786
|
+
startValue === undefined ||
|
|
787
|
+
countValue === undefined ||
|
|
788
|
+
replacementValue === undefined) {
|
|
789
|
+
return error(ErrorCode.Value);
|
|
790
|
+
}
|
|
791
|
+
const start = coercePositiveStart(startValue, 1);
|
|
792
|
+
if (isErrorValue(start)) {
|
|
793
|
+
return start;
|
|
794
|
+
}
|
|
795
|
+
const count = coerceLength(countValue, 0);
|
|
796
|
+
if (isErrorValue(count)) {
|
|
797
|
+
return count;
|
|
798
|
+
}
|
|
799
|
+
const replacement = coerceText(replacementValue);
|
|
800
|
+
const replaced = replaceSingle(coerceText(textValue), start, count, replacement);
|
|
801
|
+
return stringResult(replaced);
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function createSubstituteBuiltin() {
|
|
805
|
+
return (...args) => {
|
|
806
|
+
const existingError = firstError(args);
|
|
807
|
+
if (existingError) {
|
|
808
|
+
return existingError;
|
|
809
|
+
}
|
|
810
|
+
const [textValue, oldValue, newValue, instanceValue] = args;
|
|
811
|
+
if (textValue === undefined || oldValue === undefined || newValue === undefined) {
|
|
812
|
+
return error(ErrorCode.Value);
|
|
813
|
+
}
|
|
814
|
+
const text = coerceText(textValue);
|
|
815
|
+
const oldText = coerceText(oldValue);
|
|
816
|
+
if (oldText === "") {
|
|
817
|
+
return error(ErrorCode.Value);
|
|
818
|
+
}
|
|
819
|
+
const newText = coerceText(newValue);
|
|
820
|
+
if (instanceValue === undefined) {
|
|
821
|
+
return stringResult(substituteText(text, oldText, newText));
|
|
822
|
+
}
|
|
823
|
+
const instance = coercePositiveStart(instanceValue, 1);
|
|
824
|
+
if (isErrorValue(instance)) {
|
|
825
|
+
return instance;
|
|
826
|
+
}
|
|
827
|
+
return stringResult(substituteText(text, oldText, newText, instance));
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function createReptBuiltin() {
|
|
831
|
+
return (...args) => {
|
|
832
|
+
const existingError = firstError(args);
|
|
833
|
+
if (existingError) {
|
|
834
|
+
return existingError;
|
|
835
|
+
}
|
|
836
|
+
const [textValue, countValue] = args;
|
|
837
|
+
if (textValue === undefined || countValue === undefined) {
|
|
838
|
+
return error(ErrorCode.Value);
|
|
839
|
+
}
|
|
840
|
+
const count = coerceNonNegativeInt(countValue, 0);
|
|
841
|
+
if (isErrorValue(count)) {
|
|
842
|
+
return count;
|
|
843
|
+
}
|
|
844
|
+
const text = coerceText(textValue);
|
|
845
|
+
let repeated = "";
|
|
846
|
+
for (let index = 0; index < count; index += 1) {
|
|
847
|
+
repeated += text;
|
|
848
|
+
}
|
|
849
|
+
return stringResult(repeated);
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
function excelTrim(input) {
|
|
853
|
+
let start = 0;
|
|
854
|
+
let end = input.length;
|
|
855
|
+
while (start < end && input.charCodeAt(start) === 32) {
|
|
856
|
+
start += 1;
|
|
857
|
+
}
|
|
858
|
+
while (end > start && input.charCodeAt(end - 1) === 32) {
|
|
859
|
+
end -= 1;
|
|
860
|
+
}
|
|
861
|
+
return input.slice(start, end).replace(/ {2,}/g, " ");
|
|
862
|
+
}
|
|
863
|
+
function stripControlCharacters(input) {
|
|
864
|
+
let output = "";
|
|
865
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
866
|
+
const char = input.charCodeAt(index);
|
|
867
|
+
if ((char >= 0 && char <= 31) || char === 127) {
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
output += input[index] ?? "";
|
|
871
|
+
}
|
|
872
|
+
return output;
|
|
873
|
+
}
|
|
874
|
+
const halfWidthKanaToFullWidthMap = new Map([
|
|
875
|
+
["。", "。"],
|
|
876
|
+
["「", "「"],
|
|
877
|
+
["」", "」"],
|
|
878
|
+
["、", "、"],
|
|
879
|
+
["・", "・"],
|
|
880
|
+
["ヲ", "ヲ"],
|
|
881
|
+
["ァ", "ァ"],
|
|
882
|
+
["ィ", "ィ"],
|
|
883
|
+
["ゥ", "ゥ"],
|
|
884
|
+
["ェ", "ェ"],
|
|
885
|
+
["ォ", "ォ"],
|
|
886
|
+
["ャ", "ャ"],
|
|
887
|
+
["ュ", "ュ"],
|
|
888
|
+
["ョ", "ョ"],
|
|
889
|
+
["ッ", "ッ"],
|
|
890
|
+
["ー", "ー"],
|
|
891
|
+
["ア", "ア"],
|
|
892
|
+
["イ", "イ"],
|
|
893
|
+
["ウ", "ウ"],
|
|
894
|
+
["エ", "エ"],
|
|
895
|
+
["オ", "オ"],
|
|
896
|
+
["カ", "カ"],
|
|
897
|
+
["キ", "キ"],
|
|
898
|
+
["ク", "ク"],
|
|
899
|
+
["ケ", "ケ"],
|
|
900
|
+
["コ", "コ"],
|
|
901
|
+
["サ", "サ"],
|
|
902
|
+
["シ", "シ"],
|
|
903
|
+
["ス", "ス"],
|
|
904
|
+
["セ", "セ"],
|
|
905
|
+
["ソ", "ソ"],
|
|
906
|
+
["タ", "タ"],
|
|
907
|
+
["チ", "チ"],
|
|
908
|
+
["ツ", "ツ"],
|
|
909
|
+
["テ", "テ"],
|
|
910
|
+
["ト", "ト"],
|
|
911
|
+
["ナ", "ナ"],
|
|
912
|
+
["ニ", "ニ"],
|
|
913
|
+
["ヌ", "ヌ"],
|
|
914
|
+
["ネ", "ネ"],
|
|
915
|
+
["ノ", "ノ"],
|
|
916
|
+
["ハ", "ハ"],
|
|
917
|
+
["ヒ", "ヒ"],
|
|
918
|
+
["フ", "フ"],
|
|
919
|
+
["ヘ", "ヘ"],
|
|
920
|
+
["ホ", "ホ"],
|
|
921
|
+
["マ", "マ"],
|
|
922
|
+
["ミ", "ミ"],
|
|
923
|
+
["ム", "ム"],
|
|
924
|
+
["メ", "メ"],
|
|
925
|
+
["モ", "モ"],
|
|
926
|
+
["ヤ", "ヤ"],
|
|
927
|
+
["ユ", "ユ"],
|
|
928
|
+
["ヨ", "ヨ"],
|
|
929
|
+
["ラ", "ラ"],
|
|
930
|
+
["リ", "リ"],
|
|
931
|
+
["ル", "ル"],
|
|
932
|
+
["レ", "レ"],
|
|
933
|
+
["ロ", "ロ"],
|
|
934
|
+
["ワ", "ワ"],
|
|
935
|
+
["ン", "ン"],
|
|
936
|
+
]);
|
|
937
|
+
const halfWidthVoicedKanaToFullWidthMap = new Map([
|
|
938
|
+
["ヴ", "ヴ"],
|
|
939
|
+
["ガ", "ガ"],
|
|
940
|
+
["ギ", "ギ"],
|
|
941
|
+
["グ", "グ"],
|
|
942
|
+
["ゲ", "ゲ"],
|
|
943
|
+
["ゴ", "ゴ"],
|
|
944
|
+
["ザ", "ザ"],
|
|
945
|
+
["ジ", "ジ"],
|
|
946
|
+
["ズ", "ズ"],
|
|
947
|
+
["ゼ", "ゼ"],
|
|
948
|
+
["ゾ", "ゾ"],
|
|
949
|
+
["ダ", "ダ"],
|
|
950
|
+
["ヂ", "ヂ"],
|
|
951
|
+
["ヅ", "ヅ"],
|
|
952
|
+
["デ", "デ"],
|
|
953
|
+
["ド", "ド"],
|
|
954
|
+
["バ", "バ"],
|
|
955
|
+
["ビ", "ビ"],
|
|
956
|
+
["ブ", "ブ"],
|
|
957
|
+
["ベ", "ベ"],
|
|
958
|
+
["ボ", "ボ"],
|
|
959
|
+
["パ", "パ"],
|
|
960
|
+
["ピ", "ピ"],
|
|
961
|
+
["プ", "プ"],
|
|
962
|
+
["ペ", "ペ"],
|
|
963
|
+
["ポ", "ポ"],
|
|
964
|
+
]);
|
|
965
|
+
const fullWidthKanaToHalfWidthMap = new Map([
|
|
966
|
+
...[...halfWidthKanaToFullWidthMap.entries()].map(([half, full]) => [full, half]),
|
|
967
|
+
...[...halfWidthVoicedKanaToFullWidthMap.entries()].map(([half, full]) => [full, half]),
|
|
968
|
+
]);
|
|
969
|
+
function isLeadingSurrogate(codeUnit) {
|
|
970
|
+
return codeUnit >= 0xd800 && codeUnit <= 0xdbff;
|
|
971
|
+
}
|
|
972
|
+
function isTrailingSurrogate(codeUnit) {
|
|
973
|
+
return codeUnit >= 0xdc00 && codeUnit <= 0xdfff;
|
|
974
|
+
}
|
|
975
|
+
function toJapaneseFullWidth(input) {
|
|
976
|
+
let output = "";
|
|
977
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
978
|
+
const char = input[index];
|
|
979
|
+
const code = input.charCodeAt(index);
|
|
980
|
+
if (isLeadingSurrogate(code) && index + 1 < input.length) {
|
|
981
|
+
const nextCode = input.charCodeAt(index + 1);
|
|
982
|
+
if (isTrailingSurrogate(nextCode)) {
|
|
983
|
+
output += input.slice(index, index + 2);
|
|
984
|
+
index += 1;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if (code === 0x20) {
|
|
989
|
+
output += "\u3000";
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (code >= 0x21 && code <= 0x7e) {
|
|
993
|
+
output += String.fromCharCode(code + 0xfee0);
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const next = input[index + 1];
|
|
997
|
+
if (next !== undefined) {
|
|
998
|
+
const voiced = halfWidthVoicedKanaToFullWidthMap.get(char + next);
|
|
999
|
+
if (voiced !== undefined) {
|
|
1000
|
+
output += voiced;
|
|
1001
|
+
index += 1;
|
|
1002
|
+
continue;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
const mapped = halfWidthKanaToFullWidthMap.get(char);
|
|
1006
|
+
output += mapped ?? char;
|
|
1007
|
+
}
|
|
1008
|
+
return output;
|
|
1009
|
+
}
|
|
1010
|
+
function toJapaneseHalfWidth(input) {
|
|
1011
|
+
let output = "";
|
|
1012
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1013
|
+
const char = input[index];
|
|
1014
|
+
const code = input.charCodeAt(index);
|
|
1015
|
+
if (isLeadingSurrogate(code) && index + 1 < input.length) {
|
|
1016
|
+
const nextCode = input.charCodeAt(index + 1);
|
|
1017
|
+
if (isTrailingSurrogate(nextCode)) {
|
|
1018
|
+
output += input.slice(index, index + 2);
|
|
1019
|
+
index += 1;
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (code === 0x3000) {
|
|
1024
|
+
output += " ";
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
if (code >= 0xff01 && code <= 0xff5e) {
|
|
1028
|
+
output += String.fromCharCode(code - 0xfee0);
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const mapped = fullWidthKanaToHalfWidthMap.get(char);
|
|
1032
|
+
output += mapped ?? char;
|
|
1033
|
+
}
|
|
1034
|
+
return output;
|
|
1035
|
+
}
|
|
1036
|
+
function toTitleCase(input) {
|
|
1037
|
+
let result = "";
|
|
1038
|
+
let capitalizeNext = true;
|
|
1039
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
1040
|
+
const char = input[index] ?? "";
|
|
1041
|
+
const code = char.charCodeAt(0);
|
|
1042
|
+
const isAlpha = (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
1043
|
+
if (!isAlpha) {
|
|
1044
|
+
capitalizeNext = true;
|
|
1045
|
+
result += char;
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
result += capitalizeNext ? char.toUpperCase() : char.toLowerCase();
|
|
1049
|
+
capitalizeNext = false;
|
|
1050
|
+
}
|
|
1051
|
+
return result;
|
|
1052
|
+
}
|
|
1053
|
+
function escapeRegExp(value) {
|
|
1054
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1055
|
+
}
|
|
1056
|
+
function indexOfWithMode(text, delimiter, start, matchMode) {
|
|
1057
|
+
if (matchMode === 1) {
|
|
1058
|
+
return text.toLowerCase().indexOf(delimiter.toLowerCase(), start);
|
|
1059
|
+
}
|
|
1060
|
+
return text.indexOf(delimiter, start);
|
|
1061
|
+
}
|
|
1062
|
+
function lastIndexOfWithMode(text, delimiter, start, matchMode) {
|
|
1063
|
+
if (matchMode === 1) {
|
|
1064
|
+
return text.toLowerCase().lastIndexOf(delimiter.toLowerCase(), start);
|
|
1065
|
+
}
|
|
1066
|
+
return text.lastIndexOf(delimiter, start);
|
|
1067
|
+
}
|
|
1068
|
+
function hasSearchSyntax(pattern) {
|
|
1069
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
1070
|
+
const char = pattern[index];
|
|
1071
|
+
if (char === "~") {
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
1074
|
+
if (char === "*" || char === "?") {
|
|
1075
|
+
return true;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
function buildSearchRegex(pattern) {
|
|
1081
|
+
let source = "^";
|
|
1082
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
1083
|
+
const char = pattern[index];
|
|
1084
|
+
if (char === "~") {
|
|
1085
|
+
const next = pattern[index + 1];
|
|
1086
|
+
if (next === undefined) {
|
|
1087
|
+
source += escapeRegExp(char);
|
|
1088
|
+
}
|
|
1089
|
+
else {
|
|
1090
|
+
source += escapeRegExp(next);
|
|
1091
|
+
index += 1;
|
|
1092
|
+
}
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
if (char === "*") {
|
|
1096
|
+
source += "[\\s\\S]*";
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
if (char === "?") {
|
|
1100
|
+
source += "[\\s\\S]";
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
source += escapeRegExp(char);
|
|
1104
|
+
}
|
|
1105
|
+
return new RegExp(source, "i");
|
|
1106
|
+
}
|
|
1107
|
+
function findPosition(needle, haystack, start, caseSensitive, wildcardAware) {
|
|
1108
|
+
const startIndex = start - 1;
|
|
1109
|
+
if (needle === "") {
|
|
1110
|
+
return start;
|
|
1111
|
+
}
|
|
1112
|
+
if (startIndex > haystack.length) {
|
|
1113
|
+
return error(ErrorCode.Value);
|
|
1114
|
+
}
|
|
1115
|
+
if (!wildcardAware || !hasSearchSyntax(needle)) {
|
|
1116
|
+
const normalizedHaystack = caseSensitive ? haystack : haystack.toLowerCase();
|
|
1117
|
+
const normalizedNeedle = caseSensitive ? needle : needle.toLowerCase();
|
|
1118
|
+
const found = normalizedHaystack.indexOf(normalizedNeedle, startIndex);
|
|
1119
|
+
return found === -1 ? error(ErrorCode.Value) : found + 1;
|
|
1120
|
+
}
|
|
1121
|
+
const regex = buildSearchRegex(needle);
|
|
1122
|
+
for (let index = startIndex; index <= haystack.length; index += 1) {
|
|
1123
|
+
if (regex.test(haystack.slice(index))) {
|
|
1124
|
+
return index + 1;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return error(ErrorCode.Value);
|
|
1128
|
+
}
|
|
1129
|
+
function charCodeFromArgument(value) {
|
|
1130
|
+
if (value === undefined) {
|
|
1131
|
+
return error(ErrorCode.Value);
|
|
1132
|
+
}
|
|
1133
|
+
const code = coerceNumber(value);
|
|
1134
|
+
if (code === undefined) {
|
|
1135
|
+
return error(ErrorCode.Value);
|
|
1136
|
+
}
|
|
1137
|
+
const integerCode = Math.trunc(code);
|
|
1138
|
+
if (!Number.isFinite(integerCode) || integerCode < 1 || integerCode > 255) {
|
|
1139
|
+
return error(ErrorCode.Value);
|
|
1140
|
+
}
|
|
1141
|
+
return integerCode;
|
|
1142
|
+
}
|
|
1143
|
+
const textPlaceholderBuiltins = createBlockedBuiltinMap(textPlaceholderBuiltinNames);
|
|
1144
|
+
export const textBuiltins = {
|
|
1145
|
+
LEN: (...args) => {
|
|
1146
|
+
const existingError = firstError(args);
|
|
1147
|
+
if (existingError) {
|
|
1148
|
+
return existingError;
|
|
1149
|
+
}
|
|
1150
|
+
const [value] = args;
|
|
1151
|
+
if (value === undefined) {
|
|
1152
|
+
return error(ErrorCode.Value);
|
|
1153
|
+
}
|
|
1154
|
+
return numberResult(coerceText(value).length);
|
|
1155
|
+
},
|
|
1156
|
+
LENB: (...args) => {
|
|
1157
|
+
const existingError = firstError(args);
|
|
1158
|
+
if (existingError) {
|
|
1159
|
+
return existingError;
|
|
1160
|
+
}
|
|
1161
|
+
const [value] = args;
|
|
1162
|
+
if (value === undefined) {
|
|
1163
|
+
return error(ErrorCode.Value);
|
|
1164
|
+
}
|
|
1165
|
+
return numberResult(utf8Bytes(coerceText(value)).length);
|
|
1166
|
+
},
|
|
1167
|
+
CHAR: (...args) => {
|
|
1168
|
+
const [codeValue] = args;
|
|
1169
|
+
const codePoint = charCodeFromArgument(codeValue);
|
|
1170
|
+
if (isErrorValue(codePoint)) {
|
|
1171
|
+
return codePoint;
|
|
1172
|
+
}
|
|
1173
|
+
return stringResult(String.fromCodePoint(codePoint));
|
|
1174
|
+
},
|
|
1175
|
+
CODE: (...args) => {
|
|
1176
|
+
const [textValue] = args;
|
|
1177
|
+
if (textValue === undefined) {
|
|
1178
|
+
return error(ErrorCode.Value);
|
|
1179
|
+
}
|
|
1180
|
+
const text = coerceText(textValue);
|
|
1181
|
+
if (text.length === 0) {
|
|
1182
|
+
return error(ErrorCode.Value);
|
|
1183
|
+
}
|
|
1184
|
+
const codePoint = text.codePointAt(0);
|
|
1185
|
+
return codePoint === undefined ? error(ErrorCode.Value) : numberResult(codePoint);
|
|
1186
|
+
},
|
|
1187
|
+
UNICODE: (...args) => {
|
|
1188
|
+
const [textValue] = args;
|
|
1189
|
+
if (textValue === undefined) {
|
|
1190
|
+
return error(ErrorCode.Value);
|
|
1191
|
+
}
|
|
1192
|
+
const text = coerceText(textValue);
|
|
1193
|
+
if (text.length === 0) {
|
|
1194
|
+
return error(ErrorCode.Value);
|
|
1195
|
+
}
|
|
1196
|
+
const codePoint = text.codePointAt(0);
|
|
1197
|
+
return codePoint === undefined ? error(ErrorCode.Value) : numberResult(codePoint);
|
|
1198
|
+
},
|
|
1199
|
+
UNICHAR: (...args) => {
|
|
1200
|
+
const [codeValue] = args;
|
|
1201
|
+
if (codeValue === undefined) {
|
|
1202
|
+
return error(ErrorCode.Value);
|
|
1203
|
+
}
|
|
1204
|
+
const code = coerceNumber(codeValue);
|
|
1205
|
+
if (code === undefined) {
|
|
1206
|
+
return error(ErrorCode.Value);
|
|
1207
|
+
}
|
|
1208
|
+
const integerCode = Math.trunc(code);
|
|
1209
|
+
if (!Number.isFinite(integerCode) || integerCode < 0 || integerCode > 0x10ffff) {
|
|
1210
|
+
return error(ErrorCode.Value);
|
|
1211
|
+
}
|
|
1212
|
+
return stringResult(String.fromCodePoint(integerCode));
|
|
1213
|
+
},
|
|
1214
|
+
CLEAN: (...args) => {
|
|
1215
|
+
const existingError = firstError(args);
|
|
1216
|
+
if (existingError) {
|
|
1217
|
+
return existingError;
|
|
1218
|
+
}
|
|
1219
|
+
const [textValue] = args;
|
|
1220
|
+
if (textValue === undefined) {
|
|
1221
|
+
return error(ErrorCode.Value);
|
|
1222
|
+
}
|
|
1223
|
+
return stringResult(stripControlCharacters(coerceText(textValue)));
|
|
1224
|
+
},
|
|
1225
|
+
ASC: (...args) => {
|
|
1226
|
+
const existingError = firstError(args);
|
|
1227
|
+
if (existingError) {
|
|
1228
|
+
return existingError;
|
|
1229
|
+
}
|
|
1230
|
+
const [textValue] = args;
|
|
1231
|
+
if (textValue === undefined) {
|
|
1232
|
+
return error(ErrorCode.Value);
|
|
1233
|
+
}
|
|
1234
|
+
return stringResult(toJapaneseHalfWidth(coerceText(textValue)));
|
|
1235
|
+
},
|
|
1236
|
+
JIS: (...args) => {
|
|
1237
|
+
const existingError = firstError(args);
|
|
1238
|
+
if (existingError) {
|
|
1239
|
+
return existingError;
|
|
1240
|
+
}
|
|
1241
|
+
const [textValue] = args;
|
|
1242
|
+
if (textValue === undefined) {
|
|
1243
|
+
return error(ErrorCode.Value);
|
|
1244
|
+
}
|
|
1245
|
+
return stringResult(toJapaneseFullWidth(coerceText(textValue)));
|
|
1246
|
+
},
|
|
1247
|
+
DBCS: (...args) => {
|
|
1248
|
+
const existingError = firstError(args);
|
|
1249
|
+
if (existingError) {
|
|
1250
|
+
return existingError;
|
|
1251
|
+
}
|
|
1252
|
+
const [textValue] = args;
|
|
1253
|
+
if (textValue === undefined) {
|
|
1254
|
+
return error(ErrorCode.Value);
|
|
1255
|
+
}
|
|
1256
|
+
return stringResult(toJapaneseFullWidth(coerceText(textValue)));
|
|
1257
|
+
},
|
|
1258
|
+
BAHTTEXT: (...args) => {
|
|
1259
|
+
const existingError = firstError(args);
|
|
1260
|
+
if (existingError) {
|
|
1261
|
+
return existingError;
|
|
1262
|
+
}
|
|
1263
|
+
const [value] = args;
|
|
1264
|
+
if (value === undefined) {
|
|
1265
|
+
return error(ErrorCode.Value);
|
|
1266
|
+
}
|
|
1267
|
+
const numeric = coerceNumber(value);
|
|
1268
|
+
return numeric === undefined ? error(ErrorCode.Value) : bahtTextFromNumber(numeric);
|
|
1269
|
+
},
|
|
1270
|
+
TEXT: (...args) => {
|
|
1271
|
+
const existingError = firstError(args);
|
|
1272
|
+
if (existingError) {
|
|
1273
|
+
return existingError;
|
|
1274
|
+
}
|
|
1275
|
+
const [value, formatValue] = args;
|
|
1276
|
+
if (value === undefined || formatValue === undefined) {
|
|
1277
|
+
return error(ErrorCode.Value);
|
|
1278
|
+
}
|
|
1279
|
+
return formatTextBuiltinValue(value, coerceText(formatValue));
|
|
1280
|
+
},
|
|
1281
|
+
PHONETIC: (...args) => {
|
|
1282
|
+
const existingError = firstError(args);
|
|
1283
|
+
if (existingError) {
|
|
1284
|
+
return existingError;
|
|
1285
|
+
}
|
|
1286
|
+
const [value] = args;
|
|
1287
|
+
if (value === undefined) {
|
|
1288
|
+
return error(ErrorCode.Value);
|
|
1289
|
+
}
|
|
1290
|
+
return stringResult(coerceText(value));
|
|
1291
|
+
},
|
|
1292
|
+
CONCATENATE: (...args) => {
|
|
1293
|
+
const existingError = firstError(args);
|
|
1294
|
+
if (existingError) {
|
|
1295
|
+
return existingError;
|
|
1296
|
+
}
|
|
1297
|
+
if (args.length === 0) {
|
|
1298
|
+
return error(ErrorCode.Value);
|
|
1299
|
+
}
|
|
1300
|
+
return stringResult(args.map(coerceText).join(""));
|
|
1301
|
+
},
|
|
1302
|
+
CONCAT: (...args) => {
|
|
1303
|
+
const existingError = firstError(args);
|
|
1304
|
+
if (existingError) {
|
|
1305
|
+
return existingError;
|
|
1306
|
+
}
|
|
1307
|
+
return stringResult(args.map(coerceText).join(""));
|
|
1308
|
+
},
|
|
1309
|
+
PROPER: (...args) => {
|
|
1310
|
+
const existingError = firstError(args);
|
|
1311
|
+
if (existingError) {
|
|
1312
|
+
return existingError;
|
|
1313
|
+
}
|
|
1314
|
+
const [textValue] = args;
|
|
1315
|
+
if (textValue === undefined) {
|
|
1316
|
+
return error(ErrorCode.Value);
|
|
1317
|
+
}
|
|
1318
|
+
return stringResult(toTitleCase(coerceText(textValue)));
|
|
1319
|
+
},
|
|
1320
|
+
EXACT: (...args) => {
|
|
1321
|
+
const existingError = firstError(args);
|
|
1322
|
+
if (existingError) {
|
|
1323
|
+
return existingError;
|
|
1324
|
+
}
|
|
1325
|
+
const [leftValue, rightValue] = args;
|
|
1326
|
+
if (leftValue === undefined || rightValue === undefined) {
|
|
1327
|
+
return error(ErrorCode.Value);
|
|
1328
|
+
}
|
|
1329
|
+
return { tag: ValueTag.Boolean, value: coerceText(leftValue) === coerceText(rightValue) };
|
|
1330
|
+
},
|
|
1331
|
+
LEFT: (...args) => {
|
|
1332
|
+
const existingError = firstError(args);
|
|
1333
|
+
if (existingError) {
|
|
1334
|
+
return existingError;
|
|
1335
|
+
}
|
|
1336
|
+
const [textValue, countValue] = args;
|
|
1337
|
+
if (textValue === undefined) {
|
|
1338
|
+
return error(ErrorCode.Value);
|
|
1339
|
+
}
|
|
1340
|
+
const count = coerceLength(countValue, 1);
|
|
1341
|
+
if (isErrorValue(count)) {
|
|
1342
|
+
return count;
|
|
1343
|
+
}
|
|
1344
|
+
return stringResult(coerceText(textValue).slice(0, count));
|
|
1345
|
+
},
|
|
1346
|
+
RIGHT: (...args) => {
|
|
1347
|
+
const existingError = firstError(args);
|
|
1348
|
+
if (existingError) {
|
|
1349
|
+
return existingError;
|
|
1350
|
+
}
|
|
1351
|
+
const [textValue, countValue] = args;
|
|
1352
|
+
if (textValue === undefined) {
|
|
1353
|
+
return error(ErrorCode.Value);
|
|
1354
|
+
}
|
|
1355
|
+
const count = coerceLength(countValue, 1);
|
|
1356
|
+
if (isErrorValue(count)) {
|
|
1357
|
+
return count;
|
|
1358
|
+
}
|
|
1359
|
+
const text = coerceText(textValue);
|
|
1360
|
+
return stringResult(count === 0 ? "" : text.slice(-count));
|
|
1361
|
+
},
|
|
1362
|
+
MID: (...args) => {
|
|
1363
|
+
const existingError = firstError(args);
|
|
1364
|
+
if (existingError) {
|
|
1365
|
+
return existingError;
|
|
1366
|
+
}
|
|
1367
|
+
const [textValue, startValue, countValue] = args;
|
|
1368
|
+
if (textValue === undefined || startValue === undefined || countValue === undefined) {
|
|
1369
|
+
return error(ErrorCode.Value);
|
|
1370
|
+
}
|
|
1371
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1372
|
+
if (isErrorValue(start)) {
|
|
1373
|
+
return start;
|
|
1374
|
+
}
|
|
1375
|
+
const count = coerceLength(countValue, 0);
|
|
1376
|
+
if (isErrorValue(count)) {
|
|
1377
|
+
return count;
|
|
1378
|
+
}
|
|
1379
|
+
const text = coerceText(textValue);
|
|
1380
|
+
return stringResult(text.slice(start - 1, start - 1 + count));
|
|
1381
|
+
},
|
|
1382
|
+
TRIM: (...args) => {
|
|
1383
|
+
const existingError = firstError(args);
|
|
1384
|
+
if (existingError) {
|
|
1385
|
+
return existingError;
|
|
1386
|
+
}
|
|
1387
|
+
const [value] = args;
|
|
1388
|
+
if (value === undefined) {
|
|
1389
|
+
return error(ErrorCode.Value);
|
|
1390
|
+
}
|
|
1391
|
+
return stringResult(excelTrim(coerceText(value)));
|
|
1392
|
+
},
|
|
1393
|
+
UPPER: (...args) => {
|
|
1394
|
+
const existingError = firstError(args);
|
|
1395
|
+
if (existingError) {
|
|
1396
|
+
return existingError;
|
|
1397
|
+
}
|
|
1398
|
+
const [value] = args;
|
|
1399
|
+
if (value === undefined) {
|
|
1400
|
+
return error(ErrorCode.Value);
|
|
1401
|
+
}
|
|
1402
|
+
return stringResult(coerceText(value).toUpperCase());
|
|
1403
|
+
},
|
|
1404
|
+
LOWER: (...args) => {
|
|
1405
|
+
const existingError = firstError(args);
|
|
1406
|
+
if (existingError) {
|
|
1407
|
+
return existingError;
|
|
1408
|
+
}
|
|
1409
|
+
const [value] = args;
|
|
1410
|
+
if (value === undefined) {
|
|
1411
|
+
return error(ErrorCode.Value);
|
|
1412
|
+
}
|
|
1413
|
+
return stringResult(coerceText(value).toLowerCase());
|
|
1414
|
+
},
|
|
1415
|
+
FIND: (...args) => {
|
|
1416
|
+
const existingError = firstError(args);
|
|
1417
|
+
if (existingError) {
|
|
1418
|
+
return existingError;
|
|
1419
|
+
}
|
|
1420
|
+
const [findTextValue, withinTextValue, startValue] = args;
|
|
1421
|
+
if (findTextValue === undefined || withinTextValue === undefined) {
|
|
1422
|
+
return error(ErrorCode.Value);
|
|
1423
|
+
}
|
|
1424
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1425
|
+
if (isErrorValue(start)) {
|
|
1426
|
+
return start;
|
|
1427
|
+
}
|
|
1428
|
+
const found = findPosition(coerceText(findTextValue), coerceText(withinTextValue), start, true, false);
|
|
1429
|
+
return isErrorValue(found) ? found : numberResult(found);
|
|
1430
|
+
},
|
|
1431
|
+
SEARCH: (...args) => {
|
|
1432
|
+
const existingError = firstError(args);
|
|
1433
|
+
if (existingError) {
|
|
1434
|
+
return existingError;
|
|
1435
|
+
}
|
|
1436
|
+
const [findTextValue, withinTextValue, startValue] = args;
|
|
1437
|
+
if (findTextValue === undefined || withinTextValue === undefined) {
|
|
1438
|
+
return error(ErrorCode.Value);
|
|
1439
|
+
}
|
|
1440
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1441
|
+
if (isErrorValue(start)) {
|
|
1442
|
+
return start;
|
|
1443
|
+
}
|
|
1444
|
+
const found = findPosition(coerceText(findTextValue), coerceText(withinTextValue), start, false, true);
|
|
1445
|
+
return isErrorValue(found) ? found : numberResult(found);
|
|
1446
|
+
},
|
|
1447
|
+
SEARCHB: (...args) => {
|
|
1448
|
+
const existingError = firstError(args);
|
|
1449
|
+
if (existingError) {
|
|
1450
|
+
return existingError;
|
|
1451
|
+
}
|
|
1452
|
+
const [findTextValue, withinTextValue, startValue] = args;
|
|
1453
|
+
if (findTextValue === undefined || withinTextValue === undefined) {
|
|
1454
|
+
return error(ErrorCode.Value);
|
|
1455
|
+
}
|
|
1456
|
+
const text = coerceText(withinTextValue);
|
|
1457
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1458
|
+
if (isErrorValue(start)) {
|
|
1459
|
+
return start;
|
|
1460
|
+
}
|
|
1461
|
+
if (start > utf8Bytes(text).length + 1) {
|
|
1462
|
+
return error(ErrorCode.Value);
|
|
1463
|
+
}
|
|
1464
|
+
const found = findPosition(coerceText(findTextValue), text, bytePositionToCharPosition(text, start), false, true);
|
|
1465
|
+
return isErrorValue(found) ? found : numberResult(charPositionToBytePosition(text, found));
|
|
1466
|
+
},
|
|
1467
|
+
ENCODEURL: (...args) => {
|
|
1468
|
+
const existingError = firstError(args);
|
|
1469
|
+
if (existingError) {
|
|
1470
|
+
return existingError;
|
|
1471
|
+
}
|
|
1472
|
+
const [value] = args;
|
|
1473
|
+
if (value === undefined) {
|
|
1474
|
+
return error(ErrorCode.Value);
|
|
1475
|
+
}
|
|
1476
|
+
return stringResult(encodeURI(coerceText(value)));
|
|
1477
|
+
},
|
|
1478
|
+
FINDB: (...args) => {
|
|
1479
|
+
const existingError = firstError(args);
|
|
1480
|
+
if (existingError) {
|
|
1481
|
+
return existingError;
|
|
1482
|
+
}
|
|
1483
|
+
const [findTextValue, withinTextValue, startValue] = args;
|
|
1484
|
+
if (findTextValue === undefined || withinTextValue === undefined) {
|
|
1485
|
+
return error(ErrorCode.Value);
|
|
1486
|
+
}
|
|
1487
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1488
|
+
if (isErrorValue(start)) {
|
|
1489
|
+
return start;
|
|
1490
|
+
}
|
|
1491
|
+
const findBytes = utf8Bytes(coerceText(findTextValue));
|
|
1492
|
+
const withinBytes = utf8Bytes(coerceText(withinTextValue));
|
|
1493
|
+
if (start > withinBytes.length + 1) {
|
|
1494
|
+
return error(ErrorCode.Value);
|
|
1495
|
+
}
|
|
1496
|
+
const found = findSubBytes(withinBytes, findBytes, start - 1);
|
|
1497
|
+
return found === -1 ? error(ErrorCode.Value) : numberResult(found + 1);
|
|
1498
|
+
},
|
|
1499
|
+
LEFTB: (...args) => {
|
|
1500
|
+
const existingError = firstError(args);
|
|
1501
|
+
if (existingError) {
|
|
1502
|
+
return existingError;
|
|
1503
|
+
}
|
|
1504
|
+
const [textValue, countValue] = args;
|
|
1505
|
+
if (textValue === undefined) {
|
|
1506
|
+
return error(ErrorCode.Value);
|
|
1507
|
+
}
|
|
1508
|
+
const count = coerceLength(countValue, 1);
|
|
1509
|
+
if (isErrorValue(count)) {
|
|
1510
|
+
return count;
|
|
1511
|
+
}
|
|
1512
|
+
return stringResult(leftBytes(coerceText(textValue), count));
|
|
1513
|
+
},
|
|
1514
|
+
MIDB: (...args) => {
|
|
1515
|
+
const existingError = firstError(args);
|
|
1516
|
+
if (existingError) {
|
|
1517
|
+
return existingError;
|
|
1518
|
+
}
|
|
1519
|
+
const [textValue, startValue, countValue] = args;
|
|
1520
|
+
if (textValue === undefined || startValue === undefined || countValue === undefined) {
|
|
1521
|
+
return error(ErrorCode.Value);
|
|
1522
|
+
}
|
|
1523
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1524
|
+
if (isErrorValue(start)) {
|
|
1525
|
+
return start;
|
|
1526
|
+
}
|
|
1527
|
+
const count = coerceLength(countValue, 0);
|
|
1528
|
+
if (isErrorValue(count)) {
|
|
1529
|
+
return count;
|
|
1530
|
+
}
|
|
1531
|
+
return stringResult(midBytes(coerceText(textValue), start, count));
|
|
1532
|
+
},
|
|
1533
|
+
RIGHTB: (...args) => {
|
|
1534
|
+
const existingError = firstError(args);
|
|
1535
|
+
if (existingError) {
|
|
1536
|
+
return existingError;
|
|
1537
|
+
}
|
|
1538
|
+
const [textValue, countValue] = args;
|
|
1539
|
+
if (textValue === undefined) {
|
|
1540
|
+
return error(ErrorCode.Value);
|
|
1541
|
+
}
|
|
1542
|
+
const count = coerceLength(countValue, 1);
|
|
1543
|
+
if (isErrorValue(count)) {
|
|
1544
|
+
return count;
|
|
1545
|
+
}
|
|
1546
|
+
return stringResult(rightBytes(coerceText(textValue), count));
|
|
1547
|
+
},
|
|
1548
|
+
VALUE: (...args) => {
|
|
1549
|
+
const existingError = firstError(args);
|
|
1550
|
+
if (existingError) {
|
|
1551
|
+
return existingError;
|
|
1552
|
+
}
|
|
1553
|
+
const [value] = args;
|
|
1554
|
+
if (value === undefined) {
|
|
1555
|
+
return error(ErrorCode.Value);
|
|
1556
|
+
}
|
|
1557
|
+
const coerced = coerceNumber(value);
|
|
1558
|
+
return coerced === undefined ? error(ErrorCode.Value) : numberResult(coerced);
|
|
1559
|
+
},
|
|
1560
|
+
NUMBERVALUE: (...args) => {
|
|
1561
|
+
const existingError = firstError(args);
|
|
1562
|
+
if (existingError) {
|
|
1563
|
+
return existingError;
|
|
1564
|
+
}
|
|
1565
|
+
const [textValue, decimalSeparatorValue, groupSeparatorValue] = args;
|
|
1566
|
+
if (textValue === undefined) {
|
|
1567
|
+
return error(ErrorCode.Value);
|
|
1568
|
+
}
|
|
1569
|
+
const text = coerceText(textValue);
|
|
1570
|
+
const decimalSeparator = decimalSeparatorValue === undefined ? "." : coerceText(decimalSeparatorValue);
|
|
1571
|
+
const groupSeparator = groupSeparatorValue === undefined ? "," : coerceText(groupSeparatorValue);
|
|
1572
|
+
const parsed = parseNumberValueText(text, decimalSeparator, groupSeparator);
|
|
1573
|
+
return parsed === undefined ? error(ErrorCode.Value) : numberResult(parsed);
|
|
1574
|
+
},
|
|
1575
|
+
VALUETOTEXT: (...args) => {
|
|
1576
|
+
const existingError = firstError(args);
|
|
1577
|
+
if (existingError) {
|
|
1578
|
+
return valueToTextResult(existingError, 0);
|
|
1579
|
+
}
|
|
1580
|
+
const [value, formatValue] = args;
|
|
1581
|
+
if (value === undefined) {
|
|
1582
|
+
return error(ErrorCode.Value);
|
|
1583
|
+
}
|
|
1584
|
+
const format = coerceInteger(formatValue, 0);
|
|
1585
|
+
if (isErrorValue(format)) {
|
|
1586
|
+
return format;
|
|
1587
|
+
}
|
|
1588
|
+
return valueToTextResult(value, format);
|
|
1589
|
+
},
|
|
1590
|
+
REGEXTEST: (...args) => {
|
|
1591
|
+
const existingError = firstError(args);
|
|
1592
|
+
if (existingError) {
|
|
1593
|
+
return existingError;
|
|
1594
|
+
}
|
|
1595
|
+
const [textValue, patternValue, caseSensitivityValue] = args;
|
|
1596
|
+
if (textValue === undefined || patternValue === undefined) {
|
|
1597
|
+
return error(ErrorCode.Value);
|
|
1598
|
+
}
|
|
1599
|
+
const caseSensitivity = coerceInteger(caseSensitivityValue, 0);
|
|
1600
|
+
if (isErrorValue(caseSensitivity) || (caseSensitivity !== 0 && caseSensitivity !== 1)) {
|
|
1601
|
+
return error(ErrorCode.Value);
|
|
1602
|
+
}
|
|
1603
|
+
const pattern = coerceText(patternValue);
|
|
1604
|
+
const regex = compileRegex(pattern, caseSensitivity);
|
|
1605
|
+
if (isRegexError(regex)) {
|
|
1606
|
+
return regex;
|
|
1607
|
+
}
|
|
1608
|
+
return booleanResult(regex.test(coerceText(textValue)));
|
|
1609
|
+
},
|
|
1610
|
+
REGEXREPLACE: (...args) => {
|
|
1611
|
+
const existingError = firstError(args);
|
|
1612
|
+
if (existingError) {
|
|
1613
|
+
return existingError;
|
|
1614
|
+
}
|
|
1615
|
+
const [textValue, patternValue, replacementValue, occurrenceValue, caseSensitivityValue] = args;
|
|
1616
|
+
if (textValue === undefined || patternValue === undefined || replacementValue === undefined) {
|
|
1617
|
+
return error(ErrorCode.Value);
|
|
1618
|
+
}
|
|
1619
|
+
const occurrence = coerceInteger(occurrenceValue, 0);
|
|
1620
|
+
const caseSensitivity = coerceInteger(caseSensitivityValue, 0);
|
|
1621
|
+
if (isErrorValue(occurrence) ||
|
|
1622
|
+
isErrorValue(caseSensitivity) ||
|
|
1623
|
+
(caseSensitivity !== 0 && caseSensitivity !== 1)) {
|
|
1624
|
+
return error(ErrorCode.Value);
|
|
1625
|
+
}
|
|
1626
|
+
const text = coerceText(textValue);
|
|
1627
|
+
const replacement = coerceText(replacementValue);
|
|
1628
|
+
const regex = compileRegex(coerceText(patternValue), caseSensitivity, true);
|
|
1629
|
+
if (isRegexError(regex)) {
|
|
1630
|
+
return regex;
|
|
1631
|
+
}
|
|
1632
|
+
if (occurrence === 0) {
|
|
1633
|
+
return stringResult(text.replace(regex, replacement));
|
|
1634
|
+
}
|
|
1635
|
+
const matches = [...text.matchAll(regex)];
|
|
1636
|
+
if (matches.length === 0) {
|
|
1637
|
+
return stringResult(text);
|
|
1638
|
+
}
|
|
1639
|
+
const targetIndex = occurrence > 0 ? occurrence - 1 : matches.length + occurrence;
|
|
1640
|
+
if (targetIndex < 0 || targetIndex >= matches.length) {
|
|
1641
|
+
return stringResult(text);
|
|
1642
|
+
}
|
|
1643
|
+
let currentIndex = -1;
|
|
1644
|
+
return stringResult(text.replace(regex, (match, ...rest) => {
|
|
1645
|
+
currentIndex += 1;
|
|
1646
|
+
if (currentIndex !== targetIndex) {
|
|
1647
|
+
return match;
|
|
1648
|
+
}
|
|
1649
|
+
const captures = rest
|
|
1650
|
+
.slice(0, -2)
|
|
1651
|
+
.map((value) => (typeof value === "string" ? value : undefined));
|
|
1652
|
+
return applyReplacementTemplate(replacement, match, captures);
|
|
1653
|
+
}));
|
|
1654
|
+
},
|
|
1655
|
+
REGEXEXTRACT: (...args) => {
|
|
1656
|
+
const existingError = firstError(args);
|
|
1657
|
+
if (existingError) {
|
|
1658
|
+
return existingError;
|
|
1659
|
+
}
|
|
1660
|
+
const [textValue, patternValue, returnModeValue, caseSensitivityValue] = args;
|
|
1661
|
+
if (textValue === undefined || patternValue === undefined) {
|
|
1662
|
+
return error(ErrorCode.Value);
|
|
1663
|
+
}
|
|
1664
|
+
const returnMode = coerceInteger(returnModeValue, 0);
|
|
1665
|
+
const caseSensitivity = coerceInteger(caseSensitivityValue, 0);
|
|
1666
|
+
if (isErrorValue(returnMode) ||
|
|
1667
|
+
isErrorValue(caseSensitivity) ||
|
|
1668
|
+
![0, 1, 2].includes(returnMode) ||
|
|
1669
|
+
(caseSensitivity !== 0 && caseSensitivity !== 1)) {
|
|
1670
|
+
return error(ErrorCode.Value);
|
|
1671
|
+
}
|
|
1672
|
+
const text = coerceText(textValue);
|
|
1673
|
+
const pattern = coerceText(patternValue);
|
|
1674
|
+
if (returnMode === 1) {
|
|
1675
|
+
const regex = compileRegex(pattern, caseSensitivity, true);
|
|
1676
|
+
if (isRegexError(regex)) {
|
|
1677
|
+
return regex;
|
|
1678
|
+
}
|
|
1679
|
+
const matches = [...text.matchAll(regex)].map((entry) => entry[0]);
|
|
1680
|
+
if (matches.length === 0) {
|
|
1681
|
+
return error(ErrorCode.NA);
|
|
1682
|
+
}
|
|
1683
|
+
return {
|
|
1684
|
+
kind: "array",
|
|
1685
|
+
rows: matches.length,
|
|
1686
|
+
cols: 1,
|
|
1687
|
+
values: matches.map((match) => stringResult(match)),
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
const regex = compileRegex(pattern, caseSensitivity, false);
|
|
1691
|
+
if (isRegexError(regex)) {
|
|
1692
|
+
return regex;
|
|
1693
|
+
}
|
|
1694
|
+
const match = text.match(regex);
|
|
1695
|
+
if (!match) {
|
|
1696
|
+
return error(ErrorCode.NA);
|
|
1697
|
+
}
|
|
1698
|
+
if (returnMode === 0) {
|
|
1699
|
+
return stringResult(match[0]);
|
|
1700
|
+
}
|
|
1701
|
+
const groups = match.slice(1);
|
|
1702
|
+
if (groups.length === 0) {
|
|
1703
|
+
return error(ErrorCode.NA);
|
|
1704
|
+
}
|
|
1705
|
+
return {
|
|
1706
|
+
kind: "array",
|
|
1707
|
+
rows: 1,
|
|
1708
|
+
cols: groups.length,
|
|
1709
|
+
values: groups.map((group) => stringResult(group ?? "")),
|
|
1710
|
+
};
|
|
1711
|
+
},
|
|
1712
|
+
TEXTBEFORE: (...args) => {
|
|
1713
|
+
const existingError = firstError(args);
|
|
1714
|
+
if (existingError) {
|
|
1715
|
+
return existingError;
|
|
1716
|
+
}
|
|
1717
|
+
const [textValue, delimiterValue, instanceValue, matchModeValue, matchEndValue, ifNotFoundValue,] = args;
|
|
1718
|
+
if (textValue === undefined || delimiterValue === undefined) {
|
|
1719
|
+
return error(ErrorCode.Value);
|
|
1720
|
+
}
|
|
1721
|
+
const text = coerceText(textValue);
|
|
1722
|
+
const delimiter = coerceText(delimiterValue);
|
|
1723
|
+
if (delimiter === "") {
|
|
1724
|
+
return error(ErrorCode.Value);
|
|
1725
|
+
}
|
|
1726
|
+
const instanceNumber = instanceValue === undefined ? 1 : coerceNumber(instanceValue);
|
|
1727
|
+
const matchMode = matchModeValue === undefined ? 0 : coerceNumber(matchModeValue);
|
|
1728
|
+
const matchEndNumber = matchEndValue === undefined ? 0 : coerceNumber(matchEndValue);
|
|
1729
|
+
if (instanceNumber === undefined ||
|
|
1730
|
+
matchMode === undefined ||
|
|
1731
|
+
matchEndNumber === undefined ||
|
|
1732
|
+
!Number.isInteger(instanceNumber) ||
|
|
1733
|
+
instanceNumber === 0 ||
|
|
1734
|
+
!Number.isInteger(matchMode) ||
|
|
1735
|
+
(matchMode !== 0 && matchMode !== 1)) {
|
|
1736
|
+
return error(ErrorCode.Value);
|
|
1737
|
+
}
|
|
1738
|
+
const matchEnd = matchEndNumber !== 0;
|
|
1739
|
+
if (instanceNumber > 0) {
|
|
1740
|
+
let searchFrom = 0;
|
|
1741
|
+
let found = -1;
|
|
1742
|
+
for (let count = 0; count < instanceNumber; count += 1) {
|
|
1743
|
+
found = indexOfWithMode(text, delimiter, searchFrom, matchMode);
|
|
1744
|
+
if (found === -1) {
|
|
1745
|
+
return ifNotFoundValue ?? error(ErrorCode.NA);
|
|
1746
|
+
}
|
|
1747
|
+
searchFrom = found + delimiter.length;
|
|
1748
|
+
}
|
|
1749
|
+
return stringResult(text.slice(0, found));
|
|
1750
|
+
}
|
|
1751
|
+
let searchFrom = text.length;
|
|
1752
|
+
let found = matchEnd ? text.length : -1;
|
|
1753
|
+
for (let count = 0; count < Math.abs(instanceNumber); count += 1) {
|
|
1754
|
+
found = lastIndexOfWithMode(text, delimiter, searchFrom, matchMode);
|
|
1755
|
+
if (found === -1) {
|
|
1756
|
+
return ifNotFoundValue ?? error(ErrorCode.NA);
|
|
1757
|
+
}
|
|
1758
|
+
searchFrom = found - 1;
|
|
1759
|
+
}
|
|
1760
|
+
return stringResult(text.slice(0, found));
|
|
1761
|
+
},
|
|
1762
|
+
TEXTAFTER: (...args) => {
|
|
1763
|
+
const existingError = firstError(args);
|
|
1764
|
+
if (existingError) {
|
|
1765
|
+
return existingError;
|
|
1766
|
+
}
|
|
1767
|
+
const [textValue, delimiterValue, instanceValue, matchModeValue, matchEndValue, ifNotFoundValue,] = args;
|
|
1768
|
+
if (textValue === undefined || delimiterValue === undefined) {
|
|
1769
|
+
return error(ErrorCode.Value);
|
|
1770
|
+
}
|
|
1771
|
+
const text = coerceText(textValue);
|
|
1772
|
+
const delimiter = coerceText(delimiterValue);
|
|
1773
|
+
if (delimiter === "") {
|
|
1774
|
+
return error(ErrorCode.Value);
|
|
1775
|
+
}
|
|
1776
|
+
const instanceNumber = instanceValue === undefined ? 1 : coerceNumber(instanceValue);
|
|
1777
|
+
const matchMode = matchModeValue === undefined ? 0 : coerceNumber(matchModeValue);
|
|
1778
|
+
const matchEndNumber = matchEndValue === undefined ? 0 : coerceNumber(matchEndValue);
|
|
1779
|
+
if (instanceNumber === undefined ||
|
|
1780
|
+
matchMode === undefined ||
|
|
1781
|
+
matchEndNumber === undefined ||
|
|
1782
|
+
!Number.isInteger(instanceNumber) ||
|
|
1783
|
+
instanceNumber === 0 ||
|
|
1784
|
+
!Number.isInteger(matchMode) ||
|
|
1785
|
+
(matchMode !== 0 && matchMode !== 1)) {
|
|
1786
|
+
return error(ErrorCode.Value);
|
|
1787
|
+
}
|
|
1788
|
+
const matchEnd = matchEndNumber !== 0;
|
|
1789
|
+
if (instanceNumber > 0) {
|
|
1790
|
+
let searchFrom = 0;
|
|
1791
|
+
let found = -1;
|
|
1792
|
+
for (let count = 0; count < instanceNumber; count += 1) {
|
|
1793
|
+
found = indexOfWithMode(text, delimiter, searchFrom, matchMode);
|
|
1794
|
+
if (found === -1) {
|
|
1795
|
+
return ifNotFoundValue ?? error(ErrorCode.NA);
|
|
1796
|
+
}
|
|
1797
|
+
searchFrom = found + delimiter.length;
|
|
1798
|
+
}
|
|
1799
|
+
return stringResult(text.slice(found + delimiter.length));
|
|
1800
|
+
}
|
|
1801
|
+
let searchFrom = text.length;
|
|
1802
|
+
let found = matchEnd ? text.length : -1;
|
|
1803
|
+
for (let count = 0; count < Math.abs(instanceNumber); count += 1) {
|
|
1804
|
+
found = lastIndexOfWithMode(text, delimiter, searchFrom, matchMode);
|
|
1805
|
+
if (found === -1) {
|
|
1806
|
+
return ifNotFoundValue ?? error(ErrorCode.NA);
|
|
1807
|
+
}
|
|
1808
|
+
searchFrom = found - 1;
|
|
1809
|
+
}
|
|
1810
|
+
const start = found + delimiter.length;
|
|
1811
|
+
return stringResult(text.slice(start));
|
|
1812
|
+
},
|
|
1813
|
+
TEXTJOIN: (...args) => {
|
|
1814
|
+
const existingError = firstError(args);
|
|
1815
|
+
if (existingError) {
|
|
1816
|
+
return existingError;
|
|
1817
|
+
}
|
|
1818
|
+
const [delimiterValue, ignoreEmptyValue, ...values] = args;
|
|
1819
|
+
if (delimiterValue === undefined || ignoreEmptyValue === undefined || values.length === 0) {
|
|
1820
|
+
return error(ErrorCode.Value);
|
|
1821
|
+
}
|
|
1822
|
+
const delimiter = coerceText(delimiterValue);
|
|
1823
|
+
const ignoreEmpty = coerceBoolean(ignoreEmptyValue, false);
|
|
1824
|
+
if (typeof ignoreEmpty !== "boolean") {
|
|
1825
|
+
return ignoreEmpty;
|
|
1826
|
+
}
|
|
1827
|
+
const valuesJoined = [];
|
|
1828
|
+
for (const value of values) {
|
|
1829
|
+
if (value === undefined) {
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
if (value.tag === ValueTag.Empty) {
|
|
1833
|
+
if (!ignoreEmpty) {
|
|
1834
|
+
valuesJoined.push("");
|
|
1835
|
+
}
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (value.tag === ValueTag.String && value.value === "" && !ignoreEmpty) {
|
|
1839
|
+
valuesJoined.push("");
|
|
1840
|
+
continue;
|
|
1841
|
+
}
|
|
1842
|
+
if (value.tag === ValueTag.String && value.value === "" && ignoreEmpty) {
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
valuesJoined.push(coerceText(value));
|
|
1846
|
+
}
|
|
1847
|
+
return stringResult(valuesJoined.join(delimiter));
|
|
1848
|
+
},
|
|
1849
|
+
REPLACE: createReplaceBuiltin(),
|
|
1850
|
+
REPLACEB: (...args) => {
|
|
1851
|
+
const existingError = firstError(args);
|
|
1852
|
+
if (existingError) {
|
|
1853
|
+
return existingError;
|
|
1854
|
+
}
|
|
1855
|
+
const [textValue, startValue, countValue, replacementValue] = args;
|
|
1856
|
+
if (textValue === undefined ||
|
|
1857
|
+
startValue === undefined ||
|
|
1858
|
+
countValue === undefined ||
|
|
1859
|
+
replacementValue === undefined) {
|
|
1860
|
+
return error(ErrorCode.Value);
|
|
1861
|
+
}
|
|
1862
|
+
const start = coercePositiveStart(startValue, 1);
|
|
1863
|
+
if (isErrorValue(start)) {
|
|
1864
|
+
return start;
|
|
1865
|
+
}
|
|
1866
|
+
const count = coerceLength(countValue, 0);
|
|
1867
|
+
if (isErrorValue(count)) {
|
|
1868
|
+
return count;
|
|
1869
|
+
}
|
|
1870
|
+
return stringResult(replaceBytes(coerceText(textValue), start, count, coerceText(replacementValue)));
|
|
1871
|
+
},
|
|
1872
|
+
SUBSTITUTE: createSubstituteBuiltin(),
|
|
1873
|
+
REPT: createReptBuiltin(),
|
|
1874
|
+
...textPlaceholderBuiltins,
|
|
1875
|
+
};
|
|
1876
|
+
export function getTextBuiltin(name) {
|
|
1877
|
+
return textBuiltins[name.toUpperCase()];
|
|
1878
|
+
}
|
|
1879
|
+
//# sourceMappingURL=text.js.map
|