/* * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ "use strict"; (/** * @param {Window} window * @param {undefined} undefined */ function (window, undefined) { var g_cCharDelimiter = AscCommon.g_cCharDelimiter; var parserHelp = AscCommon.parserHelp; var gc_nMaxRow0 = AscCommon.gc_nMaxRow0; var gc_nMaxCol0 = AscCommon.gc_nMaxCol0; var g_oCellAddressUtils = AscCommon.g_oCellAddressUtils; var CellAddress = AscCommon.CellAddress; var cElementType = AscCommonExcel.cElementType; var cElementTypeWeight = AscCommonExcel.cElementTypeWeight; var cErrorType = AscCommonExcel.cErrorType; var cNumber = AscCommonExcel.cNumber; var cString = AscCommonExcel.cString; var cBool = AscCommonExcel.cBool; var cError = AscCommonExcel.cError; var cArea = AscCommonExcel.cArea; var cArea3D = AscCommonExcel.cArea3D; var cRef = AscCommonExcel.cRef; var cRef3D = AscCommonExcel.cRef3D; var cName = AscCommonExcel.cName; var cName3D = AscCommonExcel.cName3D; var cEmpty = AscCommonExcel.cEmpty; var cArray = AscCommonExcel.cArray; var cBaseFunction = AscCommonExcel.cBaseFunction; var checkTypeCell = AscCommonExcel.checkTypeCell; var cFormulaFunctionGroup = AscCommonExcel.cFormulaFunctionGroup; var argType = Asc.c_oAscFormulaArgumentType; var _func = AscCommonExcel._func; cFormulaFunctionGroup['LookupAndReference'] = cFormulaFunctionGroup['LookupAndReference'] || []; cFormulaFunctionGroup['LookupAndReference'].push(cADDRESS, cAREAS, cCHOOSE, cCHOOSECOLS, cCHOOSEROWS, cCOLUMN, cCOLUMNS, cDROP, cEXPAND, cFILTER, cFORMULATEXT, cGETPIVOTDATA, cHLOOKUP, cHYPERLINK, cINDEX, cINDIRECT, cLOOKUP, cMATCH, cOFFSET, cROW, cROWS, cSORT, cSORTBY, cRTD, cTRANSPOSE, cTAKE, cUNIQUE, cVLOOKUP, cXLOOKUP, cVSTACK, cHSTACK, cTOROW, cTOCOL, cWRAPROWS, cWRAPCOLS, cXMATCH); cFormulaFunctionGroup['NotRealised'] = cFormulaFunctionGroup['NotRealised'] || []; cFormulaFunctionGroup['NotRealised'].push(cRTD); function searchRegExp(str, flags) { var vFS = str .replace(/(\\)/g, "\\") .replace(/(\^)/g, "\\^") .replace(/(\()/g, "\\(") .replace(/(\))/g, "\\)") .replace(/(\+)/g, "\\+") .replace(/(\[)/g, "\\[") .replace(/(\])/g, "\\]") .replace(/(\{)/g, "\\{") .replace(/(\})/g, "\\}") .replace(/(\$)/g, "\\$") .replace(/(~)?\*/g, function ($0, $1) { return $1 ? $0 : '(.*)'; }) .replace(/(~)?\?/g, function ($0, $1) { return $1 ? $0 : '.{1}'; }) .replace(/(~\*)/g, "\\*").replace(/(~\?)/g, "\\?"); return new RegExp(vFS + "$", flags ? flags : "i"); } // TODO переделать поиск: добиться такого же результата как в ms function XBinarySearch (target, array, match_mode, isReverse) { let mid, item; let index = -1; let leftPointer = 0, rightPointer = array.length - 1; function bidirectionalSearch (secondIteration, isDescending) { while(leftPointer <= rightPointer) { mid = Math.floor((leftPointer + rightPointer) / 2); item = undefined !== array[mid].v ? array[mid].v : array[mid]; if(-1 === match_mode || 0 === match_mode || 1 === match_mode) { if (leftPointer === 0 && rightPointer === array.length - 1 && !secondIteration) { if ((isDescending && target >= item) || (!isDescending && target <= item)) { rightPointer = mid - 1; } else { leftPointer = mid + 1; } } else if(!secondIteration) { if (target == item) { index = mid; break; } else if ((isDescending && target > item) || (!isDescending && target < item)) { rightPointer = mid - 1; } else { leftPointer = mid + 1; } } if(secondIteration) { // exact if (0 === match_mode) { if(item == target) { index = mid; break; } else if(item < target) { if (isDescending) { rightPointer = mid - 1; } else { leftPointer = mid + 1; } } else { if (isDescending) { leftPointer = mid + 1 } else { rightPointer = mid - 1; } } } // exact or larger if (1 === match_mode) { if (leftPointer === 0 && rightPointer === array.length - 1) { if (item == target) { index = mid; break; } else if (item < target) { if (isDescending) { rightPointer = mid - 1; } else { leftPointer = mid + 1; } } else { if (isDescending) { leftPointer = mid + 1; } else { rightPointer = mid - 1; } } } else { if (item > target || item == target) { index = mid; break; } else { if (isDescending) { rightPointer = mid - 1; } else { leftPointer = mid + 1; } } } } // exact or smaller if (-1 === match_mode) { if (leftPointer === 0 && rightPointer === array.length - 1) { if(item == target) { index = mid; break; } else if (item < target) { if (isDescending) { rightPointer = mid - 1; } else { leftPointer = mid + 1; } } else { if (isDescending) { leftPointer = mid + 1; } else { rightPointer = mid - 1; } } } else { if (item < target || item == target) { index = mid; break; } else { if (isDescending) { leftPointer = mid + 1; } else { rightPointer = mid - 1; } } } } } } } } bidirectionalSearch(false, isReverse) // second iteration if(index === -1 && (1 === match_mode || -1 === match_mode || 0 === match_mode)) { leftPointer = 0; rightPointer = array.length - 1; bidirectionalSearch(true, isReverse); } return index; } // these functions are made and used specifically for SORT & SORTBY functions function sortWithIndices (arr, sortOrder, isByCol) { const indexedArray = isByCol ? arr[0].map(function (item, index) { return { item: item, index: index } }) : arr.map(function (item, index) { item = item[0]; return { item: item, index: index }; }); indexedArray.sort(function (a, b) { const itemA = a.item; const itemB = b.item; let res = 0; if (cElementType.string === itemA.type && cElementType.string === itemB.type) { res = AscCommonExcel.stringCompare(itemA.value, itemB.value) * sortOrder; } else if (cElementType.number === itemA.type && cElementType.number === itemB.type) { res = (itemA.value - itemB.value) * sortOrder; } else if (cElementType.string === itemA.type) { // check itemB.type and make decision if (cElementType.number === itemB.type) { res = 1 * sortOrder; } else if (cElementType.bool === itemB.type || cElementType.error === itemB.type || cElementType.empty === itemB.type) { res = -1 * sortOrder; } } else if (cElementType.string === itemB.type) { // check itemA.type and make decision if (cElementType.number === itemA.type) { res = -1 * sortOrder; } else if (cElementType.bool === itemA.type || cElementType.error === itemA.type || cElementType.empty === itemA.type) { res = 1 * sortOrder; } } else if (cElementType.bool === itemA.type) { if (cElementType.error === itemB.type || cElementType.empty === itemB.type) { res = -1 * sortOrder; } else { res = 1 *sortOrder; } } else if (cElementType.bool === itemB.type) { if (cElementType.error === itemA.type || cElementType.empty === itemA.type) { res = 1 * sortOrder; } else { res = -1 *sortOrder; } } else if (cElementType.error === itemA.type) { if (cElementType.error === itemB.type) { res = 1 * sortOrder; } else if (cElementType.empty === itemB.type) { res = -1 * sortOrder; } else { res = 1 * sortOrder; } } else if (cElementType.error === itemB.type) { if (cElementType.error === itemA.type) { res = -1 * sortOrder; } else if (cElementType.empty === itemA.type) { res = 1 * sortOrder; } else { res = -1 * sortOrder; } } else if (cElementType.empty === itemA.type || cElementType.empty === itemB.type) { res = 1 * sortOrder; } else { res = 0; } return res; }); return indexedArray; } // these functions are made and used specifically for SORT & SORTBY functions function sortArray (array, by_array1, sortOrder, isByCol, sortIndex) { let resultArr = new cArray(), tempArrIndicies = [], byRowColArr, targetElem; if (by_array1) { byRowColArr = isByCol ? by_array1._getRow(0) : by_array1._getCol(0); } else { targetElem = isByCol ? array._getRow(sortIndex - 1) : array._getCol(sortIndex - 1); } // sorting an array with indices tempArrIndicies = sortWithIndices(byRowColArr ? byRowColArr : targetElem, sortOrder, isByCol); for (let i = 0; i < tempArrIndicies.length; i++) { let target = isByCol ? array._getCol(tempArrIndicies[i].index) : array._getRow(tempArrIndicies[i].index); isByCol ? resultArr.pushCol(target, 0) : resultArr.pushRow(target, 0); } return resultArr; } function sortByArrayWrapper (array, args, isByCol) { let colsRowArr = [], sortOrderArr = [], colsRowIndexesArr = [], rowCol; for (let i = 1; i < args.length; i += 2) { let by_array = args[i], sortOrder = args[i+1]; let dim = by_array.getDimensions(); // check column or row if (dim.bbox) { rowCol = isByCol ? dim.bbox.r1 : dim.bbox.c1; } else { // add array/range check for mainArray and by_array args rowCol = isByCol ? dim.col : dim.row; } // if there is no such column/row yet, push into the array if (colsRowIndexesArr.indexOf(rowCol) === -1) { colsRowIndexesArr.push(rowCol); sortOrderArr.push(sortOrder); colsRowArr.push(isByCol ? by_array._getRow(0) : by_array._getCol(0)); } } let tempArrIndicies = sortByArray(colsRowArr, sortOrderArr, isByCol); let resultArr = new cArray(); for (let i = 0; i < tempArrIndicies.length; i++) { let target = isByCol ? array._getCol(tempArrIndicies[i].index) : array._getRow(tempArrIndicies[i].index); isByCol ? resultArr.pushCol(target, 0) : resultArr.pushRow(target, 0); } return resultArr; } function sortByArray (colsRowsArr, sortOrderArr, isByCol) { let by_array1 = colsRowsArr[0], tempArrIndicies = []; tempArrIndicies = indicesBy(by_array1, isByCol); tempArrIndicies.sort(function (a, b) { let res = 0; const compareFunc = function (_a, _b, _sortOrder) { let itemA = _a.item ? _a.item : _a, itemB = _b.item ? _b.item : _b; if (cElementType.string === itemA.type && cElementType.string === itemB.type) { res = AscCommonExcel.stringCompare(itemA.value, itemB.value) * _sortOrder; } else if (cElementType.number === itemA.type && cElementType.number === itemB.type) { res = (itemA.value - itemB.value) * _sortOrder; } else if (cElementType.string === itemA.type) { // check itemB.type and make decision if (cElementType.number === itemB.type) { res = 1 * _sortOrder; } else if (cElementType.bool === itemB.type || cElementType.error === itemB.type) { res = -1 * _sortOrder; } } else if (cElementType.string === itemB.type) { // check itemA.type and make decision if (cElementType.number === itemA.type) { res = -1 * _sortOrder; } else if (cElementType.bool === itemA.type || cElementType.error === itemA.type) { res = -1 * _sortOrder; } } else if (cElementType.bool === itemA.type) { if (cElementType.error === itemB.type) { res = -1 * _sortOrder; } else { res = 1 *_sortOrder; } } else if (cElementType.bool === itemB.type) { if (cElementType.error === itemA.type) { res = 1 * _sortOrder; } else { res = -1 *_sortOrder; } } else if (cElementType.error === itemA.type) { res = 1 * _sortOrder; } else if (cElementType.error === itemA.type) { res = 1 * _sortOrder; } else { res = 0; } } compareFunc(a, b, sortOrderArr[0]); if (res === 0) { for (let i = 1; i < colsRowsArr.length; i++) { let tempA = isByCol ? colsRowsArr[i][0][a.index] : colsRowsArr[i][a.index][0]; let tempB = isByCol ? colsRowsArr[i][0][b.index] : colsRowsArr[i][b.index][0]; compareFunc(tempA, tempB, sortOrderArr[i]); if (res !== 0) { break; } } } return res; }); return tempArrIndicies; } function indicesBy (arr, isByCol) { const indexedArray = isByCol ? arr[0].map(function (item, index) { return { item: item, index: index }; }) : arr.map(function (item, index) { item = item[0]; return { item: item, index: index }; }); return indexedArray; } /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cADDRESS() { } //***array-formula*** cADDRESS.prototype = Object.create(cBaseFunction.prototype); cADDRESS.prototype.constructor = cADDRESS; cADDRESS.prototype.name = 'ADDRESS'; cADDRESS.prototype.argumentsMin = 2; cADDRESS.prototype.argumentsMax = 5; cADDRESS.prototype.argumentsType = [argType.number, argType.number, argType.number, argType.logical, argType.text]; cADDRESS.prototype.Calculate = function (arg) { var rowNumber = arg[0], colNumber = arg[1], refType = arg[2] ? arg[2] : new cNumber(1), A1RefType = arg[3] ? arg[3] : new cBool(true), sheetName = arg[4] ? arg[4] : null; if (cElementType.cellsRange === rowNumber.type || cElementType.cellsRange3D === rowNumber.type) { rowNumber = rowNumber.cross(arguments[1]); } else if (cElementType.array === rowNumber.type) { rowNumber = rowNumber.getElementRowCol(0, 0); } if (cElementType.cellsRange === colNumber.type || cElementType.cellsRange3D === colNumber.type) { colNumber = colNumber.cross(arguments[1]); } else if (cElementType.array === colNumber.type) { colNumber = colNumber.getElementRowCol(0, 0); } if (cElementType.cellsRange === refType.type || cElementType.cellsRange3D === refType.type) { refType = refType.cross(arguments[1]); } else if (cElementType.array === refType.type) { refType = refType.getElementRowCol(0, 0); } else if(cElementType.empty === refType.type) { refType = new cNumber(1); } if (cElementType.cellsRange === A1RefType.type || cElementType.cellsRange3D === A1RefType.type) { A1RefType = A1RefType.cross(arguments[1]); } else if (cElementType.array === A1RefType.type) { A1RefType = A1RefType.getElementRowCol(0, 0); } else if(cElementType.empty === A1RefType.type) { A1RefType = new cNumber(1); } if(sheetName){ if (cElementType.cellsRange === sheetName.type || cElementType.cellsRange3D === sheetName.type) { sheetName = sheetName.cross(arguments[1]); } else if (cElementType.array === sheetName.type) { sheetName = sheetName.getElementRowCol(0, 0); } else if (cElementType.cell === sheetName.type || cElementType.cell3D === sheetName.type) { sheetName = sheetName.getValue(); } else if (cElementType.empty === sheetName.type) { sheetName = null; } } rowNumber = rowNumber.tocNumber(); colNumber = colNumber.tocNumber(); refType = refType.tocNumber(); A1RefType = A1RefType.tocBool(); if (cElementType.error === rowNumber.type) { return rowNumber; } if (cElementType.error === colNumber.type) { return colNumber; } if (cElementType.error === refType.type) { return refType; } if (cElementType.error === A1RefType.type) { return A1RefType; } if (sheetName && cElementType.error === sheetName.type) { return sheetName; } rowNumber = rowNumber.getValue(); colNumber = colNumber.getValue(); refType = refType.getValue(); //TODO tocBool - if !isBoolean -> value error? if (!A1RefType.toBool) { return new cError(cErrorType.wrong_value_type); } else { A1RefType = A1RefType.toBool(); } rowNumber = parseInt(rowNumber); colNumber = parseInt(colNumber); if (refType > 4 || refType < 1 || rowNumber < 1 || rowNumber > AscCommon.gc_nMaxRow || colNumber < 1 || colNumber > AscCommon.gc_nMaxCol) { return new cError(cErrorType.wrong_value_type); } var strRef; var absR, absC; switch (refType - 1) { case AscCommonExcel.referenceType.A: absR = true; absC = true; break; case AscCommonExcel.referenceType.ARRC: absR = true; absC = false; break; case AscCommonExcel.referenceType.RRAC: absR = false; absC = true; break; case AscCommonExcel.referenceType.R: absR = false; absC = false; break; } strRef = this._getRef(this._absolute(absR, rowNumber, A1RefType), this._absolute(absC, A1RefType ? g_oCellAddressUtils.colnumToColstrFromWsView(colNumber) : colNumber, A1RefType), A1RefType); var res = strRef; if(sheetName){ if("" === sheetName.getValue()){ res = "!" + strRef; } else { res = parserHelp.get3DRef(sheetName.toString(), strRef); } } return new cString(res); }; cADDRESS.prototype._getRef = function (row, col, A1RefType) { return A1RefType ? col + row : 'R' + row + 'C' + col; }; cADDRESS.prototype._absolute = function (abs, val, A1RefType) { return abs ? (A1RefType ? '$' + val : val) : (A1RefType ? val : '[' + val + ']'); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cAREAS() { } cAREAS.prototype = Object.create(cBaseFunction.prototype); cAREAS.prototype.constructor = cAREAS; cAREAS.prototype.name = 'AREAS'; cAREAS.prototype.argumentsMin = 1; cAREAS.prototype.argumentsMax = 1; cAREAS.prototype.argumentsType = [argType.reference]; cAREAS.prototype.arrayIndexes = {0: 1}; cAREAS.prototype.Calculate = function (arg) { let arg0 = arg[0]; if (arg0.type !== AscCommonExcel.cElementType.cell && arg0.type !== AscCommonExcel.cElementType.cell3D && arg0.type !== AscCommonExcel.cElementType.cellsRange && arg0.type !== AscCommonExcel.cElementType.cellsRange3D) { return new cError(cErrorType.wrong_value_type); } let areasCounter = 1; // todo In future versions, after adding the processing of the brackets inside the argument, make the counter of several areas inside arg return new cNumber(areasCounter); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cCHOOSE() { } //***array-formula*** cCHOOSE.prototype = Object.create(cBaseFunction.prototype); cCHOOSE.prototype.constructor = cCHOOSE; cCHOOSE.prototype.name = 'CHOOSE'; cCHOOSE.prototype.argumentsMin = 2; cCHOOSE.prototype.argumentsMax = 30; // todo add arrayIndex to all n-arguments (in array) cCHOOSE.prototype.arrayIndexes = {0: 1, 1: 1, 2: 1, 3: 1, 4: 1}; cCHOOSE.prototype.argumentsType = [argType.number, [argType.any]]; cCHOOSE.prototype.Calculate = function (arg) { const args = arguments; let arg0 = arg[0]; const chooseArgument = function (_arg0) { if (cElementType.cellsRange === _arg0.type || cElementType.cellsRange3D === _arg0.type) { _arg0 = _arg0.cross(args[1]); } _arg0 = _arg0.tocNumber(); if (cElementType.error === _arg0.type) { return _arg0; } if (cElementType.number === _arg0.type) { _arg0 = Math.floor(_arg0.getValue()); if (_arg0 < 1 || _arg0 > arg.length - 1) { return new cError(cErrorType.wrong_value_type); } return arg[Math.floor(_arg0)]; } return new cError(cErrorType.wrong_value_type); } if (cElementType.array === arg0.type || cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { // TODO refactor // go through the array and return result for each element let resArr = new cArray(); let tempArraySize, maxArraySize = arg0.getDimensions(); let arg0Rows = maxArraySize.row, arg0Cols = maxArraySize.col; // get max array size by first loop arg0.foreach2(function (elem) { let chosenArgument = chooseArgument(elem); if (chosenArgument && chosenArgument.type === cElementType.cellsRange || chosenArgument.type === cElementType.cellsRange3D || chosenArgument.type === cElementType.array) { tempArraySize = chosenArgument.getDimensions(); maxArraySize.row = tempArraySize.row > maxArraySize.row ? tempArraySize.row : maxArraySize.row; maxArraySize.col = tempArraySize.col > maxArraySize.col ? tempArraySize.col : maxArraySize.col; } }); for (let r = 0; r < arg0Rows; r++) { for (let c = 0; c < arg0Cols; c++) { let elem = arg0.getValue2(r, c); let chosenArgument = chooseArgument(elem); let argDimensions = chosenArgument.getDimensions(); let singleRow = arg0Rows === 1; let singleCol = arg0Cols === 1; let tempArr = []; if (singleRow || singleCol) { // if the first argument has one row or column we need to fully take this row or column and pass it to the resulting array for (let i = 0; i < (singleRow ? maxArraySize.row : maxArraySize.col); i++) { let elemFromChosenArgument; if (chosenArgument.type === cElementType.array || chosenArgument.type === cElementType.cellsRange || chosenArgument.type === cElementType.cellsRange3D) { if (argDimensions.col === 1) { // return elem from first col elemFromChosenArgument = chosenArgument.getElementRowCol ? chosenArgument.getElementRowCol(singleRow ? i : r, 0) : chosenArgument.getValueByRowCol(singleRow ? i : r, 0); } else if (argDimensions.row === 1) { // return elem from first row elemFromChosenArgument = chosenArgument.getElementRowCol ? chosenArgument.getElementRowCol(0, singleRow ? c : i) : chosenArgument.getValueByRowCol(0, singleRow ? c : i); } else { // return r/c elem elemFromChosenArgument = chosenArgument.getElementRowCol ? chosenArgument.getElementRowCol(singleRow ? i : r, singleRow ? c : i) : chosenArgument.getValueByRowCol(singleRow ? i : r, singleRow ? c : i); } // if we go outside the range, we must return the #N/A error to the array if ((singleRow && argDimensions.row - 1 !== 0 && argDimensions.row - 1 < i) || (singleCol && argDimensions.col - 1 !== 0 && argDimensions.col - 1 < i)) { elemFromChosenArgument = new cError(cErrorType.not_available); } } else { elemFromChosenArgument = chosenArgument; } // undefined can be obtained when accessing an empty cell in the range, in which case we need to return cEmpty if (elemFromChosenArgument === undefined) { elemFromChosenArgument = new cEmpty(); } singleRow ? tempArr.push([elemFromChosenArgument]) : tempArr.push(elemFromChosenArgument); } singleRow ? resArr.pushCol(tempArr, 0) : resArr.pushRow([tempArr], 0); } else { // get r/c part from chosen argument let elemFromChosenArgument; if (chosenArgument.type === cElementType.array || chosenArgument.type === cElementType.cellsRange || chosenArgument.type === cElementType.cellsRange3D) { if (argDimensions.row === 1) { elemFromChosenArgument = chosenArgument.getElementRowCol ? chosenArgument.getElementRowCol(0, c) : chosenArgument.getValueByRowCol(0, c); } else if (argDimensions.col === 1) { elemFromChosenArgument = chosenArgument.getElementRowCol ? chosenArgument.getElementRowCol(r, 0) : chosenArgument.getValueByRowCol(r, 0); } else { elemFromChosenArgument = chosenArgument.getElementRowCol ? chosenArgument.getElementRowCol(r, c) : chosenArgument.getValueByRowCol(r, c); } if (argDimensions.col - 1 !== 0 && argDimensions.col - 1 < c) { elemFromChosenArgument = new cError(cErrorType.not_available); } } else { elemFromChosenArgument = chosenArgument; } // undefined can be obtained when accessing an empty cell in the range, in which case we need to return cEmpty if (elemFromChosenArgument === undefined) { elemFromChosenArgument = new cEmpty(); } if (!resArr.array[r]) { resArr.addRow(); } resArr.addElement(elemFromChosenArgument); } } } if (resArr.getRowCount() < maxArraySize.row) { // fill the rest of array with #N/A error for (let i = resArr.getRowCount(); i < maxArraySize.row; i++) { resArr.addRow(); for (let j = 0; j < resArr.getCountElementInRow(); j++) { resArr.addElement(new cError(cErrorType.not_available)); } } } return resArr; } return chooseArgument(arg0); }; function chooseRowsCols(arg, argument1, byCol) { var argError = cBaseFunction.prototype._checkErrorArg.call(this, arg); if (argError) { return argError; } let arg1 = arg[0]; let matrix; if (arg1.type === cElementType.cellsRange || arg1.type === cElementType.array || arg1.type === cElementType.cell || arg1.type === cElementType.cell3D) { matrix = arg1.getMatrix(); } else if (arg1.type === cElementType.cellsRange3D) { if (arg1.isSingleSheet()) { matrix = arg1.getMatrix()[0]; } else { return new cError(cErrorType.bad_reference); } } else if (arg1.type === cElementType.error) { return arg1; } else if (arg1.type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } else { matrix = [[arg1]]; } let pushData = function (_argInside) { _argInside = _argInside.tocNumber(); if (_argInside.type === cElementType.error) { error = _argInside; return false; } _argInside = _argInside.toNumber(); let reverse = _argInside < 0; _argInside = Math.abs(_argInside); _argInside = parseInt(_argInside); if (_argInside < 1 || (_argInside > dimension.col && byCol) || (_argInside > dimension.row && !byCol)) { error = new cError(cErrorType.wrong_value_type); return false; } if (!res) { res = new cArray(); } if (byCol) { res.pushCol(matrix, reverse ? dimension.col - (_argInside - 1) - 1 : _argInside - 1); } else { res.pushRow(matrix, reverse ? dimension.row - (_argInside - 1) - 1 : _argInside - 1); } return true; }; let dimension = arg1.getDimensions(); let res; let error; for (let i = 1; i < arg.length; i++) { let _arg = arg[i]; if (cElementType.cellsRange === _arg.type || cElementType.cellsRange3D === _arg.type || cElementType.array === _arg.type) { let argDimensions = _arg.getDimensions(); if (argDimensions.col === 1 || argDimensions.row === 1) { let byCol = argDimensions.row > 1; for (let j = 0; j < Math.max(argDimensions.col, argDimensions.row); j++) { if (cElementType.array === _arg.type) { if (!pushData(_arg.getElementRowCol(!byCol ? 0 : j, !byCol ? j : 0))) { return error; } } else { if (!pushData(_arg.getValue2(!byCol ? 0 : j, !byCol ? j : 0))) { return error; } } } } else { return new cError(cErrorType.wrong_value_type); } continue; } if (!pushData(_arg)) { return error; } } return res ? res : new cError(cErrorType.wrong_value_type); } /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cCHOOSECOLS() { } //***array-formula*** cCHOOSECOLS.prototype = Object.create(cBaseFunction.prototype); cCHOOSECOLS.prototype.constructor = cCHOOSECOLS; cCHOOSECOLS.prototype.name = 'CHOOSECOLS'; cCHOOSECOLS.prototype.argumentsMin = 2; cCHOOSECOLS.prototype.argumentsMax = 253; cCHOOSECOLS.prototype.argumentsType = [argType.reference, [argType.number]]; cCHOOSECOLS.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cCHOOSECOLS.prototype.isXLFN = true; cCHOOSECOLS.prototype.Calculate = function (arg) { return chooseRowsCols(arg, arguments[1], true); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cCHOOSEROWS() { } //***array-formula*** cCHOOSEROWS.prototype = Object.create(cBaseFunction.prototype); cCHOOSEROWS.prototype.constructor = cCHOOSEROWS; cCHOOSEROWS.prototype.name = 'CHOOSEROWS'; cCHOOSEROWS.prototype.argumentsMin = 2; cCHOOSEROWS.prototype.argumentsMax = 253; cCHOOSEROWS.prototype.argumentsType = [argType.reference, [argType.number]]; cCHOOSEROWS.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cCHOOSEROWS.prototype.isXLFN = true; cCHOOSEROWS.prototype.Calculate = function (arg) { return chooseRowsCols(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cCOLUMN() { } //***array-formula*** cCOLUMN.prototype = Object.create(cBaseFunction.prototype); cCOLUMN.prototype.constructor = cCOLUMN; cCOLUMN.prototype.name = 'COLUMN'; cCOLUMN.prototype.argumentsMax = 1; cCOLUMN.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.setArrayRefAsArg; cCOLUMN.prototype.argumentsType = [argType.reference]; cCOLUMN.prototype.Calculate = function (arg) { var bbox; var res; var opt_col = arguments[6]; if (opt_col !== undefined) { return new cNumber(opt_col + 1); } else if (0 === arg.length) { bbox = arguments[1]; res = bbox ? new cNumber(bbox.c1 + 1) : null; } else { var arg0 = arg[0]; if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { bbox = arg0.getRange(); bbox = bbox && bbox.bbox; res = bbox ? new cNumber(bbox.c1 + 1) : null; } else if (cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { bbox = arg0.getRange(); bbox = bbox && bbox.bbox; if (bbox && bbox.c2 > bbox.c1) { res = new cArray(); for (var i = bbox.c1; i <= bbox.c2; i++) { res.addElement(new cNumber(i + 1)) } } else { res = bbox ? new cNumber(bbox.c1 + 1) : null; } } } return res ? res : new cError(cErrorType.bad_reference); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cCOLUMNS() { } //***array-formula*** cCOLUMNS.prototype = Object.create(cBaseFunction.prototype); cCOLUMNS.prototype.constructor = cCOLUMNS; cCOLUMNS.prototype.name = 'COLUMNS'; cCOLUMNS.prototype.argumentsMin = 1; cCOLUMNS.prototype.argumentsMax = 1; cCOLUMNS.prototype.arrayIndexes = {0: 1}; cCOLUMNS.prototype.argumentsType = [argType.reference]; cCOLUMNS.prototype.Calculate = function (arg) { var arg0 = arg[0]; var range; if (cElementType.array === arg0.type) { return new cNumber(arg0.getCountElementInRow()); } else if (cElementType.cellsRange === arg0.type || cElementType.cell === arg0.type || cElementType.cell3D === arg0.type || cElementType.cellsRange3D === arg0.type) { range = arg0.getRange(); } return (range ? new cNumber(Math.abs(range.getBBox0().c1 - range.getBBox0().c2) + 1) : new cError(cErrorType.wrong_value_type)); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cEXPAND() { } //***array-formula*** cEXPAND.prototype = Object.create(cBaseFunction.prototype); cEXPAND.prototype.constructor = cEXPAND; cEXPAND.prototype.name = 'EXPAND'; cEXPAND.prototype.isXLFN = true; cEXPAND.prototype.argumentsMin = 2; cEXPAND.prototype.argumentsMax = 4; cEXPAND.prototype.arrayIndexes = {0: 1, 3: 1}; cEXPAND.prototype.argumentsType = [argType.reference, argType.number, argType.number, argType.any]; cEXPAND.prototype.Calculate = function (arg) { const MAX_ARRAY_SIZE = 1048576; let array, arg0 = arg[0], arg3 = arg[3] ? arg[3] : new cError(cErrorType.not_available); function expandedArrayNew (arr, arg0Dimensions, maxRows, maxCols) { // maxRows/cols - dimensions for resulting array // arg0Dimensions.row/col - dimensions of existing array let res = new cArray(); for (let i = 0; i < maxRows; i++) { res.addRow(); for (let j = 0; j < maxCols; j++) { if (i >= arg0Dimensions.row || j >= arg0Dimensions.col) { res.addElement(pad_with); continue } let elem = arr.getElementRowCol ? arr.getElementRowCol(i,j) : arr.getValueByRowCol(i,j); elem ? res.addElement(elem) : res.addElement(new cEmpty()); } } return res; } // --------------------- arg0(array) type check ----------------------// if (cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type || cElementType.array === arg0.type) { array = arg0; } else if (cElementType.error === arg0.type) { return arg0; } else if (cElementType.empty === arg0.type) { return new cError(cErrorType.wrong_value_type); } else { array = new cArray(); if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { if (arg0.getValue().type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } array.addElement(arg0.getValue()); } else { array.addElement(arg0); } } // --------------------- arg1(row) type check ----------------------// let rows = arg[1], arg0Dimensions = array.getDimensions(); if (cElementType.empty === rows.type) { rows = new cNumber(arg0Dimensions.row); } else if(cElementType.array === rows.type) { rows = rows.getElementRowCol(0, 0); } else if(cElementType.cellsRange === rows.type || cElementType.cellsRange3D === rows.type) { // TODO не получилось точно выяснить поведение функции при передаче в нее cellsRange вторым или третьим аргументом, поэтому пока возвращаем ошибку rows = new cError(cErrorType.wrong_value_type); }; rows = rows.tocNumber(); if (cElementType.error === rows.type) { return rows; } rows = rows.toNumber(); // --------------------- arg2(column) type check ----------------------// let columns = arg[2] ? arg[2] : new cEmpty(); if(cElementType.empty === columns.type) { columns = new cNumber(arg0Dimensions.col); } else if(cElementType.array === columns.type) { columns = columns.getElementRowCol(0, 0); } else if(cElementType.cellsRange === columns.type || cElementType.cellsRange3D === columns.type) { // TODO не получилось точно выяснить поведение функции при передаче в нее cellsRange вторым или третьим аргументом, поэтому пока возвращаем ошибку columns = new cError(cErrorType.wrong_value_type); } columns = columns.tocNumber(); if(cElementType.error === columns.type) { return columns; } columns = columns.toNumber(); // --------------------- arg3(pad_with) type check ----------------------// let pad_with = arg3; if (cElementType.cellsRange === arg3.type || cElementType.cellsRange3D === arg3.type || cElementType.array === arg3.type) { return new cError(cErrorType.wrong_value_type); } // check length and max array size if (((rows * columns) > MAX_ARRAY_SIZE)) { return new cError(cErrorType.not_numeric); } else if (rows < arg0Dimensions.row || columns < arg0Dimensions.col) { return new cError(cErrorType.wrong_value_type); } else if(rows >= arg0Dimensions.row && columns >= arg0Dimensions.col) { return expandedArrayNew (array, arg0Dimensions, rows, columns); } return new cError(cErrorType.wrong_value_type); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cFILTER() { } //***array-formula*** cFILTER.prototype = Object.create(cBaseFunction.prototype); cFILTER.prototype.constructor = cFILTER; cFILTER.prototype.name = 'FILTER'; cFILTER.prototype.argumentsMin = 2; cFILTER.prototype.argumentsMax = 3; cFILTER.prototype.isXLFN = true; cFILTER.prototype.isXLWS = true; cFILTER.prototype.arrayIndexes = {0: 1, 1: 1}; cFILTER.prototype.argumentsType = [argType.reference, argType.reference, argType.any]; cFILTER.prototype.Calculate = function (arg) { function rangeModeLoop (rows, columns, isColumnMode) { let resArr = new cArray(); for (let i = 0; i < rows; i++) { for (let j = 0; j < columns; j++) { let val = arg1.getValueByRowCol ? arg1.getValueByRowCol(i, j) : arg1.getElementRowCol(i, j, true); val = val.tocBool(); val = val.toBool ? val.toBool() : new cError(cErrorType.wrong_value_type); if (cElementType.error === val.type) { return val; } if (val) { isColumnMode ? resArr.pushCol(arg0._getCol(j), 0) : resArr.pushRow(arg0._getRow(i), 0); } } } return resArr; } let resultArr = new cArray(), arg0 = arg[0], arg1 = arg[1], arg2 = arg[2] ? arg[2] : new cEmpty(), baseMode = false, // val && range || val && val || range && val rangeMode = false; // range && range if (cElementType.empty === arg0.type || cElementType.empty === arg1.type) { return new cError(cErrorType.wrong_value_type); } if (cElementType.error === arg0.type) { return arg0; } if (cElementType.error === arg1.type) { return arg1; } // 4 options: 1) range && range; 2) range && value; 3) value && range; 4) value && value if ((cElementType.array === arg0.type || cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) && (cElementType.array === arg1.type || cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type)) { // 1) range && range rangeMode = true; } else if ((cElementType.array === arg0.type || cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) && (cElementType.array !== arg1.type && cElementType.cellsRange !== arg1.type && cElementType.cellsRange3D !== arg1.type)) { // 2) range && value // Return array arg0 if arg1 === true and if array arg0 is one-dimensional let arg0Dimensons = arg0.getDimensions(); if ((arg0Dimensons.row > 1 && arg0Dimensons.col > 1)) { return new cError(cErrorType.wrong_value_type); } else { baseMode = true; } } else if ((cElementType.array !== arg0.type && cElementType.cellsRange !== arg0.type && cElementType.cellsRange3D !== arg0.type) && (cElementType.array === arg1.type || cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type)) { // 3) value && range baseMode = true; arg1 = arg1.isOneElement() ? arg1.getFirstElement() : new cError(cErrorType.wrong_value_type); } else { // 4) value && value baseMode = true; } if (cElementType.error === arg0.type) { return arg0; } if (cElementType.error === arg1.type) { return arg1; } if (rangeMode) { const initialArrayDimensions = arg0.getDimensions(true), initRows = initialArrayDimensions.row, initColumns = initialArrayDimensions.col, lookingArrayDimensions = arg1.getDimensions(true); // check for matching array sizes if (lookingArrayDimensions.row === 1 && lookingArrayDimensions.col === initColumns) { resultArr = rangeModeLoop(lookingArrayDimensions.row, lookingArrayDimensions.col, true); } else if (lookingArrayDimensions.row === initRows && lookingArrayDimensions.col === 1) { resultArr = rangeModeLoop(lookingArrayDimensions.row, lookingArrayDimensions.col, false); } else { // the size of the desired array does not match the initial return new cError(cErrorType.wrong_value_type); } if (resultArr.type === cElementType.error) { return resultArr; } else { resultArr = (resultArr.countElement > 0 || resultArr.rowCount > 0) ? resultArr : ((cElementType.empty !== arg2.type) ? arg2 : new cError(cErrorType.wrong_value_type)); } } else if (baseMode) { arg1 = arg1.tocBool(); arg1 = arg1.toBool ? arg1.toBool() : new cError(cErrorType.wrong_value_type); if (cElementType.error === arg1.type) { resultArr = arg1; } else if (arg1) { if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { arg0 = arg0.getValue(); } resultArr = arg0; } else { // should be #CALC! resultArr = (cElementType.empty !== arg2.type) ? arg2 : new cError(cErrorType.wrong_value_type); } } if (cElementType.cellsRange === resultArr.type || cElementType.cellsRange3D === resultArr.type) { resultArr = resultArr.getFullArray(); } return resultArr; }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cFORMULATEXT() { } //***array-formula*** cFORMULATEXT.prototype = Object.create(cBaseFunction.prototype); cFORMULATEXT.prototype.constructor = cFORMULATEXT; cFORMULATEXT.prototype.name = 'FORMULATEXT'; cFORMULATEXT.prototype.argumentsMin = 1; cFORMULATEXT.prototype.argumentsMax = 1; cFORMULATEXT.prototype.isXLFN = true; cFORMULATEXT.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.area_to_ref; cFORMULATEXT.prototype.argumentsType = [argType.reference]; cFORMULATEXT.prototype.Calculate = function (arg) { var arg0 = arg[0]; if (cElementType.error === arg0.type) { return arg0; } var res = null; if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type || cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { var bbox = arg0.getRange(); var formula = bbox.isFormula(); if (!formula) { return new cError(cErrorType.not_available); } else { res = new cString(bbox.getValueForEdit(true)); } } return (null !== res ? res : new cError(cErrorType.wrong_value_type)); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cGETPIVOTDATA() { } cGETPIVOTDATA.prototype = Object.create(cBaseFunction.prototype); cGETPIVOTDATA.prototype.constructor = cGETPIVOTDATA; cGETPIVOTDATA.prototype.name = 'GETPIVOTDATA'; cGETPIVOTDATA.prototype.argumentsMin = 2; cGETPIVOTDATA.prototype.argumentsMax = 254; cGETPIVOTDATA.prototype.arrayIndexes = {0: 1, 1: 1}; cGETPIVOTDATA.prototype.argumentsType = [argType.text, argType.text, [argType.text, argType.any]]; cGETPIVOTDATA.prototype.numFormat = AscCommonExcel.cNumFormatNone; cGETPIVOTDATA.prototype.Calculate = function (arg) { // arg0 - data_field - pivot table name //The name can be entered exactly like the existing field name or only the root of the name, for example, if you enter "second" in the argument, a field named "Sum of second", "Count of second", etc. will be returned. // arg1 - pivot_table - pivot table range // // If none of the cells in the range touch the table, then return #REF // ...arg2 - [field1,item1] - [field name, element] - the name and element pair point to an element in the field const getPivotData = function (looking_field, pivot_table_ref, items_array) { if (cElementType.cell !== pivot_table_ref.type && cElementType.cell3D !== pivot_table_ref.type && cElementType.cellsRange !== pivot_table_ref.type && cElementType.cellsRange3D !== pivot_table_ref.type) { return refError; } let worksheet = pivot_table_ref.getWS(); let bbox = pivot_table_ref.getBBox0(); let pivotTables = worksheet.getPivotTablesIntersectingRange(bbox); let pivotTable = pivotTables && pivotTables.length > 0 && pivotTables[pivotTables.length - 1]; if (pivotTable) { let cell = pivotTable.getCellByGetPivotDataParams({ dataFieldName: looking_field, optParams: prepareItemsArray(items_array) }); if (cell) { res = new cRef(worksheet.getCell3(cell.row, cell.col).getName(), worksheet); return res.tocNumber(); } } return refError; }; const getPivotDataByTwoArgs = function(pivotTableRef, stringOrCell) { const bbox = pivotTableRef.getBBox0(); const worksheet = pivotTableRef.ws; const pivotTables = worksheet.getPivotTablesIntersectingRange(bbox); const pivotTable = pivotTables && pivotTables.length > 0 && pivotTables[pivotTables.length - 1]; if (pivotTable) { return pivotTable.getCellByGetPivotDataString(stringOrCell); } return refError; }; const prepareItemsArray = function (array) { return array.map(function(elem) { return elem.getValue(); }); }; const t = this; let arg0 = arg[0], arg1 = arg[1], arg2 = arg.slice(2); let refError = new cError(cErrorType.bad_reference); let ws = arguments[3], res; if (ws) { if (arg.length === 2) { if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type || cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { return getPivotDataByTwoArgs(arg0, arg1); } } if (cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { return refError; } if (cElementType.array === arg0.type) { if (!arg0.isOneElement()) { let resArr = new cArray(); arg0.foreach(function (elem, r, c) { if (!resArr.array[r]) { resArr.addRow(); } let looking_data_field = elem.tocString(); if (cElementType.error === looking_data_field.type) { // return arg0; // push error in to arr and go to the next value resArr.addElement(looking_data_field); } else { resArr.addElement(getPivotData(looking_data_field.getValue(), arg1, arg)); } }); return resArr; } arg0 = arg0.getFirstElement(); } arg0 = arg0.tocString(); if (cElementType.error === arg0.type) { return arg0; } res = getPivotData(arg0.getValue(), arg1, arg2); } if (res) { return res } return refError; }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cHLOOKUP() { } //***array-formula*** cHLOOKUP.prototype = Object.create(cBaseFunction.prototype); cHLOOKUP.prototype.constructor = cHLOOKUP; cHLOOKUP.prototype.name = 'HLOOKUP'; cHLOOKUP.prototype.argumentsMin = 3; cHLOOKUP.prototype.argumentsMax = 4; cHLOOKUP.prototype.arrayIndexes = {0: 1, 1: 1, 2: 1}; cHLOOKUP.prototype.argumentsType = [argType.any, argType.number, argType.number, argType.logical]; cHLOOKUP.prototype.Calculate = function (arg) { let retArr = new cArray(); let error = false; if (arg[0].type === cElementType.array) { if (arg[2] && arg[2].type === cElementType.cellsRange || arg[2].type === cElementType.cellsRange3D || arg[2].type === cElementType.array) { arg[2] = arg[2].getValue2(0,0); } let dimension = arg[0].getDimensions(); for (let r = 0; r < dimension.row; r++) { retArr.addRow(); for (let c = 0; c < dimension.col; c++) { retArr.addElement(g_oHLOOKUPCache.calculate([arg[0].getValue2(r, c), arg[1], arg[2], arg[3]], arguments[1])); } } return retArr; } if (arg[2] && (arg[2].type === cElementType.array)) { let dimension = arg[2].getDimensions(); for (let r = 0; r < dimension.row; r++) { retArr.addRow(); for (let c = 0; c < dimension.col; c++) { if (!error) { let res = g_oHLOOKUPCache.calculate([arg[0], arg[1], arg[2].getValue2(r, c), arg[3]], arguments[1]); if (res.type === cElementType.error) { error = true } retArr.addElement(res); } else { break } } } return error ? new cError(cErrorType.bad_reference) : retArr; } return g_oHLOOKUPCache.calculate(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cHYPERLINK() { } cHYPERLINK.prototype = Object.create(cBaseFunction.prototype); cHYPERLINK.prototype.constructor = cHYPERLINK; cHYPERLINK.prototype.name = 'HYPERLINK'; cHYPERLINK.prototype.argumentsMin = 1; cHYPERLINK.prototype.argumentsMax = 2; cHYPERLINK.prototype.argumentsType = [argType.text, argType.any]; cHYPERLINK.prototype.Calculate = function (arg) { var arg0 = arg[0], arg1 = arg.length === 1 ? null : arg[1]; if (arg0 instanceof cArea || arg0 instanceof cArea3D) { arg0 = arg0.cross(arguments[1]); } else if (arg0 instanceof cArray) { arg0 = arg0.getElementRowCol(0, 0); } arg0 = arg0.tocString(); if(arg1) { if (arg1 instanceof cArea || arg1 instanceof cArea3D) { arg1 = arg1.cross(arguments[1]); } else if (arg1 instanceof cArray) { arg1 = arg1.getElementRowCol(0, 0); } if(arg1 instanceof cRef || arg1 instanceof cRef3D) { arg1 = arg1.getValue(); } if(arg1 instanceof cEmpty) { arg1 = new cNumber(0); } } else { arg1 = arg0.tocString(); } if (arg0 instanceof cError) { arg0.hyperlink = ""; return arg0; } if (arg1 instanceof cError) { arg1.hyperlink = ""; return arg1; } var res = arg1; res.hyperlink = arg0.getValue(); return res; }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cINDEX() { } //***array-formula*** cINDEX.prototype = Object.create(cBaseFunction.prototype); cINDEX.prototype.constructor = cINDEX; cINDEX.prototype.name = 'INDEX'; cINDEX.prototype.argumentsMin = 2; cINDEX.prototype.argumentsMax = 4; cINDEX.prototype.numFormat = AscCommonExcel.cNumFormatNone; cINDEX.prototype.arrayIndexes = {0: 1}; cINDEX.prototype.argumentsType = [argType.reference, argType.number, argType.number]; cINDEX.prototype.Calculate = function (arg) { let arg0 = arg[0], arg1 = arg[1] && (cElementType.empty !== arg[1].type) ? arg[1] : new cNumber(0), arg2 = arg[2] && (cElementType.empty !== arg[2].type) ? arg[2] : new cNumber(0), arg3 = arg[3] && (cElementType.empty !== arg[3].type) ? arg[3] : new cNumber(1), res; if (cElementType.cellsRange3D === arg0.type) { arg0 = arg0.tocArea(); if (!arg0) { return new cError(cErrorType.not_available); } } else if (cElementType.error === arg0.type) { return arg0; } let argError; if (argError = this._checkErrorArg([arg0, arg1, arg2, arg3])) { return argError; } arg1 = arg1.tocNumber(); arg2 = arg2.tocNumber(); arg3 = arg3.tocNumber(); if (cElementType.error === arg1.type || cElementType.error === arg2.type || cElementType.error === arg3.type) { return new cError(cErrorType.wrong_value_type); } //TODO в дальнейшем необходимо продумать преобразования аргументов на основе argumentsType!!! if (cElementType.array === arg1.type) { arg1 = arg1.getElementRowCol(0,0); if (cElementType.error === arg1.type) { return new cError(cErrorType.wrong_value_type); } } if (cElementType.array === arg2.type) { arg2 = arg2.getElementRowCol(0,0); if (cElementType.error === arg2.type) { return new cError(cErrorType.wrong_value_type); } } if(arg[3] && cElementType.empty !== arg[3].type && arg3 > 1) { return new cError(cErrorType.bad_reference); } arg1 = arg1.getValue(); arg2 = arg2.getValue(); if (arg1 < 0 || arg2 < 0) { return new cError(cErrorType.wrong_value_type); } const generateArray = function (_from, row, col) { let ret = null; let _colCount = _from.getCountElementInRow(); let _rowCount = _from.rowCount; let i; row = row !== undefined ? Math.ceil(row) : row; col = col !== undefined ? Math.ceil(col) : col; if (undefined !== row) { if (_rowCount < row) { if (col === undefined && _rowCount === 1 && _from.array[0] && _from.array[0][row - 1]) { ret = new cArray(); ret.addElement(_from.array[0][row - 1]); return ret; } else { return null; } } ret = new cArray(); for (i = 0; i < _colCount; i++) { ret.addElement(_from.array[row - 1][i]) } } else if (undefined !== col) { if (_colCount < col) { if (row === undefined && _colCount === 1 && _from.array[col - 1] && _from.array[col - 1][0]) { ret = new cArray(); ret.addElement(_from.array[col - 1][0]); return ret; } else { return null; } } ret = new cArray(); for (i = 0; i < _rowCount; i++) { ret.addRow(); ret.addElement(_from.array[i][col - 1]) } } return ret; }; AscCommonExcel.executeInR1C1Mode(false, function () { let dimension = arg0.getDimensions(); let isSingleRowCol = (dimension.row > 1 && dimension.col > 1) ? false : true; let isByColumn = (dimension.row > 1) ? true : false; let isArray = cElementType.array === arg0.type; let diffArg1 = arg1 === 0 ? 0 : 1; let diffArg2 = arg2 === 0 ? 0 : 1; if (arg[2] !== undefined && (arg1 > dimension.row || arg2 > dimension.col)) { /* if the col_num and row_num in the arguments is greater than the array size, return an error */ res = new cError(cErrorType.bad_reference); } else if (!isArray && arg[2] === undefined && !isSingleRowCol) { /* if the second arg is ommited and range(exactly reference) is two dimensional, return an error */ res = new cError(cErrorType.bad_reference); } else if (cElementType.array === arg0.type || cElementType.cellsRange === arg0.type) { let ws = arg0.getWS ? arg0.getWS() : null, bbox = arg0.getBBox0 ? arg0.getBBox0() : null; if (!isSingleRowCol) { /* r&c > 1 */ if (arg1 === 0 && arg2 === 0) { res = arg0; } else if (arg1 === 0) { // return full column if (isArray) { res = generateArray(arg0, undefined, arg2); } else { res = new Asc.Range(bbox.c1 + arg2 - diffArg2, bbox.r1, bbox.c1 + arg2 - diffArg2, bbox.r2); res = new cArea(res.getName(), ws); } } else if (arg2 === 0) { // return full row if (isArray) { res = generateArray(arg0, arg1, undefined); } else { res = new Asc.Range(bbox.c1, bbox.r1 + arg1 - diffArg1, bbox.c2, bbox.r1 + arg1 - diffArg1); res = new cArea(res.getName(), ws); } } else { if (isArray) { res = arg0.getValue2(arg1 > 0 ? arg1 - 1 : 0, arg2 > 0 ? arg2 - 1 : 0); } else { res = new Asc.Range(bbox.c1 + arg2 - diffArg2, bbox.r1 + arg1 - diffArg1, bbox.c1 + arg2 - diffArg2, bbox.r1 + arg1 - diffArg1); res = new cRef(res.getName(), ws); } } } else { /* r|c === 1 */ if (arg[2] === undefined && ((isByColumn && arg1 > dimension.row) || (!isByColumn && arg1 > dimension.col))) { res = new cError(cErrorType.bad_reference); } else if (arg1 === 0 && isByColumn) { // res = isByColumn ? arg0 : arg0.getValue2(1, arg2); res = arg0; } else if (undefined !== arg[2] && arg2 === 0 && !isByColumn) { res = arg0; } else if (undefined === arg[2] && dimension.row === 1) { // if the last argument is omitted and 1 line is selected if (isArray) { res = arg0.getValue2(0, arg1 > 0 ? arg1 - 1 : 0); } else { res = new Asc.Range(bbox.c1 + arg1 - diffArg1, bbox.r1, bbox.c1 + arg1 - diffArg1, bbox.r1); res = new cRef(res.getName(), ws); } } else { if (isArray) { res = arg0.getValue2(arg1 > 0 ? arg1 - 1 : 0, arg2 > 0 ? arg2 - 1 : 0); } else { res = new Asc.Range(bbox.c1 + arg2 - diffArg2, bbox.r1 + arg1 - diffArg1, bbox.c1 + arg2 - diffArg2, bbox.r1 + arg1 - diffArg1); res = new cRef(res.getName(), ws); } } } } else if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { if ((0 === arg1 || 1 === arg1) && (0 === arg2 || 1 === arg2)) { res = arg0.getValue(); } } else { res = new cError(cErrorType.wrong_value_type); } }); return res ? res : new cError(cErrorType.bad_reference); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cINDIRECT() { } //TODO есть разница с MS - в тестовом файле E6 //***array-formula*** cINDIRECT.prototype = Object.create(cBaseFunction.prototype); cINDIRECT.prototype.constructor = cINDIRECT; cINDIRECT.prototype.name = 'INDIRECT'; cINDIRECT.prototype.argumentsMin = 1; cINDIRECT.prototype.argumentsMax = 2; cINDIRECT.prototype.ca = true; cINDIRECT.prototype.argumentsType = [argType.text, argType.logical]; cINDIRECT.prototype.Calculate = function (arg) { let t = this, arg0 = arg[0].tocString(), arg1 = arg[1] ? arg[1] : new cBool(true), ws = arguments[3], wb = ws.workbook, o = { Formula: "", pCurrPos: 0 }, ref, found_operand, ret; const _getWorksheetByName = function(name) { if(!name) { return null; } for(var i = 0; i < wb.aWorksheets.length; i++) if(wb.aWorksheets[i].getName().toLowerCase() == name.toLowerCase()){ return wb.aWorksheets[i]; } return null; }; function parseReference() { let _tableTMP; if ((ref = parserHelp.is3DRef.call(o, o.Formula, o.pCurrPos, true))[0]) { let wsFrom = _getWorksheetByName(ref[1]); let wsTo = (null !== ref[2]) ? _getWorksheetByName(ref[2]) : wsFrom; if (!(wsFrom && wsTo)) { return new cError(cErrorType.bad_reference); } if (parserHelp.isArea.call(o, o.Formula, o.pCurrPos)) { found_operand = new cArea3D(o.real_str ? o.real_str.toUpperCase() : o.operand_str.toUpperCase(), wsFrom, wsTo); } else if (parserHelp.isRef.call(o, o.Formula, o.pCurrPos)) { if (wsTo !== wsFrom) { found_operand = new cArea3D(o.real_str ? o.real_str.toUpperCase() : o.operand_str.toUpperCase(), wsFrom, wsTo); } else { found_operand = new cRef3D(o.real_str ? o.real_str.toUpperCase() : o.operand_str.toUpperCase(), wsFrom); } } else if (parserHelp.isName.call(o, o.Formula, o.pCurrPos)) { found_operand = new cName3D(o.operand_str, wsFrom); } } else if (parserHelp.isArea.call(o, o.Formula, o.pCurrPos)) { found_operand = new cArea(o.real_str ? o.real_str.toUpperCase() : o.operand_str.toUpperCase(), ws); } else if (parserHelp.isRef.call(o, o.Formula, o.pCurrPos, true)) { found_operand = new cRef(o.real_str ? o.real_str.toUpperCase() : o.operand_str.toUpperCase(), ws); } else if (parserHelp.isName.call(o, o.Formula, o.pCurrPos)) { found_operand = new cName(o.operand_str, ws); } else if (_tableTMP = parserHelp.isTable.call(o, o.Formula, o.pCurrPos)) { found_operand = AscCommonExcel.cStrucTable.prototype.createFromVal(_tableTMP, wb, ws); if (found_operand.type === cElementType.error) { found_operand = null; } else { found_operand = found_operand.toRef ? found_operand.toRef() : null; } } } if (cElementType.array === arg0.type) { ret = new cArray(); arg0.foreach(function (elem, r) { o = {Formula: elem.toString(), pCurrPos: 0}; AscCommonExcel.executeInR1C1Mode(!!(arg1 && arg1.value === false), parseReference); if (!ret.array[r]) { ret.addRow(); } ret.addElement(found_operand) }); return ret; } else { o.Formula = arg0.toString(); AscCommonExcel.executeInR1C1Mode(!!(arg1 && arg1.value == false), parseReference); if (found_operand) { if (cElementType.name === found_operand.type || cElementType.name3D === found_operand.type) { found_operand = found_operand.toRef(arguments[1]); if (found_operand && cElementType.error === found_operand.type) { ret = new cError(cErrorType.bad_reference); } else { ret = found_operand; } } else { ret = found_operand; } } else { ret = new cError(cErrorType.bad_reference); } } // Save result for recursion check AscCommonExcel.g_cCalcRecursion.saveFunctionResult(this.name, ret); return ret; }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cLOOKUP() { } //***array-formula*** cLOOKUP.prototype = Object.create(cBaseFunction.prototype); cLOOKUP.prototype.constructor = cLOOKUP; cLOOKUP.prototype.name = 'LOOKUP'; cLOOKUP.prototype.argumentsMin = 2; cLOOKUP.prototype.argumentsMax = 3; cLOOKUP.prototype.arrayIndexes = {0: 1, 1: 1, 2: 1}; cLOOKUP.prototype.argumentsType = [argType.any, argType.reference, argType.reference]; cLOOKUP.prototype.Calculate = function (arg) { if (!AscCommonExcel.bIsSupportDynamicArrays && arg[0].type === cElementType.cellsRange || arg[0].type === cElementType.cellsRange3D) { arg[0] = arg[0].isOneElement() ? arg[0].getFirstElement() : arg[0].cross(arguments[1]); } else if (arg[0].type === cElementType.array) { let retArr = new cArray(); let dimension = arg[0].getDimensions(); for (let r = 0; r < dimension.row; r++) { retArr.addRow(); for (let c = 0; c < dimension.col; c++) { retArr.addElement(g_oLOOKUPCache.calculate([arg[0].getValue2(r, c), arg[1], arg[2]], arguments[1])); } } return retArr; } return g_oLOOKUPCache.calculate(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cMATCH() { } //***array-formula*** cMATCH.prototype = Object.create(cBaseFunction.prototype); cMATCH.prototype.constructor = cMATCH; cMATCH.prototype.name = 'MATCH'; cMATCH.prototype.argumentsMin = 2; cMATCH.prototype.argumentsMax = 3; cMATCH.prototype.arrayIndexes = {1: 1}; cMATCH.prototype.argumentsType = [argType.any, argType.number, argType.number]; cMATCH.prototype.Calculate = function (arg) { return g_oMatchCache.calculate(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cOFFSET() { } //***array-formula*** cOFFSET.prototype = Object.create(cBaseFunction.prototype); cOFFSET.prototype.constructor = cOFFSET; cOFFSET.prototype.name = 'OFFSET'; cOFFSET.prototype.argumentsMin = 3; cOFFSET.prototype.argumentsMax = 5; cOFFSET.prototype.ca = true; cOFFSET.prototype.arrayIndexes = {0: 1}; cOFFSET.prototype.argumentsType = [argType.reference, argType.number, argType.number, argType.number, argType.number]; cOFFSET.prototype.Calculate = function (arg) { function validBBOX(bbox) { return 0 <= bbox.r1 && bbox.r1 <= gc_nMaxRow0 && 0 <= bbox.c1 && bbox.c1 <= gc_nMaxCol0 && 0 <= bbox.r2 && bbox.r2 <= gc_nMaxRow0 && 0 <= bbox.c2 && bbox.c2 <= gc_nMaxCol0; } var arg0 = arg[0], arg1 = arg[1].tocNumber(), arg2 = arg[2].tocNumber(); var arg3 = 3 < arg.length ? (cElementType.empty === arg[3].type ? new cNumber(1) : arg[3].tocNumber()) : new cNumber(-1); var arg4 = 4 < arg.length ? (cElementType.empty === arg[4].type ? new cNumber(1) : arg[4].tocNumber()) : new cNumber(-1); var argError; if (argError = this._checkErrorArg([arg0, arg1, arg2, arg3, arg4])) { return argError; } arg1 = arg1.getValue(); arg2 = arg2.getValue(); arg3 = arg3.getValue(); arg4 = arg4.getValue(); if (arg3 == 0 || arg4 == 0) { return new cError(cErrorType.bad_reference); } var res; if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type || cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { var box = arg0.getBBox0(); if (box) { box = box.clone(true); //в документации написано, что в отрицательных значений в 4 и 5 аргументах быть не может //но на деле ms рассчитывает такие формулы //сделал аналогично box.c1 = box.c1 + arg2; box.r1 = box.r1 + arg1; box.c2 = box.c2 + arg2; box.r2 = box.r2 + arg1; if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { if (arg.length > 3) { if (arg4 < 0) { box.c1 = box.c1 + arg4 + 1; } else { box.c2 = box.c1 + arg4 - 1; } if (arg3 < 0) { box.r1 = box.r1 + arg3 + 1; } else { box.r2 = box.r1 + arg3 - 1; } } } else { if (arg.length > 3) { if (arg4 < 0) { box.c1 = box.c1 + arg4 + 1; box.c2 = box.c1 - arg4 - 1; } else { box.c2 = box.c1 + arg4 - 1; } if (arg3 < 0) { box.r1 = box.r1 + arg3 + 1; box.r2 = box.r1 - arg3 - 1; } else { box.r2 = box.r1 + arg3 - 1; } } } if (!validBBOX(box)) { return new cError(cErrorType.bad_reference); } var name; AscCommonExcel.executeInR1C1Mode(false, function () { name = box.getName(); }); var ws = arg0.getWS(); var wsCell = arguments[3]; if (box.isOneCell()) { res = wsCell === ws ? new cRef(name, ws) : new cRef3D(name, ws); } else { res = wsCell === ws ? new cArea(name, ws) : new cArea3D(name, ws, ws); } } } if (!res) { res = new cError(cErrorType.wrong_value_type); } // Save result for recursion check AscCommonExcel.g_cCalcRecursion.saveFunctionResult(this.name, res); return res; }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cROW() { } //***array-formula*** cROW.prototype = Object.create(cBaseFunction.prototype); cROW.prototype.constructor = cROW; cROW.prototype.name = 'ROW'; cROW.prototype.argumentsMax = 1; cROW.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.setArrayRefAsArg; cROW.prototype.argumentsType = [argType.reference]; cROW.prototype.Calculate = function (arg) { var bbox; var res; var opt_row = arguments[5]; if (opt_row !== undefined) { return new cNumber(opt_row + 1); } else if (0 === arg.length) { bbox = arguments[1]; res = bbox ? new cNumber(bbox.r1 + 1) : null; } else { var arg0 = arg[0]; if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { bbox = arg0.getRange(); bbox = bbox && bbox.bbox; res = bbox ? new cNumber(bbox.r1 + 1) : null; } else if (cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { bbox = arg0.getRange(); bbox = bbox && bbox.bbox; if (bbox && bbox.r2 > bbox.r1) { res = new cArray(); for (var i = bbox.r1; i <= bbox.r2; i++) { res.addRow(); res.addElement(new cNumber(i + 1)) } } else { res = bbox ? new cNumber(bbox.r1 + 1) : null; } } } return res ? res : new cError(cErrorType.bad_reference); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cROWS() { } //***array-formula*** cROWS.prototype = Object.create(cBaseFunction.prototype); cROWS.prototype.constructor = cROWS; cROWS.prototype.name = 'ROWS'; cROWS.prototype.argumentsMin = 1; cROWS.prototype.argumentsMax = 1; cROWS.prototype.arrayIndexes = {0: 1}; cROWS.prototype.argumentsType = [argType.reference]; cROWS.prototype.Calculate = function (arg) { var arg0 = arg[0]; var range; if (cElementType.array === arg0.type) { return new cNumber(arg0.getRowCount()); } else if (cElementType.cellsRange === arg0.type || cElementType.cell === arg0.type || cElementType.cell3D === arg0.type || cElementType.cellsRange3D === arg0.type) { range = arg0.getRange(); } return (range ? new cNumber(Math.abs(range.getBBox0().r1 - range.getBBox0().r2) + 1) : new cError(cErrorType.wrong_value_type)); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cRTD() { } cRTD.prototype = Object.create(cBaseFunction.prototype); cRTD.prototype.constructor = cRTD; cRTD.prototype.name = 'RTD'; cRTD.prototype.argumentsType = [argType.text, argType.text, [argType.text]]; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cSORT() { } cSORT.prototype = Object.create(cBaseFunction.prototype); cSORT.prototype.constructor = cSORT; cSORT.prototype.name = 'SORT'; cSORT.prototype.argumentsMin = 1; cSORT.prototype.argumentsMax = 4; cSORT.prototype.isXLFN = true; cSORT.prototype.isXLWS = true; cSORT.prototype.arrayIndexes = {0: 1, 1: 1, 2: 1, 3: 1}; cSORT.prototype.argumentsType = [argType.reference, argType.number, argType.number, argType.logical]; cSORT.prototype.Calculate = function (arg) { function arrayHelper (byColArray) { let dimensions = byColArray.getDimensions(), resArr = new cArray(), errVal = new cError(cErrorType.wrong_value_type), byColVal, isFirstValRecieved = false, isSecondValRecieved = false; // find the "truthy" values for (let i = 0; i < dimensions.row; i++) { resArr.addRow(); for (let j = 0; j < dimensions.col; j++) { let elem = byColArray.getValueByRowCol ? byColArray.getValueByRowCol(i, j) : byColArray.getElementRowCol(i, j); if (!elem) { elem = new cEmpty(); } if (cElementType.bool === elem.type || cElementType.number === elem.type || cElementType.empty === elem.type) { byColVal = elem.tocBool(); } else { elem.type === cElementType.error ? resArr.addElement(elem) : resArr.addElement(errVal); continue; } if (cElementType.error === byColVal.type || (isFirstValRecieved && isSecondValRecieved)) { resArr.addElement(errVal); } else if (!isFirstValRecieved) { let fValue = sortArray(array, null, sort_order, byColVal.toBool(), sort_index).getFirstElement(); resArr.addElement(fValue); isFirstValRecieved = true; } else if (!isSecondValRecieved) { let sValue = new cNumber(0); resArr.addElement(sValue); isSecondValRecieved = true; } } } return resArr; } function isValidArray (array, maxRowCol) { let dimensions = array.getDimensions(); for (let i = 0; i < dimensions.row; i++) { for (let j = 0; j < dimensions.col; j++) { let elem = array.getValueByRowCol ? array.getValueByRowCol(i, j) : array.getElementRowCol(i, j); if (!elem) { return false; } elem = elem.tocNumber(); if (elem.type === cElementType.error) { return false; } else if (Math.floor(elem.getValue()) > maxRowCol || Math.floor(elem.getValue()) <= 0) { return false; } } } return true; } let arg0 = arg[0], // array arg1 = arg[1] ? arg[1] : new cNumber(1), // sort_index arg2 = arg[2] ? arg[2] : new cNumber(1), // sort_order arg3 = arg[3] ? arg[3] : new cBool(false); // by_col ? // check args err if (cElementType.error === arg0.type) { return arg0; } else if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { if (cElementType.error === arg0.getValue().type) { return arg0; } } if (cElementType.error === arg1.type) { return arg1; } else if (cElementType.cell === arg1.type || cElementType.cell3D === arg1.type) { if (cElementType.error === arg1.getValue().type) { return arg1; } } if (cElementType.error === arg2.type) { return arg2; } else if (cElementType.cell === arg2.type || cElementType.cell3D === arg2.type) { if (cElementType.error === arg2.getValue().type) { return arg2; } } if (cElementType.error === arg3.type) { return arg3; } else if (cElementType.cell === arg3.type || cElementType.cell3D === arg3.type) { if (cElementType.error === arg3.getValue().type) { return arg3; } } // check args empty if (cElementType.empty === arg1.type) { arg1 = new cNumber(1); } if (cElementType.empty === arg2.type) { arg2 = new cNumber(1); } if (cElementType.empty === arg3.type) { arg3 = new cBool(false); } let array, sort_index, sort_order, by_col, isArg1Array = false, isArg3Array = false, maxRows, maxCols; // check args type: // arg0(initial array) check if (cElementType.array !== arg0.type && cElementType.cellsRange !== arg0.type && cElementType.cellsRange3D !== arg0.type) { let elem; if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { elem = arg0.getValue(); } else { elem = arg0; } array = new cArray(); array.addElement(elem); } else { array = arg0; } maxRows = array.getDimensions().row; maxCols = array.getDimensions().col; // arg1(sort_index) check if (cElementType.array !== arg1.type && cElementType.cellsRange !== arg1.type && cElementType.cellsRange3D !== arg1.type) { sort_index = arg1.tocNumber(); } else { isArg1Array = true; let arg1Dimensions = arg1.getDimensions(); if (arg1Dimensions.row > maxRows || arg1Dimensions.col > maxCols) { return new cError(cErrorType.wrong_value_type); } else { let firstElement = arg1.getFirstElement(); if (!firstElement) { firstElement = new cEmpty(); } sort_index = firstElement.tocNumber(); } } // arg2(sort_order) check if (cElementType.array !== arg2.type && cElementType.cellsRange !== arg2.type && cElementType.cellsRange3D !== arg2.type) { sort_order = arg2.tocNumber(); } else if (arg2.isOneElement()) { sort_order = arg2.getFirstElement(); } else { return new cError(cErrorType.wrong_value_type); } // arg3(by_col) check if (cElementType.array !== arg3.type && cElementType.cellsRange !== arg3.type && cElementType.cellsRange3D !== arg3.type) { by_col = arg3.tocBool(); } else { if (!by_col) { by_col = new cBool(false); } isArg3Array = true; } if (cElementType.error === sort_index.type) { return sort_index; } else { sort_index = Math.floor(sort_index.getValue()); } if (cElementType.error === sort_order.type) { return sort_order; } else { sort_order = Math.floor(sort_order.getValue()); } if (cElementType.error === by_col.type) { return by_col; } else if (!isArg3Array && cElementType.bool !== by_col.type) { return new cError(cErrorType.wrong_value_type); } else if (!isArg3Array) { by_col = by_col.toBool(); } if (sort_index <= 0 || (sort_order !== -1 && sort_order !== 1)) { return new cError(cErrorType.wrong_value_type); } if (!by_col) { if ((sort_index > maxCols) || (isArg1Array && !isValidArray(arg1, maxCols))) { return new cError(cErrorType.wrong_value_type); } } else { if ((sort_index > maxRows) || (isArg1Array && !isValidArray(arg1, maxRows))) { return new cError(cErrorType.wrong_value_type); } } if (isArg3Array) { // TODO it is not completely clear how the function works when receiving an array as the last argument return arrayHelper(arg3); } return sortArray(array, null, sort_order, by_col, sort_index); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cSORTBY() { } cSORTBY.prototype = Object.create(cBaseFunction.prototype); cSORTBY.prototype.constructor = cSORTBY; cSORTBY.prototype.name = 'SORTBY'; cSORTBY.prototype.argumentsMin = 2; cSORTBY.prototype.isXLFN = true; // TODO infinite arrayIndexes for even/odd arguments cSORTBY.prototype.arrayIndexes = {0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1}; cSORTBY.prototype.getArrayIndex = function (index) { return 1; }; cSORTBY.prototype.argumentsType = [argType.array, argType.array, argType.number, [argType.array, argType.number]]; cSORTBY.prototype.Calculate = function (arg) { function arrayHelper (arr, args) { // Helper logic: // We are looking for the maximum size of the array, which will later become the result // Create the resulting array after 2 cycles (i < row, j < col) // When creating each element, iterate through all elements with the same row col value and return the value according to the condition // If the "correct" element has already been written at least once (true/false flag), then in this case we return 0 or #VALUE! ? let resArr = new cArray(), resCol = 1, resRow = 1, sort_order1, by_array1, sortOrderArr = [], isByCol, isFirstElemReceived; // get max row & col for (let i = 1; i < args.length; i += 2) { let by_array = args[i], sortOrder = args[i+1]; by_array1 = i === 1 ? by_array : by_array1; // TODO can be array with single item and can be just single item if (sortOrder.type === cElementType.array || sortOrder.type === cElementType.cellsRange || sortOrder.type === cElementType.cellsRange3D) { // if single element in array, fill array with it element let resDimensoins = sortOrder.getDimensions(); if (resRow < resDimensoins.row) { resRow = resDimensoins.row; } if (resCol < resDimensoins.col) { resCol = resDimensoins.col; } } else { // create array with single element and fill it let resArr = new cArray(); for (let i = 0; i < by_array1.getDimensions().row; i++) { resArr.addRow(); for (let k = 0; k < by_array1.getDimensions().col; k++) { resArr.addElement(sortOrder); } } sortOrder = resArr; } sortOrderArr.push(sortOrder); } // fill resArr, go through sortOrderArr for (let i = 0; i < resRow; i++) { resArr.addRow(); for (let j = 0; j < resCol; j++) { let overallSortOrder; for (let k = 0; k < sortOrderArr.length; k++) { overallSortOrder = sortOrderArr[k].getElementRowCol ? sortOrderArr[k].getElementRowCol(i, j) : sortOrderArr[k].getValueByRowCol(i, j); // check element if (!overallSortOrder) { overallSortOrder = new cError(cErrorType.not_available); } else if (overallSortOrder.type !== cElementType.number) { overallSortOrder = overallSortOrder.tocNumber(); } // if any error break the cycle if (overallSortOrder.type === cElementType.error) { break; } if (overallSortOrder.type === cElementType.number) { // matching number check let value = Math.floor(overallSortOrder.getValue()); if (value !== -1 && value !== 1) { overallSortOrder = new cError(cErrorType.wrong_value_type); } else { overallSortOrder = new cNumber(value); } } sort_order1 = k === 0 ? overallSortOrder : sort_order1; // if any error break the cycle if (sort_order1.type === cElementType.error) { break; } } // if elem is correct, do sort and get first element from sorted array if (overallSortOrder.type !== cElementType.error) { let byArrDimensions = by_array1.getDimensions(); isByCol = byArrDimensions.row === 1 ? true : false; if (isFirstElemReceived) { if (isByCol) { overallSortOrder = new cNumber(0); resArr.addElement(overallSortOrder); } else { overallSortOrder = new cError(cErrorType.wrong_value_type); resArr.addElement(overallSortOrder); } } // TODO need more research: // ?If single col and many rows -> return only first correct element and errors // ?If single row and many cols -> return not only the first correct element, but also subsequent // else if (isFirstElemReceived && args.length > 3) { // elem = new cError(cErrorType.wrong_value_type); // resArr.addElement(elem); // } else { let firstElem = sortArray(arr, by_array1, sort_order1.getValue(), isByCol).getFirstElement(); resArr.addElement(firstElem); isFirstElemReceived = true; } } else { resArr.addElement(overallSortOrder); } } } return resArr; } let args = arg.slice(); let array, by_array, sort_order, maxRows, maxCols, arrayDimensions, isByCol, isSortOrderArray, isByArrayNotArray; // check arg0 if (cElementType.error === args[0].type) { return args[0]; } else if (cElementType.cell === args[0].type || cElementType.cell3D === args[0].type) { if (cElementType.error === args[0].getValue().type) { return args[0]; } } if (cElementType.array !== args[0].type && cElementType.cellsRange !== args[0].type && cElementType.cellsRange3D !== args[0].type) { let elem; if (cElementType.cell === args[0].type || cElementType.cell3D === args[0].type) { elem = args[0].getValue(); } else { elem = args[0]; } array = new cArray(); array.addElement(elem); } else if (cElementType.cellsRange === args[0].type || cElementType.cellsRange3D === args[0].type) { array = args[0].getFullArray(); } else { array = args[0]; } arrayDimensions = array.getDimensions(); maxRows = arrayDimensions.row; maxCols = arrayDimensions.col; if (args.length < 3) { // add default sort_by args[2] = new cNumber(1); } // check args err&empty for (let i = 1; i < args.length; i++) { // check errors if (cElementType.error === args[i].type) { return args[i]; } else if (cElementType.cell === args[i].type || cElementType.cell3D === args[i].type) { if (cElementType.error === args[i].getValue().type) { return args[i]; } } // check by_array arguments if (i % 2 !== 0) { if (cElementType.array !== args[i].type && cElementType.cellsRange !== args[i].type && cElementType.cellsRange3D !== args[i].type) { let elem; if (cElementType.cell === args[i].type || cElementType.cell3D === args[i].type) { elem = args[i].getValue(); } else { elem = args[i]; } by_array = new cArray(); by_array.addElement(elem); args[i] = by_array; isByArrayNotArray = i === 1 ? true : isByArrayNotArray; } } // check sort_order arguments if (i % 2 === 0) { // empty check if (cElementType.empty === args[i].type && (i % 2 === 0)) { args[i] = new cNumber(1); } // variable typing if (cElementType.array !== args[i].type && cElementType.cellsRange !== args[i].type && cElementType.cellsRange3D !== args[i].type) { if (cElementType.cell === args[i].type || cElementType.cell3D === args[i].type) { sort_order = args[i].getValue().tocNumber(); } else { sort_order = args[i].tocNumber(); } } else if (args[i].isOneElement()) { sort_order = args[i].getFirstElement(); } else { sort_order = args[i]; isSortOrderArray = true; } // check after typing and round if (cElementType.error === sort_order.type) { return sort_order; } else if (!isSortOrderArray) { sort_order = Math.floor(sort_order.getValue()); if (sort_order !== 1 && sort_order !== -1) { return new cError(cErrorType.wrong_value_type); } sort_order = new cNumber(sort_order); } // check sort_order value args[i] = sort_order; } } // if the first of the by_array arguments is not an array/area - return initial array(arg0) if (isByArrayNotArray && !isSortOrderArray) { return array; } if (isSortOrderArray) { return arrayHelper(array, args); } else { // dimensions check: // check on errors first, then check on truthy dimensions and do things with it for (let i = 1; i < args.length; i += 2) { let byArrDimensions = args[i].getDimensions(); // TODO if there is a match on the single row, but not on the col - return the original array if (maxRows === 1) { // single row with elements if (maxRows === 1 && byArrDimensions.row === 1) { if (maxCols !== byArrDimensions.col) { // area to array if (cElementType.cellsRange === array.type || cElementType.cellsRange3D === array.type) { return array.getFullArray(); } return array; } // else { // // return sorted array // } } } // isByCol or not determined by the first byarray arg if ((byArrDimensions.row === 1 && byArrDimensions.col !== maxCols) || (byArrDimensions.col === 1 && byArrDimensions.row !== maxRows) || (byArrDimensions.col > 1 && byArrDimensions.row > 1)) { return new cError(cErrorType.wrong_value_type); } else if (byArrDimensions.row === 1 && byArrDimensions.col === maxCols) { isByCol = i === 1 ? true : isByCol; } else if (byArrDimensions.col === 1 && byArrDimensions.row === maxRows) { isByCol = i === 1 ? false : isByCol; } } } return sortByArrayWrapper(array, args, isByCol); }; cSORTBY.prototype.checkArguments = function (countArguments) { return countArguments === 2 ? true : 1 === countArguments % 2 && cBaseFunction.prototype.checkArguments.apply(this, arguments); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cTRANSPOSE() { } //***array-formula*** cTRANSPOSE.prototype = Object.create(cBaseFunction.prototype); cTRANSPOSE.prototype.constructor = cTRANSPOSE; cTRANSPOSE.prototype.name = 'TRANSPOSE'; cTRANSPOSE.prototype.argumentsMin = 1; cTRANSPOSE.prototype.argumentsMax = 1; cTRANSPOSE.prototype.numFormat = AscCommonExcel.cNumFormatNone; cTRANSPOSE.prototype.arrayIndexes = {0: 1}; cTRANSPOSE.prototype.argumentsType = [argType.any]; cTRANSPOSE.prototype.Calculate = function (arg) { function TransposeMatrix(A) { var tMatrix = [], res = new cArray(); for (var i = 0; i < A.length; i++) { for (var j = 0; j < A[i].length; j++) { if (!tMatrix[j]) { tMatrix[j] = []; } tMatrix[j][i] = A[i][j]; } } res.fillFromArray(tMatrix); return res; } var arg0 = arg[0]; if (cElementType.cellsRange === arg0.type) { //TODO возможно стоит на вход функции Calculate в случае применения как формулы массива сразу передавать преобразованный range в array if(!this.bArrayFormula) { arg0 = arg0.cross(arguments[1]); return arg0; } else { arg0 = arg0.getMatrix(); } } else if(cElementType.cellsRange3D === arg0.type) { //TODO возможно стоит на вход функции в случае применения как формулы массива сразу передавать преобразованный range в array arg0 = arg0.getMatrix()[0]; } else if(cElementType.array === arg0.type) { arg0 = arg0.getMatrix(); } else if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { return arg0.getValue(); } else if (cElementType.number === arg0.type || cElementType.string === arg0.type || cElementType.bool === arg0.type || cElementType.error === arg0.type) { return arg0; } else { return new cError(cErrorType.not_available); } if (cElementType.error === arg0.type) { return arg0; } if(0 === arg0.length){ return new cError(cErrorType.wrong_value_type); } return TransposeMatrix(arg0); }; function takeDrop(arg, argument1, isDrop) { var argError = cBaseFunction.prototype._checkErrorArg.call(this, arg); if (argError) { return argError; } let arg1 = arg[0]; let matrix; if (arg1.type === cElementType.array) { // array.getMatrix() doesn't make an independent copy and leaves a link to the original array matrix = arg1.getMatrixCopy(); } else if (arg1.type === cElementType.cellsRange || arg1.type === cElementType.cell || arg1.type === cElementType.cell3D) { matrix = arg1.getMatrix(); } else if (arg1.type === cElementType.cellsRange3D) { if (arg1.isSingleSheet()) { matrix = arg1.getMatrix()[0]; } else { return new cError(cErrorType.bad_reference); } } else if (arg1.type === cElementType.error) { return arg1; } else if (arg1.type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } else { matrix = [[arg1]]; } let array = new cArray(); array.fillFromArray(matrix); let arg2 = arg[1]; if (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type) { //_arg = _arg.getValue2(0,0); return new cError(cErrorType.wrong_value_type); } else if (cElementType.array === arg2.type) { //_arg = _arg.getElementRowCol(0, 0); return new cError(cErrorType.wrong_value_type); } if (cElementType.empty === arg2.type) { arg2 = null; } else { arg2 = arg2.tocNumber(); if (arg2.type === cElementType.error) { return arg2; } arg2 = arg2.toNumber(); arg2 = parseInt(arg2); if (Math.abs(arg2) < 1) { return new cError(cErrorType.array_not_calc); } } let arg3 = arg[2] ? arg[2] : new cEmpty(); if (cElementType.cellsRange === arg3.type || cElementType.cellsRange3D === arg3.type) { //_arg = _arg.getValue2(0,0); return new cError(cErrorType.wrong_value_type); } else if (cElementType.array === arg3.type) { //_arg = _arg.getElementRowCol(0, 0); return new cError(cErrorType.wrong_value_type); } if (cElementType.empty === arg3.type) { arg3 = null; } else { arg3 = arg3.tocNumber(); if (arg3.type === cElementType.error) { return arg3; } arg3 = arg3.toNumber(); arg3 = parseInt(arg3); if (Math.abs(arg3) < 1) { return new cError(cErrorType.wrong_value_type); } } if (isDrop) { let dimensions = array.getDimensions(); if (arg2 && dimensions.row <= Math.abs(arg2)) { return new cError(cErrorType.wrong_value_type); } if (arg3 && dimensions.col <= Math.abs(arg3)) { return new cError(cErrorType.wrong_value_type); } if (arg2) { if (arg2 < 0) { arg2 = dimensions.row - Math.abs(arg2); } else { arg2 = -1 * (dimensions.row - arg2); } } if (arg3) { if (arg3 < 0) { arg3 = dimensions.col - Math.abs(arg3); } else { arg3 = -1 * (dimensions.col - arg3); } } } let res = array.crop(arg2, arg3); return res ? res : new cError(cErrorType.wrong_value_type); } /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cTAKE() { } //***array-formula*** cTAKE.prototype = Object.create(cBaseFunction.prototype); cTAKE.prototype.constructor = cTAKE; cTAKE.prototype.name = 'TAKE'; cTAKE.prototype.argumentsMin = 2; cTAKE.prototype.argumentsMax = 3; cTAKE.prototype.arrayIndexes = {0: 1}; cTAKE.prototype.numFormat = AscCommonExcel.cNumFormatNone; cTAKE.prototype.isXLFN = true; cTAKE.prototype.argumentsType = [argType.reference, argType.number, argType.number]; cTAKE.prototype.arrayIndexes = {0: 1}; cTAKE.prototype.Calculate = function (arg) { return takeDrop(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cDROP() { } //***array-formula*** cDROP.prototype = Object.create(cBaseFunction.prototype); cDROP.prototype.constructor = cDROP; cDROP.prototype.name = 'DROP'; cDROP.prototype.argumentsMin = 2; cDROP.prototype.argumentsMax = 3; cDROP.prototype.arrayIndexes = {0: 1}; cDROP.prototype.numFormat = AscCommonExcel.cNumFormatNone; cDROP.prototype.isXLFN = true; cDROP.prototype.argumentsType = [argType.reference, argType.number, argType.number]; cDROP.prototype.arrayIndexes = {0: 1}; cDROP.prototype.Calculate = function (arg) { return takeDrop(arg, arguments[1], true); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cUNIQUE() { } //***array-formula*** cUNIQUE.prototype = Object.create(cBaseFunction.prototype); cUNIQUE.prototype.constructor = cUNIQUE; cUNIQUE.prototype.name = 'UNIQUE'; cUNIQUE.prototype.argumentsMin = 1; cUNIQUE.prototype.argumentsMax = 3; cUNIQUE.prototype.arrayIndexes = {0: 1}; cUNIQUE.prototype.argumentsType = [argType.reference, argType.logical, argType.logical]; cUNIQUE.prototype.isXLFN = true; cUNIQUE.prototype.numFormat = AscCommonExcel.cNumFormatNone; cUNIQUE.prototype.Calculate = function (arg) { var _getUniqueArr = function (_arr, _byCol, _exactlyOnce) { var rowCount = _arr && _arr.length; var colCount = _arr && _arr[0] && _arr[0].length; if (!rowCount || !colCount) { return cError(cErrorType.wrong_value_type); } var res = new cArray(); var repeateArr = []; var i, j, n, _value; var resArr = []; var _key; if (!_byCol) { var _rowCount = 0; for (i = 0; i < rowCount; i++) { _key = ""; for (j = 0; j < colCount; j++) { _value = _arr[i][j].getValue(); _key += _value + ";"; if (j === colCount - 1) { if (!repeateArr[_key]) { repeateArr[_key] = {index: _rowCount, count: 1}; for (n = 0; n < colCount; n++) { if (!resArr[_rowCount]) { resArr[_rowCount] = []; } resArr[_rowCount].push(_arr[i][n]); } _rowCount++; } else { repeateArr[_key].count++; } } } } } else { var _colCount = 0; for (i = 0; i < colCount; i++) { _key = ""; for (j = 0; j < rowCount; j++) { _value = _arr[j][i].getValue(); _key += _value + ";"; if (j === rowCount - 1) { if (!repeateArr[_key]) { repeateArr[_key] = {index: _colCount, count: 1}; for (n = 0; n < rowCount; n++) { if (!resArr[n]) { resArr[n] = []; } resArr[n][_colCount] = _arr[n][i]; } _colCount++; } else { repeateArr[_key].count++; } } } } } if (_exactlyOnce) { var tempArr = []; var _counter = 0; for (i in repeateArr) { var _elem = repeateArr[i]; if (_elem.count > 1) { continue; } if (!_byCol) { tempArr[_counter] = resArr[_elem.index]; } else { for (j = 0; j < rowCount; j++) { if (!tempArr[j]) { tempArr[j] = []; } tempArr[j][_counter] = resArr[j][_elem.index]; } } _counter++; } resArr = tempArr; } if (!resArr.length) { return new cError(cErrorType.wrong_value_type); } res.fillFromArray(resArr); return res; }; var arg0 = arg[0]; if (cElementType.cellsRange === arg0.type) { arg0 = arg0.getMatrix(); } else if(cElementType.cellsRange3D === arg0.type) { arg0 = arg0.getMatrix()[0]; } else if(cElementType.array === arg0.type) { arg0 = arg0.getMatrix(); } else if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { return arg0.getValue(); } else if (cElementType.number === arg0.type || cElementType.string === arg0.type || cElementType.bool === arg0.type || cElementType.error === arg0.type) { return arg0; } else { return new cError(cErrorType.not_available); } if (cElementType.error === arg0.type) { return arg0; } if(0 === arg0.length){ return new cError(cErrorType.wrong_value_type); } var arg1 = !arg[1] ? false : arg[1].tocBool(); if (arg1 && cElementType.error === arg1.type) { return arg1; } else if (arg1) { arg1 = arg1.toBool(); } var arg2 = !arg[2] ? false : arg[2].tocBool(); if (arg2 && cElementType.error === arg2.type) { return arg2; } else if (arg2) { arg2 = arg2.toBool(); } return _getUniqueArr(arg0, arg1, arg2); }; /** * @typedef {cNumber | cString | cBool | cError} LookUpElement */ function TypedMapCache() { this.data = {}; } TypedMapCache.prototype.getData = function (ws, rowCol, type, startIndex, endIndex, bHor) { const wsId = ws.getId(); if (!this.data[wsId]) { this.data[wsId] = {vertical: {}, horizontal: {}}; } const axisData = bHor ? this.data[wsId].horizontal : this.data[wsId].vertical; if (!axisData[rowCol]) { axisData[rowCol] = {start: startIndex, end: startIndex - 1, data: {}}; const c1 = bHor ? startIndex : rowCol; const r1 = bHor ? rowCol : startIndex; const c2 = bHor ? endIndex : rowCol; const r2 = bHor ? rowCol : endIndex; const fullRange = ws.getRange3(r1, c1, r2, c2); fullRange._foreachNoEmpty(function (cell, r, c) { const value = checkTypeCell(cell, true); const index = bHor ? c : r; if (index > axisData[rowCol].end) { if (!axisData[rowCol].data[value.type]) { axisData[rowCol].data[value.type] = new Map(); } const map = axisData[rowCol].data[value.type]; if (!map.has(value.value)) { map.set(value.value, []); } const arr = axisData[rowCol].data[value.type].get(value.value); arr.push(index); axisData[rowCol].end = index; } }); axisData[rowCol].end = endIndex; } else { if (startIndex < axisData[rowCol].start) { const c1 = bHor ? startIndex : rowCol; const r1 = bHor ? rowCol : startIndex; const c2 = bHor ? axisData[rowCol].start - 1: rowCol; const r2 = bHor ? rowCol : axisData[rowCol].start - 1; const fullRange = ws.getRange3(r1, c1, r2, c2); const unshiftMaps = {}; axisData[rowCol].start = startIndex; fullRange._foreachNoEmpty(function (cell, r, c) { const value = checkTypeCell(cell, true); const index = bHor ? c : r; if (!axisData[rowCol].data[value.type]) { axisData[rowCol].data[value.type] = new Map(); } const map = axisData[rowCol].data[value.type]; if (!map.has(value.value)) { map.set(value.value, [index]); } else { if (!unshiftMaps[value.type]) { unshiftMaps[value.type] = new Map(); } const unshiftMap = unshiftMaps[value.type]; if (!unshiftMap.has(value.value)) { unshiftMap.set(value.value, []); } const arr = unshiftMap.get(value.value); arr.push(index); } }); for (let i in unshiftMaps) { const unshiftMap = unshiftMaps[i]; unshiftMap.forEach(function (value, key) { const map = axisData[rowCol].data[i]; const prevArr = map.get(key); map.set(key, value.concat(prevArr)); }) } } if (endIndex > axisData[rowCol].end) { const c1 = bHor ? axisData[rowCol].end + 1: rowCol; const r1 = bHor ? rowCol : axisData[rowCol].end + 1; const c2 = bHor ? endIndex : rowCol; const r2 = bHor ? rowCol : endIndex; const fullRange = ws.getRange3(r1, c1, r2, c2); fullRange._foreachNoEmpty(function (cell, r, c) { const value = checkTypeCell(cell, true); const index = bHor ? c : r; if (index > axisData[rowCol].end) { if (!axisData[rowCol].data[value.type]) { axisData[rowCol].data[value.type] = new Map(); } const map = axisData[rowCol].data[value.type]; if (!map.has(value.value)) { map.set(value.value, []); } const arr = axisData[rowCol].data[value.type].get(value.value); arr.push(index); axisData[rowCol].end = index; } }); axisData[rowCol].end = endIndex; } } return axisData[rowCol].data[type]; }; TypedMapCache.prototype.changeAxisData = function (cellRowOrCol, axisData, oldValue, oldType, newValue, newType, bHor) { const staticIndex = bHor ? cellRowOrCol.nRow : cellRowOrCol.nCol; const changedIndex = bHor ? cellRowOrCol.nCol : cellRowOrCol.nRow; const colRowData = axisData[staticIndex]; if (colRowData && changedIndex >= colRowData.start && changedIndex <= colRowData.end) { if (oldValue !== null) { const mapOldType = colRowData.data[oldType]; if (mapOldType) { if (mapOldType.has(oldValue)) { const arr = mapOldType.get(oldValue); if (arr.length === 1) { mapOldType.delete(oldValue); } else { const newArr = []; for (let i = 0; i < arr.length; i += 1) { if (arr[i] !== changedIndex) { newArr.push(arr[i]); } } mapOldType.set(oldValue, newArr); } } } } if (newValue !== null) { const mapNewType = colRowData.data[newType]; if (mapNewType) { if (mapNewType.has(newValue)) { const arr = mapNewType.get(newValue); let newArr = []; if (arr.length === 1) { newArr = changedIndex > arr[0] ? [arr[0], changedIndex] :[changedIndex, arr[0]]; } else { let isAdded = false; for (let i = 0; i < arr.length; i += 1) { if (changedIndex < arr[i] && !isAdded) { isAdded = true; newArr.push(changedIndex); } newArr.push(arr[i]); } if (changedIndex > arr[arr.length - 1]) { newArr.push(changedIndex); } } mapNewType.set(newValue, newArr); } else { mapNewType.set(newValue, [changedIndex]); } } else { colRowData.data[newType] = new Map(); colRowData.data[newType].set(newValue, [changedIndex]) } } } }; TypedMapCache.prototype.changeData = function (cell, dataOld, dataNew) { const wsId = cell.ws.getId(); const data = this.data[wsId]; if (data) { const verticalData = data.vertical; const horizontalData = data.horizontal; let oldValue = null; let oldType = null; if (dataOld) { const oldCellValue = dataOld && dataOld.value; oldType = oldCellValue && oldCellValue.type; oldValue = oldType === cElementType.number ? oldCellValue.number : oldCellValue.text; if (oldValue && oldType === cElementType.string) { oldValue = oldValue.toLowerCase(); } } let newValue = null; let newType = null; if (dataNew) { newType = dataNew.type; newValue = dataNew.value; if (newValue && newType === cElementType.string) { newValue = newValue.toLowerCase(); } } if (oldType === newType && newValue === oldValue) { return; } this.changeAxisData(cell, verticalData, oldValue, oldType, newValue, newType, false); this.changeAxisData(cell, horizontalData, oldValue, oldType, newValue, newType, true); } }; /** * @constructor */ function VHLOOKUPCache(bHor) { this.cacheId = {}; this.cacheRanges = {}; this.bHor = bHor; this.sortedCache = new TypedMapCache(); this.typedCache = new TypedCache(); this.typedCacheValuesMap = new TypedCache(); } VHLOOKUPCache.prototype.calculate = function (arg, argument1) { let arg0 = arg[0], arg1 = arg[1], arg2 = arg[2]; let arg3 = arg[3] ? arg[3].tocBool().value : true; let opt_xlookup = arg[4] !== undefined; let opt_arg4, opt_arg5; if (opt_xlookup) { opt_arg4 = arg[4]; opt_arg5 = arg[5]; } let t = this, number, r, c, res = -1, count; if (!opt_xlookup) { if (cElementType.cell3D === arg2.type || cElementType.cell === arg2.type) { arg2 = arg2.getValue(); } else if (cElementType.array === arg2.type) { arg2 = arg2.getElementRowCol(0, 0); } else if (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type) { arg2 = arg2.cross(argument1); } if (cElementType.error === arg2.type) { return arg2; } number = arg2.getValue() - 1; if (isNaN(number)) { return new cError(cErrorType.bad_reference); } if (number < 0) { return new cError(cErrorType.wrong_value_type); } } if (cElementType.cell3D === arg0.type || cElementType.cell === arg0.type) { arg0 = arg0.getValue(); } if (cElementType.error === arg0.type) { return arg0; } let arg0Val; if (cElementType.array === arg0.type) { arg0Val = arg0.getElementRowCol(0, 0); } else { if (cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { arg0Val = arg0.cross(argument1); } else { arg0Val = arg0; } } if (cElementType.error === arg0Val.type) { return arg0Val; } if (cElementType.empty === arg0Val.type) { if (!opt_xlookup) { arg0Val = arg0Val.tocNumber(); } } let arg0ValType = arg0Val.type if (cElementType.array === arg1.type && !opt_xlookup) { let arrayToSearch; if (this.bHor) { arrayToSearch = arg1.getRow(0); } else { arrayToSearch = arg1.getCol(0); } if (arrayToSearch) { if (arg3) { // approximate(binary) search res = _func.lookupBinarySearch(arg0Val, arrayToSearch, false); } else { let searchValue = arg0Val.getValue(); if (arg0Val.type === cElementType.string) { searchValue = searchValue.toLowerCase(); } // exact (simple) search for (let i = 0; i < arrayToSearch.length; i++) { let elem = arrayToSearch[i]; if (elem.type !== arg0ValType) { continue; } let elemValue = elem.getValue(); if (elem.type === cElementType.string) { elemValue = elemValue.toLowerCase(); } if (elemValue === searchValue) { res = i; break } } } } if (-1 === res) { return new cError(cErrorType.not_available); } count = this.bHor ? arg1.getRowCount() : arg1.getCountElementInRow(); if (number > count - 1) { return new cError(cErrorType.bad_reference); } r = this.bHor ? number : res; c = this.bHor ? res : number; return arg1.getElementRowCol(r, c); } let range; if (cElementType.cell === arg1.type || cElementType.cell3D === arg1.type || cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type) { range = arg1.getRange(); } else if (cElementType.array === arg1.type && opt_xlookup) { let _cacheElem = {elements: []}; arg1.foreach(function (elem, r, c) { if (elem && elem.type === cElementType.string) { elem.value = elem.value.toLowerCase(); } _cacheElem.elements.push({v: elem, i: (t.bHor ? c : r)}); }); const elems = _cacheElem.elements; return this._calculate(arg0Val, null, opt_arg4, opt_arg5, elems, null, null, 0, elems.length - 1); } if (!range) { return new cError(cErrorType.bad_reference); } let bb = range.getBBox0(); count = this.bHor ? (bb.r2 - bb.r1) : (bb.c2 - bb.c1); if (number > count) { return new cError(cErrorType.bad_reference); } let ws = arg1.getWS(); r = this.bHor ? bb.r1 : bb.r2; c = this.bHor ? bb.c2 : bb.c1; let oSearchRange = ws.getRange3(bb.r1, bb.c1, r, c); res = this._get(oSearchRange, arg0Val, arg3, opt_arg4, opt_arg5); if (opt_xlookup) { return res; } if (-1 === res) { return new cError(cErrorType.not_available); } r = this.bHor ? bb.r1 + number : res; c = this.bHor ? res : bb.c1 + number; let resVal; arg1.getWS()._getCellNoEmpty(r, c, function (cell) { resVal = checkTypeCell(cell); }); if (cElementType.empty === resVal.type) { resVal = new cNumber(0); } return resVal; }; /** * @typedef {{number: {cElementType: Uint32Array}}} TypedCacheAxis */ /** * @constructor * @property {{string: {horizontal: TypedCacheAxis, vertical: TypedCacheAxis}}} data */ function TypedCache() { this.data = {}; } /** * @static * @param tmpArrays * @return {any} */ TypedCache.prototype.sortValues = function(tmpArrays) { for(let i in tmpArrays) { if (i === String(cElementType.number)) { tmpArrays[i].sort(function (a, b) { return a.v - b.v; }); } else { tmpArrays[i].sort(function (a, b) { const valueA = a.v; const valueB = b.v; return AscCommonExcel.stringCompare(valueA, valueB); }); } } return tmpArrays; } /** * @param {Worksheet} ws * @param {boolean} bHor * @param {number} rowCol * @param {cElementType} elementType * @param {(value: LookUpElement, i: number) => any} savingValueCallback * @param {(value: any) => number} tmpToTypedCallback * @param {(value: {cElementType: any[]}) => void} [tmpArrayCallback] * @return {Uint32Array} */ TypedCache.prototype.getCache = function(ws, bHor, rowCol, elementType, savingValueCallback, tmpToTypedCallback, tmpArrayCallback) { const wsId = ws.Get_Id(); if (!this.data[wsId]) { this.data[wsId] = {horizontal: {}, vertical: {}}; } /** @type {TypedCacheAxis} */ const axisData = bHor? this.data[wsId].horizontal : this.data[wsId].vertical; if (!axisData[rowCol]) { let container = {}; container[rowCol] = {}; this.generateCache(ws, bHor, rowCol, savingValueCallback, tmpToTypedCallback, tmpArrayCallback, container); axisData[rowCol] = container[rowCol]; } return axisData[rowCol][elementType]; }; /** * @param {Worksheet} ws * @param {boolean} bHor * @param {number} rowCol * @param {(value: LookUpElement, i: number) => any} savingValueCallback * @param {(value: any) => number} tmpToTypedCallback * @param {(value: {cElementType: any[]}) => void} [tmpArrayCallback] * @param {Object} [opt_container] - Optional container object to use instead of the default axis data structure. * @return {Uint32Array} */ TypedCache.prototype.generateCache = function(ws, bHor, rowCol, savingValueCallback, tmpToTypedCallback, tmpArrayCallback, opt_container) { const wsId = ws.Get_Id(); const axisData = opt_container ? opt_container : (bHor ? this.data[wsId].horizontal : this.data[wsId].vertical); const tmpArrays = {}; const c1 = bHor ? 0 : rowCol; const r1 = bHor ? rowCol : 0; const c2 = bHor ? AscCommon.gc_nMaxCol : rowCol; const r2 = bHor ? rowCol : AscCommon.gc_nMaxRow; const fullRange = ws.getRange3(r1, c1, r2, c2); fullRange._foreachNoEmpty(function (cell, r, c) { const value = checkTypeCell(cell, true); if (!tmpArrays[value.type]) { tmpArrays[value.type] = []; } const valueToSave = savingValueCallback(value, bHor ? c : r) tmpArrays[value.type].push(valueToSave); }); if (tmpArrayCallback) { tmpArrayCallback(tmpArrays); } return this.saveRange(axisData, rowCol, tmpArrays, tmpToTypedCallback); }; /** * @param {TypedCacheAxis} axisData * @param {number} rowColIndex * @param {{number: any[]}} tmpArrays * @param {(value: any) => number} tmpToTypedCallback * @return {{cElementType: Uint32Array}} */ TypedCache.prototype.saveRange = function(axisData, rowColIndex, tmpArrays, tmpToTypedCallback) { const res = axisData[rowColIndex]; for (let elementType in tmpArrays) { const elements = tmpArrays[elementType]; const typed = new Uint32Array(elements.length); for (let i = 0; i < elements.length; i += 1) { typed[i] = tmpToTypedCallback(elements[i]); } res[elementType] = typed; } return axisData[rowColIndex]; }; TypedCache.prototype.clean = function() { this.data = {}; } VHLOOKUPCache.prototype._get = function (range, valueForSearching, arg3Value, opt_arg4, opt_arg5) { var res, _this = this, wsId = range.getWorksheet().getId(); var opt_xlookup = opt_arg4 !== undefined; var sRangeName; AscCommonExcel.executeInR1C1Mode(false, function () { sRangeName = wsId + g_cCharDelimiter + range.getName(); }); const ws = range.getWorksheet(); var cacheElem = this.cacheId[sRangeName]; if (!cacheElem) { cacheElem = {results: {}}; } var sInputKey; if (!opt_xlookup) { sInputKey = valueForSearching.getValue() + g_cCharDelimiter + arg3Value + g_cCharDelimiter + valueForSearching.type; } else { sInputKey = valueForSearching.getValue() + g_cCharDelimiter + opt_arg4 + g_cCharDelimiter + opt_arg5 + g_cCharDelimiter + valueForSearching.type; } res = cacheElem.results[sInputKey]; const rowCol = this.bHor ? range.bbox.r1 : range.bbox.c1 if (!res) { const startIndex = this.bHor ? range.bbox.c1 : range.bbox.r1; const endIndex = this.bHor ? range.bbox.c2 : range.bbox.r2; cacheElem.results[sInputKey] = res = this._calculate(valueForSearching, arg3Value, opt_arg4, opt_arg5, null, ws, rowCol, startIndex, endIndex); } return res; }; VHLOOKUPCache.prototype._compareValues = function (val1, val2, op, opt_arg4) { if (opt_arg4 === 2 && val2.type === cElementType.string) { let matchingInfo = AscCommonExcel.matchingValue(val1); return AscCommonExcel.matching(val2, matchingInfo) } else { let res = _func[val1.type][val2.type](val1, val2, op, null, null, true); return res; } }; /** * Compare data types (numbers < strings < booleans < errors) * * @private * @param {LookUpElement} val1 - First value to compare * @param {LookUpElement} val2 - Second value to compare * @return {number} Negative if val1.type < val2.type, positive if val1.type > val2.type, zero if equal */ VHLOOKUPCache.prototype._compareTypes = function (val1, val2) { return val1.type - val2.type; }; /** * A simple linear traversal of a column or row. * When the specified XLOOKUP parameter (opt_arg4) is set, * it also retains the nearest larger or smaller element * than the specified value. * * @private * @param {LookUpElement} valueForSearching * @param {boolean} revert * @param {Worksheet} ws * @param {number} startIndex * @param {number} endIndex * @param {number} rowCol * @param {LookUpElement[]} [opt_array] * @param {number} [opt_arg4] * @return {number} */ VHLOOKUPCache.prototype._simpleSearch = function (valueForSearching, revert, ws, startIndex, endIndex, rowCol, opt_arg4, opt_array) { const t = this; let resultIndex = -1; let nearestIndex = -1; let nearestValue = null; const getValue = function (index) { if (opt_array) { return opt_array[index].v; } const cell = ws.getCell3(t.bHor ? rowCol : index, t.bHor ? index : rowCol); return checkTypeCell(cell, true); } const updateNearest = function(value, valueIndex) { if (opt_arg4 === 1 && t._compareTypes(value, valueForSearching) >= 0 && t._compareValues(value, valueForSearching, ">")) { if (nearestValue === null) { nearestValue = value; nearestIndex = valueIndex; } if (t._compareTypes(value, nearestValue) <= 0 && t._compareValues(value, nearestValue, "<")) { nearestIndex = valueIndex; nearestValue = value; } } if (opt_arg4 === -1 && t._compareTypes(value, valueForSearching) <= 0 && t._compareValues(value, valueForSearching,"<")) { if (nearestValue === null) { nearestValue = value; nearestIndex = valueIndex; } if (t._compareTypes(value, nearestValue) >= 0 && t._compareValues(value, nearestValue, ">")) { nearestIndex = valueIndex; nearestValue = value; } } } if (revert) { for (let i = endIndex; i >= startIndex; i -= 1) { const val = getValue(i); if (val.type === valueForSearching.type && this._compareValues(valueForSearching, val, "=", opt_arg4)) { resultIndex = opt_array ? opt_array[i].i : i; break; } if (opt_arg4 !== undefined) { updateNearest(val, i); } } } else { for (let i = startIndex; i <= endIndex; i += 1) { const val = getValue(i); if (val.type === valueForSearching.type && this._compareValues(valueForSearching, val, "=", opt_arg4)) { resultIndex = opt_array ? opt_array[i].i : i; break; } if (opt_arg4 !== undefined) { updateNearest(val, i); } } } if (resultIndex === -1) { resultIndex = nearestIndex; } return resultIndex; }; /** * Traversal of a pre-saved and sorted typed array using binary search. * Used only for exact match lookups in VLOOKUP, HLOOKUP, or XLOOKUP when opt_arg4 is set to 0. * @private * @param {Map} map * @param {LookUpElement} valueForSearching * @param {boolean} revert * @param {Worksheet} ws * @param {number} rowCol * @param {number} startIndex * @param {number} endIndex * @return {number} */ VHLOOKUPCache.prototype._indexedBinarySearch = function (map, valueForSearching, revert, ws, rowCol, startIndex, endIndex) { const searchValue = valueForSearching.value; if (!map.has(searchValue)) { return -1; } const arr = map.get(searchValue); let left = 0; let right = arr.length - 1; let resultIndex = -1; if (revert) { while (left <= right) { const mid = Math.floor((left + right) / 2); if (arr[mid] < startIndex) { left = mid + 1; } else if (arr[mid] > endIndex) { right = mid - 1; } else { resultIndex = arr[mid]; left = mid + 1; } } } else { while (left <= right) { const mid = Math.floor((left + right) / 2); if (arr[mid] < startIndex) { left = mid + 1; } else if (arr[mid] > endIndex) { right = mid - 1; } else { resultIndex = arr[mid]; right = mid - 1; } } } return resultIndex; }; /** * Binary search down a typed array that returns the position of the nearest element with the same type as the target element. * @private * @param {number} currentIndex * @param {Uint32Array} typed * @param {number} currentEnd * @return {number} */ VHLOOKUPCache.prototype._findNextCorrectType = function(currentIndex, typed, currentEnd) { let i = 0; let j = typed.length - 1; let result = currentIndex; // Binary search for first element >= currentIndex while (i <= j) { let k = Math.floor((i + j) / 2); if (typed[k] < currentIndex) { i = k + 1; } else { j = k - 1; } } // i > j and points to first element >= currentIndex if (i < typed.length && typed[i] <= currentEnd) { result = typed[i]; } return result; }; /** * A method that retrieves an element in a typed array by row or column index. * @private * @param {number} currentIndex * @param {Uint32Array} typed * @return {number} */ VHLOOKUPCache.prototype._findIndexInTyped = function(currentIndex, typed) { let i = 0; let j = typed.length - 1 let foundCurrent = -1; while (i <= j) { let k = Math.floor((i + j) / 2); if (typed[k] === currentIndex) { foundCurrent = k; break; } else if (typed[k] < currentIndex) { i = k + 1; } else { j = k - 1; } } return foundCurrent; }; /** * Finds the last occurrence of the same value in a sorted typed array. * This function is crucial for Excel's VLOOKUP/HLOOKUP approximate match behavior, * which requires returning the position of the LAST duplicate value when exact matches exist. * * Uses binary search to efficiently locate the rightmost occurrence of a value * within the specified range boundaries. * * @private * @param {number} currentIndexInTyped - The index in the typed array where the first match was found * @param {Uint32Array} typed - Sorted array containing worksheet row/column indices of cells with matching data types * @param {Uint32Array} typedMap - Mapping array where each element represents a unique value group identifier, used for efficient duplicate detection * @param {number} endIndex - The maximum allowed index boundary for the search range * @return {number} The worksheet row/column index of the last occurrence of the same value within the specified range */ VHLOOKUPCache.prototype._findLastSame = function(currentIndexInTyped, typed, typedMap, endIndex) { const currentValue = typedMap[currentIndexInTyped]; let i = currentIndexInTyped + 1; let j = typedMap.length - 1 let res = currentIndexInTyped; let resultIndex = typed[currentIndexInTyped]; while (i <= j) { let k = Math.floor((i + j) / 2); if (typedMap[k] > currentValue || typed[k] > endIndex) { j = k - 1; } else { i = k + 1; res = k; } } if (typedMap[res] === currentValue) { resultIndex = typed[res]; } return resultIndex; }; /** * Performs a binary search to find the position of a value in a sorted range or array. * This is the default binary search implementation used for approximate match lookups * in VLOOKUP and HLOOKUP functions when the range_lookup parameter is TRUE. * * The function searches for the largest value that is less than or equal to the lookup value. * If an exact match is found, it returns the position of the last occurrence of that value. * The search is optimized using typed arrays for better performance with large datasets. * * @private * @param {LookUpElement} valueForSearching - The value to search for in the range/array * @param {number} startIndex - The starting index of the search range (inclusive) * @param {number} endIndex - The ending index of the search range (inclusive) * @param {Worksheet} ws - The worksheet containing the data to search * @param {number} rowCol - The row (for VLOOKUP) or column (for HLOOKUP) index to search in * @param {Uint32Array} typed - Pre-sorted typed array containing indices of cells with matching data types * @param {Uint32Array} typedMap - Mapping array for efficient value comparison during binary search * @param {{i: number, v: LookUpElement}[]} [opt_array] - Optional array of objects with index and value properties, used when searching in arrays instead of worksheet ranges * @return {number} The index of the found element (0-based), or -1 if no suitable match is found. For approximate matches, returns the index of the largest value that is less than or equal to the search value. */ VHLOOKUPCache.prototype._defaultBinarySearch = function (valueForSearching, startIndex, endIndex, ws, rowCol, typed, typedMap, opt_array) { let i = startIndex; let j = endIndex; const t = this; const getValue = function (index) { if (opt_array) { return opt_array[index].v; } const cell = ws.getCell3(t.bHor ? rowCol : index, t.bHor ? index : rowCol); return checkTypeCell(cell, true); } let resultIndex = -1; while (i <= j) { let k = Math.floor((i + j) / 2); // Check if we've gone beyond the bounds of the typed array if (k > typed[typed.length - 1]) { j = k - 1; continue; } let val = getValue(k); // IMPORTANT FEATURE: Check data type matching // In Excel, only values of the same type are compared (number with number, string with string) if (val.type !== valueForSearching.type) { // If types don't match, find nearest element with correct type k = this._findNextCorrectType(k, typed, j); val = getValue(k); } // Main comparison logic: // If current value is greater than searched value OR types don't match, // narrow search to left half if (val.type !== valueForSearching.type || this._compareValues(val, valueForSearching, ">")) { j = k - 1; } else { // If current value is less than or equal to searched value, // remember it as potential result resultIndex = k; // KEY FEATURE: if exact match is found, // search for the LAST occurrence of this value in the range if (this._compareValues(val, valueForSearching, "=")) { // Find position in typed array let currentIndexInTyped = this._findIndexInTyped(k, typed); if (currentIndexInTyped !== -1) { // Search for last occurrence of the same value resultIndex = this._findLastSame(currentIndexInTyped, typed, typedMap, endIndex); } break; } // If no exact match, continue searching in right half // to find largest value that is <= searched value i = k + 1; } } // Final result processing: // If working with array, return original element index if (opt_array && resultIndex >= 0 && resultIndex < opt_array.length) { resultIndex = opt_array[resultIndex].i; } return resultIndex; }; /** * Performs a specialized binary search for XLOOKUP function with support for different match modes. * This function implements Excel's XLOOKUP binary search behavior, which differs from standard * VLOOKUP/HLOOKUP by supporting exact matches, next larger/smaller value searches, and reverse search direction. * @private * @param {LookUpElement} valueForSearching - The value to search for in the range/array * @param {boolean} revert - Whether to search in reverse direction (from end to start) * @param {number} opt_arg4 - Match mode: 0 = exact, 1 = exact or next larger, -1 = exact or next smaller * @param {number} startIndex - The starting index of the search range (inclusive) * @param {number} endIndex - The ending index of the search range (inclusive) * @param {Worksheet} ws - The worksheet containing the data to search (null if using opt_array) * @param {number} rowCol - The row (for VLOOKUP) or column (for HLOOKUP) index to search in * @param {LookUpElement[]} opt_array - Optional array of objects with index and value properties for array-based search * @return {number} The index of the found element, or -1 if no suitable match is found according to the specified match mode */ VHLOOKUPCache.prototype._xlookupBinarySearch = function (valueForSearching, revert, opt_arg4, startIndex, endIndex, ws, rowCol, opt_array) { let i = startIndex; let j = endIndex; const t = this; const getValue = function (index) { if (opt_array) { return opt_array[index].v; } const cell = ws.getCell3(t.bHor ? rowCol : index, t.bHor ? index : rowCol); return checkTypeCell(cell, true); } let resultNearest = -1; // Stores index of nearest value (for approximate matches) let resultIndex = -1; // Stores index of exact match while (i <= j) { const k = Math.floor((i + j) / 2); const val = getValue(k); // Compare data types first (numbers < strings < booleans < errors in Excel) const typeComparison = this._compareTypes(val, valueForSearching); if (typeComparison < 0 || (typeComparison === 0 && this._compareValues(val, valueForSearching, "<", opt_arg4))) { revert ? j = k - 1: i = k + 1; if (opt_arg4 === -1) { resultNearest = k; } } else { if (typeComparison === 0 && this._compareValues(valueForSearching, val, "=", opt_arg4)) { resultIndex = k; // Found exact match } if (opt_arg4 === 1) { resultNearest = k; } revert ? i = k + 1 : j = k - 1; } } if (opt_arg4 && resultIndex === -1) { resultIndex = resultNearest; } return resultIndex; }; /** * Retrieves or generates a sorted cache for exact match lookups in XLOOKUP. * Creates a typed array containing worksheet indices sorted by cell values, * used for efficient binary search operations when opt_arg4 is 0. * * @private * @param {Worksheet} ws - The worksheet containing the data * @param {number} rowCol - Row (VLOOKUP) or column (HLOOKUP) index * @param {cElementType} type - Data type to filter and cache * @return {Uint32Array} Sorted array of worksheet indices for the specified data type */ VHLOOKUPCache.prototype._getSortedCache = function(ws, rowCol, type, startIndex, endIndex, bHor) { return this.sortedCache.getData(ws, rowCol, type, startIndex, endIndex, bHor); }; /** * Retrieves or generates a typed cache containing worksheet indices for approximate match lookups. * Creates a typed array of row/column indices for cells of the specified data type, * used in VLOOKUP/HLOOKUP binary search operations. * * @private * @param {Worksheet} ws - The worksheet containing the data * @param {number} rowCol - Row (VLOOKUP) or column (HLOOKUP) index * @param {cElementType} type - Data type to filter and cache * @return {Uint32Array} Array of worksheet indices for cells of the specified type */ VHLOOKUPCache.prototype._getTypedCache = function(ws, rowCol, type) { return this.typedCache.getCache(ws, this.bHor, rowCol, type, function(value, index) { return index; }, function (value) { return value; }); }; /** * Retrieves or generates a values mapping cache for efficient duplicate detection in binary search. * Creates a typed array where each element represents a unique value group identifier, * used to quickly find the last occurrence of duplicate values in VLOOKUP/HLOOKUP operations. * * @private * @param {Worksheet} ws - The worksheet containing the data * @param {number} rowCol - Row (VLOOKUP) or column (HLOOKUP) index * @param {cElementType} type - Data type to filter and cache * @return {Uint32Array} Array mapping each cell position to its value group identifier */ VHLOOKUPCache.prototype._getTypedCacheValuesMap = function(ws, rowCol, type) { const t = this; let idx = 0; let lastCellValue = null; return this.typedCacheValuesMap.getCache(ws, this.bHor, rowCol, type, function(value) { if (lastCellValue !== null && t._compareValues(lastCellValue, value, '<>')) { idx += 1; } lastCellValue = value; return idx; }, function (value) { return value; }); }; /** * Main calculation method that routes to appropriate search algorithm based on function type and parameters. * Handles LOOKUP, VLOOKUP, HLOOKUP, and XLOOKUP search operations by selecting the optimal search strategy * (simple linear search, binary search, or specialized V/HLOOKUP binary search) based on the input parameters. * * @private * @param {LookUpElement} valueForSearching - The value to search for * @param {boolean} lookup - True for VLOOKUP/HLOOKUP approximate match, false for exact match * @param {number} [opt_arg4] - XLOOKUP match mode (0=exact, 1=exact or larger, -1=exact or smaller) * @param {number} [opt_arg5] - XLOOKUP search mode (1=first to last, -1=last to first, 2=binary ascending, -2=binary descending) * @param {LookUpElement[]} [opt_array] - Optional array for array-based search instead of worksheet * @param {Worksheet} ws - Worksheet containing the data (null if using opt_array) * @param {number} rowCol - Row (VLOOKUP) or column (HLOOKUP) index to search in * @param {number} startIndex - Starting index of search range * @param {number} endIndex - Ending index of search range * @return {number} Index of found element or -1 if not found */ VHLOOKUPCache.prototype._calculate = function (valueForSearching, lookup, opt_arg4, opt_arg5, opt_array, ws, rowCol, startIndex, endIndex) { const t = this; let res = -1; let xlookup = opt_arg4 !== undefined && opt_arg5 !== undefined; const revert = opt_arg5 < 0; if (valueForSearching.type === cElementType.string) { valueForSearching = new cString(valueForSearching.getValue().toLowerCase()); } if (xlookup) { if (Math.abs(opt_arg5) === 1) { if (opt_array) { res = this._simpleSearch(valueForSearching, revert, ws, startIndex, endIndex, rowCol,opt_arg4, opt_array); } else if (opt_arg4 === 0) { const sorted = this._getSortedCache(ws, rowCol, valueForSearching.type, startIndex, endIndex, this.bHor); if (sorted) { res = this._indexedBinarySearch(sorted, valueForSearching, revert, ws, rowCol, startIndex, endIndex); } } else { res = this._simpleSearch(valueForSearching, revert, ws,startIndex, endIndex, rowCol, opt_arg4); } } else if (Math.abs(opt_arg5) === 2) { res = this._xlookupBinarySearch(valueForSearching, revert, opt_arg4, startIndex, endIndex, ws, rowCol, opt_array); } } else if (lookup) { const typedCache = this._getTypedCache(ws, rowCol, valueForSearching.type); const typedCacheValuesMap = this._getTypedCacheValuesMap(ws, rowCol, valueForSearching.type); if (typedCache) { res = this._defaultBinarySearch(valueForSearching, startIndex, endIndex, ws, rowCol, typedCache, typedCacheValuesMap); } } else { if (opt_array) { res = this._simpleSearch(valueForSearching, false, ws, startIndex, endIndex, rowCol,opt_arg4, opt_array); } else { const sorted = this._getSortedCache(ws, rowCol, valueForSearching.type, startIndex, endIndex, this.bHor); if (sorted) { res = this._indexedBinarySearch(sorted, valueForSearching, false, ws, rowCol, startIndex, endIndex); } } } return res; }; VHLOOKUPCache.prototype.remove = function (cell, dataOld, dataNew) { var wsId = cell.ws.getId(); var cacheRange = this.cacheRanges[wsId]; if (cacheRange) { var oGetRes = cacheRange.get(new Asc.Range(cell.nCol, cell.nRow, cell.nCol, cell.nRow)); for (var i = 0, length = oGetRes.all.length; i < length; ++i) { var elem = oGetRes.all[i]; elem.data.results = {}; } } this.sortedCache.changeData(cell, dataOld, dataNew); }; VHLOOKUPCache.prototype.clean = function () { this.cacheId = {}; this.cacheRanges = {}; this.sortedCache = new TypedMapCache(); this.typedCache.clean(); this.typedCacheValuesMap.clean(); }; function MatchCache() { VHLOOKUPCache.call(this); } MatchCache.prototype = Object.create(VHLOOKUPCache.prototype); MatchCache.prototype.constructor = MatchCache; MatchCache.prototype.calculate = function (arg, _arg1) { let arg0 = arg[0], arg1 = arg[1], arg2, arg3; let isXMatch = arg[4]; let argError = cBaseFunction.prototype._checkErrorArg.call(this, arg); if (argError) { return argError; } if(isXMatch) { if (cElementType.empty === arg1.type) { return new cError(cErrorType.wrong_value_type); } // default values for XMatch arg2 = arg[2] ? arg[2] : new cNumber(0); arg3 = arg[3] ? arg[3] : new cNumber(1); } else { if (cElementType.array !== arg1.type && cElementType.cellsRange !== arg1.type && cElementType.cellsRange3D !== arg1.type) { return new cError(cErrorType.not_available); } // default values for Match arg2 = arg[2] ? arg[2] : new cNumber(1); arg3 = new cNumber(1); } if (cElementType.cellsRange3D === arg0.type || cElementType.cellsRange === arg0.type) { // TODO пересмотреть поведение функции при получении массива первым аргументом arg0 = isXMatch ? arg0.getFullArray().getElementRowCol(0,0) : arg0.cross(_arg1); if (cElementType.empty === arg0.type) { return new cError(cErrorType.not_available); } } else if (cElementType.array === arg0.type) { arg0 = arg0.getElementRowCol(0,0); } else if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { let _valueArg0 = arg0.getValue(); if (cElementType.error === _valueArg0.type) { return _valueArg0; } } else if (cElementType.error === arg0.type) { return arg0; } let a2Value; if (cElementType.array === arg2.type || cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type) { a2Value = arg2.getFirstElement(); if (!a2Value) { a2Value = new cEmpty(); } a2Value = a2Value.tocNumber(); } else if (cElementType.error === arg2.type) { return arg2; } else { if (cElementType.cell === arg2.type || cElementType.cell3D === arg2.type) { a2Value = arg2.getValue().tocNumber(); } a2Value = arg2.tocNumber(); } if (cElementType.error === a2Value.type) { return a2Value; } a2Value = Math.floor(a2Value.toNumber()); if (!(-1 === a2Value || 0 === a2Value || 1 === a2Value || 2 === a2Value)) { return new cError(cErrorType.wrong_value_type); } let a3Value; if (cElementType.array === arg3.type || cElementType.cellsRange === arg3.type || cElementType.cellsRange3D === arg3.type) { a3Value = arg3.getFirstElement(); if (!a3Value) { a3Value = new cEmpty(); } a3Value = a3Value.tocNumber(); } else if (cElementType.error === arg3.type) { return arg3; } else { if (cElementType.cell === arg3.type || cElementType.cell3D === arg3.type) { a3Value = arg3.getValue().tocNumber(); } a3Value = arg3.tocNumber(); } if (cElementType.error === a3Value.type) { return a3Value; } a3Value = Math.floor(a3Value.toNumber()); if(!(-2 === a3Value || -1 === a3Value || 1 === a3Value || 2 === a3Value)) { return new cError(cErrorType.wrong_value_type); } if(cElementType.error === arg1.type) { return arg1; } else if (cElementType.cellsRange !== arg1.type && cElementType.cellsRange3D !== arg1.type && cElementType.array !== arg1.type) { // if value is not array/range, make it array let arg1Array = new cArray(); if (cElementType.cell === arg1.type || cElementType.cell3D === arg1.type) { arg1 = arg1.getValue(); } arg1Array.addElement(arg1); arg1 = arg1Array; } if (cElementType.array === arg1.type) { arg1 = arg1.getMatrix(); let i, a1RowCount = arg1.length, a1ColumnCount = arg1[0].length, arr; if (a1RowCount > 1 && a1ColumnCount > 1) { return new cError(cErrorType.not_available); } else if (a1RowCount === 1 && a1ColumnCount >= 1) { arr = arg1[0]; } else { arr = []; for (i = 0; i < a1RowCount; i++) { arr[i] = arg1[i][0]; } } return isXMatch ? this._xMatchCalculate(arr, arg0, a2Value, a3Value, true) : this._calculate(arr, arg0, a2Value); } else if (cElementType.cell === arg1.type || cElementType.cell3D === arg1.type || cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type) { // add range.isonecell let oSearchRange = arg1.getRange(); if (!oSearchRange) { return new cError(cErrorType.bad_reference); } let a1RowCount = oSearchRange.bbox.r2 - oSearchRange.bbox.r1 + 1, a1ColumnCount = oSearchRange.bbox.c2 - oSearchRange.bbox.c1 + 1; let bHor = false; if (a1RowCount > 1 && a1ColumnCount > 1) { return new cError(cErrorType.not_available); } else if (a1RowCount === 1 && a1ColumnCount >= 1) { bHor = true; } return this._get(oSearchRange, arg0, a2Value, a3Value, bHor, isXMatch); } else { return new cError(cErrorType.wrong_value_type); } }; MatchCache.prototype._get = function (range, arg0, arg2, arg3, bHor, isXMatch) { let res, _this = this, wsId = range.getWorksheet().getId(), sRangeName = wsId + g_cCharDelimiter + range.getName(), cacheElem = this.cacheId[sRangeName], valueForSearching = arg0.getValue(), arg2Value = arg2, arg3Value = arg3; if (!cacheElem) { cacheElem = {elements: [], results: {}}; range._foreachNoEmpty(function (cell, r, c) { cacheElem.elements.push({v: checkTypeCell(cell), i: (bHor ? c - range.bbox.c1 : r - range.bbox.r1)}); }); this.cacheId[sRangeName] = cacheElem; let cacheRange = this.cacheRanges[wsId]; if (!cacheRange) { cacheRange = new AscCommonExcel.RangeDataManager(null); this.cacheRanges[wsId] = cacheRange; } cacheRange.add(range.getBBox0(), cacheElem); } let sInputKey = arg3Value ? (valueForSearching + g_cCharDelimiter + arg2Value + g_cCharDelimiter + arg3Value) : (valueForSearching + g_cCharDelimiter + arg2Value); res = cacheElem.results[sInputKey]; if(!res && isXMatch) { cacheElem.results[sInputKey] = res = this._xMatchCalculate(cacheElem.elements, arg0, arg2, arg3, false); } else if (!res) { cacheElem.results[sInputKey] = res = this._calculate(cacheElem.elements, arg0, arg2); } return res; }; MatchCache.prototype._calculate = function (arr, a0, a2) { let a2Value = a2, a0Type = a0.type, a0Value = a0.getValue(); if (!(cElementType.number === a0Type || cElementType.string === a0Type || cElementType.bool === a0Type || cElementType.error === a0Type || cElementType.empty === a0Type)) { if(cElementType.empty === a0Value.type) { a0Value = a0Value.tocNumber(); } a0Type = a0Value.type; a0Value = a0Value.getValue(); } let item, index = -1, curIndex; for (let i = 0; i < arr.length; ++i) { item = undefined !== arr[i].v ? arr[i].v : arr[i]; curIndex = undefined !== arr[i].i ? arr[i].i : i; if (item.type === a0Type) { if (0 === a2Value) { if (cElementType.string === a0Type) { if (AscCommonExcel.searchRegExp2(item.toString(), a0Value)) { index = curIndex; break; } } else { if (item == a0Value) { index = curIndex; break; } } } else if (1 === a2Value) { if (item <= a0Value) { index = curIndex; } else { break; } } else if (-1 === a2Value) { if (item >= a0Value) { index = curIndex; } else { break; } } } } return (-1 < index) ? new cNumber(index + 1) : new cError(cErrorType.not_available); }; MatchCache.prototype._xMatchCalculate = function (arr, a0, a2, a3, isArray) { let a0Type, a0Value, a2Value = a2, a3Value = a3, tempArr = arr.slice(); if (a0.type === cElementType.cell || a0.type === cElementType.cell3D) { a0Type = a0.getValue().type; a0Value = a0.getValue().getValue(); } else { a0Type = a0.type; a0Value = a0.getValue(); } if (!(cElementType.number === a0Type || cElementType.string === a0Type || cElementType.bool === a0Type || cElementType.error === a0Type || cElementType.empty === a0Type)) { if(cElementType.empty === a0Value.type) { a0Value = a0Value.tocNumber(); } a0Type = a0Value.type; a0Value = a0Value.getValue(); } let item, index = -1, curIndex, moreEqualArr, lessEqualArr; if (a3Value === 1 || a3Value === -1) { if (isArray) { // make array universal tempArr = tempArr.map(function (item, index) { return { v: item, i: index, } }); } if (a3Value === -1) { tempArr.reverse(); } // exact search for (let i = 0; i < tempArr.length; ++i) { item = tempArr[i].v ? tempArr[i].v : tempArr[i]; curIndex = tempArr[i].i ? tempArr[i].i : i; if (item.type === a0Type) { if (cElementType.string === a0Type) { if (AscCommonExcel.searchRegExp2(item.toString(), a0Value)) { index = curIndex; break; } } else { if (item.getValue() === a0Value) { index = curIndex; break; } } } } // approximate search if (a2Value === 1 && index === -1) { // looking for the smallest value of those that are greater than the looking moreEqualArr = tempArr.filter(function(item) { if (a0Type === cElementType.number) { if (item.v.type === a0Type) { return item.v.getValue() >= a0Value; } else if (item.v.type === cElementType.string) { return item.v; } } else if (a0Type === cElementType.string) { if (item.v.type === a0Type) { return item.v.getValue() >= a0Value; } else if (item.v.type === cElementType.bool) { return item.v; } } else if (a0Type === cElementType.bool) { if (item.v.type === a0Type) { return item.v.getValue() >= a0Value; } else if (item.v.type === cElementType.error || item.v.type === cElementType.empty) { return item.v; } } }); moreEqualArr.sort(function(a, b) { if (cElementType.number === a0Type) { if (cElementType.string === a.v.type && cElementType.string === b.v.type) { return AscCommonExcel.stringCompare(a.v.getValue(), b.v.getValue()); } else if (cElementType.number === a.v.type && cElementType.number === b.v.type) { return a.v.getValue() - b.v.getValue(); } else if (cElementType.error === a.v.type || cElementType.error === b.v.type) { return 0; } else { return cElementType.string === b.v.type ? -1 : 1; } } else if (cElementType.string === a0Type) { if (cElementType.string === a.v.type && cElementType.string === b.v.type) { return AscCommonExcel.stringCompare(a.v.getValue(), b.v.getValue()); } else if (cElementType.string === a.v.type || cElementType.string === b.v.type) { return 1; } else if (cElementType.bool === a.v.type && cElementType.bool === b.v.type) { if (a.v.getValue() !== b.v.getValue()) { return a.v.getValue() > b.v.getValue() ? 1 : -1; } return 0; } } else if (cElementType.bool === a0Type) { // cElementType.bool == 2, cElementType.empty == 4, cElementType.error == 3 if (a.v.type > b.v.type) { return 1; } if (a.v.type < b.v.type) { return -1; } if (a.v.type === cElementType.bool) { return 0; } else if (a.v.type === cElementType.error) { return 0; } else if (a.v.type === cElementType.empty) { return a.i - b.i; } } }); index = moreEqualArr.length > 0 ? moreEqualArr[0].i : index; } else if (a2Value === -1 && index === -1) { // looking for the largest value of those that are smaller than the looking if (cElementType.empty === a0Type) { // special search mode for empty cell where error > bool > string > number const priorityArr = tempArr.map(function(item) { const getTypePriority = function(element) { switch (element.v.type) { case cElementType.error: return 4; case cElementType.bool: return 3; case cElementType.string: return 2; case cElementType.number: return 1; default: return Infinity; } }; const typePriority = getTypePriority(item); return { v: item.v, i: item.i, priority: typePriority, }; }).sort(function(a,b) { if (a.priority === b.priority) { if (a.v.getValue() === b.v.getValue()) { if (a3Value === -1) { return a.i > b.i ? -1 : 1; } else if (a3Value === 1) { return 0; } } return a.v.getValue() > b.v.getValue() ? -1 : 1; } return a.priority > b.priority ? -1 : 1; }); index = priorityArr.length > 0 ? priorityArr[0].i : index; } else { lessEqualArr = tempArr.filter(function(item) { if (a0Type === cElementType.number) { if (item.v.type === a0Type) { return item.v.getValue() <= a0Value; } } else if (a0Type === cElementType.string) { if (item.v.type === a0Type) { return item.v.getValue() <= a0Value; } else if (item.v.type === cElementType.number) { return item.v; } } else if (a0Type === cElementType.bool) { if (item.v.type === a0Type) { return item.v <= a0Value; } else if (item.v.type === cElementType.string) { return item.v; } } }); lessEqualArr.sort(function(a, b) { if (cElementType.number === a0Type) { return b.v.getValue() - a.v.getValue(); } else if (cElementType.string === a0Type) { if (cElementType.string === a.v.type && cElementType.string === b.v.type) { return AscCommonExcel.stringCompare(a.v.getValue(), b.v.getValue()); } else if (cElementType.string === a.v.type || cElementType.string === b.v.type) { return 1; } else if (cElementType.number === a.v.type && cElementType.number === b.v.type) { return a.v.getValue() > b.v.getValue() ? -1 : 1; } else { return 0; } } else if (cElementType.bool === a0Type) { if (cElementType.bool === a.v.type && cElementType.bool === b.v.type) { return -1; } else if (cElementType.string === a.v.type && cElementType.string === b.v.type) { if (a.v.getValue() === b.v.getValue()) { if (a3Value === 1) { return a.i - b.i; } else if(a3Value === -1) { return b.i - a.i; } } return a.v.getValue() > b.v.getValue() ? -1 : 1; } else if (cElementType.bool === a.v.type) { return -1; } else { return 0; } } }); index = lessEqualArr.length > 0 ? lessEqualArr[0].i : index; } } } else if (a3Value === 2) { if (2 === a2Value) { // wildcard match(err) return new cError(cErrorType.wrong_value_type); } index = XBinarySearch(a0Value, tempArr, a2Value, false); } else if (a3Value === -2) { if (2 === a2Value) { // wildcard match(err) return new cError(cErrorType.wrong_value_type); } index = XBinarySearch(a0Value, tempArr, a2Value, true); } return (-1 < index) ? new cNumber(index + 1) : new cError(cErrorType.not_available); }; function LOOKUPCache() { VHLOOKUPCache.call(this); } LOOKUPCache.prototype = Object.create(VHLOOKUPCache.prototype); LOOKUPCache.prototype.constructor = LOOKUPCache; LOOKUPCache.prototype.calculate = function (arg) { function compareValues (val1, val2) { if (val1.type !== val2.type) { return false; } else if (val1.type === cElementType.string) { let str1 = val1.toString().toLowerCase(); let str2 = val2.toString().toLowerCase(); if (str1 >= str2) { return true } return false; } else if (val1.value >= val2.value) { return true } return false; } function arrFinder(arr) { if (arr.getRowCount() > arr.getCountElementInRow()) { // searching in the first column resC = arr.getCountElementInRow() > 1 ? 1 : 0; let arrCol = arr.getCol(0); resR = _func.lookupBinarySearch(arg0, arrCol, false); } else { // searching in the first row resR = arr.getRowCount() > 1 ? 1 : 0; let arrRow = arr.getRow(0); resC = _func.lookupBinarySearch(arg0, arrRow, false); } } function findIndexInArray(lookingValue, arr) { let resIndex = -1; if (arr.getRowCount() >= arr.getCountElementInRow()) { // searching in the first column and return elem with same position (index) from last column let arrCol = arr.getCol(0); resIndex = _func.lookupBinarySearch(lookingValue, arrCol, false); } else { // searching in the first row and return elem with same position (index) from last row let arrRow = arr.getRow(0); resIndex = _func.lookupBinarySearch(lookingValue, arrRow, false); } return resIndex; } // .calculate - base args checks, dimensions checks and got to ._get func // ._get - get noEmpty elements from range and add to cache, then get typed array and add calculation result to the cache // ._calculate - calculate result(binary search) let arrayMode = arg.length === 2 ? true : false; let arg0 = arg[0], arg1 = arg[1], arg2 = 2 === arg.length ? arg1 : arg[2], resC = -1, resR = -1, t = this, res, arg2SingleElem; /* arg0 (looking value) check */ if (cElementType.cellsRange === arg0.type || cElementType.cellsRange3D === arg0.type) { if (arg0.isOneElement()) { arg0 = arg0.getFirstElement(); } else { arg0 = new cError(cErrorType.wrong_value_type); } } else if (cElementType.array === arg0.type) { arg0 = arg0.getElementRowCol(0, 0); } if (cElementType.cell === arg0.type || cElementType.cell3D === arg0.type) { arg0 = arg0.getValue(); } if (cElementType.error === arg0.type) { return arg0; } if (cElementType.empty === arg0.type) { arg0 = arg0.tocNumber(); } /* arg1 (looking array) check */ if (arg1.type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } if ((arg1.type === cElementType.cellsRange || arg1.type === cElementType.cellsRange3D || arg1.type === cElementType.array) && arg1.isOneElement()) { arg1 = arg1.getFirstElement(); } if (arg1.type === cElementType.error) { return arg1; } /* arg2 (return array) check */ if (arg2.type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } if ((arg2.type === cElementType.cellsRange || arg2.type === cElementType.cellsRange3D || arg2.type === cElementType.array) && arg2.isOneElement()) { arg2 = arg2.getFirstElement(); } if (arg2.type === cElementType.error) { return arg2; } if (!(cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type || cElementType.array === arg2.type)) { arg2SingleElem = arg2; } // variants check: /* arg1 is not array/area */ if ( !(cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type || cElementType.array === arg1.type) ) { if (arg1.type === cElementType.cell || arg1.type === cElementType.cell3D) { arg1 = arg1.getValue(); } if (arg1.type === cElementType.error) { return arg1; } if (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type || cElementType.array === arg2.type) { if (cElementType.array === arg2.type && (arg2.getRowCount() > 1 && arg2.getCountElementInRow() > 1)) { return new cError(cErrorType.not_available); } arg2 = arg2.getFirstElement(); } if (arg2.type === cElementType.cell || arg2.type === cElementType.cell3D) { arg2 = arg2.getValue(); } if (arg2.type === cElementType.error) { return arg2; } // compare values let res = compareValues(arg0, arg1); if (res) { return arg2; } return new cError(cErrorType.wrong_value_type); } if (cElementType.array === arg1.type && cElementType.array === arg2.type) { /* arg1 & arg2 is arrays */ let lookingIndex = -1; let arg1Rows = arg1.getRowCount(), arg2Rows = arg2.getRowCount(), arg1Cols = arg1.getCountElementInRow(), arg2Cols = arg2.getCountElementInRow(); /* check two dimensional array but only in vector form */ if (!arrayMode && (((arg1Rows < arg2Rows) && arg2Cols > 1) || (arg1Rows > 1 && arg1Cols > 1 && arg2Rows > 1 && arg2Cols > 1))) { return new cError(cErrorType.not_available); } // arrFinder(arg1); lookingIndex = findIndexInArray(arg0, arg1); let byRow, byCol; if (arg1Rows >= arg1Cols) { // search by col byCol = true } else { byRow = true } if (lookingIndex === -1) { return new cError(cErrorType.not_available); } if (arg2Rows === 1) { if (lookingIndex >= arg2.getCountElementInRow()) { return new cError(cErrorType.not_available) } return arg2.getElementRowCol(0, lookingIndex); } else if (arg2Cols === 1) { if (lookingIndex >= arg2.rowCount) { return new cError(cErrorType.not_available) } return arg2.getElementRowCol(lookingIndex, 0); } else { // return from last row/col return arg2.getElementRowCol(byCol ? lookingIndex : arg2Rows - 1, byCol ? arg2Cols - 1 : lookingIndex); } } else if (cElementType.array === arg1.type || cElementType.array === arg2.type) { /* arg1 || arg2 is array */ let lookingIndex = -1; let _arg1 = cElementType.array === arg1.type ? arg1 : arg1.getFullArray(); // !!! slow // find resR & resC position in array/area // arrFinder(_arg1); // old solution with finding resR and resC lookingIndex = findIndexInArray(arg0, _arg1); if (lookingIndex < 0) { return new cError(cErrorType.not_available); } if (arg2SingleElem) { return lookingIndex === 0 ? arg2SingleElem : new cError(cErrorType.not_available); } let byRow, byCol; let dimension = arg2.getDimensions ? arg2.getDimensions() : null; if (dimension) { if (dimension.row >= dimension.col) { byCol = true } else { byRow = true } if (dimension.row === 1) { return arg2.getValueByRowCol ? arg2.getValueByRowCol(0, lookingIndex, true) : arg2.getElementRowCol(0, lookingIndex); } else if (dimension.col === 1) { return arg2.getValueByRowCol ? arg2.getValueByRowCol(lookingIndex, 0, true) : arg2.getElementRowCol(lookingIndex, 0); } else { return new cError(cErrorType.not_available); } } else { return new cError(cErrorType.not_available); } } else { /* arg1 & arg2 is area */ if (cElementType.cellsRange3D === arg1.type && !arg1.isSingleSheet() || cElementType.cellsRange3D === arg2.type && !arg2.isSingleSheet()) { return new cError(cErrorType.not_available); } let arg2RowsLength; let bbox; if (cElementType.cellsRange3D === arg1.type) { bbox = arg1.bbox; } else if (cElementType.cellsRange === arg1.type) { bbox = arg1.range.bbox; } if (cElementType.cellsRange3D === arg2.type) { arg2RowsLength = arg2.bbox.r2 - arg2.bbox.r1 + 1; } else if (cElementType.cellsRange === arg2.type) { arg2RowsLength = arg2.range.bbox.r2 - arg2.range.bbox.r1 + 1; } let bVertical = bbox.r2 - bbox.r1 >= bbox.c2 - bbox.c1; let index; if(index === undefined) { let ws = arg1.getWS(), r = bVertical ? bbox.r2 : bbox.r1, c = bVertical ? bbox.c1 : bbox.c2; let oSearchRange = ws.getRange3(bbox.r1, bbox.c1, r, c); t.bHor = bVertical ? false : true; index = this._get(/* searchRange */oSearchRange, arg0, true); if (index === undefined || index < 0) { return new cError(cErrorType.not_available); } } let ws = cElementType.cellsRange3D === arg1.type && arg1.isSingleSheet() ? arg1.getWS() : arg1.ws; if (cElementType.cellsRange3D === arg1.type) { if (arg1.isSingleSheet()) { ws = arg1.getWS(); } else { return new cError(cErrorType.bad_reference); } } else if (cElementType.cellsRange === arg1.type) { ws = arg1.getWS(); } else { return new cError(cErrorType.bad_reference); } if (arg2SingleElem) { return arg2SingleElem; } AscCommonExcel.executeInR1C1Mode(false, function () { let b = arg2.getBBox0(); if (2 === arg.length) { if (!bVertical) { // res = new cRef(ws.getCell3(b.r2, b.c1 + index).getName(), ws); res = new cRef(ws.getCell3(b.r2, Math.abs(bbox.c1 - b.c1) + index).getName(), ws); } else { // res = new cRef(ws.getCell3(b.r1 + index, b.c2).getName(), ws); res = new cRef(ws.getCell3(Math.abs(bbox.r1 - b.r1) + index, b.c2).getName(), ws); } } else { if (1 === arg2RowsLength) { // res = new cRef(ws.getCell3(b.r2, b.c1 + index).getName(), ws); res = new cRef(ws.getCell3(b.r2, Math.abs(bbox.c1 - b.c1) + index).getName(), ws); } else { // res = new cRef(ws.getCell3(b.r1 + index, b.c2).getName(), ws); res = new cRef(ws.getCell3(Math.abs(bbox.r1 - b.r1) + index, b.c2).getName(), ws); } } }); return res; } }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cVLOOKUP() { } cVLOOKUP.prototype = Object.create(cBaseFunction.prototype); cVLOOKUP.prototype.constructor = cVLOOKUP; cVLOOKUP.prototype.name = 'VLOOKUP'; cVLOOKUP.prototype.argumentsMin = 3; cVLOOKUP.prototype.argumentsMax = 4; cVLOOKUP.prototype.arrayIndexes = {/*0: 1,*/ 1: 1, /*2: 1*/}; cVLOOKUP.prototype.numFormat = AscCommonExcel.cNumFormatNone; // cVLOOKUP.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.value_replace_area; cVLOOKUP.prototype.argumentsType = [argType.any, argType.number, argType.number, argType.logical]; cVLOOKUP.prototype.Calculate = function (arg) { let retArr = new cArray(); let error = false; if (arg[0].type === cElementType.array) { if (arg[2] && arg[2].type === cElementType.cellsRange || arg[2].type === cElementType.cellsRange3D || arg[2].type === cElementType.array) { arg[2] = arg[2].getValue2(0,0); } let dimension = arg[0].getDimensions(); for (let r = 0; r < dimension.row; r++) { retArr.addRow(); for (let c = 0; c < dimension.col; c++) { retArr.addElement(g_oVLOOKUPCache.calculate([arg[0].getValue2(r, c), arg[1], arg[2], arg[3]], arguments[1])); } } return retArr; } if (arg[2] && (arg[2].type === cElementType.array)) { let dimension = arg[2].getDimensions(); for (let r = 0; r < dimension.row; r++) { retArr.addRow(); for (let c = 0; c < dimension.col; c++) { if (!error) { let res = g_oVLOOKUPCache.calculate([arg[0], arg[1], arg[2].getValue2(r, c), arg[3]], arguments[1]); if (res.type === cElementType.error) { error = true; } retArr.addElement(res); } else { break; } } } return error ? new cError(cErrorType.bad_reference) : retArr } return g_oVLOOKUPCache.calculate(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cXLOOKUP() { } //***array-formula*** cXLOOKUP.prototype = Object.create(cBaseFunction.prototype); cXLOOKUP.prototype.constructor = cXLOOKUP; cXLOOKUP.prototype.name = 'XLOOKUP'; cXLOOKUP.prototype.argumentsMin = 3; cXLOOKUP.prototype.argumentsMax = 6; cXLOOKUP.prototype.arrayIndexes = {1: 1, 2: 1}; cXLOOKUP.prototype.argumentsType = [argType.any, argType.reference, argType.reference, argType.any, argType.number, argType.number]; cXLOOKUP.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.value_replace_area; cXLOOKUP.prototype.isXLFN = true; cXLOOKUP.prototype.Calculate = function (arg) { let arg0 = arg[0], arg1 = arg[1], arg2 = arg[2]; let arg3 = arg[3], arg4 = arg[4], arg5 = arg[5]; if (cElementType.error === arg0.type) { return arg0; } if (cElementType.cell === arg1.type) { arg1 = arg1.getValue(); } if (cElementType.number === arg1.type || cElementType.string === arg1.type || cElementType.error === arg1.type || cElementType.cell === arg1.type) { const arr1 = new cArray(); arr1.addElement(arg1); arg1 = arr1; const arr2 = new cArray(); arr2.addElement(arg2); arg2 = arr2; } if (!((cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type || cElementType.array === arg1.type) && (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type || cElementType.array === arg2.type))) { return new cError(cErrorType.not_available); } //[if_not_found] if (!arg3 || arg3.type === cElementType.empty) { arg3 = new cError(cErrorType.not_available); } //arg4/arg5 - только число //[match_mode] //0 - If none found, return #N/A. This is the default. //-1 - If none found, return the next smaller item. //1 - If none found, return the next larger item. //2 - A wildcard match where *, ?, and ~ //TODO если аргумент массив/area - результат становится размером с этот массив //TODO либо обрабатывать выше и вызывать эту функцию для каждого элемента массива, либо здесь вычислять этот массив if (!arg4) { arg4 = new cNumber(0); } arg4 = arg4.tocNumber(); if (cElementType.error === arg4.type) { return arg4; } else { arg4 = parseInt(arg4.toNumber()); if (!(arg4 >= -1 && arg4 <= 2)) { return new cError(cErrorType.wrong_value_type); } } //TODO если аргумент массив/area - результат становится размером с этот массив //TODO либо обрабатывать выше и вызывать эту функцию для каждого элемента массива, либо здесь вычислять этот массив //[search_mode] //1 - Perform a search starting at the first item. This is the default. //-1 - Perform a reverse search starting at the last item. //2 - Perform a binary search that relies on lookup_array being sorted in ascending order. If not sorted, invalid results will be returned. //-2 - Perform a binary search that relies on lookup_array being sorted in descending order. If not sorted, invalid results will be returned. if (!arg5) { arg5 = new cNumber(1); } arg5 = arg5.tocNumber(); if (cElementType.error === arg5.type) { return arg5; } else { arg5 = parseInt(arg5.toNumber()); if (!(arg5 >= -2 && arg5 <= 2)) { return new cError(cErrorType.wrong_value_type); } } //массив arg1 должен содержать 1 строку или 1 столбец let dimensions1 = arg1.getDimensions(); let dimensions2 = arg2.getDimensions(); let bVertical = null; if (dimensions1 && dimensions2) { if (dimensions1.col === 1 && dimensions2.row === dimensions1.row) { bVertical = true; } else if (dimensions1.row === 1 && dimensions2.col === dimensions1.col) { bVertical = false; } } if (bVertical === null) { return new cError(cErrorType.wrong_value_type); } else { let res, arrayOffset = 0; if (bVertical) { res = g_oVLOOKUPCache.calculate([arg0, arg1, null, null, arg4, arg5], arguments[1]); } else { res = g_oHLOOKUPCache.calculate([arg0, arg1, null, null, arg4, arg5], arguments[1]); } if (res === -1) { return arg3; } else { if (res.type && res.type === cElementType.error) { return res; } if (arg1.type === cElementType.array) { if (dimensions2.bbox) { arrayOffset = bVertical ? dimensions2.bbox.r1 : dimensions2.bbox.c1; res += arrayOffset; } } //возвращаем из arg2 строку или столбец let _startRange = 0; if (dimensions2.bbox) { _startRange = bVertical ? dimensions2.bbox.r1 : dimensions2.bbox.c1; } else if (dimensions1.bbox) { _startRange = bVertical ? dimensions1.bbox.r1 : dimensions1.bbox.c1; } if (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type) { let _r1 = !bVertical ? dimensions2.bbox.r1 : res - _startRange + dimensions2.bbox.r1; let _cl = bVertical ? dimensions2.bbox.c1 : res - _startRange + dimensions2.bbox.c1; let _r2 = !bVertical ? dimensions2.bbox.r2 : res - _startRange + dimensions2.bbox.r1; let _c2 = bVertical ? dimensions2.bbox.c2 : res - _startRange + dimensions2.bbox.c1; let _range = new Asc.Range(_cl, _r1, _c2, _r2); let _res; let rangeName; AscCommonExcel.executeInR1C1Mode(false, function () { rangeName = _range.getName(); }); if (cElementType.cellsRange === arg2.type) { _res = _range.isOneCell() ? new cRef(rangeName, arg2.getWS()) : new cArea(rangeName, arg2.getWS()); } else { _res = _range.isOneCell() ? new cRef3D(rangeName, arg2.getWS()) : new cArea3D(rangeName, arg2.getWS()); } return _res; } else { let _length = !bVertical ? dimensions2.row : dimensions2.col; let _array = new cArray(); for (let i = 0; i < _length; i++) { let _row = !bVertical ? i : res - _startRange; let _col = bVertical ? i : res - _startRange; let _elem = arg2.getElementRowCol ? arg2.getElementRowCol(_row, _col) : arg2.getValueByRowCol(_row, _col, true); if (!bVertical) { _array.addRow(); _array.addElement(_elem); } else { _array.addElement(_elem); } } return _array; } } } }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cVSTACK() { } //***array-formula*** cVSTACK.prototype = Object.create(cBaseFunction.prototype); cVSTACK.prototype.constructor = cVSTACK; cVSTACK.prototype.name = 'VSTACK'; cVSTACK.prototype.argumentsMin = 1; cVSTACK.prototype.numFormat = AscCommonExcel.cNumFormatNone; cVSTACK.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cVSTACK.prototype.argumentsType = [[argType.reference]]; cVSTACK.prototype.isXLFN = true; cVSTACK.prototype.Calculate = function (arg) { let unionArray; for (let i = 0; i < arg.length; i++) { let matrix; if (arg[i].type === cElementType.cellsRange || arg[i].type === cElementType.array || arg[i].type === cElementType.cell || arg[i].type === cElementType.cell3D) { matrix = arg[i].getMatrix(); } else if (arg[i].type === cElementType.cellsRange3D) { if (arg[i].isSingleSheet()) { matrix = arg[i].getMatrix()[0]; } else { return new cError(cErrorType.bad_reference); } } else if (arg[i].type === cElementType.error) { return arg[i]; } else if (arg[i].type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } else { matrix = [[arg[i]]]; } //добавляем по строкам for (let j = 0; j < matrix.length; j++) { if (matrix[j]) { if (!unionArray) { unionArray = []; } unionArray.push(matrix[j]); } } } if (unionArray) { let res = new cArray(); res.fillFromArray(unionArray); res.fillMatrix(new cError(cErrorType.not_available)); return res; } else { return new cError(cErrorType.wrong_value_type); } }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cHSTACK() { } //***array-formula*** cHSTACK.prototype = Object.create(cBaseFunction.prototype); cHSTACK.prototype.constructor = cHSTACK; cHSTACK.prototype.name = 'HSTACK'; cHSTACK.prototype.argumentsMin = 1; cHSTACK.prototype.numFormat = AscCommonExcel.cNumFormatNone; cHSTACK.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cHSTACK.prototype.argumentsType = [[argType.reference]]; cHSTACK.prototype.isXLFN = true; cHSTACK.prototype.Calculate = function (arg) { let unionArray; let startCol = 0; for (let i = 0; i < arg.length; i++) { let matrix; if (arg[i].type === cElementType.cellsRange || arg[i].type === cElementType.array || arg[i].type === cElementType.cell || arg[i].type === cElementType.cell3D) { matrix = arg[i].getMatrix(); } else if (arg[i].type === cElementType.cellsRange3D) { if (arg[i].isSingleSheet()) { matrix = arg[i].getMatrix()[0]; } else { return new cError(cErrorType.bad_reference); } } else if (arg[i].type === cElementType.error) { return arg[i]; } else if (arg[i].type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } else { matrix = [[arg[i]]]; } let maxColCount = 0; for (let j = 0; j < matrix.length; j++) { if (matrix[j]) { maxColCount = Math.max(maxColCount, matrix[j].length); for (let k = 0; k < matrix[j].length; k++) { if (matrix[j][k]) { if (!unionArray) { unionArray = []; } if (!unionArray[j]) { unionArray[j] = []; } unionArray[j][k + startCol] = matrix[j][k]; } } } } startCol += maxColCount; } if (unionArray) { let res = new cArray(); res.fillFromArray(unionArray); res.fillMatrix(new cError(cErrorType.not_available)); return res; } else { return new cError(cErrorType.wrong_value_type); } }; function toRowCol(arg, argument1, toCol) { var argError = cBaseFunction.prototype._checkErrorArg.call(this, arg); if (argError) { return argError; } //из документации: //Excel returns a #VALUE! when an array constant contains one or more numbers that are not a whole number. //не повторил в мс let arg1 = arg[0]; if (arg1.type === arg1.empty) { return new cError(cErrorType.wrong_value_type); } if (arg1.type === cElementType.cellsRange3D) { arg1 = arg1.toArray()[0]; } else { arg1 = arg1.toArray(); } //Excel returns a #NUM when array is too large. let elemCount = arg1.length * arg1[0].length; if (elemCount > 1048578) { return new cError(cErrorType.not_available); } //0 Keep all values (default) //1 Ignore blanks //2 Ignore errors //3 Ignore blanks and errors let arg2 = arg[1] ? arg[1] : new cNumber(0); if (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type) { arg2 = arg2.cross(argument1); } else if (cElementType.array === arg2.type) { arg2 = arg2.getElementRowCol(0, 0); } arg2 = arg2.tocNumber(); if (arg2.type === cElementType.error) { return arg2; } arg2 = arg2.toNumber(); //scan_by_column let arg3 = arg[2] ? arg[2] : new cBool(false); if (cElementType.cellsRange === arg3.type || cElementType.cellsRange3D === arg3.type) { arg3 = arg3.cross(argument1); } else if (cElementType.array === arg3.type) { arg3 = arg3.getElementRowCol(0, 0); } arg3 = arg3.tocBool(); if (arg3.type === cElementType.error) { return arg3; } arg3 = arg3.toBool(); let arg1_array = new cArray(); arg1_array.fillFromArray(arg1); var res = new cArray(); arg1_array.foreach2(function (elem, r, c) { if (elem) { let needAdd = true; switch (arg2) { case 0: break; case 1: if (elem.type === cElementType.empty) { needAdd = false; } break; case 2: if (elem.type === cElementType.error) { needAdd = false; } break; case 3: if (elem.type === cElementType.error || elem.type === cElementType.empty) { needAdd = false; } break; } if (needAdd) { if (toCol) { res.addRow(); } res.addElement(elem); } } }, arg3); return res; } /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cTOROW() { } //***array-formula*** cTOROW.prototype = Object.create(cBaseFunction.prototype); cTOROW.prototype.constructor = cTOROW; cTOROW.prototype.name = 'TOROW'; cTOROW.prototype.argumentsMin = 1; cTOROW.prototype.argumentsMax = 3; cTOROW.prototype.numFormat = AscCommonExcel.cNumFormatNone; cTOROW.prototype.arrayIndexes = {0: 1}; cTOROW.prototype.argumentsType = [argType.reference, argType.number, argType.bool]; cTOROW.prototype.isXLFN = true; cTOROW.prototype.Calculate = function (arg) { return toRowCol(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cTOCOL() { } //***array-formula*** cTOCOL.prototype = Object.create(cBaseFunction.prototype); cTOCOL.prototype.constructor = cTOCOL; cTOCOL.prototype.name = 'TOCOL'; cTOCOL.prototype.argumentsMin = 1; cTOCOL.prototype.argumentsMax = 3; cTOCOL.prototype.numFormat = AscCommonExcel.cNumFormatNone; cTOCOL.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cTOCOL.prototype.argumentsType = [argType.reference, argType.number, argType.bool]; cTOCOL.prototype.isXLFN = true; cTOCOL.prototype.Calculate = function (arg) { return toRowCol(arg, arguments[1], true); }; function wrapRowsCols(arg, argument1, toCol) { let argError = cBaseFunction.prototype._checkErrorArg.call(this, arg); if (argError) { return argError; } let arg1 = arg[0]; if (arg1.type === cElementType.empty) { return new cError(cErrorType.wrong_value_type); } let arg0Dimensions = arg1.getDimensions(); if (arg0Dimensions.col > 1 && arg0Dimensions.row > 1) { return new cError(cErrorType.wrong_value_type); } let arg2 = arg[1]; if (cElementType.cellsRange === arg2.type || cElementType.cellsRange3D === arg2.type) { arg2 = arg2.getValueByRowCol(0,0); } else if (cElementType.array === arg2.type) { arg2 = arg2.getElementRowCol(0, 0); } else if (arg2.type === cElementType.empty) { return new cError(cErrorType.not_numeric); } if (!arg2) { arg2 = new cEmpty(); } arg2 = arg2.tocNumber(); if (arg2.type === cElementType.error) { return arg2; } arg2 = arg2.toNumber(); if (arg2 < 1) { return new cError(cErrorType.not_numeric); } let arg3 = arg[2] ? arg[2] : new cError(cErrorType.not_available); if (cElementType.cellsRange === arg3.type || cElementType.cellsRange3D === arg3.type) { arg3 = arg3.getValueByRowCol(0,0,true); } else if (cElementType.array === arg3.type) { arg3 = arg3.getElementRowCol(0, 0); } let res = new cArray(); if (cElementType.cellsRange === arg1.type || cElementType.cellsRange3D === arg1.type || cElementType.array === arg1.type) { let rowCounter = 0, colCounter = 0; arg1.foreach2(function (val) { if (toCol) { /*if (res.array.l && res.array[res.array.length - 1].length === arg2) { res.addRow(); }*/ if (rowCounter === arg2) { colCounter++; rowCounter = 0; } if (!res.array[rowCounter]) { res.array[rowCounter] = []; } res.array[rowCounter][colCounter] = val; rowCounter++; } else { if (res.array[res.array.length - 1] && res.array[res.array.length - 1].length === arg2) { res.addRow(); } res.addElement(val); } }); if (toCol) { res.recalculate(); } res.fillMatrix(arg3); } else { if (cElementType.cell === arg1.type || cElementType.cell3D === arg1.type) { arg1 = arg1.getValue(); } res.addElement(arg1); } return res; } /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cWRAPROWS() { } //***array-formula*** cWRAPROWS.prototype = Object.create(cBaseFunction.prototype); cWRAPROWS.prototype.constructor = cWRAPROWS; cWRAPROWS.prototype.name = 'WRAPROWS'; cWRAPROWS.prototype.argumentsMin = 2; cWRAPROWS.prototype.argumentsMax = 3; cWRAPROWS.prototype.numFormat = AscCommonExcel.cNumFormatNone; cWRAPROWS.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cWRAPROWS.prototype.argumentsType = [argType.any/*vector*/, argType.number, argType.any]; cWRAPROWS.prototype.isXLFN = true; cWRAPROWS.prototype.Calculate = function (arg) { return wrapRowsCols(arg, arguments[1]); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cWRAPCOLS() { } //***array-formula*** cWRAPCOLS.prototype = Object.create(cBaseFunction.prototype); cWRAPCOLS.prototype.constructor = cWRAPCOLS; cWRAPCOLS.prototype.name = 'WRAPCOLS'; cWRAPCOLS.prototype.argumentsMin = 2; cWRAPCOLS.prototype.argumentsMax = 3; cWRAPCOLS.prototype.numFormat = AscCommonExcel.cNumFormatNone; cWRAPCOLS.prototype.returnValueType = AscCommonExcel.cReturnFormulaType.array; cWRAPCOLS.prototype.argumentsType = [argType.any/*vector*/, argType.number, argType.any]; cWRAPCOLS.prototype.isXLFN = true; cWRAPCOLS.prototype.Calculate = function (arg) { return wrapRowsCols(arg, arguments[1], true); }; /** * @constructor * @extends {AscCommonExcel.cBaseFunction} */ function cXMATCH() { } //***array-formula*** cXMATCH.prototype = Object.create(cBaseFunction.prototype); cXMATCH.prototype.constructor = cXMATCH; cXMATCH.prototype.name = 'XMATCH'; cXMATCH.prototype.argumentsMin = 2; cXMATCH.prototype.argumentsMax = 4; cXMATCH.prototype.arrayIndexes = {1: 1}; cXMATCH.prototype.argumentsType = [argType.any, argType.reference, argType.number, argType.number]; cXMATCH.prototype.isXLFN = true; cXMATCH.prototype.Calculate = function (arg) { arg[4] = true; return g_oMatchCache.calculate(arg, arguments[1]); }; var g_oVLOOKUPCache = new VHLOOKUPCache(false); var g_oHLOOKUPCache = new VHLOOKUPCache(true); var g_oMatchCache = new MatchCache(); var g_oLOOKUPCache = new LOOKUPCache(); //----------------------------------------------------------export---------------------------------------------------- window['AscCommonExcel'] = window['AscCommonExcel'] || {}; window['AscCommonExcel'].g_oLOOKUPCache = g_oLOOKUPCache; window['AscCommonExcel'].g_oVLOOKUPCache = g_oVLOOKUPCache; window['AscCommonExcel'].g_oHLOOKUPCache = g_oHLOOKUPCache; window['AscCommonExcel'].g_oMatchCache = g_oMatchCache; })(window);