@bilig/xlsx 0.164.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 +25 -0
- package/dist/address.d.ts +15 -0
- package/dist/address.js +82 -0
- package/dist/address.js.map +1 -0
- package/dist/external-workbook-types.d.ts +44 -0
- package/dist/external-workbook-types.js +12 -0
- package/dist/external-workbook-types.js.map +1 -0
- package/dist/file-source.d.ts +10 -0
- package/dist/file-source.js +76 -0
- package/dist/file-source.js.map +1 -0
- package/dist/formula-cache-reader.d.ts +30 -0
- package/dist/formula-cache-reader.js +350 -0
- package/dist/formula-cache-reader.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/simple-workbook-writer.d.ts +114 -0
- package/dist/simple-workbook-writer.js +621 -0
- package/dist/simple-workbook-writer.js.map +1 -0
- package/dist/source-preserving-literal-patches.d.ts +43 -0
- package/dist/source-preserving-literal-patches.js +615 -0
- package/dist/source-preserving-literal-patches.js.map +1 -0
- package/dist/source-preserving-zip.d.ts +25 -0
- package/dist/source-preserving-zip.js +341 -0
- package/dist/source-preserving-zip.js.map +1 -0
- package/dist/streaming-native-cell-arena.d.ts +57 -0
- package/dist/streaming-native-cell-arena.js +233 -0
- package/dist/streaming-native-cell-arena.js.map +1 -0
- package/dist/streaming-native-external-cache.d.ts +13 -0
- package/dist/streaming-native-external-cache.js +753 -0
- package/dist/streaming-native-external-cache.js.map +1 -0
- package/dist/streaming-native-inspect.d.ts +42 -0
- package/dist/streaming-native-inspect.js +297 -0
- package/dist/streaming-native-inspect.js.map +1 -0
- package/dist/streaming-native-lookup-wasm.d.ts +13 -0
- package/dist/streaming-native-lookup-wasm.js +250 -0
- package/dist/streaming-native-lookup-wasm.js.map +1 -0
- package/dist/streaming-native-recalc-evaluator.d.ts +17 -0
- package/dist/streaming-native-recalc-evaluator.js +743 -0
- package/dist/streaming-native-recalc-evaluator.js.map +1 -0
- package/dist/streaming-native-recalc.d.ts +97 -0
- package/dist/streaming-native-recalc.js +652 -0
- package/dist/streaming-native-recalc.js.map +1 -0
- package/dist/streaming-native-row-chain-conditionals.d.ts +8 -0
- package/dist/streaming-native-row-chain-conditionals.js +385 -0
- package/dist/streaming-native-row-chain-conditionals.js.map +1 -0
- package/dist/streaming-native-row-chain-dependencies.d.ts +17 -0
- package/dist/streaming-native-row-chain-dependencies.js +365 -0
- package/dist/streaming-native-row-chain-dependencies.js.map +1 -0
- package/dist/streaming-native-row-chain-references.d.ts +3 -0
- package/dist/streaming-native-row-chain-references.js +36 -0
- package/dist/streaming-native-row-chain-references.js.map +1 -0
- package/dist/streaming-native-row-chain-wasm.d.ts +56 -0
- package/dist/streaming-native-row-chain-wasm.js +546 -0
- package/dist/streaming-native-row-chain-wasm.js.map +1 -0
- package/dist/streaming-native-text.d.ts +2 -0
- package/dist/streaming-native-text.js +14 -0
- package/dist/streaming-native-text.js.map +1 -0
- package/dist/streaming-native-workbook-core.d.ts +47 -0
- package/dist/streaming-native-workbook-core.js +110 -0
- package/dist/streaming-native-workbook-core.js.map +1 -0
- package/dist/targeted-cell-reader.d.ts +11 -0
- package/dist/targeted-cell-reader.js +92 -0
- package/dist/targeted-cell-reader.js.map +1 -0
- package/dist/workbook-cell-reader.d.ts +29 -0
- package/dist/workbook-cell-reader.js +200 -0
- package/dist/workbook-cell-reader.js.map +1 -0
- package/dist/workbook-compatibility-report.d.ts +101 -0
- package/dist/workbook-compatibility-report.js +654 -0
- package/dist/workbook-compatibility-report.js.map +1 -0
- package/dist/workbook-sheet-paths.d.ts +8 -0
- package/dist/workbook-sheet-paths.js +79 -0
- package/dist/workbook-sheet-paths.js.map +1 -0
- package/dist/xml-part-patch.d.ts +12 -0
- package/dist/xml-part-patch.js +45 -0
- package/dist/xml-part-patch.js.map +1 -0
- package/dist/xml.d.ts +9 -0
- package/dist/xml.js +42 -0
- package/dist/xml.js.map +1 -0
- package/dist/zip-reader.d.ts +51 -0
- package/dist/zip-reader.js +448 -0
- package/dist/zip-reader.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,743 @@
|
|
|
1
|
+
import { parseFormula, translateFormulaReferences } from '@bilig/formula';
|
|
2
|
+
import { ErrorCode, formatErrorCode, ValueTag } from '@bilig/protocol';
|
|
3
|
+
import { decodeCellAddress } from './address.js';
|
|
4
|
+
import { isStreamingNativeExternalReferenceAlias, normalizeExternalWorkbookReferences } from './streaming-native-external-cache.js';
|
|
5
|
+
import { evaluateStreamingNativeWasmFormulas } from './streaming-native-row-chain-wasm.js';
|
|
6
|
+
import { normalizeStructuredReferenceColumnName } from './streaming-native-text.js';
|
|
7
|
+
export class UnsupportedStreamingNativeFormulaError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'UnsupportedStreamingNativeFormulaError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const emptyCellValue = Object.freeze({ tag: ValueTag.Empty });
|
|
14
|
+
function stringCellValue(value) {
|
|
15
|
+
return { tag: ValueTag.String, value, stringId: 0 };
|
|
16
|
+
}
|
|
17
|
+
export function evaluateFormulaCells(sheetScans, tablesBySheet, externalCachedRowsByAlias, patches) {
|
|
18
|
+
const nativeFormulaCells = evaluateStreamingNativeWasmFormulas({
|
|
19
|
+
sheetScans,
|
|
20
|
+
tablesBySheet,
|
|
21
|
+
resolveFormulaSource: (scan, cell) => normalizeExternalWorkbookReferences(resolveFormulaSource(scan, cell)),
|
|
22
|
+
});
|
|
23
|
+
let evaluatedFormulaCellCount = nativeFormulaCells.evaluatedFormulaCellCount;
|
|
24
|
+
let patchedFormulaCacheCount = nativeFormulaCells.patches.length;
|
|
25
|
+
let unsupportedFormulaCellCount = 0;
|
|
26
|
+
patches.push(...nativeFormulaCells.patches);
|
|
27
|
+
const sheetRowsByName = new Map([...sheetScans].map(([sheetName, scan]) => [sheetName, scan.rows]));
|
|
28
|
+
for (const scan of sheetScans.values()) {
|
|
29
|
+
const formulaCells = scan.formulaCells.toSorted((left, right) => left.row - right.row || left.col - right.col);
|
|
30
|
+
const formulaPatches = new Map();
|
|
31
|
+
const maxPasses = Math.max(1, formulaCells.length + 1);
|
|
32
|
+
let converged = formulaCells.length === 0;
|
|
33
|
+
for (let pass = 0; pass < maxPasses; pass += 1) {
|
|
34
|
+
let changed = false;
|
|
35
|
+
for (const cell of formulaCells) {
|
|
36
|
+
if (nativeFormulaCells.processedCells.has(`${cell.sheetName}!${cell.address}`)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const rowValues = scan.rows.get(cell.row);
|
|
40
|
+
if (!rowValues) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const formula = resolveFormulaSource(scan, cell);
|
|
45
|
+
const ast = parseStreamingNativeFormula(formula);
|
|
46
|
+
const value = normalizeFormulaResultValue(evaluateFormulaAst(ast, {
|
|
47
|
+
sheetName: cell.sheetName,
|
|
48
|
+
row: cell.row,
|
|
49
|
+
col: cell.col,
|
|
50
|
+
rowValues,
|
|
51
|
+
sheetRows: scan.rows,
|
|
52
|
+
sheetRowsByName,
|
|
53
|
+
externalCachedRowsByAlias,
|
|
54
|
+
tablesBySheet,
|
|
55
|
+
}));
|
|
56
|
+
changed = changed || !cellValuesEqual(resolvedCellValue(rowValues.get(cell.col)), value);
|
|
57
|
+
rowValues.set(cell.col, value);
|
|
58
|
+
const patchValue = literalInputForFormulaCache(value);
|
|
59
|
+
if (patchValue === undefined) {
|
|
60
|
+
const resultDetail = value.tag === ValueTag.Error ? ` error ${String(value.code)}` : '';
|
|
61
|
+
throw new UnsupportedStreamingNativeFormulaError(`unsupported formula result${resultDetail} at ${cell.sheetName}!${cell.address}`);
|
|
62
|
+
}
|
|
63
|
+
formulaPatches.set(`${cell.sheetName}!${cell.address}`, {
|
|
64
|
+
sheetName: cell.sheetName,
|
|
65
|
+
address: cell.address,
|
|
66
|
+
value: patchValue,
|
|
67
|
+
preserveFormula: true,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
unsupportedFormulaCellCount += 1;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!changed) {
|
|
76
|
+
converged = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!converged) {
|
|
81
|
+
throw new UnsupportedStreamingNativeFormulaError(`row-local formulas did not converge for sheet ${scan.sheetName}`);
|
|
82
|
+
}
|
|
83
|
+
evaluatedFormulaCellCount += formulaPatches.size;
|
|
84
|
+
patchedFormulaCacheCount += formulaPatches.size;
|
|
85
|
+
patches.push(...formulaPatches.values());
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
scannedFormulaCellCount: [...sheetScans.values()].reduce((sum, scan) => sum + scan.scannedFormulaCellCount, 0),
|
|
89
|
+
targetedFormulaCellCount: [...sheetScans.values()].reduce((sum, scan) => sum + scan.formulaCells.length, 0),
|
|
90
|
+
evaluatedFormulaCellCount,
|
|
91
|
+
patchedFormulaCacheCount,
|
|
92
|
+
unsupportedFormulaCellCount,
|
|
93
|
+
nativeKernelFormulaCellCount: nativeFormulaCells.evaluatedFormulaCellCount,
|
|
94
|
+
nativeKernelBatchCount: nativeFormulaCells.batchCount,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function cellValuesEqual(left, right) {
|
|
98
|
+
if (left.tag !== right.tag) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
switch (left.tag) {
|
|
102
|
+
case ValueTag.Empty:
|
|
103
|
+
return true;
|
|
104
|
+
case ValueTag.Number:
|
|
105
|
+
return right.tag === ValueTag.Number && Object.is(left.value, right.value);
|
|
106
|
+
case ValueTag.Boolean:
|
|
107
|
+
return right.tag === ValueTag.Boolean && left.value === right.value;
|
|
108
|
+
case ValueTag.String:
|
|
109
|
+
return right.tag === ValueTag.String && left.value === right.value;
|
|
110
|
+
case ValueTag.Error:
|
|
111
|
+
return right.tag === ValueTag.Error && left.code === right.code;
|
|
112
|
+
default:
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function resolveFormulaSource(scan, cell) {
|
|
117
|
+
if (cell.formula) {
|
|
118
|
+
return cell.formula;
|
|
119
|
+
}
|
|
120
|
+
if (!cell.sharedFormulaIndex) {
|
|
121
|
+
throw new UnsupportedStreamingNativeFormulaError(`missing formula text at ${cell.sheetName}!${cell.address}`);
|
|
122
|
+
}
|
|
123
|
+
const master = scan.sharedFormulaMasters.get(cell.sharedFormulaIndex);
|
|
124
|
+
if (!master) {
|
|
125
|
+
throw new UnsupportedStreamingNativeFormulaError(`missing shared formula master ${cell.sharedFormulaIndex} at ${cell.sheetName}!${cell.address}`);
|
|
126
|
+
}
|
|
127
|
+
return translateFormulaReferences(master.formula, cell.row - master.row, cell.col - master.col);
|
|
128
|
+
}
|
|
129
|
+
function parseStreamingNativeFormula(formula) {
|
|
130
|
+
return parseFormula(normalizeExternalWorkbookReferences(formula));
|
|
131
|
+
}
|
|
132
|
+
function evaluateFormulaAst(node, context) {
|
|
133
|
+
switch (node.kind) {
|
|
134
|
+
case 'NumberLiteral':
|
|
135
|
+
return { tag: ValueTag.Number, value: node.value };
|
|
136
|
+
case 'BooleanLiteral':
|
|
137
|
+
return { tag: ValueTag.Boolean, value: node.value };
|
|
138
|
+
case 'StringLiteral':
|
|
139
|
+
return stringCellValue(node.value);
|
|
140
|
+
case 'CellRef':
|
|
141
|
+
return readCellReference(node, context);
|
|
142
|
+
case 'StructuredRef':
|
|
143
|
+
return readStructuredReference(node, context);
|
|
144
|
+
case 'UnaryExpr': {
|
|
145
|
+
const value = evaluateFormulaAst(node.argument, context);
|
|
146
|
+
if (node.operator === '+' && value.tag === ValueTag.String) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
const number = coerceNumber(value);
|
|
150
|
+
return { tag: ValueTag.Number, value: node.operator === '-' ? -number : number };
|
|
151
|
+
}
|
|
152
|
+
case 'BinaryExpr':
|
|
153
|
+
return evaluateBinaryExpr(node.operator, evaluateFormulaAst(node.left, context), evaluateFormulaAst(node.right, context));
|
|
154
|
+
case 'CallExpr':
|
|
155
|
+
return evaluateCallExpr(node, context);
|
|
156
|
+
case 'ErrorLiteral':
|
|
157
|
+
return { tag: ValueTag.Error, code: node.code };
|
|
158
|
+
case 'OmittedArgument':
|
|
159
|
+
case 'ArrayConstant':
|
|
160
|
+
case 'NameRef':
|
|
161
|
+
case 'SpillRef':
|
|
162
|
+
case 'RowRef':
|
|
163
|
+
case 'ColumnRef':
|
|
164
|
+
case 'RangeRef':
|
|
165
|
+
case 'InvokeExpr':
|
|
166
|
+
throw new UnsupportedStreamingNativeFormulaError(`unsupported formula node: ${node.kind}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function readCellReference(node, context) {
|
|
170
|
+
const sheetName = node.sheetName ?? context.sheetName;
|
|
171
|
+
const address = decodeCellAddress(node.ref.replaceAll('$', ''));
|
|
172
|
+
return readScannedCell(sheetName, address.r, address.c, context);
|
|
173
|
+
}
|
|
174
|
+
function readScannedCell(sheetName, row, col, context) {
|
|
175
|
+
const externalRows = context.externalCachedRowsByAlias.get(sheetName);
|
|
176
|
+
if (externalRows) {
|
|
177
|
+
const rowValues = externalRows.get(row);
|
|
178
|
+
if (!rowValues) {
|
|
179
|
+
throw new UnsupportedStreamingNativeFormulaError(`external workbook cache row is missing for ${sheetName}!${String(row + 1)}`);
|
|
180
|
+
}
|
|
181
|
+
return resolvedCellValue(rowValues.get(col));
|
|
182
|
+
}
|
|
183
|
+
if (isStreamingNativeExternalReferenceAlias(sheetName)) {
|
|
184
|
+
throw new UnsupportedStreamingNativeFormulaError(`external workbook cache sheet is missing for ${sheetName}`);
|
|
185
|
+
}
|
|
186
|
+
const sheetRows = sheetName === context.sheetName ? context.sheetRows : context.sheetRowsByName.get(sheetName);
|
|
187
|
+
if (!sheetRows) {
|
|
188
|
+
throw new UnsupportedStreamingNativeFormulaError(`sheet was not scanned for direct reference: ${sheetName}`);
|
|
189
|
+
}
|
|
190
|
+
const rowValues = sheetName === context.sheetName && row === context.row ? context.rowValues : sheetRows.get(row);
|
|
191
|
+
return resolvedCellValue(rowValues?.get(col));
|
|
192
|
+
}
|
|
193
|
+
function readStructuredReference(node, context) {
|
|
194
|
+
if (node.endColumnName) {
|
|
195
|
+
throw new UnsupportedStreamingNativeFormulaError(`multi-column structured reference is not scalar: ${node.columnName}:${node.endColumnName}`);
|
|
196
|
+
}
|
|
197
|
+
if (node.section !== undefined && node.section !== 'this-row') {
|
|
198
|
+
throw new UnsupportedStreamingNativeFormulaError(`unsupported structured reference section: ${node.section}`);
|
|
199
|
+
}
|
|
200
|
+
const table = findCurrentRowTable(node.tableName, context);
|
|
201
|
+
const columnName = normalizeStructuredReferenceColumnName(node.columnName);
|
|
202
|
+
const columnIndex = table.columns.findIndex((column) => column === columnName);
|
|
203
|
+
const normalizedColumnIndex = columnIndex >= 0
|
|
204
|
+
? columnIndex
|
|
205
|
+
: table.columns.findIndex((column) => column.toLocaleLowerCase('en-US') === columnName.toLocaleLowerCase('en-US'));
|
|
206
|
+
if (normalizedColumnIndex < 0) {
|
|
207
|
+
throw new UnsupportedStreamingNativeFormulaError(`unknown structured reference column: ${node.columnName}`);
|
|
208
|
+
}
|
|
209
|
+
return resolvedCellValue(context.rowValues.get(table.range.s.c + normalizedColumnIndex));
|
|
210
|
+
}
|
|
211
|
+
function findCurrentRowTable(tableName, context) {
|
|
212
|
+
const tables = context.tablesBySheet.get(context.sheetName) ?? [];
|
|
213
|
+
const matching = tables.filter((table) => {
|
|
214
|
+
const nameMatches = tableName.length === 0 ||
|
|
215
|
+
table.name.toLocaleLowerCase('en-US') === tableName.toLocaleLowerCase('en-US') ||
|
|
216
|
+
table.displayName.toLocaleLowerCase('en-US') === tableName.toLocaleLowerCase('en-US');
|
|
217
|
+
return nameMatches && rowIsInTableDataBody(table, context.row);
|
|
218
|
+
});
|
|
219
|
+
const containingFormulaCell = matching.find((table) => context.col >= table.range.s.c && context.col <= table.range.e.c);
|
|
220
|
+
const table = containingFormulaCell ?? matching[0];
|
|
221
|
+
if (!table) {
|
|
222
|
+
throw new UnsupportedStreamingNativeFormulaError(tableName.length === 0 ? 'unable to resolve current-row table' : `unable to resolve current-row table: ${tableName}`);
|
|
223
|
+
}
|
|
224
|
+
return table;
|
|
225
|
+
}
|
|
226
|
+
function rowIsInTableDataBody(table, row) {
|
|
227
|
+
const start = table.range.s.r + table.headerRowCount;
|
|
228
|
+
const end = table.range.e.r - table.totalsRowCount;
|
|
229
|
+
return row >= start && row <= end;
|
|
230
|
+
}
|
|
231
|
+
function evaluateBinaryExpr(operator, left, right) {
|
|
232
|
+
const error = binaryErrorValue(left, right);
|
|
233
|
+
if (error) {
|
|
234
|
+
return error;
|
|
235
|
+
}
|
|
236
|
+
switch (operator) {
|
|
237
|
+
case '+':
|
|
238
|
+
return { tag: ValueTag.Number, value: coerceNumber(left) + coerceNumber(right) };
|
|
239
|
+
case '-':
|
|
240
|
+
return { tag: ValueTag.Number, value: coerceNumber(left) - coerceNumber(right) };
|
|
241
|
+
case '*':
|
|
242
|
+
return { tag: ValueTag.Number, value: coerceNumber(left) * coerceNumber(right) };
|
|
243
|
+
case '/': {
|
|
244
|
+
const divisor = coerceNumber(right);
|
|
245
|
+
return divisor === 0 ? { tag: ValueTag.Error, code: ErrorCode.Div0 } : { tag: ValueTag.Number, value: coerceNumber(left) / divisor };
|
|
246
|
+
}
|
|
247
|
+
case '&':
|
|
248
|
+
return stringCellValue(cellValueText(left) + cellValueText(right));
|
|
249
|
+
case '^': {
|
|
250
|
+
const value = Math.pow(coerceNumber(left), coerceNumber(right));
|
|
251
|
+
return Number.isFinite(value) ? { tag: ValueTag.Number, value } : { tag: ValueTag.Error, code: ErrorCode.Num };
|
|
252
|
+
}
|
|
253
|
+
case '=':
|
|
254
|
+
return { tag: ValueTag.Boolean, value: compareCellValues(left, right) === 0 };
|
|
255
|
+
case '<>':
|
|
256
|
+
return { tag: ValueTag.Boolean, value: compareCellValues(left, right) !== 0 };
|
|
257
|
+
case '>':
|
|
258
|
+
return { tag: ValueTag.Boolean, value: compareCellValues(left, right) > 0 };
|
|
259
|
+
case '>=':
|
|
260
|
+
return { tag: ValueTag.Boolean, value: compareCellValues(left, right) >= 0 };
|
|
261
|
+
case '<':
|
|
262
|
+
return { tag: ValueTag.Boolean, value: compareCellValues(left, right) < 0 };
|
|
263
|
+
case '<=':
|
|
264
|
+
return { tag: ValueTag.Boolean, value: compareCellValues(left, right) <= 0 };
|
|
265
|
+
case ':':
|
|
266
|
+
throw new UnsupportedStreamingNativeFormulaError(`unsupported binary operator: ${operator}`);
|
|
267
|
+
default:
|
|
268
|
+
throw new UnsupportedStreamingNativeFormulaError(`unknown binary operator: ${operator}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function binaryErrorValue(left, right) {
|
|
272
|
+
if (left.tag === ValueTag.Error) {
|
|
273
|
+
return left;
|
|
274
|
+
}
|
|
275
|
+
if (right.tag === ValueTag.Error) {
|
|
276
|
+
return right;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
function evaluateCallExpr(node, context) {
|
|
281
|
+
const callee = normalizedFormulaFunctionName(node.callee);
|
|
282
|
+
if (callee === 'IF') {
|
|
283
|
+
if (node.args.length < 2 || node.args.length > 3) {
|
|
284
|
+
throw new UnsupportedStreamingNativeFormulaError('IF requires 2 or 3 arguments');
|
|
285
|
+
}
|
|
286
|
+
const condition = evaluateFormulaAst(node.args[0], context);
|
|
287
|
+
if (condition.tag === ValueTag.Error) {
|
|
288
|
+
return condition;
|
|
289
|
+
}
|
|
290
|
+
return coerceBoolean(condition)
|
|
291
|
+
? evaluateFormulaAst(node.args[1], context)
|
|
292
|
+
: node.args[2]
|
|
293
|
+
? evaluateFormulaAst(node.args[2], context)
|
|
294
|
+
: { tag: ValueTag.Boolean, value: false };
|
|
295
|
+
}
|
|
296
|
+
if (callee === 'IFS') {
|
|
297
|
+
if (node.args.length < 2 || node.args.length % 2 !== 0) {
|
|
298
|
+
throw new UnsupportedStreamingNativeFormulaError('IFS requires condition/value pairs');
|
|
299
|
+
}
|
|
300
|
+
for (let index = 0; index < node.args.length; index += 2) {
|
|
301
|
+
const condition = evaluateFormulaAst(node.args[index], context);
|
|
302
|
+
if (condition.tag === ValueTag.Error) {
|
|
303
|
+
return condition;
|
|
304
|
+
}
|
|
305
|
+
if (coerceBoolean(condition)) {
|
|
306
|
+
return evaluateFormulaAst(node.args[index + 1], context);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return { tag: ValueTag.Error, code: ErrorCode.NA };
|
|
310
|
+
}
|
|
311
|
+
if (callee === 'VLOOKUP') {
|
|
312
|
+
return evaluateVlookup(node, context);
|
|
313
|
+
}
|
|
314
|
+
if (callee === 'SUM') {
|
|
315
|
+
return evaluateSum(node, context);
|
|
316
|
+
}
|
|
317
|
+
if (callee === 'COUNTA') {
|
|
318
|
+
return evaluateCounta(node, context);
|
|
319
|
+
}
|
|
320
|
+
if (callee === 'AVERAGE') {
|
|
321
|
+
return evaluateAverage(node, context);
|
|
322
|
+
}
|
|
323
|
+
if (callee === 'ROUND') {
|
|
324
|
+
return evaluateRound(node, context);
|
|
325
|
+
}
|
|
326
|
+
if (callee === 'REPT') {
|
|
327
|
+
return evaluateRept(node, context);
|
|
328
|
+
}
|
|
329
|
+
if (callee === 'ISERROR') {
|
|
330
|
+
return evaluateIsError(node, context);
|
|
331
|
+
}
|
|
332
|
+
if (callee === 'INDEX') {
|
|
333
|
+
return evaluateIndex(node, context);
|
|
334
|
+
}
|
|
335
|
+
if (callee === 'MATCH') {
|
|
336
|
+
return evaluateMatch(node, context);
|
|
337
|
+
}
|
|
338
|
+
throw new UnsupportedStreamingNativeFormulaError(`unsupported function: ${node.callee}`);
|
|
339
|
+
}
|
|
340
|
+
function evaluateSum(node, context) {
|
|
341
|
+
if (node.args.length < 1) {
|
|
342
|
+
throw new UnsupportedStreamingNativeFormulaError('SUM requires at least 1 argument');
|
|
343
|
+
}
|
|
344
|
+
let total = 0;
|
|
345
|
+
for (const argument of node.args) {
|
|
346
|
+
if (argument.kind === 'OmittedArgument') {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (argument.kind === 'RangeRef') {
|
|
350
|
+
for (const value of readScannedCellRange(argument, context)) {
|
|
351
|
+
if (value.tag === ValueTag.Error) {
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
354
|
+
if (value.tag === ValueTag.Number) {
|
|
355
|
+
total += value.value;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const value = evaluateFormulaAst(argument, context);
|
|
361
|
+
if (value.tag === ValueTag.Error) {
|
|
362
|
+
return value;
|
|
363
|
+
}
|
|
364
|
+
total += coerceNumber(value);
|
|
365
|
+
}
|
|
366
|
+
return { tag: ValueTag.Number, value: total };
|
|
367
|
+
}
|
|
368
|
+
function evaluateCounta(node, context) {
|
|
369
|
+
if (node.args.length < 1) {
|
|
370
|
+
throw new UnsupportedStreamingNativeFormulaError('COUNTA requires at least 1 argument');
|
|
371
|
+
}
|
|
372
|
+
let count = 0;
|
|
373
|
+
for (const argument of node.args) {
|
|
374
|
+
const values = argument.kind === 'RangeRef' ? readScannedCellRange(argument, context) : [evaluateFormulaAst(argument, context)];
|
|
375
|
+
for (const value of values) {
|
|
376
|
+
if (value.tag !== ValueTag.Empty) {
|
|
377
|
+
count += 1;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return { tag: ValueTag.Number, value: count };
|
|
382
|
+
}
|
|
383
|
+
function evaluateAverage(node, context) {
|
|
384
|
+
if (node.args.length < 1) {
|
|
385
|
+
throw new UnsupportedStreamingNativeFormulaError('AVERAGE requires at least 1 argument');
|
|
386
|
+
}
|
|
387
|
+
let total = 0;
|
|
388
|
+
let count = 0;
|
|
389
|
+
for (const argument of node.args) {
|
|
390
|
+
if (argument.kind === 'RangeRef') {
|
|
391
|
+
for (const value of readScannedCellRange(argument, context)) {
|
|
392
|
+
if (value.tag === ValueTag.Error) {
|
|
393
|
+
return value;
|
|
394
|
+
}
|
|
395
|
+
if (value.tag === ValueTag.Number) {
|
|
396
|
+
total += value.value;
|
|
397
|
+
count += 1;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const value = evaluateFormulaAst(argument, context);
|
|
403
|
+
if (value.tag === ValueTag.Error) {
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
total += coerceNumber(value);
|
|
407
|
+
count += 1;
|
|
408
|
+
}
|
|
409
|
+
return count === 0 ? { tag: ValueTag.Error, code: ErrorCode.Div0 } : { tag: ValueTag.Number, value: total / count };
|
|
410
|
+
}
|
|
411
|
+
function evaluateRound(node, context) {
|
|
412
|
+
if (node.args.length !== 2) {
|
|
413
|
+
throw new UnsupportedStreamingNativeFormulaError('ROUND requires 2 arguments');
|
|
414
|
+
}
|
|
415
|
+
const valueInput = evaluateFormulaAst(node.args[0], context);
|
|
416
|
+
if (valueInput.tag === ValueTag.Error) {
|
|
417
|
+
return valueInput;
|
|
418
|
+
}
|
|
419
|
+
const digitsInput = evaluateFormulaAst(node.args[1], context);
|
|
420
|
+
if (digitsInput.tag === ValueTag.Error) {
|
|
421
|
+
return digitsInput;
|
|
422
|
+
}
|
|
423
|
+
const value = coerceNumber(valueInput);
|
|
424
|
+
const digits = Math.trunc(coerceNumber(digitsInput));
|
|
425
|
+
const rounded = roundHalfAwayFromZero(value, digits);
|
|
426
|
+
return Number.isFinite(rounded) ? { tag: ValueTag.Number, value: rounded } : { tag: ValueTag.Error, code: ErrorCode.Num };
|
|
427
|
+
}
|
|
428
|
+
function evaluateRept(node, context) {
|
|
429
|
+
if (node.args.length !== 2) {
|
|
430
|
+
throw new UnsupportedStreamingNativeFormulaError('REPT requires 2 arguments');
|
|
431
|
+
}
|
|
432
|
+
const text = cellValueText(evaluateFormulaAst(node.args[0], context));
|
|
433
|
+
const count = repeatCountForRept(evaluateFormulaAst(node.args[1], context));
|
|
434
|
+
if (count === null || count < 0) {
|
|
435
|
+
return { tag: ValueTag.Error, code: ErrorCode.Value };
|
|
436
|
+
}
|
|
437
|
+
if (text.length * count > 32767) {
|
|
438
|
+
return { tag: ValueTag.Error, code: ErrorCode.Value };
|
|
439
|
+
}
|
|
440
|
+
return stringCellValue(text.repeat(count));
|
|
441
|
+
}
|
|
442
|
+
function repeatCountForRept(value) {
|
|
443
|
+
switch (value.tag) {
|
|
444
|
+
case ValueTag.Number:
|
|
445
|
+
return Number.isFinite(value.value) ? Math.trunc(value.value) : null;
|
|
446
|
+
case ValueTag.Boolean:
|
|
447
|
+
return value.value ? 1 : 0;
|
|
448
|
+
case ValueTag.Empty:
|
|
449
|
+
return 0;
|
|
450
|
+
case ValueTag.String: {
|
|
451
|
+
const numeric = Number(value.value);
|
|
452
|
+
return Number.isFinite(numeric) ? Math.trunc(numeric) : null;
|
|
453
|
+
}
|
|
454
|
+
case ValueTag.Error:
|
|
455
|
+
return null;
|
|
456
|
+
default:
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function evaluateIsError(node, context) {
|
|
461
|
+
if (node.args.length !== 1) {
|
|
462
|
+
throw new UnsupportedStreamingNativeFormulaError('ISERROR requires 1 argument');
|
|
463
|
+
}
|
|
464
|
+
return { tag: ValueTag.Boolean, value: evaluateFormulaAst(node.args[0], context).tag === ValueTag.Error };
|
|
465
|
+
}
|
|
466
|
+
function evaluateIndex(node, context) {
|
|
467
|
+
if (node.args.length < 2 || node.args.length > 3) {
|
|
468
|
+
throw new UnsupportedStreamingNativeFormulaError('INDEX requires 2 or 3 arguments');
|
|
469
|
+
}
|
|
470
|
+
const rangeArgument = node.args[0];
|
|
471
|
+
if (rangeArgument.kind !== 'RangeRef') {
|
|
472
|
+
throw new UnsupportedStreamingNativeFormulaError('INDEX array must be a cell range');
|
|
473
|
+
}
|
|
474
|
+
const range = decodeFormulaCellRange(rangeArgument, context.sheetName);
|
|
475
|
+
const rowIndex = integerFromFormulaValue(evaluateFormulaAst(node.args[1], context));
|
|
476
|
+
const colIndex = node.args[2] ? integerFromFormulaValue(evaluateFormulaAst(node.args[2], context)) : 1;
|
|
477
|
+
if (rowIndex < 1 || colIndex < 1) {
|
|
478
|
+
return { tag: ValueTag.Error, code: ErrorCode.Value };
|
|
479
|
+
}
|
|
480
|
+
if (rowIndex > range.endRow - range.startRow + 1 || colIndex > range.width) {
|
|
481
|
+
return { tag: ValueTag.Error, code: ErrorCode.Ref };
|
|
482
|
+
}
|
|
483
|
+
return readScannedCell(range.sheetName, range.startRow + rowIndex - 1, range.startCol + colIndex - 1, context);
|
|
484
|
+
}
|
|
485
|
+
function evaluateMatch(node, context) {
|
|
486
|
+
if (node.args.length < 2 || node.args.length > 3) {
|
|
487
|
+
throw new UnsupportedStreamingNativeFormulaError('MATCH requires 2 or 3 arguments');
|
|
488
|
+
}
|
|
489
|
+
const matchType = node.args[2] ? coerceNumber(evaluateFormulaAst(node.args[2], context)) : 1;
|
|
490
|
+
if (matchType !== 0) {
|
|
491
|
+
throw new UnsupportedStreamingNativeFormulaError('MATCH supports exact match only');
|
|
492
|
+
}
|
|
493
|
+
const rangeArgument = node.args[1];
|
|
494
|
+
if (rangeArgument.kind !== 'RangeRef') {
|
|
495
|
+
throw new UnsupportedStreamingNativeFormulaError('MATCH lookup array must be a cell range');
|
|
496
|
+
}
|
|
497
|
+
const range = decodeFormulaCellRange(rangeArgument, context.sheetName);
|
|
498
|
+
if (range.width !== 1 && range.endRow !== range.startRow) {
|
|
499
|
+
throw new UnsupportedStreamingNativeFormulaError('MATCH exact lookup array must be one-dimensional');
|
|
500
|
+
}
|
|
501
|
+
const lookupValue = evaluateFormulaAst(node.args[0], context);
|
|
502
|
+
const values = readScannedCellRange(rangeArgument, context);
|
|
503
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
504
|
+
if (compareCellValues(values[index], lookupValue) === 0) {
|
|
505
|
+
return { tag: ValueTag.Number, value: index + 1 };
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return { tag: ValueTag.Error, code: ErrorCode.NA };
|
|
509
|
+
}
|
|
510
|
+
function roundHalfAwayFromZero(value, digits) {
|
|
511
|
+
if (!Number.isFinite(value) || !Number.isFinite(digits)) {
|
|
512
|
+
return Number.NaN;
|
|
513
|
+
}
|
|
514
|
+
if (digits >= 0) {
|
|
515
|
+
const factor = 10 ** digits;
|
|
516
|
+
return (Math.sign(value) * Math.round(Math.abs(value) * factor)) / factor;
|
|
517
|
+
}
|
|
518
|
+
const factor = 10 ** -digits;
|
|
519
|
+
return Math.sign(value) * Math.round(Math.abs(value) / factor) * factor;
|
|
520
|
+
}
|
|
521
|
+
function readScannedCellRange(rangeNode, context) {
|
|
522
|
+
const range = decodeFormulaCellRange(rangeNode, context.sheetName);
|
|
523
|
+
const values = [];
|
|
524
|
+
const endCol = range.startCol + range.width - 1;
|
|
525
|
+
for (let row = range.startRow; row <= range.endRow; row += 1) {
|
|
526
|
+
for (let col = range.startCol; col <= endCol; col += 1) {
|
|
527
|
+
values.push(readScannedCell(range.sheetName, row, col, context));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return values;
|
|
531
|
+
}
|
|
532
|
+
function evaluateVlookup(node, context) {
|
|
533
|
+
if (node.args.length < 3 || node.args.length > 4) {
|
|
534
|
+
throw new UnsupportedStreamingNativeFormulaError('VLOOKUP requires 3 or 4 arguments');
|
|
535
|
+
}
|
|
536
|
+
const lookupValue = evaluateFormulaAst(node.args[0], context);
|
|
537
|
+
const columnIndex = integerFromFormulaValue(evaluateFormulaAst(node.args[2], context));
|
|
538
|
+
if (columnIndex < 1) {
|
|
539
|
+
return { tag: ValueTag.Error, code: ErrorCode.Value };
|
|
540
|
+
}
|
|
541
|
+
const matchMode = vlookupMatchMode(node.args[3], context);
|
|
542
|
+
const range = resolveVlookupTableRange(node.args[1], context);
|
|
543
|
+
if (columnIndex > range.width) {
|
|
544
|
+
return { tag: ValueTag.Error, code: ErrorCode.Ref };
|
|
545
|
+
}
|
|
546
|
+
const resultCol = range.startCol + columnIndex - 1;
|
|
547
|
+
let approximateMatchRow = null;
|
|
548
|
+
for (let row = range.startRow; row <= range.endRow; row += 1) {
|
|
549
|
+
const candidate = readScannedCell(range.sheetName, row, range.startCol, context);
|
|
550
|
+
const comparison = compareVlookupValues(candidate, lookupValue);
|
|
551
|
+
if (comparison === 0) {
|
|
552
|
+
return readScannedCell(range.sheetName, row, resultCol, context);
|
|
553
|
+
}
|
|
554
|
+
if (matchMode === 'approximate' && comparison < 0) {
|
|
555
|
+
approximateMatchRow = row;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (matchMode === 'approximate' && comparison > 0) {
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (approximateMatchRow !== null) {
|
|
563
|
+
return readScannedCell(range.sheetName, approximateMatchRow, resultCol, context);
|
|
564
|
+
}
|
|
565
|
+
return { tag: ValueTag.Error, code: ErrorCode.NA };
|
|
566
|
+
}
|
|
567
|
+
function resolveVlookupTableRange(node, context) {
|
|
568
|
+
if (!node) {
|
|
569
|
+
throw new UnsupportedStreamingNativeFormulaError('VLOOKUP table array must be a scalar cell range');
|
|
570
|
+
}
|
|
571
|
+
if (node.kind === 'RangeRef') {
|
|
572
|
+
return decodeFormulaCellRange(node, context.sheetName);
|
|
573
|
+
}
|
|
574
|
+
if (node.kind === 'CallExpr' && normalizedFormulaFunctionName(node.callee) === 'INDIRECT') {
|
|
575
|
+
return decodeFormulaCellRange(resolveIndirectCellRange(node, context), context.sheetName);
|
|
576
|
+
}
|
|
577
|
+
throw new UnsupportedStreamingNativeFormulaError('VLOOKUP table array must be a scalar cell range');
|
|
578
|
+
}
|
|
579
|
+
function resolveIndirectCellRange(node, context) {
|
|
580
|
+
if (node.args.length < 1 || node.args.length > 2) {
|
|
581
|
+
throw new UnsupportedStreamingNativeFormulaError('INDIRECT requires 1 or 2 arguments');
|
|
582
|
+
}
|
|
583
|
+
if (node.args[1]) {
|
|
584
|
+
const a1Style = evaluateFormulaAst(node.args[1], context);
|
|
585
|
+
if (!coerceBoolean(a1Style)) {
|
|
586
|
+
throw new UnsupportedStreamingNativeFormulaError('INDIRECT R1C1 references are not supported');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const referenceText = cellValueText(evaluateFormulaAst(node.args[0], context));
|
|
590
|
+
const range = parseFormula(referenceText);
|
|
591
|
+
if (range.kind !== 'RangeRef' || range.refKind !== 'cells' || range.sheetEndName !== undefined) {
|
|
592
|
+
throw new UnsupportedStreamingNativeFormulaError('INDIRECT must resolve to a scalar cell range');
|
|
593
|
+
}
|
|
594
|
+
return range;
|
|
595
|
+
}
|
|
596
|
+
function normalizedFormulaFunctionName(name) {
|
|
597
|
+
return name.toUpperCase().replace(/^_XLFN\./u, '');
|
|
598
|
+
}
|
|
599
|
+
function vlookupMatchMode(node, context) {
|
|
600
|
+
if (!node) {
|
|
601
|
+
return 'approximate';
|
|
602
|
+
}
|
|
603
|
+
const value = evaluateFormulaAst(node, context);
|
|
604
|
+
if ((value.tag === ValueTag.Boolean && !value.value) || (value.tag === ValueTag.Number && value.value === 0)) {
|
|
605
|
+
return 'exact';
|
|
606
|
+
}
|
|
607
|
+
if ((value.tag === ValueTag.Boolean && value.value) || (value.tag === ValueTag.Number && value.value === 1)) {
|
|
608
|
+
return 'approximate';
|
|
609
|
+
}
|
|
610
|
+
throw new UnsupportedStreamingNativeFormulaError('VLOOKUP range lookup must be TRUE/FALSE or 1/0');
|
|
611
|
+
}
|
|
612
|
+
function integerFromFormulaValue(value) {
|
|
613
|
+
const number = coerceNumber(value);
|
|
614
|
+
if (!Number.isInteger(number)) {
|
|
615
|
+
throw new UnsupportedStreamingNativeFormulaError(`expected integer formula value, received ${String(number)}`);
|
|
616
|
+
}
|
|
617
|
+
return number;
|
|
618
|
+
}
|
|
619
|
+
function decodeFormulaCellRange(range, currentSheetName) {
|
|
620
|
+
if (range.refKind !== 'cells' || range.sheetEndName !== undefined) {
|
|
621
|
+
throw new UnsupportedStreamingNativeFormulaError('expected scalar cell range');
|
|
622
|
+
}
|
|
623
|
+
const start = decodeCellAddress(range.start.replaceAll('$', ''));
|
|
624
|
+
const end = decodeCellAddress(range.end.replaceAll('$', ''));
|
|
625
|
+
const startRow = Math.min(start.r, end.r);
|
|
626
|
+
const endRow = Math.max(start.r, end.r);
|
|
627
|
+
const startCol = Math.min(start.c, end.c);
|
|
628
|
+
const endCol = Math.max(start.c, end.c);
|
|
629
|
+
return {
|
|
630
|
+
sheetName: range.sheetName ?? currentSheetName,
|
|
631
|
+
startRow,
|
|
632
|
+
endRow,
|
|
633
|
+
startCol,
|
|
634
|
+
width: endCol - startCol + 1,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function compareVlookupValues(left, right) {
|
|
638
|
+
try {
|
|
639
|
+
return compareCellValues(left, right);
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
throw new UnsupportedStreamingNativeFormulaError(error instanceof Error ? error.message : String(error));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function coerceNumber(value) {
|
|
646
|
+
switch (value.tag) {
|
|
647
|
+
case ValueTag.Number:
|
|
648
|
+
return value.value;
|
|
649
|
+
case ValueTag.Boolean:
|
|
650
|
+
return value.value ? 1 : 0;
|
|
651
|
+
case ValueTag.Empty:
|
|
652
|
+
return 0;
|
|
653
|
+
case ValueTag.String: {
|
|
654
|
+
const numeric = Number(value.value);
|
|
655
|
+
if (Number.isFinite(numeric)) {
|
|
656
|
+
return numeric;
|
|
657
|
+
}
|
|
658
|
+
throw new UnsupportedStreamingNativeFormulaError(`cannot coerce string to number: ${value.value}`);
|
|
659
|
+
}
|
|
660
|
+
case ValueTag.Error:
|
|
661
|
+
throw new UnsupportedStreamingNativeFormulaError(`cannot coerce error to number: ${String(value.code)}`);
|
|
662
|
+
default:
|
|
663
|
+
throw new UnsupportedStreamingNativeFormulaError('cannot coerce unknown value to number');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function coerceBoolean(value) {
|
|
667
|
+
switch (value.tag) {
|
|
668
|
+
case ValueTag.Boolean:
|
|
669
|
+
return value.value;
|
|
670
|
+
case ValueTag.Number:
|
|
671
|
+
return value.value !== 0;
|
|
672
|
+
case ValueTag.Empty:
|
|
673
|
+
return false;
|
|
674
|
+
case ValueTag.String:
|
|
675
|
+
throw new UnsupportedStreamingNativeFormulaError(`cannot coerce string to boolean: ${value.value}`);
|
|
676
|
+
case ValueTag.Error:
|
|
677
|
+
throw new UnsupportedStreamingNativeFormulaError(`cannot coerce error to boolean: ${String(value.code)}`);
|
|
678
|
+
default:
|
|
679
|
+
throw new UnsupportedStreamingNativeFormulaError('cannot coerce unknown value to boolean');
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
function compareCellValues(left, right) {
|
|
683
|
+
if (isNumericComparable(left) && isNumericComparable(right)) {
|
|
684
|
+
return coerceNumber(left) - coerceNumber(right);
|
|
685
|
+
}
|
|
686
|
+
const leftText = cellValueText(left).toLocaleLowerCase('en-US');
|
|
687
|
+
const rightText = cellValueText(right).toLocaleLowerCase('en-US');
|
|
688
|
+
return leftText < rightText ? -1 : leftText > rightText ? 1 : 0;
|
|
689
|
+
}
|
|
690
|
+
function isNumericComparable(value) {
|
|
691
|
+
return value.tag === ValueTag.Number || value.tag === ValueTag.Boolean || value.tag === ValueTag.Empty;
|
|
692
|
+
}
|
|
693
|
+
function cellValueText(value) {
|
|
694
|
+
switch (value.tag) {
|
|
695
|
+
case ValueTag.Empty:
|
|
696
|
+
return '';
|
|
697
|
+
case ValueTag.Number:
|
|
698
|
+
return String(value.value);
|
|
699
|
+
case ValueTag.Boolean:
|
|
700
|
+
return value.value ? 'TRUE' : 'FALSE';
|
|
701
|
+
case ValueTag.String:
|
|
702
|
+
return value.value;
|
|
703
|
+
case ValueTag.Error:
|
|
704
|
+
throw new UnsupportedStreamingNativeFormulaError(`cannot use error as text: ${String(value.code)}`);
|
|
705
|
+
default:
|
|
706
|
+
throw new UnsupportedStreamingNativeFormulaError('cannot use unknown value as text');
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
export function readTargets(reads, sheetScans) {
|
|
710
|
+
const output = {};
|
|
711
|
+
for (const read of reads) {
|
|
712
|
+
const row = sheetScans.get(read.sheetName)?.rows.get(read.row);
|
|
713
|
+
output[read.source] = resolvedCellValue(row?.get(read.col));
|
|
714
|
+
}
|
|
715
|
+
return output;
|
|
716
|
+
}
|
|
717
|
+
function resolvedCellValue(value) {
|
|
718
|
+
if (value === undefined || isSharedStringReference(value)) {
|
|
719
|
+
return emptyCellValue;
|
|
720
|
+
}
|
|
721
|
+
return value;
|
|
722
|
+
}
|
|
723
|
+
function normalizeFormulaResultValue(value) {
|
|
724
|
+
return value.tag === ValueTag.Empty ? { tag: ValueTag.Number, value: 0 } : value;
|
|
725
|
+
}
|
|
726
|
+
function literalInputForFormulaCache(value) {
|
|
727
|
+
switch (value.tag) {
|
|
728
|
+
case ValueTag.Empty:
|
|
729
|
+
return null;
|
|
730
|
+
case ValueTag.Number:
|
|
731
|
+
return Number.isFinite(value.value) ? value.value : undefined;
|
|
732
|
+
case ValueTag.Boolean:
|
|
733
|
+
return value.value;
|
|
734
|
+
case ValueTag.String:
|
|
735
|
+
return value.value;
|
|
736
|
+
case ValueTag.Error:
|
|
737
|
+
return { kind: 'error', value: value.code === ErrorCode.Field ? '#VALUE!' : formatErrorCode(value.code) };
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function isSharedStringReference(value) {
|
|
741
|
+
return typeof value === 'object' && value !== null && 'kind' in value && value.kind === 'shared-string';
|
|
742
|
+
}
|
|
743
|
+
//# sourceMappingURL=streaming-native-recalc-evaluator.js.map
|