Files
Yajbir Singh f1b860b25c
check / markdownlint (push) Has been cancelled
check / spellchecker (push) Has been cancelled
updated
2025-12-11 19:03:17 +05:30

5357 lines
174 KiB
JavaScript

/*
* (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);