UNPKG

hyperformula

Version:

HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas

293 lines (291 loc) 12.7 kB
"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 }] } };