sdocs-dev 1.15.0 → 1.18.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/bin/sdocs-dev.js +35 -11
- package/lib/agent-block.js +253 -50
- package/lib/agent-files.js +308 -74
- package/lib/cells-verify.js +1 -0
- package/lib/cloud-bindings.js +85 -0
- package/lib/cloud-commands.js +741 -0
- package/lib/cloud-credentials.js +380 -0
- package/lib/commands.js +44 -4
- package/lib/help-text.js +272 -70
- package/lib/io.js +44 -3
- package/lib/library-commands.js +6 -15
- package/lib/library-scan.js +16 -6
- package/lib/library-server.js +4 -12
- package/lib/setup.js +145 -189
- package/lib/slides-verify.js +109 -0
- package/package.json +1 -1
- package/shared/sdocs-cells-formula.js +547 -73
- package/shared/sdocs-cells.js +164 -18
- package/shared/sdocs-shapes.js +1044 -0
- package/shared/sdocs-slide-resolve.js +258 -0
- package/shared/sdocs-slide-stdlib.js +180 -0
- package/shared/sdocs-styles.js +1 -1
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
// text starts with "=") against a grid of other cells, and recalc() resolves
|
|
7
7
|
// a whole model at once with cycle detection.
|
|
8
8
|
//
|
|
9
|
-
// Supported
|
|
9
|
+
// Supported core syntax:
|
|
10
10
|
// numbers 12, 3.5, -2, 1e3
|
|
11
|
-
//
|
|
12
|
-
//
|
|
11
|
+
// text / booleans "text", TRUE, FALSE, concatenation with &
|
|
12
|
+
// operators + - * / ^ % and comparisons, with parentheses
|
|
13
|
+
// cell references A1, $A1, A$1, $A$1 and quoted sheet names
|
|
13
14
|
// ranges A1:B3 (only inside a function's arguments)
|
|
14
|
-
// functions
|
|
15
|
-
//
|
|
16
|
-
// comparisons = <> < <= > >= (mainly for IF)
|
|
15
|
+
// functions aggregates, math, logic, text, conditional aggregates,
|
|
16
|
+
// lookup, date, and common finance functions
|
|
17
17
|
//
|
|
18
|
-
// Values: a referenced empty cell is 0 in arithmetic
|
|
19
|
-
//
|
|
18
|
+
// Values: a referenced empty cell is 0 in arithmetic. Text, booleans, and
|
|
19
|
+
// errors stay typed through calculation and Excel export.
|
|
20
20
|
// Anything that goes wrong yields an error string (#VALUE!, #DIV/0!, #NAME?,
|
|
21
21
|
// #REF!, #CIRC!) which the renderer shows in the cell, just like a real sheet.
|
|
22
22
|
(function (exports) {
|
|
@@ -49,29 +49,123 @@
|
|
|
49
49
|
|
|
50
50
|
function mkErr(code) { var e = new Error(code); e.isFormulaError = true; e.code = code; return e; }
|
|
51
51
|
|
|
52
|
+
// A formula is safe to export as live Excel code only when every node in
|
|
53
|
+
// its fully parsed tree is part of this computational grammar. Evaluation
|
|
54
|
+
// is not proof: IF is lazy, so an unchosen branch may contain a function
|
|
55
|
+
// that never ran locally but would run later in Excel.
|
|
56
|
+
var SAFE_FUNCTIONS = {
|
|
57
|
+
SUM: 1, PRODUCT: 1, MIN: 1, MAX: 1, AVERAGE: 1, AVG: 1,
|
|
58
|
+
COUNT: 1, COUNTA: 1, ROUND: 1, ROUNDUP: 1, ROUNDDOWN: 1,
|
|
59
|
+
ABS: 1, INT: 1, MOD: 1, POWER: 1, SQRT: 1, SIGN: 1,
|
|
60
|
+
CEILING: 1, FLOOR: 1, IF: 1, IFERROR: 1, IFNA: 1,
|
|
61
|
+
AND: 1, OR: 1, NOT: 1,
|
|
62
|
+
CONCAT: 1, CONCATENATE: 1, LEFT: 1, RIGHT: 1, MID: 1,
|
|
63
|
+
LEN: 1, LOWER: 1, UPPER: 1, TRIM: 1, VALUE: 1,
|
|
64
|
+
COUNTIF: 1, SUMIF: 1, AVERAGEIF: 1,
|
|
65
|
+
INDEX: 1, MATCH: 1, VLOOKUP: 1, XLOOKUP: 1,
|
|
66
|
+
DATE: 1, YEAR: 1, MONTH: 1, DAY: 1, TODAY: 1,
|
|
67
|
+
NPV: 1, PMT: 1,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function isSafeAst(node) {
|
|
71
|
+
if (!node || typeof node !== 'object') return false;
|
|
72
|
+
if (node.k === 'num' || node.k === 'str' || node.k === 'bool' || node.k === 'ref' || node.k === 'range' || node.k === 'err') return true;
|
|
73
|
+
if (node.k === 'unary' || node.k === 'percent') return isSafeAst(node.a);
|
|
74
|
+
if (node.k === 'bin' || node.k === 'cmp' || node.k === 'concat') return isSafeAst(node.a) && isSafeAst(node.b);
|
|
75
|
+
if (node.k === 'call') {
|
|
76
|
+
if (!SAFE_FUNCTIONS[node.name]) return false;
|
|
77
|
+
for (var i = 0; i < node.args.length; i++) if (!isSafeAst(node.args[i])) return false;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
52
83
|
// ── Tokenizer ────────────────────────────────────────────
|
|
53
84
|
function tokenize(src) {
|
|
54
85
|
var toks = [];
|
|
55
86
|
var i = 0, n = src.length;
|
|
87
|
+
// Excel stores at most 8,192 characters in one formula. Matching that
|
|
88
|
+
// limit also bounds tokenizer and parser work for untrusted documents.
|
|
89
|
+
if (n > 8192) throw mkErr('#VALUE!');
|
|
56
90
|
function isDigit(c) { return c >= '0' && c <= '9'; }
|
|
57
91
|
function isAlpha(c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
|
|
92
|
+
function isNameStart(c) { return isAlpha(c) || c === '_'; }
|
|
93
|
+
function isNamePart(c) { return isAlpha(c) || isDigit(c) || c === '_' || c === '.'; }
|
|
94
|
+
function readRef(at) {
|
|
95
|
+
var j = at, absCol = false, absRow = false, letters = '', digits = '';
|
|
96
|
+
if (src[j] === '$') { absCol = true; j++; }
|
|
97
|
+
while (j < n && isAlpha(src[j])) letters += src[j++];
|
|
98
|
+
if (!letters) return null;
|
|
99
|
+
if (src[j] === '$') { absRow = true; j++; }
|
|
100
|
+
while (j < n && isDigit(src[j])) digits += src[j++];
|
|
101
|
+
if (!digits) return null;
|
|
102
|
+
var col = colIndex(letters.toUpperCase()), row = parseInt(digits, 10) - 1;
|
|
103
|
+
// Excel's grid ends at XFD1048576. Reject invalid addresses before a
|
|
104
|
+
// range can turn into unbounded work.
|
|
105
|
+
if (col < 0 || col > 16383 || row < 0 || row > 1048575) throw mkErr('#REF!');
|
|
106
|
+
return { end: j, col: col, row: row, absCol: absCol, absRow: absRow };
|
|
107
|
+
}
|
|
58
108
|
while (i < n) {
|
|
59
109
|
var c = src[i];
|
|
60
110
|
if (c === ' ' || c === '\t') { i++; continue; }
|
|
61
|
-
if (
|
|
62
|
-
var
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
111
|
+
if (c === '"') {
|
|
112
|
+
var str = ''; i++;
|
|
113
|
+
var closed = false;
|
|
114
|
+
while (i < n) {
|
|
115
|
+
if (src[i] === '"') {
|
|
116
|
+
if (src[i + 1] === '"') { str += '"'; i += 2; continue; }
|
|
117
|
+
i++; closed = true; break;
|
|
118
|
+
}
|
|
119
|
+
str += src[i++];
|
|
68
120
|
}
|
|
69
|
-
|
|
121
|
+
if (!closed) throw mkErr('#VALUE!');
|
|
122
|
+
toks.push({ t: 'str', v: str });
|
|
70
123
|
continue;
|
|
71
124
|
}
|
|
72
|
-
if (
|
|
125
|
+
if (c === "'") {
|
|
126
|
+
var quoted = ''; i++;
|
|
127
|
+
var qclosed = false;
|
|
128
|
+
while (i < n) {
|
|
129
|
+
if (src[i] === "'") {
|
|
130
|
+
if (src[i + 1] === "'") { quoted += "'"; i += 2; continue; }
|
|
131
|
+
i++; qclosed = true; break;
|
|
132
|
+
}
|
|
133
|
+
quoted += src[i++];
|
|
134
|
+
}
|
|
135
|
+
if (!qclosed || src[i] !== '!') throw mkErr('#REF!');
|
|
136
|
+
i++;
|
|
137
|
+
var qref = readRef(i);
|
|
138
|
+
if (!qref) throw mkErr('#REF!');
|
|
139
|
+
toks.push({ t: 'ref', sheet: quoted, col: qref.col, row: qref.row,
|
|
140
|
+
absCol: qref.absCol, absRow: qref.absRow });
|
|
141
|
+
i = qref.end;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (isDigit(c) || (c === '.' && isDigit(src[i + 1]))) {
|
|
145
|
+
var nm = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/.exec(src.slice(i));
|
|
146
|
+
if (!nm) throw mkErr('#VALUE!');
|
|
147
|
+
toks.push({ t: 'num', v: Number(nm[0]) });
|
|
148
|
+
i += nm[0].length;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (c === '$') {
|
|
152
|
+
var dollarRef = readRef(i);
|
|
153
|
+
if (!dollarRef) throw mkErr('#REF!');
|
|
154
|
+
toks.push({ t: 'ref', col: dollarRef.col, row: dollarRef.row,
|
|
155
|
+
absCol: dollarRef.absCol, absRow: dollarRef.absRow });
|
|
156
|
+
i = dollarRef.end;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (isNameStart(c)) {
|
|
160
|
+
var leadingRef = readRef(i);
|
|
161
|
+
if (leadingRef && src[leadingRef.end] !== '!') {
|
|
162
|
+
toks.push({ t: 'ref', col: leadingRef.col, row: leadingRef.row,
|
|
163
|
+
absCol: leadingRef.absCol, absRow: leadingRef.absRow });
|
|
164
|
+
i = leadingRef.end;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
73
167
|
var word = '';
|
|
74
|
-
while (i < n && (
|
|
168
|
+
while (i < n && isNamePart(src[i])) word += src[i++];
|
|
75
169
|
// A sheet-qualified reference: Sheet!A1. The word before '!' is the
|
|
76
170
|
// sheet name (any letters/digits run - Sales, Summary, Sheet1, Q1);
|
|
77
171
|
// the part after '!' must be a plain cell ref. Emitted as ONE ref
|
|
@@ -80,16 +174,18 @@
|
|
|
80
174
|
if (src[i] === '!') {
|
|
81
175
|
var sheet = word;
|
|
82
176
|
i++; // consume '!'
|
|
83
|
-
var
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
177
|
+
var sheetRef = readRef(i);
|
|
178
|
+
if (!sheetRef) throw mkErr('#REF!');
|
|
179
|
+
toks.push({ t: 'ref', sheet: sheet, col: sheetRef.col, row: sheetRef.row,
|
|
180
|
+
absCol: sheetRef.absCol, absRow: sheetRef.absRow });
|
|
181
|
+
i = sheetRef.end;
|
|
88
182
|
continue;
|
|
89
183
|
}
|
|
90
184
|
var m = /^([A-Za-z]+)([0-9]+)$/.exec(word);
|
|
91
185
|
if (m && src[i] !== '(') {
|
|
92
|
-
|
|
186
|
+
var plainCol = colIndex(m[1].toUpperCase()), plainRow = parseInt(m[2], 10) - 1;
|
|
187
|
+
if (plainCol > 16383 || plainRow < 0 || plainRow > 1048575) throw mkErr('#REF!');
|
|
188
|
+
toks.push({ t: 'ref', col: plainCol, row: plainRow, absCol: false, absRow: false });
|
|
93
189
|
} else {
|
|
94
190
|
toks.push({ t: 'name', v: word.toUpperCase() });
|
|
95
191
|
}
|
|
@@ -101,10 +197,16 @@
|
|
|
101
197
|
toks.push({ t: 'op', v: op });
|
|
102
198
|
continue;
|
|
103
199
|
}
|
|
104
|
-
if ('+-*/^%()
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
if (c === '#')
|
|
200
|
+
if ('+-*/^%(),:=&'.indexOf(c) !== -1) { toks.push({ t: 'op', v: c }); i++; continue; }
|
|
201
|
+
// Error literals are tokens, not early throws. The parser must consume
|
|
202
|
+
// the complete formula so export vetting sees every later branch.
|
|
203
|
+
if (c === '#') {
|
|
204
|
+
var em = /^(#REF!|#DIV\/0!|#VALUE!|#NAME\?|#N\/A|#NUM!|#NULL!|#CIRC!)/i.exec(src.slice(i));
|
|
205
|
+
if (!em) throw mkErr('#NAME?');
|
|
206
|
+
toks.push({ t: 'err', v: em[1].toUpperCase() });
|
|
207
|
+
i += em[1].length;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
108
210
|
throw mkErr('#NAME?');
|
|
109
211
|
}
|
|
110
212
|
return toks;
|
|
@@ -118,14 +220,21 @@
|
|
|
118
220
|
function expectOp(v) { var t = next(); if (!t || t.t !== 'op' || t.v !== v) throw mkErr('#VALUE!'); }
|
|
119
221
|
|
|
120
222
|
function parseCompare() {
|
|
121
|
-
var left =
|
|
223
|
+
var left = parseConcat();
|
|
122
224
|
var t = peek();
|
|
123
225
|
if (t && t.t === 'op' && ['=', '<>', '<', '<=', '>', '>='].indexOf(t.v) !== -1) {
|
|
124
226
|
next();
|
|
125
|
-
return { k: 'cmp', op: t.v, a: left, b:
|
|
227
|
+
return { k: 'cmp', op: t.v, a: left, b: parseConcat() };
|
|
126
228
|
}
|
|
127
229
|
return left;
|
|
128
230
|
}
|
|
231
|
+
function parseConcat() {
|
|
232
|
+
var node = parseAdd();
|
|
233
|
+
while (peek() && peek().t === 'op' && peek().v === '&') {
|
|
234
|
+
next(); node = { k: 'concat', a: node, b: parseAdd() };
|
|
235
|
+
}
|
|
236
|
+
return node;
|
|
237
|
+
}
|
|
129
238
|
function parseAdd() {
|
|
130
239
|
var node = parseMul();
|
|
131
240
|
while (peek() && peek().t === 'op' && (peek().v === '+' || peek().v === '-')) {
|
|
@@ -163,6 +272,8 @@
|
|
|
163
272
|
var t = next();
|
|
164
273
|
if (!t) throw mkErr('#VALUE!');
|
|
165
274
|
if (t.t === 'num') return { k: 'num', v: t.v };
|
|
275
|
+
if (t.t === 'str') return { k: 'str', v: t.v };
|
|
276
|
+
if (t.t === 'err') return { k: 'err', code: t.v };
|
|
166
277
|
if (t.t === 'ref') {
|
|
167
278
|
if (peek() && peek().t === 'op' && peek().v === ':') {
|
|
168
279
|
next(); var end = next();
|
|
@@ -188,8 +299,8 @@
|
|
|
188
299
|
expectOp(')');
|
|
189
300
|
return { k: 'call', name: t.v, args: args };
|
|
190
301
|
}
|
|
191
|
-
if (t.v === 'TRUE') return { k: '
|
|
192
|
-
if (t.v === 'FALSE') return { k: '
|
|
302
|
+
if (t.v === 'TRUE') return { k: 'bool', v: true };
|
|
303
|
+
if (t.v === 'FALSE') return { k: 'bool', v: false };
|
|
193
304
|
throw mkErr('#NAME?');
|
|
194
305
|
}
|
|
195
306
|
if (t.t === 'op' && t.v === '(') { var e = parseCompare(); expectOp(')'); return e; }
|
|
@@ -205,6 +316,9 @@
|
|
|
205
316
|
function evalAst(node, ctx) {
|
|
206
317
|
switch (node.k) {
|
|
207
318
|
case 'num': return node.v;
|
|
319
|
+
case 'str': return node.v;
|
|
320
|
+
case 'bool': return node.v;
|
|
321
|
+
case 'err': throw mkErr(node.code);
|
|
208
322
|
case 'unary': { var v = num(evalAst(node.a, ctx)); return node.op === '-' ? -v : v; }
|
|
209
323
|
case 'percent': return num(evalAst(node.a, ctx)) / 100;
|
|
210
324
|
case 'bin': {
|
|
@@ -218,8 +332,14 @@
|
|
|
218
332
|
}
|
|
219
333
|
throw mkErr('#VALUE!');
|
|
220
334
|
}
|
|
335
|
+
case 'concat': return toText(evalAst(node.a, ctx)) + toText(evalAst(node.b, ctx));
|
|
221
336
|
case 'cmp': {
|
|
222
|
-
var x =
|
|
337
|
+
var x = evalAst(node.a, ctx), y = evalAst(node.b, ctx), r;
|
|
338
|
+
if (typeof x === 'string' && typeof y === 'string') {
|
|
339
|
+
x = x.toLowerCase(); y = y.toLowerCase();
|
|
340
|
+
} else if (typeof x !== typeof y) {
|
|
341
|
+
x = num(x); y = num(y);
|
|
342
|
+
}
|
|
223
343
|
switch (node.op) {
|
|
224
344
|
case '=': r = x === y; break;
|
|
225
345
|
case '<>': r = x !== y; break;
|
|
@@ -229,7 +349,7 @@
|
|
|
229
349
|
case '>=': r = x >= y; break;
|
|
230
350
|
default: throw mkErr('#VALUE!');
|
|
231
351
|
}
|
|
232
|
-
return r
|
|
352
|
+
return r;
|
|
233
353
|
}
|
|
234
354
|
case 'ref': return refValue(ctx.cell(node.col, node.row, node.sheet));
|
|
235
355
|
case 'range': throw mkErr('#VALUE!');
|
|
@@ -241,14 +361,35 @@
|
|
|
241
361
|
function refValue(cell) {
|
|
242
362
|
if (!cell || cell.kind === 'empty') return 0;
|
|
243
363
|
if (cell.kind === 'number') return cell.value;
|
|
364
|
+
if (cell.kind === 'text') return cell.value;
|
|
365
|
+
if (cell.kind === 'boolean') return cell.value;
|
|
244
366
|
if (cell.kind === 'error') throw mkErr(cell.code || '#VALUE!');
|
|
245
367
|
throw mkErr('#VALUE!');
|
|
246
368
|
}
|
|
247
369
|
|
|
248
|
-
function num(v) {
|
|
370
|
+
function num(v) {
|
|
371
|
+
if (typeof v === 'boolean') return v ? 1 : 0;
|
|
372
|
+
if (typeof v === 'string' && v.trim() !== '' && isFinite(Number(v))) return Number(v);
|
|
373
|
+
if (typeof v !== 'number' || !isFinite(v)) throw mkErr('#VALUE!');
|
|
374
|
+
return v;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function toText(v) {
|
|
378
|
+
if (v == null) return '';
|
|
379
|
+
if (typeof v === 'boolean') return v ? 'TRUE' : 'FALSE';
|
|
380
|
+
return String(v);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function truthy(v) {
|
|
384
|
+
if (typeof v === 'boolean') return v;
|
|
385
|
+
if (typeof v === 'number') return v !== 0;
|
|
386
|
+
if (typeof v === 'string') return v !== '' && v.toUpperCase() !== 'FALSE';
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
249
389
|
|
|
250
390
|
function argCells(arg, ctx) {
|
|
251
391
|
if (arg.k === 'range') {
|
|
392
|
+
if (ctx.range) return ctx.range(arg.c0, arg.r0, arg.c1, arg.r1, arg.sheet);
|
|
252
393
|
var out = [];
|
|
253
394
|
var r0 = Math.min(arg.r0, arg.r1), r1 = Math.max(arg.r0, arg.r1);
|
|
254
395
|
var c0 = Math.min(arg.c0, arg.c1), c1 = Math.max(arg.c0, arg.c1);
|
|
@@ -257,7 +398,86 @@
|
|
|
257
398
|
}
|
|
258
399
|
return out;
|
|
259
400
|
}
|
|
260
|
-
|
|
401
|
+
var value = evalAst(arg, ctx);
|
|
402
|
+
if (typeof value === 'number') return [{ kind: 'number', value: value }];
|
|
403
|
+
if (typeof value === 'boolean') return [{ kind: 'boolean', value: value }];
|
|
404
|
+
return [{ kind: 'text', value: toText(value) }];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function cellValue(cell) {
|
|
408
|
+
if (!cell || cell.kind === 'empty') return '';
|
|
409
|
+
if (cell.kind === 'error') throw mkErr(cell.code || '#VALUE!');
|
|
410
|
+
return cell.value;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function argMatrix(arg, ctx) {
|
|
414
|
+
if (arg.k !== 'range') return [[cellValue(argCells(arg, ctx)[0])]];
|
|
415
|
+
if (ctx.rangeMatrix) return ctx.rangeMatrix(arg.c0, arg.r0, arg.c1, arg.r1, arg.sheet);
|
|
416
|
+
var rows = [], r0 = Math.min(arg.r0, arg.r1), r1 = Math.max(arg.r0, arg.r1);
|
|
417
|
+
var c0 = Math.min(arg.c0, arg.c1), c1 = Math.max(arg.c0, arg.c1);
|
|
418
|
+
for (var r = r0; r <= r1; r++) {
|
|
419
|
+
var row = [];
|
|
420
|
+
for (var c = c0; c <= c1; c++) row.push(cellValue(ctx.cell(c, r, arg.sheet)));
|
|
421
|
+
rows.push(row);
|
|
422
|
+
}
|
|
423
|
+
return rows;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function flattenMatrix(matrix) {
|
|
427
|
+
var out = [];
|
|
428
|
+
for (var r = 0; r < matrix.length; r++) {
|
|
429
|
+
for (var c = 0; c < matrix[r].length; c++) out.push(matrix[r][c]);
|
|
430
|
+
}
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function criterionMatches(value, criterion) {
|
|
435
|
+
if (typeof criterion === 'number' || typeof criterion === 'boolean') return value === criterion;
|
|
436
|
+
var s = String(criterion), m = /^(<=|>=|<>|=|<|>)(.*)$/.exec(s);
|
|
437
|
+
var op = m ? m[1] : '=', rhs = m ? m[2] : s;
|
|
438
|
+
var left = value, right = rhs;
|
|
439
|
+
if (rhs.trim() !== '' && isFinite(Number(rhs))) { left = Number(value); right = Number(rhs); }
|
|
440
|
+
if (op === '=' && typeof right === 'string' && /[?*]/.test(right)) {
|
|
441
|
+
var pattern = right.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\?/g, '.').replace(/\*/g, '.*');
|
|
442
|
+
return new RegExp('^' + pattern + '$', 'i').test(String(value));
|
|
443
|
+
}
|
|
444
|
+
if (typeof left === 'string' && typeof right === 'string') {
|
|
445
|
+
left = left.toLowerCase(); right = right.toLowerCase();
|
|
446
|
+
}
|
|
447
|
+
if (op === '=') return left === right;
|
|
448
|
+
if (op === '<>') return left !== right;
|
|
449
|
+
if (op === '<') return left < right;
|
|
450
|
+
if (op === '<=') return left <= right;
|
|
451
|
+
if (op === '>') return left > right;
|
|
452
|
+
return left >= right;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function valuesEqual(a, b) {
|
|
456
|
+
if (typeof a === 'string' && typeof b === 'string') return a.toLowerCase() === b.toLowerCase();
|
|
457
|
+
return a === b;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function excelRound(value, digits, mode) {
|
|
461
|
+
var factor = Math.pow(10, digits), scaled = (Math.abs(value) + Number.EPSILON) * factor;
|
|
462
|
+
var rounded;
|
|
463
|
+
if (mode === 'up') rounded = Math.ceil(scaled - Number.EPSILON);
|
|
464
|
+
else if (mode === 'down') rounded = Math.floor(scaled + Number.EPSILON);
|
|
465
|
+
else rounded = Math.round(scaled + Number.EPSILON);
|
|
466
|
+
return (value < 0 ? -1 : 1) * rounded / factor;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
var DAY_MS = 86400000;
|
|
470
|
+
var EXCEL_EPOCH = Date.UTC(1899, 11, 31);
|
|
471
|
+
function dateSerial(year, month, day) {
|
|
472
|
+
var utc = Date.UTC(year, month - 1, day);
|
|
473
|
+
var serial = (utc - EXCEL_EPOCH) / DAY_MS;
|
|
474
|
+
if (utc >= Date.UTC(1900, 2, 1)) serial++;
|
|
475
|
+
return serial;
|
|
476
|
+
}
|
|
477
|
+
function serialDate(serial) {
|
|
478
|
+
var n = num(serial);
|
|
479
|
+
if (n >= 60) n--;
|
|
480
|
+
return new Date(EXCEL_EPOCH + n * DAY_MS);
|
|
261
481
|
}
|
|
262
482
|
|
|
263
483
|
function callFn(node, ctx) {
|
|
@@ -270,17 +490,18 @@
|
|
|
270
490
|
var cells = argCells(args[i], ctx);
|
|
271
491
|
for (var j = 0; j < cells.length; j++) {
|
|
272
492
|
var cl = cells[j];
|
|
273
|
-
if (cl.kind === 'error') { errored = cl.code || '#VALUE!'; }
|
|
493
|
+
if (cl.kind === 'error') { errored = cl.code || '#VALUE!'; counted++; }
|
|
274
494
|
else if (cl.kind === 'number') { nums.push(cl.value); counted++; }
|
|
275
|
-
else if (cl.kind === 'text') { counted++; }
|
|
495
|
+
else if (cl.kind === 'text' || cl.kind === 'boolean') { counted++; }
|
|
276
496
|
}
|
|
277
497
|
}
|
|
278
|
-
if (errored && name !== 'COUNTA') throw mkErr(errored);
|
|
498
|
+
if (errored && name !== 'COUNTA' && name !== 'COUNT') throw mkErr(errored);
|
|
279
499
|
if (name === 'COUNT') return nums.length;
|
|
280
500
|
if (name === 'COUNTA') return counted;
|
|
281
501
|
if (!nums.length) {
|
|
282
502
|
if (name === 'SUM') return 0;
|
|
283
503
|
if (name === 'PRODUCT') return 0;
|
|
504
|
+
if (name === 'MIN' || name === 'MAX') return 0;
|
|
284
505
|
throw mkErr('#DIV/0!');
|
|
285
506
|
}
|
|
286
507
|
if (name === 'SUM') return nums.reduce(function (a, b) { return a + b; }, 0);
|
|
@@ -289,22 +510,178 @@
|
|
|
289
510
|
if (name === 'MAX') return Math.max.apply(null, nums);
|
|
290
511
|
return nums.reduce(function (a, b) { return a + b; }, 0) / nums.length; // AVERAGE
|
|
291
512
|
}
|
|
292
|
-
case 'ROUND': {
|
|
513
|
+
case 'ROUND': case 'ROUNDUP': case 'ROUNDDOWN': {
|
|
293
514
|
if (args.length < 1) throw mkErr('#VALUE!');
|
|
294
515
|
var x = num(evalAst(args[0], ctx));
|
|
295
516
|
var d = args.length > 1 ? num(evalAst(args[1], ctx)) : 0;
|
|
296
|
-
|
|
297
|
-
return Math.round(x * f) / f;
|
|
517
|
+
return excelRound(x, d, name === 'ROUNDUP' ? 'up' : (name === 'ROUNDDOWN' ? 'down' : 'round'));
|
|
298
518
|
}
|
|
299
519
|
case 'ABS':
|
|
300
520
|
if (args.length !== 1) throw mkErr('#VALUE!');
|
|
301
521
|
return Math.abs(num(evalAst(args[0], ctx)));
|
|
522
|
+
case 'INT': return Math.floor(num(evalAst(args[0], ctx)));
|
|
523
|
+
case 'MOD': {
|
|
524
|
+
var modDivisor = num(evalAst(args[1], ctx));
|
|
525
|
+
if (modDivisor === 0) throw mkErr('#DIV/0!');
|
|
526
|
+
return num(evalAst(args[0], ctx)) - modDivisor * Math.floor(num(evalAst(args[0], ctx)) / modDivisor);
|
|
527
|
+
}
|
|
528
|
+
case 'POWER': return Math.pow(num(evalAst(args[0], ctx)), num(evalAst(args[1], ctx)));
|
|
529
|
+
case 'SQRT': {
|
|
530
|
+
var root = num(evalAst(args[0], ctx));
|
|
531
|
+
if (root < 0) throw mkErr('#NUM!');
|
|
532
|
+
return Math.sqrt(root);
|
|
533
|
+
}
|
|
534
|
+
case 'SIGN': { var sign = num(evalAst(args[0], ctx)); return sign === 0 ? 0 : (sign < 0 ? -1 : 1); }
|
|
535
|
+
case 'CEILING': case 'FLOOR': {
|
|
536
|
+
var cv = num(evalAst(args[0], ctx));
|
|
537
|
+
var significance = args.length > 1 ? Math.abs(num(evalAst(args[1], ctx))) : 1;
|
|
538
|
+
if (!significance) return 0;
|
|
539
|
+
return (name === 'CEILING' ? Math.ceil(cv / significance) : Math.floor(cv / significance)) * significance;
|
|
540
|
+
}
|
|
302
541
|
case 'IF': {
|
|
303
542
|
if (args.length < 2) throw mkErr('#VALUE!');
|
|
304
|
-
var cond =
|
|
305
|
-
return cond
|
|
543
|
+
var cond = truthy(evalAst(args[0], ctx));
|
|
544
|
+
return cond ? evalAst(args[1], ctx)
|
|
306
545
|
: (args.length > 2 ? evalAst(args[2], ctx) : 0);
|
|
307
546
|
}
|
|
547
|
+
case 'IFERROR': case 'IFNA': {
|
|
548
|
+
if (args.length < 2) throw mkErr('#VALUE!');
|
|
549
|
+
try { return evalAst(args[0], ctx); }
|
|
550
|
+
catch (e) {
|
|
551
|
+
if (name === 'IFNA' && (!e || e.code !== '#N/A')) throw e;
|
|
552
|
+
return evalAst(args[1], ctx);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
case 'AND': case 'OR': {
|
|
556
|
+
var bool = name === 'AND';
|
|
557
|
+
var boolError = null;
|
|
558
|
+
for (var ai = 0; ai < args.length; ai++) {
|
|
559
|
+
try {
|
|
560
|
+
var av = truthy(evalAst(args[ai], ctx));
|
|
561
|
+
if (name === 'AND') bool = bool && av;
|
|
562
|
+
else bool = bool || av;
|
|
563
|
+
} catch (e) {
|
|
564
|
+
if (!boolError) boolError = e;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (boolError) throw boolError;
|
|
568
|
+
return bool;
|
|
569
|
+
}
|
|
570
|
+
case 'NOT': return !truthy(evalAst(args[0], ctx));
|
|
571
|
+
case 'CONCAT': case 'CONCATENATE': {
|
|
572
|
+
var joined = '';
|
|
573
|
+
for (var ci = 0; ci < args.length; ci++) {
|
|
574
|
+
var concatCells = argCells(args[ci], ctx);
|
|
575
|
+
for (var cj = 0; cj < concatCells.length; cj++) joined += toText(cellValue(concatCells[cj]));
|
|
576
|
+
}
|
|
577
|
+
return joined;
|
|
578
|
+
}
|
|
579
|
+
case 'LEFT': {
|
|
580
|
+
var leftText = toText(evalAst(args[0], ctx));
|
|
581
|
+
var leftCount = args.length > 1 ? num(evalAst(args[1], ctx)) : 1;
|
|
582
|
+
if (leftCount < 0) throw mkErr('#VALUE!');
|
|
583
|
+
return leftText.slice(0, leftCount);
|
|
584
|
+
}
|
|
585
|
+
case 'RIGHT': {
|
|
586
|
+
var rightText = toText(evalAst(args[0], ctx));
|
|
587
|
+
var rightCount = args.length > 1 ? num(evalAst(args[1], ctx)) : 1;
|
|
588
|
+
if (rightCount < 0) throw mkErr('#VALUE!');
|
|
589
|
+
return rightText.slice(Math.max(0, rightText.length - rightCount));
|
|
590
|
+
}
|
|
591
|
+
case 'MID': {
|
|
592
|
+
var midStart = num(evalAst(args[1], ctx)), midCount = num(evalAst(args[2], ctx));
|
|
593
|
+
if (midStart < 1 || midCount < 0) throw mkErr('#VALUE!');
|
|
594
|
+
return toText(evalAst(args[0], ctx)).substr(midStart - 1, midCount);
|
|
595
|
+
}
|
|
596
|
+
case 'LEN': return toText(evalAst(args[0], ctx)).length;
|
|
597
|
+
case 'LOWER': return toText(evalAst(args[0], ctx)).toLowerCase();
|
|
598
|
+
case 'UPPER': return toText(evalAst(args[0], ctx)).toUpperCase();
|
|
599
|
+
case 'TRIM': return toText(evalAst(args[0], ctx)).trim().replace(/\s+/g, ' ');
|
|
600
|
+
case 'VALUE': return num(evalAst(args[0], ctx));
|
|
601
|
+
case 'COUNTIF': case 'SUMIF': case 'AVERAGEIF': {
|
|
602
|
+
if (args.length < 2) throw mkErr('#VALUE!');
|
|
603
|
+
var testValues = flattenMatrix(argMatrix(args[0], ctx));
|
|
604
|
+
var criterion = evalAst(args[1], ctx);
|
|
605
|
+
var sumValues = args.length > 2 ? flattenMatrix(argMatrix(args[2], ctx)) : testValues;
|
|
606
|
+
var matched = 0, numericMatched = 0, total = 0;
|
|
607
|
+
for (var ti = 0; ti < testValues.length; ti++) {
|
|
608
|
+
if (!criterionMatches(testValues[ti], criterion)) continue;
|
|
609
|
+
matched++;
|
|
610
|
+
if (ti < sumValues.length && typeof sumValues[ti] === 'number') {
|
|
611
|
+
total += sumValues[ti]; numericMatched++;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (name === 'COUNTIF') return matched;
|
|
615
|
+
if (name === 'AVERAGEIF') {
|
|
616
|
+
if (!numericMatched) throw mkErr('#DIV/0!');
|
|
617
|
+
return total / numericMatched;
|
|
618
|
+
}
|
|
619
|
+
return total;
|
|
620
|
+
}
|
|
621
|
+
case 'INDEX': {
|
|
622
|
+
var indexMatrix = argMatrix(args[0], ctx);
|
|
623
|
+
var indexRow = num(evalAst(args[1], ctx)) - 1;
|
|
624
|
+
var indexCol = args.length > 2 ? num(evalAst(args[2], ctx)) - 1 : 0;
|
|
625
|
+
if (!indexMatrix[indexRow] || indexMatrix[indexRow][indexCol] === undefined) throw mkErr('#REF!');
|
|
626
|
+
return indexMatrix[indexRow][indexCol];
|
|
627
|
+
}
|
|
628
|
+
case 'MATCH': {
|
|
629
|
+
var needle = evalAst(args[0], ctx), matchValues = flattenMatrix(argMatrix(args[1], ctx));
|
|
630
|
+
var matchMode = args.length > 2 ? num(evalAst(args[2], ctx)) : 1;
|
|
631
|
+
var found = -1;
|
|
632
|
+
for (var mi = 0; mi < matchValues.length; mi++) {
|
|
633
|
+
if (valuesEqual(matchValues[mi], needle)) { found = mi; break; }
|
|
634
|
+
if (matchMode === 1 && matchValues[mi] <= needle) found = mi;
|
|
635
|
+
if (matchMode === -1 && found < 0 && matchValues[mi] >= needle) found = mi;
|
|
636
|
+
}
|
|
637
|
+
if (found < 0) throw mkErr('#N/A');
|
|
638
|
+
return found + 1;
|
|
639
|
+
}
|
|
640
|
+
case 'VLOOKUP': {
|
|
641
|
+
var lookup = evalAst(args[0], ctx), table = argMatrix(args[1], ctx);
|
|
642
|
+
var returnCol = num(evalAst(args[2], ctx)) - 1;
|
|
643
|
+
var approximate = args.length < 4 || truthy(evalAst(args[3], ctx));
|
|
644
|
+
var candidate = -1;
|
|
645
|
+
for (var vr = 0; vr < table.length; vr++) {
|
|
646
|
+
if (valuesEqual(table[vr][0], lookup)) { candidate = vr; break; }
|
|
647
|
+
if (approximate && table[vr][0] <= lookup) candidate = vr;
|
|
648
|
+
}
|
|
649
|
+
if (candidate < 0 || !table[candidate] || table[candidate][returnCol] === undefined) throw mkErr('#N/A');
|
|
650
|
+
return table[candidate][returnCol];
|
|
651
|
+
}
|
|
652
|
+
case 'XLOOKUP': {
|
|
653
|
+
var xneedle = evalAst(args[0], ctx);
|
|
654
|
+
var search = flattenMatrix(argMatrix(args[1], ctx));
|
|
655
|
+
var returns = flattenMatrix(argMatrix(args[2], ctx));
|
|
656
|
+
for (var xi = 0; xi < search.length; xi++) if (valuesEqual(search[xi], xneedle)) return returns[xi];
|
|
657
|
+
if (args.length > 3) return evalAst(args[3], ctx);
|
|
658
|
+
throw mkErr('#N/A');
|
|
659
|
+
}
|
|
660
|
+
case 'DATE': return dateSerial(num(evalAst(args[0], ctx)), num(evalAst(args[1], ctx)), num(evalAst(args[2], ctx)));
|
|
661
|
+
case 'YEAR': return serialDate(evalAst(args[0], ctx)).getUTCFullYear();
|
|
662
|
+
case 'MONTH': return serialDate(evalAst(args[0], ctx)).getUTCMonth() + 1;
|
|
663
|
+
case 'DAY': return serialDate(evalAst(args[0], ctx)).getUTCDate();
|
|
664
|
+
case 'TODAY': {
|
|
665
|
+
var now = new Date();
|
|
666
|
+
return dateSerial(now.getUTCFullYear(), now.getUTCMonth() + 1, now.getUTCDate());
|
|
667
|
+
}
|
|
668
|
+
case 'NPV': {
|
|
669
|
+
var rate = num(evalAst(args[0], ctx)), cash = [];
|
|
670
|
+
for (var ni = 1; ni < args.length; ni++) {
|
|
671
|
+
var nc = argCells(args[ni], ctx);
|
|
672
|
+
for (var nj = 0; nj < nc.length; nj++) if (nc[nj].kind === 'number') cash.push(nc[nj].value);
|
|
673
|
+
}
|
|
674
|
+
return cash.reduce(function (sum, value, index) { return sum + value / Math.pow(1 + rate, index + 1); }, 0);
|
|
675
|
+
}
|
|
676
|
+
case 'PMT': {
|
|
677
|
+
var pr = num(evalAst(args[0], ctx)), periods = num(evalAst(args[1], ctx));
|
|
678
|
+
var present = num(evalAst(args[2], ctx));
|
|
679
|
+
var future = args.length > 3 ? num(evalAst(args[3], ctx)) : 0;
|
|
680
|
+
var timing = args.length > 4 ? num(evalAst(args[4], ctx)) : 0;
|
|
681
|
+
if (pr === 0) return -(present + future) / periods;
|
|
682
|
+
var pow = Math.pow(1 + pr, periods);
|
|
683
|
+
return -(pr * (future + pow * present)) / ((1 + pr * timing) * (pow - 1));
|
|
684
|
+
}
|
|
308
685
|
}
|
|
309
686
|
throw mkErr('#NAME?');
|
|
310
687
|
}
|
|
@@ -323,30 +700,64 @@
|
|
|
323
700
|
var i = 0, n = src.length;
|
|
324
701
|
function isAlpha(ch) { return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); }
|
|
325
702
|
function isDigit(ch) { return ch >= '0' && ch <= '9'; }
|
|
703
|
+
function isNameStart(ch) { return isAlpha(ch) || ch === '_'; }
|
|
704
|
+
function isNamePart(ch) { return isAlpha(ch) || isDigit(ch) || ch === '_' || ch === '.'; }
|
|
705
|
+
function readRef(at) {
|
|
706
|
+
var j = at, absCol = false, absRow = false, letters = '', digits = '';
|
|
707
|
+
if (src[j] === '$') { absCol = true; j++; }
|
|
708
|
+
while (j < n && isAlpha(src[j])) letters += src[j++];
|
|
709
|
+
if (!letters) return null;
|
|
710
|
+
if (src[j] === '$') { absRow = true; j++; }
|
|
711
|
+
while (j < n && isDigit(src[j])) digits += src[j++];
|
|
712
|
+
if (!digits) return null;
|
|
713
|
+
return { end: j, letters: letters, row: parseInt(digits, 10) - 1,
|
|
714
|
+
col: colIndex(letters.toUpperCase()), absCol: absCol, absRow: absRow };
|
|
715
|
+
}
|
|
326
716
|
while (i < n) {
|
|
327
717
|
var c = src[i];
|
|
328
|
-
if (
|
|
329
|
-
var
|
|
330
|
-
while (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
out += word + '!';
|
|
338
|
-
i = j + 1;
|
|
339
|
-
continue;
|
|
718
|
+
if (c === '"') {
|
|
719
|
+
var ds = i++;
|
|
720
|
+
while (i < n) {
|
|
721
|
+
if (src[i] === '"') {
|
|
722
|
+
i++;
|
|
723
|
+
if (src[i] === '"') { i++; continue; }
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
i++;
|
|
340
727
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
728
|
+
out += src.slice(ds, i);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (c === "'") {
|
|
732
|
+
var qs = i++;
|
|
733
|
+
while (i < n) {
|
|
734
|
+
if (src[i] === "'") {
|
|
735
|
+
i++;
|
|
736
|
+
if (src[i] === "'") { i++; continue; }
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
i++;
|
|
348
740
|
}
|
|
349
|
-
i
|
|
741
|
+
if (src[i] === '!') i++;
|
|
742
|
+
out += src.slice(qs, i);
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (isNameStart(c)) {
|
|
746
|
+
var nameEnd = i + 1;
|
|
747
|
+
while (nameEnd < n && isNamePart(src[nameEnd])) nameEnd++;
|
|
748
|
+
if (src[nameEnd] === '!') {
|
|
749
|
+
out += src.slice(i, nameEnd + 1);
|
|
750
|
+
i = nameEnd + 1;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
var ref = (c === '$' || isAlpha(c)) ? readRef(i) : null;
|
|
755
|
+
if (ref && src[ref.end] !== '(') {
|
|
756
|
+
var col = ref.col + (ref.absCol ? 0 : dc);
|
|
757
|
+
var row = ref.row + (ref.absRow ? 0 : dr);
|
|
758
|
+
if (col < 0 || row < 0 || col > 16383 || row > 1048575) out += '#REF!';
|
|
759
|
+
else out += (ref.absCol ? '$' : '') + colName(col) + (ref.absRow ? '$' : '') + (row + 1);
|
|
760
|
+
i = ref.end;
|
|
350
761
|
continue;
|
|
351
762
|
}
|
|
352
763
|
out += c;
|
|
@@ -357,14 +768,20 @@
|
|
|
357
768
|
|
|
358
769
|
// Evaluate one formula string against ctx. Returns { value } or { error }.
|
|
359
770
|
function evaluate(formula, ctx) {
|
|
771
|
+
var ast;
|
|
360
772
|
try {
|
|
361
773
|
var src = formula.charAt(0) === '=' ? formula.slice(1) : formula;
|
|
362
|
-
|
|
774
|
+
ast = parse(tokenize(src));
|
|
775
|
+
var safe = isSafeAst(ast);
|
|
363
776
|
var v = evalAst(ast, ctx);
|
|
364
|
-
if (typeof v
|
|
365
|
-
|
|
777
|
+
if (typeof v === 'number' && !isFinite(v)) return { error: '#NUM!', safe: safe };
|
|
778
|
+
if (typeof v !== 'number' && typeof v !== 'string' && typeof v !== 'boolean') {
|
|
779
|
+
return { error: '#VALUE!', safe: safe };
|
|
780
|
+
}
|
|
781
|
+
return { value: v, kind: typeof v === 'number' ? 'number'
|
|
782
|
+
: (typeof v === 'boolean' ? 'boolean' : 'text'), safe: safe };
|
|
366
783
|
} catch (e) {
|
|
367
|
-
return { error: e && e.isFormulaError ? e.code : '#VALUE!' };
|
|
784
|
+
return { error: e && e.isFormulaError ? e.code : '#VALUE!', safe: ast ? isSafeAst(ast) : false };
|
|
368
785
|
}
|
|
369
786
|
}
|
|
370
787
|
|
|
@@ -435,22 +852,77 @@
|
|
|
435
852
|
var raw = rawAt(si, c, r);
|
|
436
853
|
if (!isFormula(raw)) return (rRes[c] = baseKind(si, c, r));
|
|
437
854
|
rState[c] = 1;
|
|
855
|
+
var dependenciesSafe = true;
|
|
856
|
+
function trackDependency(cell) {
|
|
857
|
+
if (cell && cell.safe === false) dependenciesSafe = false;
|
|
858
|
+
return cell;
|
|
859
|
+
}
|
|
438
860
|
// ctx.cell(col, row, sheet): an undefined `sheet` means the formula's
|
|
439
861
|
// own sheet (si); a named sheet is looked up in nameToIndex; a name with
|
|
440
862
|
// no matching sheet is #REF! and never enters resolve.
|
|
441
863
|
var ctx = {
|
|
442
864
|
cell: function (cc, rr, sheetName) {
|
|
443
|
-
if (sheetName == null) return resolve(si, cc, rr);
|
|
865
|
+
if (sheetName == null) return trackDependency(resolve(si, cc, rr));
|
|
444
866
|
var key = String(sheetName).toLowerCase();
|
|
445
867
|
if (!(key in nameToIndex)) return { kind: 'error', code: '#REF!' };
|
|
446
|
-
return resolve(nameToIndex[key], cc, rr);
|
|
868
|
+
return trackDependency(resolve(nameToIndex[key], cc, rr));
|
|
869
|
+
},
|
|
870
|
+
// Aggregate functions only need populated cells. Clamp a range to the
|
|
871
|
+
// target model before walking it, so a huge sparse reference does not
|
|
872
|
+
// expand billions of empty coordinates in the browser.
|
|
873
|
+
range: function (c0, r0, c1, r1, sheetName) {
|
|
874
|
+
var target = si;
|
|
875
|
+
if (sheetName != null) {
|
|
876
|
+
var key = String(sheetName).toLowerCase();
|
|
877
|
+
if (!(key in nameToIndex)) throw mkErr('#REF!');
|
|
878
|
+
target = nameToIndex[key];
|
|
879
|
+
}
|
|
880
|
+
var model = sheets[target].model;
|
|
881
|
+
var maxRows = model && model.cells ? model.cells.length : 0;
|
|
882
|
+
var maxCols = model && model.cols ? model.cols : 0;
|
|
883
|
+
if (!maxCols && model && model.cells) {
|
|
884
|
+
for (var mr = 0; mr < model.cells.length; mr++) {
|
|
885
|
+
maxCols = Math.max(maxCols, (model.cells[mr] || []).length);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
var ra = Math.max(0, Math.min(r0, r1));
|
|
889
|
+
var rb = Math.min(maxRows - 1, Math.max(r0, r1));
|
|
890
|
+
var ca = Math.max(0, Math.min(c0, c1));
|
|
891
|
+
var cb = Math.min(maxCols - 1, Math.max(c0, c1));
|
|
892
|
+
var out = [];
|
|
893
|
+
for (var r3 = ra; r3 <= rb; r3++) {
|
|
894
|
+
for (var c3 = ca; c3 <= cb; c3++) out.push(trackDependency(resolve(target, c3, r3)));
|
|
895
|
+
}
|
|
896
|
+
return out;
|
|
897
|
+
},
|
|
898
|
+
rangeMatrix: function (c0, r0, c1, r1, sheetName) {
|
|
899
|
+
var target = si;
|
|
900
|
+
if (sheetName != null) {
|
|
901
|
+
var key = String(sheetName).toLowerCase();
|
|
902
|
+
if (!(key in nameToIndex)) throw mkErr('#REF!');
|
|
903
|
+
target = nameToIndex[key];
|
|
904
|
+
}
|
|
905
|
+
var ra = Math.max(0, Math.min(r0, r1));
|
|
906
|
+
var rb = Math.max(r0, r1);
|
|
907
|
+
var ca = Math.max(0, Math.min(c0, c1));
|
|
908
|
+
var cb = Math.max(c0, c1);
|
|
909
|
+
if ((rb - ra + 1) * (cb - ca + 1) > 100000) throw mkErr('#VALUE!');
|
|
910
|
+
var matrix = [];
|
|
911
|
+
for (var r4 = ra; r4 <= rb; r4++) {
|
|
912
|
+
var matrixRow = [];
|
|
913
|
+
for (var c4 = ca; c4 <= cb; c4++) matrixRow.push(trackDependency(resolve(target, c4, r4)));
|
|
914
|
+
matrix.push(matrixRow);
|
|
915
|
+
}
|
|
916
|
+
return matrix.map(function (row) { return row.map(cellValue); });
|
|
447
917
|
},
|
|
448
918
|
};
|
|
449
919
|
var out = evaluate(raw, ctx);
|
|
920
|
+
out.safe = out.safe === true && dependenciesSafe;
|
|
450
921
|
rState[c] = 2;
|
|
451
|
-
return (rRes[c] = out.error
|
|
452
|
-
|
|
453
|
-
:
|
|
922
|
+
if (out.error) return (rRes[c] = { kind: 'error', code: out.error, safe: out.safe });
|
|
923
|
+
var kind = out.kind || (typeof out.value === 'number' ? 'number'
|
|
924
|
+
: (typeof out.value === 'boolean' ? 'boolean' : 'text'));
|
|
925
|
+
return (rRes[c] = { kind: kind, value: out.value, safe: out.safe });
|
|
454
926
|
}
|
|
455
927
|
|
|
456
928
|
for (var si2 = 0; si2 < n; si2++) {
|
|
@@ -478,6 +950,8 @@
|
|
|
478
950
|
exports.isFormula = isFormula;
|
|
479
951
|
exports.tokenize = tokenize;
|
|
480
952
|
exports.parse = parse;
|
|
953
|
+
exports.isSafeAst = isSafeAst;
|
|
954
|
+
exports.supportedFunctions = Object.keys(SAFE_FUNCTIONS);
|
|
481
955
|
exports.evaluate = evaluate;
|
|
482
956
|
exports.recalc = recalc;
|
|
483
957
|
exports.recalcWorkbook = recalcWorkbook;
|