@rowsncolumns/fast-formula-parser 6.2.41 → 6.2.44
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/dist/cjs/index.js +308 -125
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +308 -125
- package/dist/esm/index.js.map +1 -1
- package/package.json +5 -4
package/dist/cjs/index.js
CHANGED
|
@@ -5649,6 +5649,7 @@ const FORMULA_LOCALE_DATA = {
|
|
|
5649
5649
|
"ISNUMBER": "ESTNUM",
|
|
5650
5650
|
"ISO.CEILING": "ISO.PLAFOND",
|
|
5651
5651
|
"ISODD": "EST.IMPAIR",
|
|
5652
|
+
"ISOMITTED": "ISOMITTED",
|
|
5652
5653
|
"ISOWEEKNUM": "NO.SEMAINE.ISO",
|
|
5653
5654
|
"ISPMT": "ISPMT",
|
|
5654
5655
|
"ISREF": "ESTREF",
|
|
@@ -10953,6 +10954,66 @@ const Criteria = Object.freeze({
|
|
|
10953
10954
|
return false;
|
|
10954
10955
|
}
|
|
10955
10956
|
},
|
|
10957
|
+
|
|
10958
|
+
/**
|
|
10959
|
+
* Excel's array lift for the criteria slot of the criteria aggregates. When
|
|
10960
|
+
* any accepted criterion value is a multi-cell array or range
|
|
10961
|
+
* (`SUMIF(rng,{"x","y"},sum)`, `SUMIFS(sum,rng,"x",rng2,{">1",">3"})`,
|
|
10962
|
+
* `COUNTIF(rng,rng)`), `evaluate` runs once per broadcast element with that
|
|
10963
|
+
* element parsed as a scalar criterion and the results come back as a 2-D
|
|
10964
|
+
* array — so `SUM(COUNTIF(rng,{…}))` / `SUMPRODUCT(SUMIF(…,{…}))` fold it and a
|
|
10965
|
+
* per-element error (`#DIV/0!` from an empty AVERAGEIF slot) stays in its
|
|
10966
|
+
* slot. Vectors broadcast along a unit dimension; a slot a shorter vector
|
|
10967
|
+
* does not reach is `#N/A`. A 1×1 array is a scalar. With no array criterion
|
|
10968
|
+
* `evaluate` runs exactly once with the parsed scalar criteria.
|
|
10969
|
+
* @param {Array} criteriaValues accepted (unparsed) criterion values
|
|
10970
|
+
* @param {(parsed: Array) => *} evaluate
|
|
10971
|
+
*/
|
|
10972
|
+
lift: (criteriaValues, evaluate) => {
|
|
10973
|
+
const shapes = criteriaValues.map((v) => {
|
|
10974
|
+
if (!Array.isArray(v)) return null;
|
|
10975
|
+
const shape = [v.length, v[0]?.length ?? 0];
|
|
10976
|
+
return shape[0] === 1 && shape[1] === 1 ? null : shape;
|
|
10977
|
+
});
|
|
10978
|
+
const scalarOf = (v) => (Array.isArray(v) ? v[0][0] : v);
|
|
10979
|
+
if (shapes.every((shape) => shape === null)) {
|
|
10980
|
+
return evaluate(criteriaValues.map((v) => Criteria.parse(scalarOf(v))));
|
|
10981
|
+
}
|
|
10982
|
+
let rows = 1;
|
|
10983
|
+
let cols = 1;
|
|
10984
|
+
for (const shape of shapes) {
|
|
10985
|
+
if (!shape) continue;
|
|
10986
|
+
if (shape[0] !== 1) rows = rows === 1 ? shape[0] : Math.max(rows, shape[0]);
|
|
10987
|
+
if (shape[1] !== 1) cols = cols === 1 ? shape[1] : Math.max(cols, shape[1]);
|
|
10988
|
+
}
|
|
10989
|
+
const MISSING = Symbol("missing");
|
|
10990
|
+
const pick = (v, shape, i, j) => {
|
|
10991
|
+
const r = shape[0] === 1 ? 0 : i;
|
|
10992
|
+
const c = shape[1] === 1 ? 0 : j;
|
|
10993
|
+
return r < v.length && c < v[r].length ? v[r][c] : MISSING;
|
|
10994
|
+
};
|
|
10995
|
+
const out = [];
|
|
10996
|
+
for (let i = 0; i < rows; i++) {
|
|
10997
|
+
const row = [];
|
|
10998
|
+
for (let j = 0; j < cols; j++) {
|
|
10999
|
+
const elements = criteriaValues.map((v, k) =>
|
|
11000
|
+
shapes[k] ? pick(v, shapes[k], i, j) : scalarOf(v),
|
|
11001
|
+
);
|
|
11002
|
+
if (elements.includes(MISSING)) {
|
|
11003
|
+
row.push(FormulaError.NA);
|
|
11004
|
+
continue;
|
|
11005
|
+
}
|
|
11006
|
+
try {
|
|
11007
|
+
row.push(evaluate(elements.map((e) => Criteria.parse(e))));
|
|
11008
|
+
} catch (e) {
|
|
11009
|
+
if (e instanceof FormulaError) row.push(e);
|
|
11010
|
+
else throw e;
|
|
11011
|
+
}
|
|
11012
|
+
}
|
|
11013
|
+
out.push(row);
|
|
11014
|
+
}
|
|
11015
|
+
return out;
|
|
11016
|
+
},
|
|
10956
11017
|
});
|
|
10957
11018
|
|
|
10958
11019
|
const Address = {
|
|
@@ -20678,23 +20739,26 @@ const StatisticalFunctions = {
|
|
|
20678
20739
|
range = ranges[0];
|
|
20679
20740
|
averageRange = ranges[1];
|
|
20680
20741
|
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20742
|
+
const criteriaValue = H$7.accept(H$7.retrieveArg(context, criteria));
|
|
20743
|
+
|
|
20744
|
+
const averageMatching = (parsed) => {
|
|
20745
|
+
let sum = 0,
|
|
20746
|
+
cnt = 0;
|
|
20747
|
+
range.forEach((row, rowNum) => {
|
|
20748
|
+
row.forEach((value, colNum) => {
|
|
20749
|
+
const valueToAdd = averageRange[rowNum][colNum];
|
|
20750
|
+
throwIfFormulaError(valueToAdd);
|
|
20751
|
+
if (typeof valueToAdd !== "number") return;
|
|
20752
|
+
if (Criteria.matches(parsed, value)) {
|
|
20753
|
+
sum += valueToAdd;
|
|
20754
|
+
cnt++;
|
|
20755
|
+
}
|
|
20756
|
+
});
|
|
20694
20757
|
});
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20758
|
+
if (cnt === 0) throw FormulaError.DIV0;
|
|
20759
|
+
return sum / cnt;
|
|
20760
|
+
};
|
|
20761
|
+
return Criteria.lift([criteriaValue], ([parsed]) => averageMatching(parsed));
|
|
20698
20762
|
},
|
|
20699
20763
|
|
|
20700
20764
|
/**
|
|
@@ -20724,36 +20788,42 @@ const StatisticalFunctions = {
|
|
|
20724
20788
|
assertSameShape(averageRange, criteriaRange);
|
|
20725
20789
|
criteriaPairs.push({
|
|
20726
20790
|
range: criteriaRange,
|
|
20727
|
-
criteria:
|
|
20791
|
+
criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
|
|
20728
20792
|
});
|
|
20729
20793
|
}
|
|
20730
20794
|
|
|
20731
|
-
|
|
20732
|
-
|
|
20795
|
+
const averageMatching = (parsedCriteria) => {
|
|
20796
|
+
let sum = 0;
|
|
20797
|
+
let count = 0;
|
|
20733
20798
|
|
|
20734
|
-
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20799
|
+
averageRange.forEach((row, rowNum) => {
|
|
20800
|
+
row.forEach((valueToAdd, colNum) => {
|
|
20801
|
+
throwIfFormulaError(valueToAdd);
|
|
20802
|
+
if (typeof valueToAdd !== "number") {
|
|
20803
|
+
return;
|
|
20804
|
+
}
|
|
20740
20805
|
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
|
|
20806
|
+
const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
|
|
20807
|
+
Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
|
|
20808
|
+
);
|
|
20744
20809
|
|
|
20745
|
-
|
|
20746
|
-
|
|
20747
|
-
|
|
20748
|
-
|
|
20810
|
+
if (meetsAllCriteria) {
|
|
20811
|
+
sum += valueToAdd;
|
|
20812
|
+
count++;
|
|
20813
|
+
}
|
|
20814
|
+
});
|
|
20749
20815
|
});
|
|
20750
|
-
});
|
|
20751
20816
|
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
|
|
20817
|
+
if (count === 0) {
|
|
20818
|
+
throw FormulaError.DIV0;
|
|
20819
|
+
}
|
|
20755
20820
|
|
|
20756
|
-
|
|
20821
|
+
return sum / count;
|
|
20822
|
+
};
|
|
20823
|
+
return Criteria.lift(
|
|
20824
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
20825
|
+
averageMatching,
|
|
20826
|
+
);
|
|
20757
20827
|
},
|
|
20758
20828
|
|
|
20759
20829
|
LARGE: (array, kth) => {
|
|
@@ -20825,34 +20895,40 @@ const StatisticalFunctions = {
|
|
|
20825
20895
|
assertSameShape(maxRange, criteriaRange);
|
|
20826
20896
|
criteriaPairs.push({
|
|
20827
20897
|
range: criteriaRange,
|
|
20828
|
-
criteria:
|
|
20898
|
+
criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
|
|
20829
20899
|
});
|
|
20830
20900
|
}
|
|
20831
20901
|
|
|
20832
|
-
|
|
20833
|
-
|
|
20902
|
+
const maxMatching = (parsedCriteria) => {
|
|
20903
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
20904
|
+
let matchFound = false;
|
|
20834
20905
|
|
|
20835
|
-
|
|
20836
|
-
|
|
20837
|
-
|
|
20838
|
-
|
|
20839
|
-
|
|
20840
|
-
|
|
20906
|
+
maxRange.forEach((row, rowNum) => {
|
|
20907
|
+
row.forEach((valueToAdd, colNum) => {
|
|
20908
|
+
throwIfFormulaError(valueToAdd);
|
|
20909
|
+
if (typeof valueToAdd !== "number") {
|
|
20910
|
+
return;
|
|
20911
|
+
}
|
|
20841
20912
|
|
|
20842
|
-
|
|
20843
|
-
|
|
20844
|
-
|
|
20913
|
+
const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
|
|
20914
|
+
Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
|
|
20915
|
+
);
|
|
20845
20916
|
|
|
20846
|
-
|
|
20847
|
-
|
|
20848
|
-
|
|
20917
|
+
if (meetsAllCriteria) {
|
|
20918
|
+
if (valueToAdd > max) {
|
|
20919
|
+
max = valueToAdd;
|
|
20920
|
+
}
|
|
20921
|
+
matchFound = true;
|
|
20849
20922
|
}
|
|
20850
|
-
|
|
20851
|
-
}
|
|
20923
|
+
});
|
|
20852
20924
|
});
|
|
20853
|
-
});
|
|
20854
20925
|
|
|
20855
|
-
|
|
20926
|
+
return matchFound ? max : 0;
|
|
20927
|
+
};
|
|
20928
|
+
return Criteria.lift(
|
|
20929
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
20930
|
+
maxMatching,
|
|
20931
|
+
);
|
|
20856
20932
|
},
|
|
20857
20933
|
|
|
20858
20934
|
MINA: (...params) => {
|
|
@@ -20903,34 +20979,40 @@ const StatisticalFunctions = {
|
|
|
20903
20979
|
assertSameShape(minRange, criteriaRange);
|
|
20904
20980
|
criteriaPairs.push({
|
|
20905
20981
|
range: criteriaRange,
|
|
20906
|
-
criteria:
|
|
20982
|
+
criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
|
|
20907
20983
|
});
|
|
20908
20984
|
}
|
|
20909
20985
|
|
|
20910
|
-
|
|
20911
|
-
|
|
20986
|
+
const minMatching = (parsedCriteria) => {
|
|
20987
|
+
let min = Number.POSITIVE_INFINITY;
|
|
20988
|
+
let matchFound = false;
|
|
20912
20989
|
|
|
20913
|
-
|
|
20914
|
-
|
|
20915
|
-
|
|
20916
|
-
|
|
20917
|
-
|
|
20918
|
-
|
|
20990
|
+
minRange.forEach((row, rowNum) => {
|
|
20991
|
+
row.forEach((valueToAdd, colNum) => {
|
|
20992
|
+
throwIfFormulaError(valueToAdd);
|
|
20993
|
+
if (typeof valueToAdd !== "number") {
|
|
20994
|
+
return;
|
|
20995
|
+
}
|
|
20919
20996
|
|
|
20920
|
-
|
|
20921
|
-
|
|
20922
|
-
|
|
20997
|
+
const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
|
|
20998
|
+
Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
|
|
20999
|
+
);
|
|
20923
21000
|
|
|
20924
|
-
|
|
20925
|
-
|
|
20926
|
-
|
|
21001
|
+
if (meetsAllCriteria) {
|
|
21002
|
+
if (valueToAdd < min) {
|
|
21003
|
+
min = valueToAdd;
|
|
21004
|
+
}
|
|
21005
|
+
matchFound = true;
|
|
20927
21006
|
}
|
|
20928
|
-
|
|
20929
|
-
}
|
|
21007
|
+
});
|
|
20930
21008
|
});
|
|
20931
|
-
});
|
|
20932
21009
|
|
|
20933
|
-
|
|
21010
|
+
return matchFound ? min : 0;
|
|
21011
|
+
};
|
|
21012
|
+
return Criteria.lift(
|
|
21013
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
21014
|
+
minMatching,
|
|
21015
|
+
);
|
|
20934
21016
|
},
|
|
20935
21017
|
|
|
20936
21018
|
NPV: (rate, ...values) => {
|
|
@@ -23231,26 +23313,30 @@ const MathFunctions = {
|
|
|
23231
23313
|
range = ranges[0];
|
|
23232
23314
|
sumRange = ranges[1];
|
|
23233
23315
|
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
23316
|
+
const criteriaValue = H$6.accept(H$6.retrieveArg(context, criteria));
|
|
23317
|
+
|
|
23318
|
+
const sumMatching = (parsed) => {
|
|
23319
|
+
let sum = 0;
|
|
23320
|
+
const rowCount = range.length;
|
|
23321
|
+
for (let r = 0; r < rowCount; r++) {
|
|
23322
|
+
const row = range[r];
|
|
23323
|
+
const sumRow = sumRange[r];
|
|
23324
|
+
const colCount = row.length;
|
|
23325
|
+
for (let c = 0; c < colCount; c++) {
|
|
23326
|
+
const valueToAdd = sumRow[c];
|
|
23327
|
+
if (valueToAdd instanceof FormulaError) {
|
|
23328
|
+
throw valueToAdd;
|
|
23329
|
+
}
|
|
23330
|
+
if (typeof valueToAdd !== "number") continue;
|
|
23331
|
+
if (Criteria.matches(parsed, row[c])) {
|
|
23332
|
+
sum += valueToAdd;
|
|
23333
|
+
}
|
|
23250
23334
|
}
|
|
23251
23335
|
}
|
|
23252
|
-
|
|
23253
|
-
|
|
23336
|
+
return sum;
|
|
23337
|
+
};
|
|
23338
|
+
// An array criterion spills one total per element (SUMPRODUCT(SUMIF(rng,{…},sum))).
|
|
23339
|
+
return Criteria.lift([criteriaValue], ([parsed]) => sumMatching(parsed));
|
|
23254
23340
|
},
|
|
23255
23341
|
|
|
23256
23342
|
/**
|
|
@@ -23285,37 +23371,43 @@ const MathFunctions = {
|
|
|
23285
23371
|
}
|
|
23286
23372
|
criteriaPairs.push({
|
|
23287
23373
|
range: criteriaRange,
|
|
23288
|
-
criteria:
|
|
23374
|
+
criteria: H$6.accept(H$6.retrieveArg(context, args[i + 1])),
|
|
23289
23375
|
});
|
|
23290
23376
|
}
|
|
23291
23377
|
|
|
23292
|
-
|
|
23293
|
-
|
|
23294
|
-
|
|
23295
|
-
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
|
|
23303
|
-
|
|
23304
|
-
|
|
23305
|
-
|
|
23306
|
-
|
|
23307
|
-
|
|
23308
|
-
|
|
23309
|
-
|
|
23310
|
-
|
|
23378
|
+
const sumMatching = (parsedCriteria) => {
|
|
23379
|
+
let sum = 0;
|
|
23380
|
+
const rowCount = sumRange.length;
|
|
23381
|
+
for (let r = 0; r < rowCount; r++) {
|
|
23382
|
+
const row = sumRange[r];
|
|
23383
|
+
const colCount = row.length;
|
|
23384
|
+
for (let c = 0; c < colCount; c++) {
|
|
23385
|
+
const valueToAdd = row[c];
|
|
23386
|
+
if (valueToAdd instanceof FormulaError) {
|
|
23387
|
+
throw valueToAdd;
|
|
23388
|
+
}
|
|
23389
|
+
if (typeof valueToAdd !== "number") {
|
|
23390
|
+
continue;
|
|
23391
|
+
}
|
|
23392
|
+
let allCriteriaMet = true;
|
|
23393
|
+
for (let i = 0; i < criteriaPairs.length; i++) {
|
|
23394
|
+
if (!Criteria.matches(parsedCriteria[i], criteriaPairs[i].range[r][c])) {
|
|
23395
|
+
allCriteriaMet = false;
|
|
23396
|
+
break;
|
|
23397
|
+
}
|
|
23398
|
+
}
|
|
23399
|
+
if (allCriteriaMet) {
|
|
23400
|
+
sum += valueToAdd;
|
|
23311
23401
|
}
|
|
23312
|
-
}
|
|
23313
|
-
if (allCriteriaMet) {
|
|
23314
|
-
sum += valueToAdd;
|
|
23315
23402
|
}
|
|
23316
23403
|
}
|
|
23317
|
-
|
|
23318
|
-
|
|
23404
|
+
return sum;
|
|
23405
|
+
};
|
|
23406
|
+
// Array criteria spill one total per broadcast element.
|
|
23407
|
+
return Criteria.lift(
|
|
23408
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
23409
|
+
sumMatching,
|
|
23410
|
+
);
|
|
23319
23411
|
},
|
|
23320
23412
|
|
|
23321
23413
|
SUMPRODUCT: (array1, ...arrays) => {
|
|
@@ -23908,7 +24000,23 @@ const LogicalFunctions = {
|
|
|
23908
24000
|
// never raw `undefined` (misreported as "Function IFNA is not
|
|
23909
24001
|
// implemented.").
|
|
23910
24002
|
const blankToZero = (v) => (v === undefined || v === null ? 0 : v);
|
|
23911
|
-
|
|
24003
|
+
const isNa = (cell) => cell instanceof FormulaError && FormulaError.NA.equals(cell);
|
|
24004
|
+
|
|
24005
|
+
// Excel lifts IFNA element-wise over an array / multi-cell range value, like IFERROR:
|
|
24006
|
+
// only the #N/A elements take the fallback, every other value or error passes through
|
|
24007
|
+
// (`IFNA(K1:K3,0)` → {#DIV/0!;0;0}, `SUM(IFNA(rng,0))` sums the non-#N/A cells).
|
|
24008
|
+
const hasValueProp = value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'value');
|
|
24009
|
+
if (hasValueProp && value.isArray) {
|
|
24010
|
+
const arrayValue = value.value;
|
|
24011
|
+
const handleCell = (cell) => (isNa(cell) ? blankToZero(H$4.accept(valueIfNa)) : blankToZero(cell));
|
|
24012
|
+
if (Array.isArray(arrayValue)) {
|
|
24013
|
+
return arrayValue.map((row) => (Array.isArray(row) ? row.map(handleCell) : handleCell(row)));
|
|
24014
|
+
}
|
|
24015
|
+
return handleCell(arrayValue);
|
|
24016
|
+
}
|
|
24017
|
+
|
|
24018
|
+
const actualValue = hasValueProp ? value.value : value;
|
|
24019
|
+
return isNa(actualValue)
|
|
23912
24020
|
? blankToZero(H$4.accept(valueIfNa))
|
|
23913
24021
|
: blankToZero(H$4.accept(value));
|
|
23914
24022
|
},
|
|
@@ -25587,6 +25695,55 @@ const lookupEquals = (a, b) =>
|
|
|
25587
25695
|
? a.toLowerCase() === b.toLowerCase()
|
|
25588
25696
|
: a === b;
|
|
25589
25697
|
|
|
25698
|
+
/**
|
|
25699
|
+
* The sheet range a lookup table argument was read from, or undefined when the table is an
|
|
25700
|
+
* array (an array constant, a spilled range or another function's result). The matrix the
|
|
25701
|
+
* host returned must fit inside that range — it is a window starting at `from` (a full-column
|
|
25702
|
+
* reference is trimmed to the data extent) — or no cell can be derived from a matrix index.
|
|
25703
|
+
*/
|
|
25704
|
+
const rangeOf = (arg, matrix) => {
|
|
25705
|
+
const ref = arg?.position;
|
|
25706
|
+
if (!ref || ref.isSpilledRange || ref.sheetEnd != null) return undefined;
|
|
25707
|
+
let range;
|
|
25708
|
+
if (ref.from && ref.to) {
|
|
25709
|
+
range = ref;
|
|
25710
|
+
} else if (ref.row != null && ref.col != null) {
|
|
25711
|
+
range = {
|
|
25712
|
+
sheet: ref.sheet,
|
|
25713
|
+
from: { row: ref.row, col: ref.col },
|
|
25714
|
+
to: { row: ref.row, col: ref.col },
|
|
25715
|
+
};
|
|
25716
|
+
} else {
|
|
25717
|
+
return undefined;
|
|
25718
|
+
}
|
|
25719
|
+
const fits =
|
|
25720
|
+
matrix.length <= range.to.row - range.from.row + 1 &&
|
|
25721
|
+
(matrix[0]?.length ?? 0) <= range.to.col - range.from.col + 1;
|
|
25722
|
+
return fits ? range : undefined;
|
|
25723
|
+
};
|
|
25724
|
+
|
|
25725
|
+
/**
|
|
25726
|
+
* The cell VLOOKUP/HLOOKUP found. Over a sheet range it is the cell's REFERENCE (as INDEX returns
|
|
25727
|
+
* one), so the result carries `=B2` semantics by construction: a blank cell displays 0, is 0 in
|
|
25728
|
+
* arithmetic and comparison, "" in text — `=VLOOKUP(…)+0` is 0, `=VLOOKUP(…)=""` is TRUE,
|
|
25729
|
+
* `=VLOOKUP(…)&"x"` is "x". The engine has no blank scalar to return instead: the dispatcher reads
|
|
25730
|
+
* a returned `undefined` as "function not implemented" (`#NAME?`) and `null` is the skipped-argument
|
|
25731
|
+
* marker. Over an array there is no cell to point at, so a blank slot coerces to 0 as blank does in
|
|
25732
|
+
* arithmetic. A cell holding `=""` is text, not blank, and comes back as "" either way.
|
|
25733
|
+
*/
|
|
25734
|
+
const matchedCell = (tableRange, tableArray, rowIdx, colIdx) => {
|
|
25735
|
+
if (tableRange) {
|
|
25736
|
+
return {
|
|
25737
|
+
ref: {
|
|
25738
|
+
sheet: tableRange.sheet,
|
|
25739
|
+
row: tableRange.from.row + rowIdx,
|
|
25740
|
+
col: tableRange.from.col + colIdx,
|
|
25741
|
+
},
|
|
25742
|
+
};
|
|
25743
|
+
}
|
|
25744
|
+
return tableArray[rowIdx][colIdx] ?? 0;
|
|
25745
|
+
};
|
|
25746
|
+
|
|
25590
25747
|
/**
|
|
25591
25748
|
* Exact-mode match for VLOOKUP/HLOOKUP/XLOOKUP/XMATCH: wildcards when the lookup text has
|
|
25592
25749
|
* them, otherwise case-insensitive equality.
|
|
@@ -25853,6 +26010,7 @@ const ReferenceFunctions = {
|
|
|
25853
26010
|
HLOOKUP: (lookupValue, tableArray, rowIndexNum, rangeLookup) => {
|
|
25854
26011
|
// preserve type of lookupValue
|
|
25855
26012
|
lookupValue = H$2.accept(lookupValue);
|
|
26013
|
+
const tableArg = tableArray;
|
|
25856
26014
|
try {
|
|
25857
26015
|
tableArray = H$2.accept(tableArray, Types.ARRAY, undefined, false);
|
|
25858
26016
|
} catch (e) {
|
|
@@ -25864,6 +26022,7 @@ const ReferenceFunctions = {
|
|
|
25864
26022
|
}
|
|
25865
26023
|
throw e;
|
|
25866
26024
|
}
|
|
26025
|
+
const tableRange = rangeOf(tableArg, tableArray);
|
|
25867
26026
|
rowIndexNum = H$2.accept(rowIndexNum, Types.NUMBER);
|
|
25868
26027
|
rangeLookup = H$2.accept(rangeLookup, Types.BOOLEAN, true);
|
|
25869
26028
|
|
|
@@ -25876,7 +26035,7 @@ const ReferenceFunctions = {
|
|
|
25876
26035
|
if (rangeLookup) {
|
|
25877
26036
|
const index = approximateMatchIndex(tableArray[0], lookupValue);
|
|
25878
26037
|
if (index === -1) throw FormulaError.NA;
|
|
25879
|
-
return tableArray
|
|
26038
|
+
return matchedCell(tableRange, tableArray, rowIndexNum - 1, index);
|
|
25880
26039
|
}
|
|
25881
26040
|
// exact lookup: case-insensitive, with wildcard support
|
|
25882
26041
|
else {
|
|
@@ -25885,7 +26044,7 @@ const ReferenceFunctions = {
|
|
|
25885
26044
|
);
|
|
25886
26045
|
// the exact match is not found
|
|
25887
26046
|
if (index === -1) throw FormulaError.NA;
|
|
25888
|
-
return tableArray
|
|
26047
|
+
return matchedCell(tableRange, tableArray, rowIndexNum - 1, index);
|
|
25889
26048
|
}
|
|
25890
26049
|
},
|
|
25891
26050
|
|
|
@@ -26389,6 +26548,7 @@ const ReferenceFunctions = {
|
|
|
26389
26548
|
VLOOKUP: (lookupValue, tableArray, colIndexNum, rangeLookup) => {
|
|
26390
26549
|
// preserve type of lookupValue
|
|
26391
26550
|
lookupValue = H$2.accept(lookupValue);
|
|
26551
|
+
const tableArg = tableArray;
|
|
26392
26552
|
try {
|
|
26393
26553
|
tableArray = H$2.accept(tableArray, Types.ARRAY, undefined, false);
|
|
26394
26554
|
} catch (e) {
|
|
@@ -26400,6 +26560,7 @@ const ReferenceFunctions = {
|
|
|
26400
26560
|
}
|
|
26401
26561
|
throw e;
|
|
26402
26562
|
}
|
|
26563
|
+
const tableRange = rangeOf(tableArg, tableArray);
|
|
26403
26564
|
colIndexNum = H$2.accept(colIndexNum, Types.NUMBER);
|
|
26404
26565
|
rangeLookup = H$2.accept(rangeLookup, Types.BOOLEAN, true);
|
|
26405
26566
|
|
|
@@ -26415,7 +26576,7 @@ const ReferenceFunctions = {
|
|
|
26415
26576
|
lookupValue,
|
|
26416
26577
|
);
|
|
26417
26578
|
if (index === -1) throw FormulaError.NA;
|
|
26418
|
-
return tableArray
|
|
26579
|
+
return matchedCell(tableRange, tableArray, index, colIndexNum - 1);
|
|
26419
26580
|
}
|
|
26420
26581
|
// exact lookup: case-insensitive, with wildcard support
|
|
26421
26582
|
else {
|
|
@@ -26424,7 +26585,7 @@ const ReferenceFunctions = {
|
|
|
26424
26585
|
);
|
|
26425
26586
|
// the exact match is not found
|
|
26426
26587
|
if (index === -1) throw FormulaError.NA;
|
|
26427
|
-
return tableArray
|
|
26588
|
+
return matchedCell(tableRange, tableArray, index, colIndexNum - 1);
|
|
26428
26589
|
}
|
|
26429
26590
|
},
|
|
26430
26591
|
|
|
@@ -50565,6 +50726,16 @@ class Parser extends EmbeddedActionsParser {
|
|
|
50565
50726
|
) {
|
|
50566
50727
|
return arr[0][0];
|
|
50567
50728
|
}
|
|
50729
|
+
// Excel refuses a ragged constant (`{1,2;3}`) at entry — every row must have the width
|
|
50730
|
+
// of the first. A parse-level `#ERROR!`, like any other syntax error the host stores as
|
|
50731
|
+
// the cell value (rowsncolumns/spreadsheet#939 G-02).
|
|
50732
|
+
const width = arr[0].length;
|
|
50733
|
+
const ragged = arr.findIndex((row) => row.length !== width);
|
|
50734
|
+
if (ragged !== -1) {
|
|
50735
|
+
throw FormulaError.ERROR(
|
|
50736
|
+
`Array constant rows must have the same length: row 1 has ${width} element(s), row ${ragged + 1} has ${arr[ragged].length}`
|
|
50737
|
+
);
|
|
50738
|
+
}
|
|
50568
50739
|
const array = this.utils.toArray(arr);
|
|
50569
50740
|
return context.async
|
|
50570
50741
|
? this.utils.settleArrayItems(array, $.position)
|
|
@@ -52984,8 +53155,13 @@ const normalizeNumberResult = (value, precision, epsilon, excelPrecision) => {
|
|
|
52984
53155
|
* @param {*} value
|
|
52985
53156
|
* @return {boolean}
|
|
52986
53157
|
*/
|
|
53158
|
+
// A name's definition text: `=…` (the OOXML `<definedName>` content), or a bare `LAMBDA(…)` —
|
|
53159
|
+
// the shape Excel's Name Manager and the host's "Text or Formula" names store a lambda in, which
|
|
53160
|
+
// the call path (`Dbl(21)`) already accepts without the leading `=` (spreadsheet#939 G-07).
|
|
53161
|
+
const BARE_LAMBDA_TEXT = /^(_xlfn\.)?LAMBDA\s*\(/i;
|
|
52987
53162
|
const isNamedFormulaText = (value) =>
|
|
52988
|
-
typeof value === "string" &&
|
|
53163
|
+
typeof value === "string" &&
|
|
53164
|
+
(value.trimStart().startsWith("=") || BARE_LAMBDA_TEXT.test(value.trimStart()));
|
|
52989
53165
|
|
|
52990
53166
|
class FormulaParser {
|
|
52991
53167
|
/**
|
|
@@ -53354,7 +53530,8 @@ class FormulaParser {
|
|
|
53354
53530
|
if (this.namedFormulaStack.has(key)) {
|
|
53355
53531
|
return FormulaError.REF;
|
|
53356
53532
|
}
|
|
53357
|
-
const
|
|
53533
|
+
const trimmed = text.trim();
|
|
53534
|
+
const source = (trimmed.startsWith("=") ? trimmed.slice(1) : trimmed).trim();
|
|
53358
53535
|
if (source.length === 0) {
|
|
53359
53536
|
return FormulaError.NAME;
|
|
53360
53537
|
}
|
|
@@ -53363,12 +53540,18 @@ class FormulaParser {
|
|
|
53363
53540
|
this.namedFormulaParser.namedFormulaStack = this.namedFormulaStack;
|
|
53364
53541
|
}
|
|
53365
53542
|
this.namedFormulaStack.add(key);
|
|
53543
|
+
// A name bound to a LAMBDA (`Dbl` = `=LAMBDA(x,x*2)`) is the lambda VALUE wherever the
|
|
53544
|
+
// name is read — `MAP(A1:A3,Dbl)`, `LET(f,Dbl,f(3))` — so the definition parser must hand
|
|
53545
|
+
// it back instead of turning it into `#CALC!`; the referencing cell's own
|
|
53546
|
+
// `checkFormulaResult` still makes a bare `=Dbl` `#CALC!` (spreadsheet#939 G-07).
|
|
53547
|
+
this.namedFormulaParser.keepLambdaResult = true;
|
|
53366
53548
|
try {
|
|
53367
53549
|
return this.namedFormulaParser.parse(source, this.position, true);
|
|
53368
53550
|
} catch (e) {
|
|
53369
53551
|
if (e instanceof FormulaError) return e;
|
|
53370
53552
|
return FormulaError.NAME;
|
|
53371
53553
|
} finally {
|
|
53554
|
+
this.namedFormulaParser.keepLambdaResult = false;
|
|
53372
53555
|
this.namedFormulaStack.delete(key);
|
|
53373
53556
|
}
|
|
53374
53557
|
}
|