|
|
@@ -78,50 +78,51 @@ class CellExpressionTokenType {
|
|
78
|
78
|
|
|
79
|
79
|
class CellExpressionOperation {
|
|
80
|
80
|
/** Arg is int or float */
|
|
81
|
|
- static Number = new this('Number');
|
|
|
81
|
+ static Number = new CellExpressionOperation('Number');
|
|
82
|
82
|
/** Arg is string without quotes */
|
|
83
|
|
- static String = new this('String');
|
|
|
83
|
+ static String = new CellExpressionOperation('String');
|
|
84
|
84
|
/** Arg is bool */
|
|
85
|
|
- static Boolean = new this('Boolean');
|
|
|
85
|
+ static Boolean = new CellExpressionOperation('Boolean');
|
|
86
|
86
|
/** Arg is reference address (e.g. "A5") */
|
|
87
|
|
- static Reference = new this('Reference');
|
|
|
87
|
+ static Reference = new CellExpressionOperation('Reference');
|
|
88
|
88
|
/** Args are start and end addresses (e.g. "A5", "C7") */
|
|
89
|
|
- static Range = new this('Range');
|
|
|
89
|
+ static Range = new CellExpressionOperation('Range');
|
|
90
|
90
|
|
|
91
|
91
|
/** Args are two operand CellExpressions. */
|
|
92
|
|
- static Add = new this('Add');
|
|
|
92
|
+ static Add = new CellExpressionOperation('Add');
|
|
93
|
93
|
/** Args are two operand CellExpressions */
|
|
94
|
|
- static Subtract = new this('Subtract');
|
|
|
94
|
+ static Subtract = new CellExpressionOperation('Subtract');
|
|
95
|
95
|
/** Args are two operand CellExpressions */
|
|
96
|
|
- static Multiply = new this('Multiply');
|
|
|
96
|
+ static Multiply = new CellExpressionOperation('Multiply');
|
|
97
|
97
|
/** Args are two operand CellExpressions */
|
|
98
|
|
- static Divide = new this('Divide');
|
|
|
98
|
+ static Divide = new CellExpressionOperation('Divide');
|
|
99
|
99
|
|
|
100
|
100
|
/** Args are two operand CellExpressions. */
|
|
101
|
|
- static Concatenate = new this('Concatenate');
|
|
|
101
|
+ static Concatenate = new CellExpressionOperation('Concatenate');
|
|
102
|
102
|
|
|
103
|
103
|
/** Arg is operand expression */
|
|
104
|
|
- static UnaryMinus = new this('UnaryMinus');
|
|
|
104
|
+ static UnaryMinus = new CellExpressionOperation('UnaryMinus');
|
|
105
|
105
|
|
|
106
|
106
|
/** Args are two operand CellExpressions. */
|
|
107
|
|
- static GreaterThan = new this('GreaterThan');
|
|
|
107
|
+ static GreaterThan = new CellExpressionOperation('GreaterThan');
|
|
108
|
108
|
/** Args are two operand CellExpressions. */
|
|
109
|
|
- static GreaterThanEqual = new this('GreaterThanEqual');
|
|
|
109
|
+ static GreaterThanEqual = new CellExpressionOperation('GreaterThanEqual');
|
|
110
|
110
|
/** Args are two operand CellExpressions. */
|
|
111
|
|
- static LessThan = new this('LessThan');
|
|
|
111
|
+ static LessThan = new CellExpressionOperation('LessThan');
|
|
112
|
112
|
/** Args are two operand CellExpressions. */
|
|
113
|
|
- static LessThanEqual = new this('LessThanEqual');
|
|
|
113
|
+ static LessThanEqual = new CellExpressionOperation('LessThanEqual');
|
|
114
|
114
|
/** Args are two operand CellExpressions. */
|
|
115
|
|
- static Equal = new this('Equal');
|
|
|
115
|
+ static Equal = new CellExpressionOperation('Equal');
|
|
116
|
116
|
/** Args are two operand CellExpressions. */
|
|
117
|
|
- static Unequal = new this('Unequal');
|
|
|
117
|
+ static Unequal = new CellExpressionOperation('Unequal');
|
|
118
|
118
|
|
|
119
|
119
|
/** Arg is operand expression. */
|
|
120
|
|
- static UnaryNot = new this('UnaryNot');
|
|
|
120
|
+ static UnaryNot = new CellExpressionOperation('UnaryNot');
|
|
121
|
121
|
|
|
122
|
122
|
/** Args are 0+ CellExpressions */
|
|
123
|
|
- static Function = new this('Function');
|
|
|
123
|
+ static Function = new CellExpressionOperation('Function');
|
|
124
|
124
|
|
|
|
125
|
+ /** @type {string} */
|
|
125
|
126
|
name;
|
|
126
|
127
|
|
|
127
|
128
|
constructor(name) {
|
|
|
@@ -129,7 +130,7 @@ class CellExpressionOperation {
|
|
129
|
130
|
}
|
|
130
|
131
|
|
|
131
|
132
|
toString() {
|
|
132
|
|
- return this.name;
|
|
|
133
|
+ return `${this.constructor.name}.${this.name}`;
|
|
133
|
134
|
}
|
|
134
|
135
|
|
|
135
|
136
|
equals(other) {
|
|
|
@@ -211,10 +212,16 @@ class CellExpressionSet {
|
|
211
|
212
|
cell.isCalculated = true;
|
|
212
|
213
|
if (result instanceof CellValue) {
|
|
213
|
214
|
cell.outputValue = result;
|
|
214
|
|
- } else if (Array.isArray(result) && result.length == 1) {
|
|
215
|
|
- cell.outputValue = result[0];
|
|
|
215
|
+ requeueCount = 0;
|
|
|
216
|
+ } else if (Array.isArray(result)) {
|
|
|
217
|
+ if (result.length == 1) {
|
|
|
218
|
+ cell.outputValue = result[0];
|
|
|
219
|
+ requeueCount = 0;
|
|
|
220
|
+ } else {
|
|
|
221
|
+ throw new CellEvaluationException(`Expression resolved to ${result.length} values, single value expected`);
|
|
|
222
|
+ }
|
|
216
|
223
|
} else {
|
|
217
|
|
- throw new CellEvaluationException("Expression did not resolve to a single value");
|
|
|
224
|
+ throw new CellEvaluationException(`Expression resolved to ${result && result.constructor ? result.constructor.name : typeof result}, expected CellValue`);
|
|
218
|
225
|
}
|
|
219
|
226
|
} catch (e) {
|
|
220
|
227
|
if (e instanceof CellDependencyException) {
|
|
|
@@ -298,7 +305,7 @@ class CellExpressionSet {
|
|
298
|
305
|
throw new CellEvaluationException(`No cell at ${refAddress.name}`, '#REF');
|
|
299
|
306
|
}
|
|
300
|
307
|
if (cell.outputValue === null) {
|
|
301
|
|
- throw new CellDependencyException(`Need calculated value for ${refAddress.name} to evaluate`);
|
|
|
308
|
+ throw new CellDependencyException(`Need calculated value for ${refAddress} to evaluate`);
|
|
302
|
309
|
}
|
|
303
|
310
|
return cell.outputValue;
|
|
304
|
311
|
}
|
|
|
@@ -387,7 +394,7 @@ class CellExpressionSet {
|
|
387
|
394
|
case CellExpressionOperation.Function:
|
|
388
|
395
|
return this.#callFunction(expr.qualifier, expr.arguments, address);
|
|
389
|
396
|
}
|
|
390
|
|
- console.warn(`Unhandled operation ${expr.op.name}`);
|
|
|
397
|
+ throw new CellSyntaxException(`Unhandled operation ${expr.op.name}`);
|
|
391
|
398
|
}
|
|
392
|
399
|
|
|
393
|
400
|
/**
|
|
|
@@ -618,7 +625,7 @@ class CellExpressionSet {
|
|
618
|
625
|
throw new CellEvaluationException("IFS expects an odd number of arguments");
|
|
619
|
626
|
}
|
|
620
|
627
|
const evaled = args.map((arg) => this.#evaluate(arg, address));
|
|
621
|
|
- for (var i = 0; i < evaled.length; i += 2) {
|
|
|
628
|
+ for (var i = 0; i < evaled.length - 1; i += 2) {
|
|
622
|
629
|
const test = evaled[i].booleanValue();
|
|
623
|
630
|
if (test === null) {
|
|
624
|
631
|
throw new CellEvaluationException(`IFS expects a boolean for argument ${i + 1}`);
|
|
|
@@ -678,7 +685,7 @@ class CellExpressionSet {
|
|
678
|
685
|
* @returns {CellValue}
|
|
679
|
686
|
*/
|
|
680
|
687
|
#funcMax(args, address) {
|
|
681
|
|
- const maxValue = null;
|
|
|
688
|
+ var maxValue = null;
|
|
682
|
689
|
const flattened = this.#flattenedNumericArguments('MAX', args, address);
|
|
683
|
690
|
if (flattened.length == 0) {
|
|
684
|
691
|
throw new CellEvaluationException("MAX requires at least one numeric argument");
|
|
|
@@ -781,7 +788,7 @@ class CellExpressionSet {
|
|
781
|
788
|
#funcRound(args, address) {
|
|
782
|
789
|
const evaled = this.#assertNumericArguments('ROUND', 1, 2, args, address);
|
|
783
|
790
|
const val = evaled[0];
|
|
784
|
|
- const places = sizeof(evaled) > 1 ? evaled[1].value : 0;
|
|
|
791
|
+ const places = evaled.length > 1 ? evaled[1].value : 0;
|
|
785
|
792
|
const divider = Math.pow(10.0, places);
|
|
786
|
793
|
const newValue = Math.round(val.value * divider) / divider;
|
|
787
|
794
|
return CellValue.fromValue(newValue, val.type);
|
|
|
@@ -820,7 +827,7 @@ class CellExpressionSet {
|
|
820
|
827
|
if (text === null || search === null || replace === null) {
|
|
821
|
828
|
throw new CellEvaluationException("SUBSTITUTE expects 3 string arguments");
|
|
822
|
829
|
}
|
|
823
|
|
- const result = text.replace(new RegExp(RegExp.escape(search), 'gi'), replace);
|
|
|
830
|
+ const result = text.replaceAll(search, replace);
|
|
824
|
831
|
return CellValue.fromValue(result);
|
|
825
|
832
|
}
|
|
826
|
833
|
|
|
|
@@ -879,7 +886,7 @@ class CellExpressionSet {
|
|
879
|
886
|
}
|
|
880
|
887
|
result = (result === null) ? b : (result ^ b);
|
|
881
|
888
|
}
|
|
882
|
|
- return CellValue.fromValue(result);
|
|
|
889
|
+ return CellValue.fromValue(result != 0);
|
|
883
|
890
|
}
|
|
884
|
891
|
}
|
|
885
|
892
|
|
|
|
@@ -961,13 +968,25 @@ class CellExpression {
|
|
961
|
968
|
}
|
|
962
|
969
|
}
|
|
963
|
970
|
|
|
|
971
|
+ #clone() {
|
|
|
972
|
+ const cp = new CellExpression();
|
|
|
973
|
+ cp.op = this.op;
|
|
|
974
|
+ cp.arguments = this.arguments.slice();
|
|
|
975
|
+ cp.qualifier = this.qualifier;
|
|
|
976
|
+ cp.outputType = this.outputType;
|
|
|
977
|
+ cp.outputDecimals = this.outputDecimals;
|
|
|
978
|
+ cp.fillRanges = this.fillRanges !== null ? this.fillRanges.slice() : null;
|
|
|
979
|
+ cp.location = this.location;
|
|
|
980
|
+ return cp;
|
|
|
981
|
+ }
|
|
|
982
|
+
|
|
964
|
983
|
/**
|
|
965
|
984
|
* @param {CellAddress} start
|
|
966
|
985
|
* @param {CellAddress} end
|
|
967
|
986
|
* @returns {CellExpression|null}
|
|
968
|
987
|
*/
|
|
969
|
988
|
transpose(start, end) {
|
|
970
|
|
- var transposed = structuredClone(this);
|
|
|
989
|
+ var transposed = this.#clone(); // structuredClone makes a mess of typing
|
|
971
|
990
|
transposed.arguments = [];
|
|
972
|
991
|
for (const argument of this.arguments) {
|
|
973
|
992
|
if (argument instanceof CellExpression) {
|
|
|
@@ -992,15 +1011,15 @@ class CellExpression {
|
|
992
|
1011
|
*/
|
|
993
|
1012
|
static expressionToTokens(text) {
|
|
994
|
1013
|
var tokens = [];
|
|
995
|
|
- var pos = 0;
|
|
|
1014
|
+ var pos = [0];
|
|
996
|
1015
|
this.#skipWhitespace(text, pos);
|
|
997
|
|
- if (text.substring(pos, pos + 1) == '=') {
|
|
|
1016
|
+ if (text.substring(pos[0], pos[0] + 1) == '=') {
|
|
998
|
1017
|
// Ignore equals
|
|
999
|
|
- pos++;
|
|
|
1018
|
+ pos[0]++;
|
|
1000
|
1019
|
}
|
|
1001
|
1020
|
this.#skipWhitespace(text, pos);
|
|
1002
|
1021
|
var l = text.length;
|
|
1003
|
|
- while (pos < l) {
|
|
|
1022
|
+ while (pos[0] < l) {
|
|
1004
|
1023
|
tokens.push(this.#readNextToken(text, pos));
|
|
1005
|
1024
|
this.#skipWhitespace(text, pos);
|
|
1006
|
1025
|
}
|
|
|
@@ -1208,21 +1227,24 @@ class CellExpression {
|
|
1208
|
1227
|
* @returns {string|null}
|
|
1209
|
1228
|
*/
|
|
1210
|
1229
|
static #readChars(text, pos, charTest, minimumLength = null, maximumLength = null) {
|
|
1211
|
|
- var p = [ pos[0] ];
|
|
|
1230
|
+ var p = pos[0];
|
|
1212
|
1231
|
const l = text.length;
|
|
1213
|
1232
|
var s = '';
|
|
1214
|
1233
|
var sl = 0;
|
|
1215
|
|
- while (p[0] < l && (maximumLength === null || sl < maximumLength)) {
|
|
1216
|
|
- ch = text.substring(p[0], p[0] + 1);
|
|
|
1234
|
+ while (p < l && (maximumLength === null || sl < maximumLength)) {
|
|
|
1235
|
+ const ch = text.substring(p, p + 1);
|
|
1217
|
1236
|
if (!charTest(ch)) break;
|
|
1218
|
1237
|
s += ch;
|
|
1219
|
1238
|
sl++;
|
|
1220
|
|
- p[0]++;
|
|
|
1239
|
+ p++;
|
|
|
1240
|
+ }
|
|
|
1241
|
+ if (p < l && charTest(text.substring(p, p + 1))) {
|
|
|
1242
|
+ return null;
|
|
1221
|
1243
|
}
|
|
1222
|
1244
|
if (minimumLength !== null && sl < minimumLength) {
|
|
1223
|
1245
|
return null;
|
|
1224
|
1246
|
}
|
|
1225
|
|
- pos[0] = p[0];
|
|
|
1247
|
+ pos[0] = p;
|
|
1226
|
1248
|
return s;
|
|
1227
|
1249
|
}
|
|
1228
|
1250
|
|
|
|
@@ -1509,7 +1531,7 @@ class CellExpression {
|
|
1509
|
1531
|
if (start != end) return null;
|
|
1510
|
1532
|
if (!tokens[start].type.isPotentialAddress()) return null;
|
|
1511
|
1533
|
const ref = tokens[start].content.toUpperCase();
|
|
1512
|
|
- const refAddress = new CellAddress(ref, address);
|
|
|
1534
|
+ const refAddress = CellAddress.fromString(ref, address, true);
|
|
1513
|
1535
|
return new CellExpression(CellExpressionOperation.Reference, [ refAddress ]);
|
|
1514
|
1536
|
}
|
|
1515
|
1537
|
|
|
|
@@ -1547,22 +1569,19 @@ class CellExpression {
|
|
1547
|
1569
|
const op = tokens[i].type.name;
|
|
1548
|
1570
|
const priority = opPriorities[op] ?? false;
|
|
1549
|
1571
|
if (priority === false) continue;
|
|
1550
|
|
- console.error(`Found infix candidate at ${i} for ${op} priority ${priority}`);
|
|
|
1572
|
+ //console.info(`Found infix candidate at ${i} for ${op} priority ${priority}`);
|
|
1551
|
1573
|
candidates.push({ priority: priority, i: i });
|
|
1552
|
1574
|
}
|
|
1553
|
1575
|
}
|
|
1554
|
|
- candidates.sort((a, b) => {
|
|
1555
|
|
- if (a.priority < b.priority) return 1;
|
|
1556
|
|
- if (a.priority > b.priority) return -1;
|
|
1557
|
|
- return 0;
|
|
1558
|
|
- });
|
|
|
1576
|
+ candidates.sort((a, b) => a.priority - b.priority);
|
|
1559
|
1577
|
var bestCandidate = null;
|
|
|
1578
|
+ var operand1, operand2;
|
|
1560
|
1579
|
for (const candidate of candidates) {
|
|
1561
|
1580
|
try {
|
|
1562
|
1581
|
i = candidate.i;
|
|
1563
|
|
- const operand1 = this.#tryExpression(tokens, start, i - 1, address);
|
|
|
1582
|
+ operand1 = this.#tryExpression(tokens, start, i - 1, address);
|
|
1564
|
1583
|
if (operand1 === null) continue;
|
|
1565
|
|
- const operand2 = this.#tryExpression(tokens, i + 1, end, address);
|
|
|
1584
|
+ operand2 = this.#tryExpression(tokens, i + 1, end, address);
|
|
1566
|
1585
|
if (operand2 === null) continue;
|
|
1567
|
1586
|
bestCandidate = candidate;
|
|
1568
|
1587
|
break;
|
|
|
@@ -1573,11 +1592,11 @@ class CellExpression {
|
|
1573
|
1592
|
}
|
|
1574
|
1593
|
}
|
|
1575
|
1594
|
if (bestCandidate === null) {
|
|
1576
|
|
- console.error("No best candidate found");
|
|
|
1595
|
+ //console.info("No best candidate found");
|
|
1577
|
1596
|
return null;
|
|
1578
|
1597
|
}
|
|
1579
|
1598
|
i = bestCandidate.i;
|
|
1580
|
|
- console.error(`Best candidate at token ${i}, priority ${bestCandidate.priority}`);
|
|
|
1599
|
+ //console.info(`Best candidate at token ${i}, priority ${bestCandidate.priority}`);
|
|
1581
|
1600
|
switch (tokens[bestCandidate.i].type) {
|
|
1582
|
1601
|
case CellExpressionTokenType.Plus:
|
|
1583
|
1602
|
return new CellExpression(CellExpressionOperation.Add, [ operand1, operand2 ]);
|
|
|
@@ -1662,7 +1681,7 @@ class CellAddress {
|
|
1662
|
1681
|
/**
|
|
1663
|
1682
|
* @type {string}
|
|
1664
|
1683
|
*/
|
|
1665
|
|
- get name() { this.#name; }
|
|
|
1684
|
+ get name() { return this.#name; }
|
|
1666
|
1685
|
|
|
1667
|
1686
|
#isColumnFixed = false;
|
|
1668
|
1687
|
/**
|
|
|
@@ -1679,6 +1698,12 @@ class CellAddress {
|
|
1679
|
1698
|
*/
|
|
1680
|
1699
|
get columnIndex() { return this.#columnIndex; };
|
|
1681
|
1700
|
|
|
|
1701
|
+ /**
|
|
|
1702
|
+ * Letter code for the column.
|
|
|
1703
|
+ * @type {string}
|
|
|
1704
|
+ */
|
|
|
1705
|
+ get columnLetter() { return CellAddress.#columnIndexToLetters(this.#columnIndex); }
|
|
|
1706
|
+
|
|
1682
|
1707
|
#isRowFixed = false;
|
|
1683
|
1708
|
/**
|
|
1684
|
1709
|
* Whether the row should remain unchanged when transposed. This is
|
|
|
@@ -1695,6 +1720,11 @@ class CellAddress {
|
|
1695
|
1720
|
get rowIndex() { return this.#rowIndex; }
|
|
1696
|
1721
|
|
|
1697
|
1722
|
/**
|
|
|
1723
|
+ * One-based row number. This is the human-facing row number.
|
|
|
1724
|
+ */
|
|
|
1725
|
+ get rowNumber() { return this.#rowIndex + 1; }
|
|
|
1726
|
+
|
|
|
1727
|
+ /**
|
|
1698
|
1728
|
* Whether this address has both a definite column and row.
|
|
1699
|
1729
|
* @type {boolean}
|
|
1700
|
1730
|
*/
|
|
|
@@ -1709,14 +1739,17 @@ class CellAddress {
|
|
1709
|
1739
|
* during transpositions. Denoted with a `$` in front of the row digits.
|
|
1710
|
1740
|
*/
|
|
1711
|
1741
|
constructor(columnIndex, rowIndex, isColumnFixed=false, isRowFixed=false) {
|
|
|
1742
|
+ if (typeof columnIndex != 'number') {
|
|
|
1743
|
+ throw new Error(`columnIndex must be number, got ${typeof columnIndex}`);
|
|
|
1744
|
+ }
|
|
|
1745
|
+ if (typeof rowIndex != 'number') {
|
|
|
1746
|
+ throw new Error(`rowIndex must be number, got ${typeof rowIndex}`);
|
|
|
1747
|
+ }
|
|
1712
|
1748
|
this.#columnIndex = columnIndex;
|
|
1713
|
1749
|
this.#rowIndex = rowIndex;
|
|
1714
|
1750
|
this.#isColumnFixed = isColumnFixed;
|
|
1715
|
1751
|
this.#isRowFixed = isRowFixed;
|
|
1716
|
|
- this.#name = (isColumnFixed && columnIndex >= 0 ? '$' : '') +
|
|
1717
|
|
- CellAddress.#columnIndexToLetters(columnIndex) +
|
|
1718
|
|
- (isRowFixed && rowIndex >= 0 ? '$' : '') +
|
|
1719
|
|
- (rowIndex >= 0) ? `${rowIndex + 1}` : '';
|
|
|
1752
|
+ this.#name = CellAddress.#formatAddress(columnIndex, rowIndex, isColumnFixed, isRowFixed);
|
|
1720
|
1753
|
}
|
|
1721
|
1754
|
|
|
1722
|
1755
|
/**
|
|
|
@@ -1725,7 +1758,7 @@ class CellAddress {
|
|
1725
|
1758
|
* @returns {boolean}
|
|
1726
|
1759
|
*/
|
|
1727
|
1760
|
static isAddress(text) {
|
|
1728
|
|
- return this.fromAddress(text) != null;
|
|
|
1761
|
+ return this.fromString(text) != null;
|
|
1729
|
1762
|
}
|
|
1730
|
1763
|
|
|
1731
|
1764
|
/**
|
|
|
@@ -1753,17 +1786,17 @@ class CellAddress {
|
|
1753
|
1786
|
if (!relativeFrom.isResolved || !relativeTo.isResolved) {
|
|
1754
|
1787
|
throw new CellEvaluationException("Can only transpose to and from resolved addresses");
|
|
1755
|
1788
|
}
|
|
1756
|
|
- newColumnIndex = this.columnIndex;
|
|
|
1789
|
+ var newColumnIndex = this.columnIndex;
|
|
1757
|
1790
|
if (!this.isColumnFixed) {
|
|
1758
|
|
- columnDelta = relativeTo.columnIndex - relativeFrom.columnIndex;
|
|
|
1791
|
+ const columnDelta = relativeTo.columnIndex - relativeFrom.columnIndex;
|
|
1759
|
1792
|
newColumnIndex += columnDelta;
|
|
1760
|
1793
|
}
|
|
1761
|
|
- newRowIndex = this.rowIndex;
|
|
|
1794
|
+ var newRowIndex = this.rowIndex;
|
|
1762
|
1795
|
if (!this.isResolved && resolveToRow) {
|
|
1763
|
1796
|
newRowIndex = relativeFrom.rowIndex;
|
|
1764
|
1797
|
}
|
|
1765
|
1798
|
if (newRowIndex != -1 && !this.isRowAbsolute) {
|
|
1766
|
|
- rowDelta = relativeTo.rowIndex - relativeFrom.rowIndex;
|
|
|
1799
|
+ const rowDelta = relativeTo.rowIndex - relativeFrom.rowIndex;
|
|
1767
|
1800
|
newRowIndex += rowDelta;
|
|
1768
|
1801
|
}
|
|
1769
|
1802
|
if (newColumnIndex < 0 || newRowIndex < 0) return null;
|
|
|
@@ -1788,10 +1821,10 @@ class CellAddress {
|
|
1788
|
1821
|
* Converts column letters (e.g. `A`, `C`, `AA`) to a 0-based column index.
|
|
1789
|
1822
|
* Assumes a validated well-formed column letter or else behavior is undefined.
|
|
1790
|
1823
|
*
|
|
1791
|
|
- * @param {string} letter
|
|
|
1824
|
+ * @param {string} letters
|
|
1792
|
1825
|
* @returns {number} column index
|
|
1793
|
1826
|
*/
|
|
1794
|
|
- static #lettersToColumnIndex(letter) {
|
|
|
1827
|
+ static #lettersToColumnIndex(letters) {
|
|
1795
|
1828
|
const ACodepoint = 'A'.codePointAt(0);
|
|
1796
|
1829
|
var columnIndex = 0;
|
|
1797
|
1830
|
for (var i = letters.length - 1; i >= 0; i--) {
|
|
|
@@ -1820,6 +1853,15 @@ class CellAddress {
|
|
1820
|
1853
|
return letters;
|
|
1821
|
1854
|
}
|
|
1822
|
1855
|
|
|
|
1856
|
+ static #formatAddress(columnIndex, rowIndex, isColumnFixed, isRowFixed) {
|
|
|
1857
|
+ var addr = '';
|
|
|
1858
|
+ if (isColumnFixed && columnIndex >= 0) addr += '$';
|
|
|
1859
|
+ if (columnIndex >= 0) addr += this.#columnIndexToLetters(columnIndex);
|
|
|
1860
|
+ if (isRowFixed && rowIndex >= 0) addr += '$';
|
|
|
1861
|
+ if (rowIndex >= 0) addr += `${rowIndex + 1}`;
|
|
|
1862
|
+ return addr;
|
|
|
1863
|
+ }
|
|
|
1864
|
+
|
|
1823
|
1865
|
/**
|
|
1824
|
1866
|
* @param {string} address - cell address string
|
|
1825
|
1867
|
* @param {CellAddress|null} relativeTo - address to resolve relative addresses against
|
|
|
@@ -1885,10 +1927,10 @@ class CellAddressRange {
|
|
1885
|
1927
|
* @returns {object} iterable object
|
|
1886
|
1928
|
*/
|
|
1887
|
1929
|
cellsIn(table) {
|
|
1888
|
|
- const minCol = Math.max(1, this.minColumnIndex);
|
|
1889
|
|
- const maxCol = Math.min(this.maxColumnIndex, table.columnCount - 1);
|
|
1890
|
|
- const minRow = Math.max(1, this.minRowIndex);
|
|
1891
|
|
- const maxRow = Math.min(this.maxRowIndex, table.rowCount - 1);
|
|
|
1930
|
+ const minCol = this.minColumnIndex < 0 ? 0 : this.minColumnIndex;
|
|
|
1931
|
+ const maxCol = this.maxColumnIndex < 0 ? table.columnCount - 1 : Math.min(this.maxColumnIndex, table.columnCount - 1);
|
|
|
1932
|
+ const minRow = this.minRowIndex < 0 ? 0 : this.minRowIndex;
|
|
|
1933
|
+ const maxRow = this.maxRowIndex < 0 ? table.rowCount - 1 : Math.min(this.maxRowIndex, table.rowCount - 1);
|
|
1892
|
1934
|
const iterable = {};
|
|
1893
|
1935
|
iterable[Symbol.iterator] = function() {
|
|
1894
|
1936
|
var currentCol = minCol;
|
|
|
@@ -1950,6 +1992,12 @@ class CellValue {
|
|
1950
|
1992
|
// -- Properties -----
|
|
1951
|
1993
|
|
|
1952
|
1994
|
/**
|
|
|
1995
|
+ * A blank value.
|
|
|
1996
|
+ * @type {CellValue}
|
|
|
1997
|
+ */
|
|
|
1998
|
+ static BLANK = new CellValue('', null, CellValue.TYPE_BLANK);
|
|
|
1999
|
+
|
|
|
2000
|
+ /**
|
|
1953
|
2001
|
* Type of value. One of the `TYPE_` constants.
|
|
1954
|
2002
|
* @type {string}
|
|
1955
|
2003
|
*/
|
|
|
@@ -2029,9 +2077,9 @@ class CellValue {
|
|
2029
|
2077
|
}
|
|
2030
|
2078
|
if (value instanceof Error) {
|
|
2031
|
2079
|
if (value instanceof CellException) {
|
|
2032
|
|
- return new CellValue(value.getErrorSymbol(), value.getMessage(), CellValue.TYPE_ERROR);
|
|
|
2080
|
+ return new CellValue(value.errorSymbol, value.message, CellValue.TYPE_ERROR);
|
|
2033
|
2081
|
}
|
|
2034
|
|
- return new CellValue('#ERROR', value.getMessage(), CellValue.TYPE_ERROR);
|
|
|
2082
|
+ return new CellValue('#ERROR', value.message, CellValue.TYPE_ERROR);
|
|
2035
|
2083
|
}
|
|
2036
|
2084
|
if (typeof value == 'boolean') {
|
|
2037
|
2085
|
const formatted = CellValue.formatType(value, CellValue.TYPE_BOOLEAN, 0);
|
|
|
@@ -2529,7 +2577,7 @@ class CellValue {
|
|
2529
|
2577
|
* @returns {string}
|
|
2530
|
2578
|
*/
|
|
2531
|
2579
|
static #formatNumber(value, decimals) {
|
|
2532
|
|
- return (value).toLocaleString(undefined, { minimumFractionDigits: decimals });
|
|
|
2580
|
+ return (value).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
2533
|
2581
|
}
|
|
2534
|
2582
|
|
|
2535
|
2583
|
/**
|
|
|
@@ -2538,7 +2586,7 @@ class CellValue {
|
|
2538
|
2586
|
* @returns {string}
|
|
2539
|
2587
|
*/
|
|
2540
|
2588
|
static #formatCurrency(dollars, decimals) {
|
|
2541
|
|
- var s = (dollars).toLocaleString(undefined, { minimumFractionDigits: decimals });
|
|
|
2589
|
+ var s = (dollars).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
|
|
2542
|
2590
|
if (s.startsWith('-')) {
|
|
2543
|
2591
|
return '-$' + s.substring(1);
|
|
2544
|
2592
|
}
|
|
|
@@ -2552,7 +2600,7 @@ class CellValue {
|
|
2552
|
2600
|
*/
|
|
2553
|
2601
|
static #formatPercent(value, decimals) {
|
|
2554
|
2602
|
const dec = value * 100.0;
|
|
2555
|
|
- return (dec).toLocaleString(undefined, { minimumFractionDigits: decimals }) + '%';
|
|
|
2603
|
+ return (dec).toLocaleString(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) + '%';
|
|
2556
|
2604
|
}
|
|
2557
|
2605
|
|
|
2558
|
2606
|
/**
|
|
|
@@ -2566,16 +2614,9 @@ class CellValue {
|
|
2566
|
2614
|
return CellValue.#autodecimals(value.value);
|
|
2567
|
2615
|
}
|
|
2568
|
2616
|
if (typeof value == 'number') {
|
|
2569
|
|
- var s = `${Math.abs(value)}`;
|
|
|
2617
|
+ var s = (value).toLocaleString(undefined, { maximumFractionDigits: maxDigits });
|
|
2570
|
2618
|
if (/\./.exec(s) === null) return 0;
|
|
2571
|
|
- if (s.endsWith('.0')) return 0;
|
|
2572
|
|
- const parts = s.split('.');
|
|
2573
|
|
- const whole = parts[0];
|
|
2574
|
|
- var fraction = parts[1];
|
|
2575
|
|
- if (fraction.endsWith('99')) {
|
|
2576
|
|
- // More than one 9 at the end points to floating point rounding. Lop em off.
|
|
2577
|
|
- fraction = fraction.replace(/[9]+$/, '');
|
|
2578
|
|
- }
|
|
|
2619
|
+ var fraction = s.split('.')[1];
|
|
2579
|
2620
|
return Math.min(maxDigits, fraction.length);
|
|
2580
|
2621
|
}
|
|
2581
|
2622
|
return 0;
|
|
|
@@ -2683,12 +2724,12 @@ class SpreadsheetCell {
|
|
2683
|
2724
|
/**
|
|
2684
|
2725
|
* @type {MDTableCellBlock|null}
|
|
2685
|
2726
|
*/
|
|
2686
|
|
- block;
|
|
|
2727
|
+ block = null;
|
|
2687
|
2728
|
|
|
2688
|
2729
|
/**
|
|
2689
|
|
- * @type {CellValue|null}
|
|
|
2730
|
+ * @type {CellValue}
|
|
2690
|
2731
|
*/
|
|
2691
|
|
- originalValue;
|
|
|
2732
|
+ originalValue = CellValue.BLANK;
|
|
2692
|
2733
|
|
|
2693
|
2734
|
/**
|
|
2694
|
2735
|
* @type {CellValue|null}
|
|
|
@@ -2699,7 +2740,7 @@ class SpreadsheetCell {
|
|
2699
|
2740
|
isCalculated = false;
|
|
2700
|
2741
|
|
|
2701
|
2742
|
/** @type {CellExpression|null} */
|
|
2702
|
|
- parsedExpression;
|
|
|
2743
|
+ parsedExpression = null;
|
|
2703
|
2744
|
|
|
2704
|
2745
|
/**
|
|
2705
|
2746
|
* @type {CellValue|null}
|