hyperformula
Version:
HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas
293 lines (291 loc) • 12.7 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.LookupPlugin = void 0;
var _AbsoluteCellRange = require("../../AbsoluteCellRange");
var _Cell = require("../../Cell");
var _errorMessage = require("../../error-message");
var _RowSearchStrategy = require("../../Lookup/RowSearchStrategy");
var _statistics = require("../../statistics");
var _ArithmeticHelper = require("../ArithmeticHelper");
var _SimpleRangeValue = require("../../SimpleRangeValue");
var _FunctionPlugin = require("./FunctionPlugin");
var _ArraySize = require("../../ArraySize");
/**
* @license
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/
class LookupPlugin extends _FunctionPlugin.FunctionPlugin {
constructor() {
super(...arguments);
this.rowSearch = new _RowSearchStrategy.RowSearchStrategy(this.dependencyGraph);
}
/**
* Corresponds to VLOOKUP(key, range, index, [sorted])
*
* @param ast
* @param state
*/
vlookup(ast, state) {
return this.runFunction(ast.args, state, this.metadata('VLOOKUP'), (key, rangeValue, index, sorted) => {
const range = rangeValue.range;
if (range === undefined) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.WrongType);
}
if (index < 1) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.LessThanOne);
}
if (index > range.width()) {
return new _Cell.CellError(_Cell.ErrorType.REF, _errorMessage.ErrorMessage.IndexLarge);
}
const searchOptions = {
ordering: sorted ? 'asc' : 'none',
ifNoMatch: sorted ? 'returnLowerBound' : 'returnNotFound'
};
return this.doVlookup((0, _ArithmeticHelper.zeroIfEmpty)(key), rangeValue, index - 1, searchOptions);
});
}
/**
* Corresponds to HLOOKUP(key, range, index, [sorted])
*
* @param ast
* @param state
*/
hlookup(ast, state) {
return this.runFunction(ast.args, state, this.metadata('HLOOKUP'), (key, rangeValue, index, sorted) => {
const range = rangeValue.range;
if (range === undefined) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.WrongType);
}
if (index < 1) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.LessThanOne);
}
if (index > range.height()) {
return new _Cell.CellError(_Cell.ErrorType.REF, _errorMessage.ErrorMessage.IndexLarge);
}
const searchOptions = {
ordering: sorted ? 'asc' : 'none',
ifNoMatch: sorted ? 'returnLowerBound' : 'returnNotFound'
};
return this.doHlookup((0, _ArithmeticHelper.zeroIfEmpty)(key), rangeValue, index - 1, searchOptions);
});
}
/**
* Corresponds to XLOOKUP(lookup_value, lookup_array, return_array, [if_not_found], [match_mode], [search_mode])
*
* @param ast
* @param state
*/
xlookup(ast, state) {
return this.runFunction(ast.args, state, this.metadata('XLOOKUP'), (key, lookupRangeValue, returnRangeValue, notFoundFlag, matchMode, searchMode) => {
if (![0, -1, 1, 2].includes(matchMode)) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.BadMode);
}
if (![1, -1, 2, -2].includes(searchMode)) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.BadMode);
}
const lookupRange = lookupRangeValue instanceof _SimpleRangeValue.SimpleRangeValue ? lookupRangeValue : _SimpleRangeValue.SimpleRangeValue.fromScalar(lookupRangeValue);
const returnRange = returnRangeValue instanceof _SimpleRangeValue.SimpleRangeValue ? returnRangeValue : _SimpleRangeValue.SimpleRangeValue.fromScalar(returnRangeValue);
const isWildcardMatchMode = matchMode === 2;
const searchOptions = {
ordering: searchMode === 2 ? 'asc' : searchMode === -2 ? 'desc' : 'none',
returnOccurrence: searchMode === -1 ? 'last' : 'first',
ifNoMatch: matchMode === -1 ? 'returnLowerBound' : matchMode === 1 ? 'returnUpperBound' : 'returnNotFound'
};
return this.doXlookup((0, _ArithmeticHelper.zeroIfEmpty)(key), lookupRange, returnRange, notFoundFlag, isWildcardMatchMode, searchOptions);
});
}
xlookupArraySize(ast) {
var _a, _b;
const lookupRange = (_a = ast === null || ast === void 0 ? void 0 : ast.args) === null || _a === void 0 ? void 0 : _a[1];
const returnRange = (_b = ast === null || ast === void 0 ? void 0 : ast.args) === null || _b === void 0 ? void 0 : _b[2];
if ((lookupRange === null || lookupRange === void 0 ? void 0 : lookupRange.start) == null || (lookupRange === null || lookupRange === void 0 ? void 0 : lookupRange.end) == null || (returnRange === null || returnRange === void 0 ? void 0 : returnRange.start) == null || (returnRange === null || returnRange === void 0 ? void 0 : returnRange.end) == null) {
return _ArraySize.ArraySize.error();
}
const lookupRangeHeight = lookupRange.end.row - lookupRange.start.row + 1;
const lookupRangeWidth = lookupRange.end.col - lookupRange.start.col + 1;
const returnRangeHeight = returnRange.end.row - returnRange.start.row + 1;
const returnRangeWidth = returnRange.end.col - returnRange.start.col + 1;
const isVerticalSearch = lookupRangeWidth === 1 && returnRangeHeight === lookupRangeHeight;
const isHorizontalSearch = lookupRangeHeight === 1 && returnRangeWidth === lookupRangeWidth;
if (!isVerticalSearch && !isHorizontalSearch) {
return _ArraySize.ArraySize.error();
}
if (isVerticalSearch) {
return new _ArraySize.ArraySize(returnRangeWidth, 1);
}
return new _ArraySize.ArraySize(1, returnRangeHeight);
}
match(ast, state) {
return this.runFunction(ast.args, state, this.metadata('MATCH'), (key, rangeValue, type) => {
return this.doMatch((0, _ArithmeticHelper.zeroIfEmpty)(key), rangeValue, type);
});
}
searchInRange(key, range, isWildcardMatchMode, searchOptions, searchStrategy) {
if (isWildcardMatchMode && typeof key === 'string' && this.arithmeticHelper.requiresRegex(key)) {
return searchStrategy.advancedFind(this.arithmeticHelper.eqMatcherFunction(key), range, {
returnOccurrence: searchOptions.returnOccurrence
});
}
return searchStrategy.find(key, range, searchOptions);
}
doVlookup(key, rangeValue, index, searchOptions) {
this.dependencyGraph.stats.start(_statistics.StatType.VLOOKUP);
const range = rangeValue.range;
let searchedRange;
if (range === undefined) {
searchedRange = _SimpleRangeValue.SimpleRangeValue.onlyValues(rangeValue.data.map(arg => [arg[0]]));
} else {
searchedRange = _SimpleRangeValue.SimpleRangeValue.onlyRange(_AbsoluteCellRange.AbsoluteCellRange.spanFrom(range.start, 1, range.height()), this.dependencyGraph);
}
const rowIndex = this.searchInRange(key, searchedRange, searchOptions.ordering === 'none', searchOptions, this.columnSearch);
this.dependencyGraph.stats.end(_statistics.StatType.VLOOKUP);
if (rowIndex === -1) {
return new _Cell.CellError(_Cell.ErrorType.NA, _errorMessage.ErrorMessage.ValueNotFound);
}
let value;
if (range === undefined) {
value = rangeValue.data[rowIndex][index];
} else {
const address = (0, _Cell.simpleCellAddress)(range.sheet, range.start.col + index, range.start.row + rowIndex);
value = this.dependencyGraph.getCellValue(address);
}
if (value instanceof _SimpleRangeValue.SimpleRangeValue) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.WrongType);
}
return value;
}
doHlookup(key, rangeValue, index, searchOptions) {
const range = rangeValue.range;
let searchedRange;
if (range === undefined) {
searchedRange = _SimpleRangeValue.SimpleRangeValue.onlyValues([rangeValue.data[0]]);
} else {
searchedRange = _SimpleRangeValue.SimpleRangeValue.onlyRange(_AbsoluteCellRange.AbsoluteCellRange.spanFrom(range.start, range.width(), 1), this.dependencyGraph);
}
const colIndex = this.searchInRange(key, searchedRange, searchOptions.ordering === 'none', searchOptions, this.rowSearch);
if (colIndex === -1) {
return new _Cell.CellError(_Cell.ErrorType.NA, _errorMessage.ErrorMessage.ValueNotFound);
}
let value;
if (range === undefined) {
value = rangeValue.data[index][colIndex];
} else {
const address = (0, _Cell.simpleCellAddress)(range.sheet, range.start.col + colIndex, range.start.row + index);
value = this.dependencyGraph.getCellValue(address);
}
if (value instanceof _SimpleRangeValue.SimpleRangeValue) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.WrongType);
}
return value;
}
doXlookup(key, lookupRange, returnRange, notFoundFlag, isWildcardMatchMode, searchOptions) {
const isVerticalSearch = lookupRange.width() === 1 && returnRange.height() === lookupRange.height();
const isHorizontalSearch = lookupRange.height() === 1 && returnRange.width() === lookupRange.width();
if (!isVerticalSearch && !isHorizontalSearch) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.WrongDimension);
}
const searchStrategy = isVerticalSearch ? this.columnSearch : this.rowSearch;
const indexFound = this.searchInRange(key, lookupRange, isWildcardMatchMode, searchOptions, searchStrategy);
if (indexFound === -1) {
return notFoundFlag == _Cell.ErrorType.NA ? new _Cell.CellError(_Cell.ErrorType.NA, _errorMessage.ErrorMessage.ValueNotFound) : notFoundFlag;
}
const returnValues = isVerticalSearch ? [returnRange.data[indexFound]] : returnRange.data.map(row => [row[indexFound]]);
return _SimpleRangeValue.SimpleRangeValue.onlyValues(returnValues);
}
doMatch(key, rangeValue, type) {
if (![-1, 0, 1].includes(type)) {
return new _Cell.CellError(_Cell.ErrorType.VALUE, _errorMessage.ErrorMessage.BadMode);
}
if (rangeValue.width() > 1 && rangeValue.height() > 1) {
return new _Cell.CellError(_Cell.ErrorType.NA);
}
const searchStrategy = rangeValue.width() === 1 ? this.columnSearch : this.rowSearch;
const searchOptions = type === 0 ? {
ordering: 'none',
ifNoMatch: 'returnNotFound'
} : {
ordering: type === -1 ? 'desc' : 'asc',
ifNoMatch: type === -1 ? 'returnUpperBound' : 'returnLowerBound'
};
const index = searchStrategy.find(key, rangeValue, searchOptions);
if (index === -1) {
return new _Cell.CellError(_Cell.ErrorType.NA, _errorMessage.ErrorMessage.ValueNotFound);
}
return index + 1;
}
}
exports.LookupPlugin = LookupPlugin;
LookupPlugin.implementedFunctions = {
'VLOOKUP': {
method: 'vlookup',
parameters: [{
argumentType: _FunctionPlugin.FunctionArgumentType.NOERROR
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.RANGE
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.NUMBER
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.BOOLEAN,
defaultValue: true
}]
},
'HLOOKUP': {
method: 'hlookup',
parameters: [{
argumentType: _FunctionPlugin.FunctionArgumentType.NOERROR
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.RANGE
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.NUMBER
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.BOOLEAN,
defaultValue: true
}]
},
'XLOOKUP': {
method: 'xlookup',
arraySizeMethod: 'xlookupArraySize',
parameters: [
// lookup_value
{
argumentType: _FunctionPlugin.FunctionArgumentType.NOERROR
},
// lookup_array
{
argumentType: _FunctionPlugin.FunctionArgumentType.RANGE
},
// return_array
{
argumentType: _FunctionPlugin.FunctionArgumentType.RANGE
},
// [if_not_found]
{
argumentType: _FunctionPlugin.FunctionArgumentType.SCALAR,
optionalArg: true,
defaultValue: _Cell.ErrorType.NA
},
// [match_mode]
{
argumentType: _FunctionPlugin.FunctionArgumentType.NUMBER,
optionalArg: true,
defaultValue: 0
},
// [search_mode]
{
argumentType: _FunctionPlugin.FunctionArgumentType.NUMBER,
optionalArg: true,
defaultValue: 1
}]
},
'MATCH': {
method: 'match',
parameters: [{
argumentType: _FunctionPlugin.FunctionArgumentType.NOERROR
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.RANGE
}, {
argumentType: _FunctionPlugin.FunctionArgumentType.NUMBER,
defaultValue: 1
}]
}
};