@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/esm/index.js
CHANGED
|
@@ -5645,6 +5645,7 @@ const FORMULA_LOCALE_DATA = {
|
|
|
5645
5645
|
"ISNUMBER": "ESTNUM",
|
|
5646
5646
|
"ISO.CEILING": "ISO.PLAFOND",
|
|
5647
5647
|
"ISODD": "EST.IMPAIR",
|
|
5648
|
+
"ISOMITTED": "ISOMITTED",
|
|
5648
5649
|
"ISOWEEKNUM": "NO.SEMAINE.ISO",
|
|
5649
5650
|
"ISPMT": "ISPMT",
|
|
5650
5651
|
"ISREF": "ESTREF",
|
|
@@ -10949,6 +10950,66 @@ const Criteria = Object.freeze({
|
|
|
10949
10950
|
return false;
|
|
10950
10951
|
}
|
|
10951
10952
|
},
|
|
10953
|
+
|
|
10954
|
+
/**
|
|
10955
|
+
* Excel's array lift for the criteria slot of the criteria aggregates. When
|
|
10956
|
+
* any accepted criterion value is a multi-cell array or range
|
|
10957
|
+
* (`SUMIF(rng,{"x","y"},sum)`, `SUMIFS(sum,rng,"x",rng2,{">1",">3"})`,
|
|
10958
|
+
* `COUNTIF(rng,rng)`), `evaluate` runs once per broadcast element with that
|
|
10959
|
+
* element parsed as a scalar criterion and the results come back as a 2-D
|
|
10960
|
+
* array — so `SUM(COUNTIF(rng,{…}))` / `SUMPRODUCT(SUMIF(…,{…}))` fold it and a
|
|
10961
|
+
* per-element error (`#DIV/0!` from an empty AVERAGEIF slot) stays in its
|
|
10962
|
+
* slot. Vectors broadcast along a unit dimension; a slot a shorter vector
|
|
10963
|
+
* does not reach is `#N/A`. A 1×1 array is a scalar. With no array criterion
|
|
10964
|
+
* `evaluate` runs exactly once with the parsed scalar criteria.
|
|
10965
|
+
* @param {Array} criteriaValues accepted (unparsed) criterion values
|
|
10966
|
+
* @param {(parsed: Array) => *} evaluate
|
|
10967
|
+
*/
|
|
10968
|
+
lift: (criteriaValues, evaluate) => {
|
|
10969
|
+
const shapes = criteriaValues.map((v) => {
|
|
10970
|
+
if (!Array.isArray(v)) return null;
|
|
10971
|
+
const shape = [v.length, v[0]?.length ?? 0];
|
|
10972
|
+
return shape[0] === 1 && shape[1] === 1 ? null : shape;
|
|
10973
|
+
});
|
|
10974
|
+
const scalarOf = (v) => (Array.isArray(v) ? v[0][0] : v);
|
|
10975
|
+
if (shapes.every((shape) => shape === null)) {
|
|
10976
|
+
return evaluate(criteriaValues.map((v) => Criteria.parse(scalarOf(v))));
|
|
10977
|
+
}
|
|
10978
|
+
let rows = 1;
|
|
10979
|
+
let cols = 1;
|
|
10980
|
+
for (const shape of shapes) {
|
|
10981
|
+
if (!shape) continue;
|
|
10982
|
+
if (shape[0] !== 1) rows = rows === 1 ? shape[0] : Math.max(rows, shape[0]);
|
|
10983
|
+
if (shape[1] !== 1) cols = cols === 1 ? shape[1] : Math.max(cols, shape[1]);
|
|
10984
|
+
}
|
|
10985
|
+
const MISSING = Symbol("missing");
|
|
10986
|
+
const pick = (v, shape, i, j) => {
|
|
10987
|
+
const r = shape[0] === 1 ? 0 : i;
|
|
10988
|
+
const c = shape[1] === 1 ? 0 : j;
|
|
10989
|
+
return r < v.length && c < v[r].length ? v[r][c] : MISSING;
|
|
10990
|
+
};
|
|
10991
|
+
const out = [];
|
|
10992
|
+
for (let i = 0; i < rows; i++) {
|
|
10993
|
+
const row = [];
|
|
10994
|
+
for (let j = 0; j < cols; j++) {
|
|
10995
|
+
const elements = criteriaValues.map((v, k) =>
|
|
10996
|
+
shapes[k] ? pick(v, shapes[k], i, j) : scalarOf(v),
|
|
10997
|
+
);
|
|
10998
|
+
if (elements.includes(MISSING)) {
|
|
10999
|
+
row.push(FormulaError.NA);
|
|
11000
|
+
continue;
|
|
11001
|
+
}
|
|
11002
|
+
try {
|
|
11003
|
+
row.push(evaluate(elements.map((e) => Criteria.parse(e))));
|
|
11004
|
+
} catch (e) {
|
|
11005
|
+
if (e instanceof FormulaError) row.push(e);
|
|
11006
|
+
else throw e;
|
|
11007
|
+
}
|
|
11008
|
+
}
|
|
11009
|
+
out.push(row);
|
|
11010
|
+
}
|
|
11011
|
+
return out;
|
|
11012
|
+
},
|
|
10952
11013
|
});
|
|
10953
11014
|
|
|
10954
11015
|
const Address = {
|
|
@@ -20674,23 +20735,26 @@ const StatisticalFunctions = {
|
|
|
20674
20735
|
range = ranges[0];
|
|
20675
20736
|
averageRange = ranges[1];
|
|
20676
20737
|
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20738
|
+
const criteriaValue = H$7.accept(H$7.retrieveArg(context, criteria));
|
|
20739
|
+
|
|
20740
|
+
const averageMatching = (parsed) => {
|
|
20741
|
+
let sum = 0,
|
|
20742
|
+
cnt = 0;
|
|
20743
|
+
range.forEach((row, rowNum) => {
|
|
20744
|
+
row.forEach((value, colNum) => {
|
|
20745
|
+
const valueToAdd = averageRange[rowNum][colNum];
|
|
20746
|
+
throwIfFormulaError(valueToAdd);
|
|
20747
|
+
if (typeof valueToAdd !== "number") return;
|
|
20748
|
+
if (Criteria.matches(parsed, value)) {
|
|
20749
|
+
sum += valueToAdd;
|
|
20750
|
+
cnt++;
|
|
20751
|
+
}
|
|
20752
|
+
});
|
|
20690
20753
|
});
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20754
|
+
if (cnt === 0) throw FormulaError.DIV0;
|
|
20755
|
+
return sum / cnt;
|
|
20756
|
+
};
|
|
20757
|
+
return Criteria.lift([criteriaValue], ([parsed]) => averageMatching(parsed));
|
|
20694
20758
|
},
|
|
20695
20759
|
|
|
20696
20760
|
/**
|
|
@@ -20720,36 +20784,42 @@ const StatisticalFunctions = {
|
|
|
20720
20784
|
assertSameShape(averageRange, criteriaRange);
|
|
20721
20785
|
criteriaPairs.push({
|
|
20722
20786
|
range: criteriaRange,
|
|
20723
|
-
criteria:
|
|
20787
|
+
criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
|
|
20724
20788
|
});
|
|
20725
20789
|
}
|
|
20726
20790
|
|
|
20727
|
-
|
|
20728
|
-
|
|
20791
|
+
const averageMatching = (parsedCriteria) => {
|
|
20792
|
+
let sum = 0;
|
|
20793
|
+
let count = 0;
|
|
20729
20794
|
|
|
20730
|
-
|
|
20731
|
-
|
|
20732
|
-
|
|
20733
|
-
|
|
20734
|
-
|
|
20735
|
-
|
|
20795
|
+
averageRange.forEach((row, rowNum) => {
|
|
20796
|
+
row.forEach((valueToAdd, colNum) => {
|
|
20797
|
+
throwIfFormulaError(valueToAdd);
|
|
20798
|
+
if (typeof valueToAdd !== "number") {
|
|
20799
|
+
return;
|
|
20800
|
+
}
|
|
20736
20801
|
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20802
|
+
const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
|
|
20803
|
+
Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
|
|
20804
|
+
);
|
|
20740
20805
|
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
|
|
20744
|
-
|
|
20806
|
+
if (meetsAllCriteria) {
|
|
20807
|
+
sum += valueToAdd;
|
|
20808
|
+
count++;
|
|
20809
|
+
}
|
|
20810
|
+
});
|
|
20745
20811
|
});
|
|
20746
|
-
});
|
|
20747
20812
|
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
20813
|
+
if (count === 0) {
|
|
20814
|
+
throw FormulaError.DIV0;
|
|
20815
|
+
}
|
|
20751
20816
|
|
|
20752
|
-
|
|
20817
|
+
return sum / count;
|
|
20818
|
+
};
|
|
20819
|
+
return Criteria.lift(
|
|
20820
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
20821
|
+
averageMatching,
|
|
20822
|
+
);
|
|
20753
20823
|
},
|
|
20754
20824
|
|
|
20755
20825
|
LARGE: (array, kth) => {
|
|
@@ -20821,34 +20891,40 @@ const StatisticalFunctions = {
|
|
|
20821
20891
|
assertSameShape(maxRange, criteriaRange);
|
|
20822
20892
|
criteriaPairs.push({
|
|
20823
20893
|
range: criteriaRange,
|
|
20824
|
-
criteria:
|
|
20894
|
+
criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
|
|
20825
20895
|
});
|
|
20826
20896
|
}
|
|
20827
20897
|
|
|
20828
|
-
|
|
20829
|
-
|
|
20898
|
+
const maxMatching = (parsedCriteria) => {
|
|
20899
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
20900
|
+
let matchFound = false;
|
|
20830
20901
|
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
20902
|
+
maxRange.forEach((row, rowNum) => {
|
|
20903
|
+
row.forEach((valueToAdd, colNum) => {
|
|
20904
|
+
throwIfFormulaError(valueToAdd);
|
|
20905
|
+
if (typeof valueToAdd !== "number") {
|
|
20906
|
+
return;
|
|
20907
|
+
}
|
|
20837
20908
|
|
|
20838
|
-
|
|
20839
|
-
|
|
20840
|
-
|
|
20909
|
+
const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
|
|
20910
|
+
Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
|
|
20911
|
+
);
|
|
20841
20912
|
|
|
20842
|
-
|
|
20843
|
-
|
|
20844
|
-
|
|
20913
|
+
if (meetsAllCriteria) {
|
|
20914
|
+
if (valueToAdd > max) {
|
|
20915
|
+
max = valueToAdd;
|
|
20916
|
+
}
|
|
20917
|
+
matchFound = true;
|
|
20845
20918
|
}
|
|
20846
|
-
|
|
20847
|
-
}
|
|
20919
|
+
});
|
|
20848
20920
|
});
|
|
20849
|
-
});
|
|
20850
20921
|
|
|
20851
|
-
|
|
20922
|
+
return matchFound ? max : 0;
|
|
20923
|
+
};
|
|
20924
|
+
return Criteria.lift(
|
|
20925
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
20926
|
+
maxMatching,
|
|
20927
|
+
);
|
|
20852
20928
|
},
|
|
20853
20929
|
|
|
20854
20930
|
MINA: (...params) => {
|
|
@@ -20899,34 +20975,40 @@ const StatisticalFunctions = {
|
|
|
20899
20975
|
assertSameShape(minRange, criteriaRange);
|
|
20900
20976
|
criteriaPairs.push({
|
|
20901
20977
|
range: criteriaRange,
|
|
20902
|
-
criteria:
|
|
20978
|
+
criteria: H$7.accept(H$7.retrieveArg(context, args[i + 1])),
|
|
20903
20979
|
});
|
|
20904
20980
|
}
|
|
20905
20981
|
|
|
20906
|
-
|
|
20907
|
-
|
|
20982
|
+
const minMatching = (parsedCriteria) => {
|
|
20983
|
+
let min = Number.POSITIVE_INFINITY;
|
|
20984
|
+
let matchFound = false;
|
|
20908
20985
|
|
|
20909
|
-
|
|
20910
|
-
|
|
20911
|
-
|
|
20912
|
-
|
|
20913
|
-
|
|
20914
|
-
|
|
20986
|
+
minRange.forEach((row, rowNum) => {
|
|
20987
|
+
row.forEach((valueToAdd, colNum) => {
|
|
20988
|
+
throwIfFormulaError(valueToAdd);
|
|
20989
|
+
if (typeof valueToAdd !== "number") {
|
|
20990
|
+
return;
|
|
20991
|
+
}
|
|
20915
20992
|
|
|
20916
|
-
|
|
20917
|
-
|
|
20918
|
-
|
|
20993
|
+
const meetsAllCriteria = criteriaPairs.every(({ range }, k) =>
|
|
20994
|
+
Criteria.matches(parsedCriteria[k], range[rowNum][colNum]),
|
|
20995
|
+
);
|
|
20919
20996
|
|
|
20920
|
-
|
|
20921
|
-
|
|
20922
|
-
|
|
20997
|
+
if (meetsAllCriteria) {
|
|
20998
|
+
if (valueToAdd < min) {
|
|
20999
|
+
min = valueToAdd;
|
|
21000
|
+
}
|
|
21001
|
+
matchFound = true;
|
|
20923
21002
|
}
|
|
20924
|
-
|
|
20925
|
-
}
|
|
21003
|
+
});
|
|
20926
21004
|
});
|
|
20927
|
-
});
|
|
20928
21005
|
|
|
20929
|
-
|
|
21006
|
+
return matchFound ? min : 0;
|
|
21007
|
+
};
|
|
21008
|
+
return Criteria.lift(
|
|
21009
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
21010
|
+
minMatching,
|
|
21011
|
+
);
|
|
20930
21012
|
},
|
|
20931
21013
|
|
|
20932
21014
|
NPV: (rate, ...values) => {
|
|
@@ -23227,26 +23309,30 @@ const MathFunctions = {
|
|
|
23227
23309
|
range = ranges[0];
|
|
23228
23310
|
sumRange = ranges[1];
|
|
23229
23311
|
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23312
|
+
const criteriaValue = H$6.accept(H$6.retrieveArg(context, criteria));
|
|
23313
|
+
|
|
23314
|
+
const sumMatching = (parsed) => {
|
|
23315
|
+
let sum = 0;
|
|
23316
|
+
const rowCount = range.length;
|
|
23317
|
+
for (let r = 0; r < rowCount; r++) {
|
|
23318
|
+
const row = range[r];
|
|
23319
|
+
const sumRow = sumRange[r];
|
|
23320
|
+
const colCount = row.length;
|
|
23321
|
+
for (let c = 0; c < colCount; c++) {
|
|
23322
|
+
const valueToAdd = sumRow[c];
|
|
23323
|
+
if (valueToAdd instanceof FormulaError) {
|
|
23324
|
+
throw valueToAdd;
|
|
23325
|
+
}
|
|
23326
|
+
if (typeof valueToAdd !== "number") continue;
|
|
23327
|
+
if (Criteria.matches(parsed, row[c])) {
|
|
23328
|
+
sum += valueToAdd;
|
|
23329
|
+
}
|
|
23246
23330
|
}
|
|
23247
23331
|
}
|
|
23248
|
-
|
|
23249
|
-
|
|
23332
|
+
return sum;
|
|
23333
|
+
};
|
|
23334
|
+
// An array criterion spills one total per element (SUMPRODUCT(SUMIF(rng,{…},sum))).
|
|
23335
|
+
return Criteria.lift([criteriaValue], ([parsed]) => sumMatching(parsed));
|
|
23250
23336
|
},
|
|
23251
23337
|
|
|
23252
23338
|
/**
|
|
@@ -23281,37 +23367,43 @@ const MathFunctions = {
|
|
|
23281
23367
|
}
|
|
23282
23368
|
criteriaPairs.push({
|
|
23283
23369
|
range: criteriaRange,
|
|
23284
|
-
criteria:
|
|
23370
|
+
criteria: H$6.accept(H$6.retrieveArg(context, args[i + 1])),
|
|
23285
23371
|
});
|
|
23286
23372
|
}
|
|
23287
23373
|
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23291
|
-
|
|
23292
|
-
|
|
23293
|
-
|
|
23294
|
-
|
|
23295
|
-
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
|
|
23303
|
-
|
|
23304
|
-
|
|
23305
|
-
|
|
23306
|
-
|
|
23374
|
+
const sumMatching = (parsedCriteria) => {
|
|
23375
|
+
let sum = 0;
|
|
23376
|
+
const rowCount = sumRange.length;
|
|
23377
|
+
for (let r = 0; r < rowCount; r++) {
|
|
23378
|
+
const row = sumRange[r];
|
|
23379
|
+
const colCount = row.length;
|
|
23380
|
+
for (let c = 0; c < colCount; c++) {
|
|
23381
|
+
const valueToAdd = row[c];
|
|
23382
|
+
if (valueToAdd instanceof FormulaError) {
|
|
23383
|
+
throw valueToAdd;
|
|
23384
|
+
}
|
|
23385
|
+
if (typeof valueToAdd !== "number") {
|
|
23386
|
+
continue;
|
|
23387
|
+
}
|
|
23388
|
+
let allCriteriaMet = true;
|
|
23389
|
+
for (let i = 0; i < criteriaPairs.length; i++) {
|
|
23390
|
+
if (!Criteria.matches(parsedCriteria[i], criteriaPairs[i].range[r][c])) {
|
|
23391
|
+
allCriteriaMet = false;
|
|
23392
|
+
break;
|
|
23393
|
+
}
|
|
23394
|
+
}
|
|
23395
|
+
if (allCriteriaMet) {
|
|
23396
|
+
sum += valueToAdd;
|
|
23307
23397
|
}
|
|
23308
|
-
}
|
|
23309
|
-
if (allCriteriaMet) {
|
|
23310
|
-
sum += valueToAdd;
|
|
23311
23398
|
}
|
|
23312
23399
|
}
|
|
23313
|
-
|
|
23314
|
-
|
|
23400
|
+
return sum;
|
|
23401
|
+
};
|
|
23402
|
+
// Array criteria spill one total per broadcast element.
|
|
23403
|
+
return Criteria.lift(
|
|
23404
|
+
criteriaPairs.map((pair) => pair.criteria),
|
|
23405
|
+
sumMatching,
|
|
23406
|
+
);
|
|
23315
23407
|
},
|
|
23316
23408
|
|
|
23317
23409
|
SUMPRODUCT: (array1, ...arrays) => {
|
|
@@ -23904,7 +23996,23 @@ const LogicalFunctions = {
|
|
|
23904
23996
|
// never raw `undefined` (misreported as "Function IFNA is not
|
|
23905
23997
|
// implemented.").
|
|
23906
23998
|
const blankToZero = (v) => (v === undefined || v === null ? 0 : v);
|
|
23907
|
-
|
|
23999
|
+
const isNa = (cell) => cell instanceof FormulaError && FormulaError.NA.equals(cell);
|
|
24000
|
+
|
|
24001
|
+
// Excel lifts IFNA element-wise over an array / multi-cell range value, like IFERROR:
|
|
24002
|
+
// only the #N/A elements take the fallback, every other value or error passes through
|
|
24003
|
+
// (`IFNA(K1:K3,0)` → {#DIV/0!;0;0}, `SUM(IFNA(rng,0))` sums the non-#N/A cells).
|
|
24004
|
+
const hasValueProp = value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, 'value');
|
|
24005
|
+
if (hasValueProp && value.isArray) {
|
|
24006
|
+
const arrayValue = value.value;
|
|
24007
|
+
const handleCell = (cell) => (isNa(cell) ? blankToZero(H$4.accept(valueIfNa)) : blankToZero(cell));
|
|
24008
|
+
if (Array.isArray(arrayValue)) {
|
|
24009
|
+
return arrayValue.map((row) => (Array.isArray(row) ? row.map(handleCell) : handleCell(row)));
|
|
24010
|
+
}
|
|
24011
|
+
return handleCell(arrayValue);
|
|
24012
|
+
}
|
|
24013
|
+
|
|
24014
|
+
const actualValue = hasValueProp ? value.value : value;
|
|
24015
|
+
return isNa(actualValue)
|
|
23908
24016
|
? blankToZero(H$4.accept(valueIfNa))
|
|
23909
24017
|
: blankToZero(H$4.accept(value));
|
|
23910
24018
|
},
|
|
@@ -25583,6 +25691,55 @@ const lookupEquals = (a, b) =>
|
|
|
25583
25691
|
? a.toLowerCase() === b.toLowerCase()
|
|
25584
25692
|
: a === b;
|
|
25585
25693
|
|
|
25694
|
+
/**
|
|
25695
|
+
* The sheet range a lookup table argument was read from, or undefined when the table is an
|
|
25696
|
+
* array (an array constant, a spilled range or another function's result). The matrix the
|
|
25697
|
+
* host returned must fit inside that range — it is a window starting at `from` (a full-column
|
|
25698
|
+
* reference is trimmed to the data extent) — or no cell can be derived from a matrix index.
|
|
25699
|
+
*/
|
|
25700
|
+
const rangeOf = (arg, matrix) => {
|
|
25701
|
+
const ref = arg?.position;
|
|
25702
|
+
if (!ref || ref.isSpilledRange || ref.sheetEnd != null) return undefined;
|
|
25703
|
+
let range;
|
|
25704
|
+
if (ref.from && ref.to) {
|
|
25705
|
+
range = ref;
|
|
25706
|
+
} else if (ref.row != null && ref.col != null) {
|
|
25707
|
+
range = {
|
|
25708
|
+
sheet: ref.sheet,
|
|
25709
|
+
from: { row: ref.row, col: ref.col },
|
|
25710
|
+
to: { row: ref.row, col: ref.col },
|
|
25711
|
+
};
|
|
25712
|
+
} else {
|
|
25713
|
+
return undefined;
|
|
25714
|
+
}
|
|
25715
|
+
const fits =
|
|
25716
|
+
matrix.length <= range.to.row - range.from.row + 1 &&
|
|
25717
|
+
(matrix[0]?.length ?? 0) <= range.to.col - range.from.col + 1;
|
|
25718
|
+
return fits ? range : undefined;
|
|
25719
|
+
};
|
|
25720
|
+
|
|
25721
|
+
/**
|
|
25722
|
+
* The cell VLOOKUP/HLOOKUP found. Over a sheet range it is the cell's REFERENCE (as INDEX returns
|
|
25723
|
+
* one), so the result carries `=B2` semantics by construction: a blank cell displays 0, is 0 in
|
|
25724
|
+
* arithmetic and comparison, "" in text — `=VLOOKUP(…)+0` is 0, `=VLOOKUP(…)=""` is TRUE,
|
|
25725
|
+
* `=VLOOKUP(…)&"x"` is "x". The engine has no blank scalar to return instead: the dispatcher reads
|
|
25726
|
+
* a returned `undefined` as "function not implemented" (`#NAME?`) and `null` is the skipped-argument
|
|
25727
|
+
* marker. Over an array there is no cell to point at, so a blank slot coerces to 0 as blank does in
|
|
25728
|
+
* arithmetic. A cell holding `=""` is text, not blank, and comes back as "" either way.
|
|
25729
|
+
*/
|
|
25730
|
+
const matchedCell = (tableRange, tableArray, rowIdx, colIdx) => {
|
|
25731
|
+
if (tableRange) {
|
|
25732
|
+
return {
|
|
25733
|
+
ref: {
|
|
25734
|
+
sheet: tableRange.sheet,
|
|
25735
|
+
row: tableRange.from.row + rowIdx,
|
|
25736
|
+
col: tableRange.from.col + colIdx,
|
|
25737
|
+
},
|
|
25738
|
+
};
|
|
25739
|
+
}
|
|
25740
|
+
return tableArray[rowIdx][colIdx] ?? 0;
|
|
25741
|
+
};
|
|
25742
|
+
|
|
25586
25743
|
/**
|
|
25587
25744
|
* Exact-mode match for VLOOKUP/HLOOKUP/XLOOKUP/XMATCH: wildcards when the lookup text has
|
|
25588
25745
|
* them, otherwise case-insensitive equality.
|
|
@@ -25849,6 +26006,7 @@ const ReferenceFunctions = {
|
|
|
25849
26006
|
HLOOKUP: (lookupValue, tableArray, rowIndexNum, rangeLookup) => {
|
|
25850
26007
|
// preserve type of lookupValue
|
|
25851
26008
|
lookupValue = H$2.accept(lookupValue);
|
|
26009
|
+
const tableArg = tableArray;
|
|
25852
26010
|
try {
|
|
25853
26011
|
tableArray = H$2.accept(tableArray, Types.ARRAY, undefined, false);
|
|
25854
26012
|
} catch (e) {
|
|
@@ -25860,6 +26018,7 @@ const ReferenceFunctions = {
|
|
|
25860
26018
|
}
|
|
25861
26019
|
throw e;
|
|
25862
26020
|
}
|
|
26021
|
+
const tableRange = rangeOf(tableArg, tableArray);
|
|
25863
26022
|
rowIndexNum = H$2.accept(rowIndexNum, Types.NUMBER);
|
|
25864
26023
|
rangeLookup = H$2.accept(rangeLookup, Types.BOOLEAN, true);
|
|
25865
26024
|
|
|
@@ -25872,7 +26031,7 @@ const ReferenceFunctions = {
|
|
|
25872
26031
|
if (rangeLookup) {
|
|
25873
26032
|
const index = approximateMatchIndex(tableArray[0], lookupValue);
|
|
25874
26033
|
if (index === -1) throw FormulaError.NA;
|
|
25875
|
-
return tableArray
|
|
26034
|
+
return matchedCell(tableRange, tableArray, rowIndexNum - 1, index);
|
|
25876
26035
|
}
|
|
25877
26036
|
// exact lookup: case-insensitive, with wildcard support
|
|
25878
26037
|
else {
|
|
@@ -25881,7 +26040,7 @@ const ReferenceFunctions = {
|
|
|
25881
26040
|
);
|
|
25882
26041
|
// the exact match is not found
|
|
25883
26042
|
if (index === -1) throw FormulaError.NA;
|
|
25884
|
-
return tableArray
|
|
26043
|
+
return matchedCell(tableRange, tableArray, rowIndexNum - 1, index);
|
|
25885
26044
|
}
|
|
25886
26045
|
},
|
|
25887
26046
|
|
|
@@ -26385,6 +26544,7 @@ const ReferenceFunctions = {
|
|
|
26385
26544
|
VLOOKUP: (lookupValue, tableArray, colIndexNum, rangeLookup) => {
|
|
26386
26545
|
// preserve type of lookupValue
|
|
26387
26546
|
lookupValue = H$2.accept(lookupValue);
|
|
26547
|
+
const tableArg = tableArray;
|
|
26388
26548
|
try {
|
|
26389
26549
|
tableArray = H$2.accept(tableArray, Types.ARRAY, undefined, false);
|
|
26390
26550
|
} catch (e) {
|
|
@@ -26396,6 +26556,7 @@ const ReferenceFunctions = {
|
|
|
26396
26556
|
}
|
|
26397
26557
|
throw e;
|
|
26398
26558
|
}
|
|
26559
|
+
const tableRange = rangeOf(tableArg, tableArray);
|
|
26399
26560
|
colIndexNum = H$2.accept(colIndexNum, Types.NUMBER);
|
|
26400
26561
|
rangeLookup = H$2.accept(rangeLookup, Types.BOOLEAN, true);
|
|
26401
26562
|
|
|
@@ -26411,7 +26572,7 @@ const ReferenceFunctions = {
|
|
|
26411
26572
|
lookupValue,
|
|
26412
26573
|
);
|
|
26413
26574
|
if (index === -1) throw FormulaError.NA;
|
|
26414
|
-
return tableArray
|
|
26575
|
+
return matchedCell(tableRange, tableArray, index, colIndexNum - 1);
|
|
26415
26576
|
}
|
|
26416
26577
|
// exact lookup: case-insensitive, with wildcard support
|
|
26417
26578
|
else {
|
|
@@ -26420,7 +26581,7 @@ const ReferenceFunctions = {
|
|
|
26420
26581
|
);
|
|
26421
26582
|
// the exact match is not found
|
|
26422
26583
|
if (index === -1) throw FormulaError.NA;
|
|
26423
|
-
return tableArray
|
|
26584
|
+
return matchedCell(tableRange, tableArray, index, colIndexNum - 1);
|
|
26424
26585
|
}
|
|
26425
26586
|
},
|
|
26426
26587
|
|
|
@@ -50561,6 +50722,16 @@ class Parser extends EmbeddedActionsParser {
|
|
|
50561
50722
|
) {
|
|
50562
50723
|
return arr[0][0];
|
|
50563
50724
|
}
|
|
50725
|
+
// Excel refuses a ragged constant (`{1,2;3}`) at entry — every row must have the width
|
|
50726
|
+
// of the first. A parse-level `#ERROR!`, like any other syntax error the host stores as
|
|
50727
|
+
// the cell value (rowsncolumns/spreadsheet#939 G-02).
|
|
50728
|
+
const width = arr[0].length;
|
|
50729
|
+
const ragged = arr.findIndex((row) => row.length !== width);
|
|
50730
|
+
if (ragged !== -1) {
|
|
50731
|
+
throw FormulaError.ERROR(
|
|
50732
|
+
`Array constant rows must have the same length: row 1 has ${width} element(s), row ${ragged + 1} has ${arr[ragged].length}`
|
|
50733
|
+
);
|
|
50734
|
+
}
|
|
50564
50735
|
const array = this.utils.toArray(arr);
|
|
50565
50736
|
return context.async
|
|
50566
50737
|
? this.utils.settleArrayItems(array, $.position)
|
|
@@ -52980,8 +53151,13 @@ const normalizeNumberResult = (value, precision, epsilon, excelPrecision) => {
|
|
|
52980
53151
|
* @param {*} value
|
|
52981
53152
|
* @return {boolean}
|
|
52982
53153
|
*/
|
|
53154
|
+
// A name's definition text: `=…` (the OOXML `<definedName>` content), or a bare `LAMBDA(…)` —
|
|
53155
|
+
// the shape Excel's Name Manager and the host's "Text or Formula" names store a lambda in, which
|
|
53156
|
+
// the call path (`Dbl(21)`) already accepts without the leading `=` (spreadsheet#939 G-07).
|
|
53157
|
+
const BARE_LAMBDA_TEXT = /^(_xlfn\.)?LAMBDA\s*\(/i;
|
|
52983
53158
|
const isNamedFormulaText = (value) =>
|
|
52984
|
-
typeof value === "string" &&
|
|
53159
|
+
typeof value === "string" &&
|
|
53160
|
+
(value.trimStart().startsWith("=") || BARE_LAMBDA_TEXT.test(value.trimStart()));
|
|
52985
53161
|
|
|
52986
53162
|
class FormulaParser {
|
|
52987
53163
|
/**
|
|
@@ -53350,7 +53526,8 @@ class FormulaParser {
|
|
|
53350
53526
|
if (this.namedFormulaStack.has(key)) {
|
|
53351
53527
|
return FormulaError.REF;
|
|
53352
53528
|
}
|
|
53353
|
-
const
|
|
53529
|
+
const trimmed = text.trim();
|
|
53530
|
+
const source = (trimmed.startsWith("=") ? trimmed.slice(1) : trimmed).trim();
|
|
53354
53531
|
if (source.length === 0) {
|
|
53355
53532
|
return FormulaError.NAME;
|
|
53356
53533
|
}
|
|
@@ -53359,12 +53536,18 @@ class FormulaParser {
|
|
|
53359
53536
|
this.namedFormulaParser.namedFormulaStack = this.namedFormulaStack;
|
|
53360
53537
|
}
|
|
53361
53538
|
this.namedFormulaStack.add(key);
|
|
53539
|
+
// A name bound to a LAMBDA (`Dbl` = `=LAMBDA(x,x*2)`) is the lambda VALUE wherever the
|
|
53540
|
+
// name is read — `MAP(A1:A3,Dbl)`, `LET(f,Dbl,f(3))` — so the definition parser must hand
|
|
53541
|
+
// it back instead of turning it into `#CALC!`; the referencing cell's own
|
|
53542
|
+
// `checkFormulaResult` still makes a bare `=Dbl` `#CALC!` (spreadsheet#939 G-07).
|
|
53543
|
+
this.namedFormulaParser.keepLambdaResult = true;
|
|
53362
53544
|
try {
|
|
53363
53545
|
return this.namedFormulaParser.parse(source, this.position, true);
|
|
53364
53546
|
} catch (e) {
|
|
53365
53547
|
if (e instanceof FormulaError) return e;
|
|
53366
53548
|
return FormulaError.NAME;
|
|
53367
53549
|
} finally {
|
|
53550
|
+
this.namedFormulaParser.keepLambdaResult = false;
|
|
53368
53551
|
this.namedFormulaStack.delete(key);
|
|
53369
53552
|
}
|
|
53370
53553
|
}
|