/* * (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) { /* * Import * ----------------------------------------------------------------------------- */ let asc = window["Asc"]; let asc_Range = asc.Range; let cElementType = AscCommonExcel.cElementType; function TraceDependentsManager(ws) { this.ws = ws; this.precedents = null; this.precedentsExternal = null; this.dependentsExternal = null; this.dependents = null; this.isDependetsCall = null; this.inLoop = null; this.isPrecedentsCall = null; this.precedentsAreas = null; this.precedentsAreasHeaders = null; this.data = { referenceMaxLevel: Math.pow(100, 10), lastHeaderIndex: -1, prevIndex: -1, recLevel: 0, maxRecLevel: 0, indices: { // cellIndex[From]: cellIndex[To]: recLevel } }; this.currentCalculatedPrecedentAreas = { // rangeName: { // inProgress: null, // isCalculated: null // } }; this.aPassedPrecedents = null; this.aPassedDependents = null; this._lockChangeDocument = null; /* an array with the coordinates of the start and end of all drawn lines */ this.tracesCoords = null; } TraceDependentsManager.prototype.setPrecedentsCall = function () { this.isPrecedentsCall = true; this.isDependetsCall = false; }; TraceDependentsManager.prototype.setDependentsCall = function () { this.isDependetsCall = true; this.isPrecedentsCall = false; }; TraceDependentsManager.prototype.setPrecedentExternal = function (from, to, elemRange, elemWs, externalLink) { if (!this.precedentsExternal) { this.precedentsExternal = {}; } if (!this.precedentsExternal[from]) { this.precedentsExternal[from] = {}; } let docInfo = window["Asc"]["editor"].DocInfo; let externalInfo = {range: elemRange, ws: elemWs, fullPath: null, isCurrentWorkbook: null}; let rangeName = elemRange.getName(); let wsName = elemWs.getName(); let isCurrentWorkbook = true; let eR, fileName; if (externalLink != null) { isCurrentWorkbook = false; eR = this.ws && this.ws.workbook && this.ws.workbook.model && this.ws.workbook.model.getExternalLinkByIndex(externalLink - 1); fileName = eR ? eR.Id : ""; } fileName = isCurrentWorkbook ? docInfo && docInfo.get_Title() : fileName; // in fullPath we write a line with full information about the name of the book, sheet and range let fullPath = "[" + fileName + "]" + wsName + "!" + rangeName; externalInfo.isCurrentWorkbook = isCurrentWorkbook; externalInfo.fullPath = fullPath; this.precedentsExternal[from][to] = externalInfo; }; TraceDependentsManager.prototype.checkPrecedentExternal = function (cellIndex) { if (!this.precedentsExternal) { return false; } return this.precedentsExternal[cellIndex]; }; // dependentsExternal TraceDependentsManager.prototype.setDependentsExternal = function (from, to, elemRange, elemWs) { if (!this.dependentsExternal) { this.dependentsExternal = {}; } if (!this.dependentsExternal[from]) { this.dependentsExternal[from] = {}; } let docInfo = window["Asc"]["editor"].DocInfo; let externalInfo = {range: elemRange, ws: elemWs, fullPath: null}; let rangeName = elemRange.getName(); let wsName = elemWs.getName(); let fileName = docInfo ? docInfo.get_Title() : ""; // in fullPath we write a line with complete information about the name of the book, sheet and range let fullPath = "[" + fileName + "]" + wsName + "!" + rangeName; externalInfo.fullPath = fullPath; this.dependentsExternal[from][to] = externalInfo; }; TraceDependentsManager.prototype.checkDependentExternal = function (cellIndex) { if (!this.dependentsExternal) { return false; } return this.dependentsExternal[cellIndex]; }; TraceDependentsManager.prototype.checkCircularReference = function (cellIndex, isDependentCall) { if (this.dependents && this.dependents[cellIndex] && this.precedents && this.precedents[cellIndex]) { if (isDependentCall) { for (let i in this.dependents[cellIndex]) { if (this._getDependents(i, cellIndex) && this._getDependents(cellIndex, i)) { return true; } } } else { for (let i in this.precedents) { if (this._getPrecedents(i, cellIndex) && this._getPrecedents(cellIndex, i)) { return true; } } } } }; TraceDependentsManager.prototype.clearLastDependent = function (row, col) { let ws = this.ws && this.ws.model; if (!ws || !this.dependents) { return; } if (Object.keys(this.dependents).length === 0) { return; } const t = this; if (row == null || col == null) { let selection = ws.getSelection(); let activeCell = selection.activeCell; row = activeCell.row; col = activeCell.col; let mergedRange = ws.getMergedByCell(row, col); if (mergedRange) { row = mergedRange.r1; col = mergedRange.c1; } } const findMaxNesting = function (row, col) { let currentCellIndex = AscCommonExcel.getCellIndex(row, col); if (t.data.prevIndex !== -1 && t.data.indices[t.data.prevIndex] && t.data.indices[t.data.prevIndex][currentCellIndex]) { return; } if (t.dependents[currentCellIndex]) { if (checkCircularReference(currentCellIndex)) { return; } let interLevel, fork; if (Object.keys(t.dependents[currentCellIndex]).length > 1) { fork = true; } t.data.recLevel++; t.data.maxRecLevel = t.data.recLevel > t.data.maxRecLevel ? t.data.recLevel : t.data.maxRecLevel; interLevel = t.data.recLevel; for (let j in t.dependents[currentCellIndex]) { t.data.prevIndex = currentCellIndex; if (j.includes(";")) { // [fromCurrent][toExternal] if (!t.data.indices[currentCellIndex]) { t.data.indices[currentCellIndex] = {}; } t.data.indices[currentCellIndex][j] = t.data.recLevel; continue; } let coords = AscCommonExcel.getFromCellIndex(j, true); findMaxNesting(coords.row, coords.col); t.data.recLevel = fork ? interLevel : t.data.recLevel; } } else { if (!t.data.indices[t.data.prevIndex]) { t.data.indices[t.data.prevIndex] = {}; } t.data.indices[t.data.prevIndex][currentCellIndex] = t.data.recLevel; // [from][to] } }; const checkCircularReference = function (index) { for (let i in t.dependents[index]) { if (t._getDependents(index, i) && t._getDependents(i, index)) { let related = index + "|" + i; t.data.recLevel = t.data.referenceMaxLevel; t.data.maxRecLevel = t.data.recLevel; t.data.indices[related] = t.data.recLevel; return true; } } }; findMaxNesting(row, col); const maxLevel = this.data.maxRecLevel; if (maxLevel === 0) { this._setDefaultData(); return; } else if (maxLevel === t.data.referenceMaxLevel) { // TODO improve check of cyclic references // temporary solution: now, when finding cyclic dependencies, the maximum nesting number(100^10) is set for them and only they are will be initially removed for (let i in this.data.indices) { if (this.data.indices[i] === maxLevel) { let val = i.split("|"); this._deleteDependent(val[0], val[1]); this._deletePrecedent(val[0], val[1]); this._deleteDependent(val[1], val[0]); this._deletePrecedent(val[1], val[0]); } } } for (let index in this.data.indices) { for (let i in this.data.indices[index]) { if (this.data.indices[index][i] === maxLevel) { this._deletePrecedent(i, index); this._deleteDependent(index, i); } } } this._setDefaultData(); }; TraceDependentsManager.prototype.calculateDependents = function (row, col) { let ws = this.ws && this.ws.model; if (!ws) { return; } if (row == null || col == null) { let selection = ws.getSelection(); let activeCell = selection.activeCell; row = activeCell.row; col = activeCell.col; let mergedRange = ws.getMergedByCell(row, col); if (mergedRange) { row = mergedRange.r1; col = mergedRange.c1; } } let depFormulas = ws.workbook.dependencyFormulas; if (depFormulas && depFormulas.sheetListeners) { if (!this.dependents) { this.dependents = {}; } let sheetListeners = depFormulas.sheetListeners; let curListener = sheetListeners[ws.Id]; let cellIndex = AscCommonExcel.getCellIndex(row, col); this._calculateDependents(cellIndex, curListener); this.setDependentsCall(); } }; TraceDependentsManager.prototype._calculateDependents = function (cellIndex, curListener, isSecondCall) { let t = this; let ws = this.ws.model; let wb = this.ws.model.workbook; let dependencyFormulas = wb.dependencyFormulas; let allDefNamesListeners = dependencyFormulas.defNameListeners; let cellAddress = AscCommonExcel.getFromCellIndex(cellIndex, true); const findCellListeners = function () { const listeners = {}; if (curListener && curListener.areaMap) { for (let j in curListener.areaMap) { if (curListener.areaMap.hasOwnProperty(j)) { if (curListener.areaMap[j] && curListener.areaMap[j].bbox.contains(cellAddress.col, cellAddress.row)) { Object.assign(listeners, curListener.areaMap[j].listeners); } } } } if (curListener && curListener.cellMap && curListener.cellMap[cellIndex]) { if (Object.keys(curListener.cellMap[cellIndex]).length > 0) { Object.assign(listeners, curListener.cellMap[cellIndex].listeners); } } if (curListener && curListener.defName3d) { Object.assign(listeners, curListener.defName3d); } return listeners; }; const checkIfHeader = function (tableHeader) { if (!tableHeader) { return false; } return tableHeader.col === cellAddress.col && tableHeader.row === cellAddress.row; }; const getTableHeader = function (table) { if (!table.Ref) { return false; } return {col: table.Ref.c1, row: table.Ref.r1}; }; const setDefNameIndexes = function (defName, isTable, defNameRange) { let tableHeader = isTable ? getTableHeader(ws.getTableByName(defName)) : false; let isCurrentCellHeader = isTable ? checkIfHeader(tableHeader) : false; for (let i in allDefNamesListeners) { if (allDefNamesListeners.hasOwnProperty(i) && i.toLowerCase() === defName.toLowerCase()) { for (let listener in allDefNamesListeners[i].listeners) { // TODO maybe add all listeners in 'curListener' at once // listener can be: cell, range, table, named range - there will be unique processing for each case let elem = allDefNamesListeners[i].listeners[listener]; let isArea = elem.ref ? true : false; let is3D = elem.ws.Id ? elem.ws.Id !== ws.Id : false; let isIntersect; if (isArea && !is3D && !isCurrentCellHeader) { if (defNameRange) { let defBBox = defNameRange.getBBox0(); // check clicked cell for entry into dependent areas // if the cell is not included, then the dependency will not be drawn let colShift = defBBox.c1 - elem.ref.c1, rowShift = defBBox.r1 - elem.ref.r1; isIntersect = elem.ref.contains(cellAddress.col - colShift, cellAddress.row - rowShift); } if (isIntersect) { // decompose all elements into dependencies let areaIndexes = getAllAreaIndexes(elem); if (areaIndexes) { for (let index in areaIndexes) { if (areaIndexes.hasOwnProperty(index)) { t._setDependents(cellIndex, areaIndexes[index]); t._setPrecedents(areaIndexes[index], cellIndex); } } } } continue; } let parentCellIndex = getParentIndex(elem.parent); if (parentCellIndex === null) { continue; } if (isTable) { // check Headers // if current header and listener is header, make trace only with header // check if current cell header or not if (elem.Formula.includes("Headers")) { if (isCurrentCellHeader) { t._setDependents(cellIndex, parentCellIndex); t._setPrecedents(parentCellIndex, cellIndex); } else { continue; } } else if (!elem.Formula.includes("Headers") && isCurrentCellHeader) { continue; } // ?additional check if the listener is in the same table, need to check if it is a listener of the main cell if (elem.outStack) { let arr = []; // check each element of the stack for an occurrence in the original cell for (let table in elem.outStack) { if (elem.outStack[table].type !== cElementType.table) { continue; } let bbox = elem.outStack[table].area.bbox ? elem.outStack[table].area.bbox : (elem.outStack[table].area.range.bbox ? elem.outStack[table].area.range.bbox : null); if (bbox) { arr.push(bbox.contains2(cellAddress)); } } if (!arr.includes(true)) { continue; } } // shared checks if (elem.shared !== null && !is3D) { let currentCellRange = ws.getCell3(cellAddress.row, cellAddress.col); setSharedTableIntersection(ws.getTableByName(defName).getRangeWithoutHeaderFooter(), currentCellRange, elem.shared); continue; } } t._setDependents(cellIndex, parentCellIndex); t._setPrecedents(parentCellIndex, cellIndex); } } } }; const getAllAreaIndexes = function (parserFormula) { const indexes = [], range = parserFormula.ref; if (!range) { return; } for (let c = range.c1; c <= range.c2; c++) { for (let r = range.r1; r <= range.r2; r++) { let index = AscCommonExcel.getCellIndex(r, c); indexes.push(index); } } return indexes; }; const getParentIndex = function (_parent) { if (!_parent || _parent.nCol == null || _parent.nRow == null) { return null; } let _parentCellIndex = AscCommonExcel.getCellIndex(_parent.nRow, _parent.nCol); //parent -> cell if (_parent.ws !== t.ws.model) { _parentCellIndex += ";" + _parent.ws.index; } return _parentCellIndex; }; const setSharedIntersection = function (currentRange, shared) { // get the cell is contained in one of the areaMap // if contain, call getSharedIntersect with currentRange whom contain cell and sharedRange if (curListener && curListener.areaMap) { for (let j in curListener.areaMap) { if (curListener.areaMap.hasOwnProperty(j)) { if (curListener.areaMap[j] && curListener.areaMap[j].bbox.contains(cellAddress.col, cellAddress.row)) { let isNotSharedRange; for (let listener in curListener.areaMap[j].listeners) { if (curListener.areaMap[j].listeners[listener].shared === null) { isNotSharedRange = true; } break; } let res = isNotSharedRange ? null : curListener.areaMap[j].bbox.getSharedIntersect(shared.ref, currentRange.bbox); // draw dependents to coords from res if (res) { let index = AscCommonExcel.getCellIndex(res.r1, res.c1); if (res.r1 === res.r2 && res.c1 !== res.c2) { index = res.containsCol(currentRange.bbox.c1) ? AscCommonExcel.getCellIndex(res.r1, currentRange.bbox.c1) : AscCommonExcel.getCellIndex(res.r1, res.c1); } else if (res.c1 === res.c2 && res.r1 !== res.r2) { index = res.containsRow(currentRange.bbox.r1) ? AscCommonExcel.getCellIndex(currentRange.bbox.r1, res.c1) : AscCommonExcel.getCellIndex(res.r1, res.c1); } t._setDependents(cellIndex, index); t._setPrecedents(index, cellIndex); } } } } } }; const setSharedTableIntersection = function (currentRange, currentCellRange, shared) { // row mode || col mode let isRowMode = currentRange.r1 === currentRange.r2, isColumnMode = currentRange.c1 === currentRange.c2, res, tempRange; if (isColumnMode && currentRange.r2 > shared.ref.r2) { if (!shared.ref.containsRow(currentCellRange.bbox.r2)) { return } if (currentCellRange.r2 > shared.ref.r2) { return; } // do check with rest of the currentRange tempRange = new asc_Range(currentRange.c1, currentRange.r1, currentRange.c2, shared.ref.r2); } else if (isRowMode && currentRange.c2 > shared.ref.c2) { // contains if (!shared.ref.containsCol(currentCellRange.bbox.c2)) { return } if (currentCellRange.c2 > shared.ref.c2) { return; } tempRange = new asc_Range(currentRange.c1, currentRange.r1, shared.ref.c2, currentRange.r2); } if (tempRange) { res = tempRange.getSharedIntersect(shared.ref, currentCellRange.bbox); } res = !res ? currentRange.getSharedIntersect(shared.ref, currentCellRange.bbox) : res; if (res && (res.r1 === res.r2 && res.c1 === res.c2)) { let index = AscCommonExcel.getCellIndex(res.r1, res.c1); t._setDependents(cellIndex, index); t._setPrecedents(index, cellIndex); } else { // split shared range on two parts let split = currentRange.difference(shared.ref); if (split.length > 1) { // first part res = currentRange.getSharedIntersect(split[0], currentCellRange.bbox); if (res && (res.r1 === res.r2 && res.c1 === res.c2)) { let index = AscCommonExcel.getCellIndex(res.r1, res.c1); t._setDependents(cellIndex, index); t._setPrecedents(index, cellIndex); } // second part if (split[1]) { let range = split[1], indexes = []; for (let col = range.c1; col <= range.c2; col++) { for (let row = range.r1; row <= range.r2; row++) { let index = AscCommonExcel.getCellIndex(row, col); indexes.push(index); } } if (indexes.length > 0) { for (let index in indexes) { if (indexes.hasOwnProperty(index)) { t._setDependents(cellIndex, indexes[index]); t._setPrecedents(indexes[index], cellIndex); } } } } } } }; const cellListeners = findCellListeners(); if (cellListeners && Object.keys(cellListeners).length > 0) { if (!this.dependents) { this.dependents = {}; } if (!this.dependents[cellIndex]) { // if dependents by cellIndex didn't exist, create it this.dependents[cellIndex] = {}; let parentCellIndex = null; for (let i in cellListeners) { if (cellListeners.hasOwnProperty(i)) { let parent = cellListeners[i].parent; let parentWsId = parent.ws ? parent.ws.Id : null; let isTable = parent.parsedRef ? parent.parsedRef.isTable : false; let isDefName = !!parent.name; let formula = cellListeners[i].Formula; let is3D = false; //todo slow operation. parent not have type if (parent instanceof Asc.CT_WorksheetSource) { // if the listener is a pivot table, skip the iteration continue; } if (isDefName) { let parentInnerElementType = parent.parsedRef.outStack[0] ? parent.parsedRef.outStack[0].type : false, defNameRange; if (parentInnerElementType === cElementType.cellsRange || parentInnerElementType === cElementType.cellsRange3D || parentInnerElementType === cElementType.cell3D) { defNameRange = parent.parsedRef.outStack[0].getRange(); } // TODO check external table ref setDefNameIndexes(parent.name, isTable, defNameRange); continue; } else if (cellListeners[i].is3D) { is3D = true; } if (cellListeners[i].shared !== null && !is3D) { // can be shared ref in otheer sheet let shared = cellListeners[i].getShared(); let currentCellRange = ws.getCell3(cellAddress.row, cellAddress.col); setSharedIntersection(currentCellRange, shared); continue; } if (formula.includes(":") && !is3D) { // call splitAreaListeners which return cellIndexes of each element(this will be parentCellIndex) // go through the values and set dependents for each let areaIndexes = getAllAreaIndexes(cellListeners[i]); if (areaIndexes) { for (let index in areaIndexes) { if (areaIndexes.hasOwnProperty(index)) { this._setDependents(cellIndex, areaIndexes[index]); this._setPrecedents(areaIndexes[index], cellIndex); } } continue; } } parentCellIndex = getParentIndex(parent); if (parentCellIndex === null) { //if (parentCellIndex === null || (typeof(parentCellIndex) === "number" && isNaN(parentCellIndex))) { continue; } if (is3D && typeof parentCellIndex === "string") { // the object dependentsExternal is only needed to handle a double click on an arrow, and is not used in drawing let elemRange = cellListeners[i].ref ? cellListeners[i].ref : new Asc.Range(parent.col, parent.row, parent.col, parent.row); let elemWs = parent.ws; this.setDependentsExternal(cellIndex, parentCellIndex, elemRange, elemWs); } this._setDependents(cellIndex, parentCellIndex); this._setPrecedents(parentCellIndex, cellIndex, true); } } if (Object.keys(this.dependents[cellIndex]).length === 0 && cellIndex !== parentCellIndex) { delete this.dependents[cellIndex]; this.ws.workbook.handlers.trigger("asc_onError", c_oAscError.ID.TraceDependentsNoFormulas, c_oAscError.Level.NoCritical); } } else { if (this.checkCircularReference(cellIndex, true)) { return; } if (this.checkPassedDependents(cellIndex)) { return; } // if dependents by cellIndex aldready exist, check current tree let currentIndex = Object.keys(this.dependents[cellIndex])[0]; let isUpdated = false; let bCellHasNotTrace = false; if (Object.keys(this.dependents[cellIndex]).length === 0) { bCellHasNotTrace = true; } for (let i in cellListeners) { if (cellListeners.hasOwnProperty(i)) { let parent = cellListeners[i].parent; //todo slow operation. parent not have type if (parent instanceof AscCommonExcel.DefName || parent instanceof Asc.CT_WorksheetSource) { // if the listener is a pivot table, skip the iteration continue; } let elemCellIndex = cellListeners[i].shared !== null ? currentIndex : getParentIndex(parent), formula = cellListeners[i].Formula; if (formula.includes(":") && !cellListeners[i].is3D) { // call getAllAreaIndexes which return cellIndexes of each element(this will be parentCellIndex) let areaIndexes = getAllAreaIndexes(cellListeners[i]); if (areaIndexes) { // go through the values and set dependents for each for (let index in areaIndexes) { if (areaIndexes.hasOwnProperty(index)) { this._setDependents(cellIndex, areaIndexes[index]); this._setPrecedents(areaIndexes[index], cellIndex, true); } } continue; } } // if the child cell does not yet have a dependency with listeners, create it if (!this._getDependents(cellIndex, elemCellIndex) && cellIndex !== elemCellIndex) { this._setDependents(cellIndex, elemCellIndex); this._setPrecedents(elemCellIndex, cellIndex, true); isUpdated = true; } } } if (!isUpdated) { this.setPassedDependents(cellIndex); for (let i in this.dependents[cellIndex]) { if (this.dependents[cellIndex].hasOwnProperty(i)) { this._calculateDependents(+i, curListener, true); } } if (!isSecondCall) { this.clearPassedDependents(); } } if (Object.keys(this.dependents[cellIndex]).length === 0 && bCellHasNotTrace) { this.ws.workbook.handlers.trigger("asc_onError", c_oAscError.ID.TraceDependentsNoFormulas, c_oAscError.Level.NoCritical); } } } else if (!isSecondCall) { this.ws.workbook.handlers.trigger("asc_onError", c_oAscError.ID.TraceDependentsNoFormulas, c_oAscError.Level.NoCritical); } }; TraceDependentsManager.prototype._getDependents = function (from, to) { return this.dependents[from] && this.dependents[from][to]; }; TraceDependentsManager.prototype._setDependents = function (from, to) { if (!this.dependents) { this.dependents = {}; } if (!this.dependents[from]) { this.dependents[from] = {}; } if (from === to) { return; } this.dependents[from][to] = 1; }; TraceDependentsManager.prototype._setDefaultData = function () { this.data = { referenceMaxLevel: Math.pow(100, 10), recLevel: 0, maxRecLevel: 0, lastHeaderIndex: -1, prevIndex: -1, indices: {} }; }; TraceDependentsManager.prototype.clearLastPrecedent = function (row, col) { let ws = this.ws && this.ws.model; if (!ws || !this.precedents) { return; } if (Object.keys(this.precedents).length === 0) { return; } const t = this; if (row == null || col == null) { let selection = ws.getSelection(); let activeCell = selection.activeCell; row = activeCell.row; col = activeCell.col; let mergedRange = ws.getMergedByCell(row, col); if (mergedRange) { row = mergedRange.r1; col = mergedRange.c1; } } const checkCircularReference = function (index) { for (let i in t.precedents[index]) { if (t._getPrecedents(index, i) && t._getPrecedents(i, index)) { let related = index + "|" + i; t.data.recLevel = t.data.referenceMaxLevel; t.data.maxRecLevel = t.data.recLevel; t.data.indices[related] = t.data.recLevel; return true; } } }; const checkIfHeader = function (cellIndex) { if (!t.precedentsAreas || !t.precedentsAreasHeaders) { return; } if (t.precedentsAreasHeaders[cellIndex]) { return true; } }; const getAllAreaIndexes = function (areas, areaName) { const indexes = []; if (!areas) { return; } let area = areas[areaName]; for (let r = area.range.r1; r <= area.range.r2; r++) { for (let c = area.range.c1; c <= area.range.c2; c++) { let index = AscCommonExcel.getCellIndex(r, c); indexes.push(index); } } return indexes; }; const findMaxNesting = function (row, col, callFromArea) { let currentCellIndex = AscCommonExcel.getCellIndex(row, col); if (t.data.indices[currentCellIndex] && t.data.indices[currentCellIndex][t.data.prevIndex]) { t.data.indices[currentCellIndex][t.data.prevIndex] = t.data.recLevel; return; } let ifHeader, interLevel, fork; if (t.data.recLevel > 0 && t.data.lastHeaderIndex !== currentCellIndex) { // checking if a cell is a table header ifHeader = callFromArea ? false : checkIfHeader(currentCellIndex); if (!t.precedents[currentCellIndex] && !ifHeader) { if (!t.data.indices[t.data.prevIndex]) { t.data.indices[t.data.prevIndex] = {}; } t.data.indices[t.data.prevIndex][currentCellIndex] = t.data.recLevel; // [from][to] format return; } } if (ifHeader) { // go through area let areaName = t.precedentsAreasHeaders[currentCellIndex]; let areaIndexes = getAllAreaIndexes(t.precedentsAreas, areaName); if (areaIndexes.length > 0) { fork = true; interLevel = t.data.recLevel; for (let index in areaIndexes) { if (areaIndexes.hasOwnProperty(index)) { let _index = areaIndexes[index]; let cellAddress = AscCommonExcel.getFromCellIndex(_index, true); if (_index === currentCellIndex) { t.data.lastHeaderIndex = _index; } if (!t.precedents[_index] && _index !== currentCellIndex) { continue; } findMaxNesting(cellAddress.row, cellAddress.col, true); t.data.recLevel = fork ? interLevel : t.data.recLevel; } } } } else if (t.precedents[currentCellIndex]) { if (checkCircularReference(currentCellIndex)) { return; } if (Object.keys(t.precedents[currentCellIndex]).length > 1) { fork = true; } t.data.recLevel++; t.data.maxRecLevel = t.data.recLevel > t.data.maxRecLevel ? t.data.recLevel : t.data.maxRecLevel; interLevel = t.data.recLevel; for (let j in t.precedents[currentCellIndex]) { t.data.prevIndex = currentCellIndex; if (j.includes(";")) { // [fromCurrent][toExternal] if (!t.data.indices[currentCellIndex]) { t.data.indices[currentCellIndex] = {}; } t.data.indices[currentCellIndex][j] = t.data.recLevel; continue; } let coords = AscCommonExcel.getFromCellIndex(j, true); findMaxNesting(coords.row, coords.col); t.data.recLevel = fork ? interLevel : t.data.recLevel; } } else { if (!t.data.indices[t.data.prevIndex]) { t.data.indices[t.data.prevIndex] = {}; } // [from][to] t.data.indices[t.data.prevIndex][currentCellIndex] = t.data.recLevel; } }; findMaxNesting(row, col); const maxLevel = this.data.maxRecLevel; if (maxLevel === 0) { this._setDefaultData(); return; } // TODO improve check of cyclic references // temporary solution: now, when finding cyclic dependencies, the maximum nesting number(100^10) is set for them and only they are will be initially removed else if (maxLevel === t.data.referenceMaxLevel) { for (let i in this.data.indices) { if (this.data.indices[i] === maxLevel) { let val = i.split("|"); this._deleteDependent(val[0], val[1]); this._deletePrecedent(val[0], val[1]); this._deleteDependent(val[1], val[0]); this._deletePrecedent(val[1], val[0]); } } } for (let index in this.data.indices) { for (let i in this.data.indices[index]) { if (this.data.indices[index][i] === maxLevel) { this._deletePrecedent(index, i); this._deleteDependent(i, index); } } } this.checkAreas(); this._setDefaultData(); }; TraceDependentsManager.prototype.calculatePrecedents = function (row, col, isSecondCall, callFromArea) { //depend from row/col cell let ws = this.ws && this.ws.model; if (!ws) { return; } const t = this; if (row == null || col == null) { // if first call, create/clear object with calculated areas this.currentCalculatedPrecedentAreas = {}; let selection = ws.getSelection(); let activeCell = selection.activeCell; row = activeCell.row; col = activeCell.col; let mergedRange = ws.getMergedByCell(row, col); if (mergedRange) { row = mergedRange.r1; col = mergedRange.c1; } } const cellIndex = AscCommonExcel.getCellIndex(row, col); const getAllAreaIndexesWithFormula = function (areas, areaName) { const indexes = []; if (!areas) { return; } let area = areas[areaName]; for (let i = area.range.r1; i <= area.range.r2; i++) { for (let j = area.range.c1; j <= area.range.c2; j++) { // ??? check parserFormula and return indexes only with it if (!ws.getCell3(i, j).isFormula()) { continue; } let index = AscCommonExcel.getCellIndex(i, j); indexes.push(index); } } area.isCalculated = true; return indexes; }; const isCellAreaHeader = function (cellIndex) { if (!t.precedentsAreas || !t.precedentsAreasHeaders) { return; } if (t.precedentsAreasHeaders[cellIndex]) { return true; } }; let formulaParsed; ws.getCell3(row, col)._foreachNoEmpty(function (cell) { formulaParsed = cell.formulaParsed; }); // TODO another way to check table let isAreaHeader = callFromArea ? false : isCellAreaHeader(cellIndex); if (this.precedentsAreas && isSecondCall && isAreaHeader) { // calculate all precedents in areas let areaName = this.precedentsAreasHeaders[cellIndex]; let areaIndexes = getAllAreaIndexesWithFormula(this.precedentsAreas, areaName); if (!this.currentCalculatedPrecedentAreas[areaName]) { this.currentCalculatedPrecedentAreas[areaName] = {}; // go through the values and check precedents for each for (let index in areaIndexes) { if (areaIndexes.hasOwnProperty(index)) { let cellAddress = AscCommonExcel.getFromCellIndex(areaIndexes[index], true); this.calculatePrecedents(cellAddress.row, cellAddress.col, true, true); } } } } else if (formulaParsed) { this._calculatePrecedents(formulaParsed, row, col, isSecondCall); this.setPrecedentsCall(); } else if (!isSecondCall) { this.ws.workbook.handlers.trigger("asc_onError", c_oAscError.ID.TracePrecedentsNoValidReference, c_oAscError.Level.NoCritical); } }; TraceDependentsManager.prototype._calculatePrecedents = function (formulaParsed, row, col, isSecondCall) { if (!this.precedents) { this.precedents = {}; } let t = this; let currentCellIndex = AscCommonExcel.getCellIndex(row, col); let formulaInfoObject = this.checkUnrecordedAndFormNewStack(currentCellIndex, formulaParsed), isHaveUnrecorded, newOutStack; let bCellHasNotTrace = false; if (this.precedents[currentCellIndex] && Object.keys(this.precedents[currentCellIndex]).length === 0) { bCellHasNotTrace = true; } if (formulaInfoObject) { isHaveUnrecorded = formulaInfoObject.isHaveUnrecorded; newOutStack = formulaInfoObject.newOutStack; } if (isHaveUnrecorded) { let shared, base; if (formulaParsed.shared !== null) { shared = formulaParsed.getShared(); base = shared.base; // base index - where shared formula start } if (newOutStack) { let currentWsIndex = formulaParsed.ws.index; let ref = formulaParsed.ref; // iterate through the elements and find all reference for (let index in newOutStack) { if (!newOutStack.hasOwnProperty(index)) { continue; } let elem = newOutStack[index].element; let elemType = elem.type ? elem.type : null; let is3D = elemType === cElementType.cell3D || elemType === cElementType.cellsRange3D || elemType === cElementType.name3D, isArea = elemType === cElementType.cellsRange || elemType === cElementType.name, isDefName = elemType === cElementType.name || elemType === cElementType.name3D, isTable = elemType === cElementType.table, areaName; let bPinCell = false; let sValue = null; if (elemType === cElementType.cell || is3D || isArea || isDefName || isTable) { let cellRange = new asc_Range(col, row, col, row), elemRange, elemCellIndex; if (isDefName) { let elemDefName = elem.getDefName(); let elemValue = elem.getValue(); if (!elemDefName) { continue } let defNameParentWsIndex = elemDefName.parsedRef.outStack[0].wsFrom ? elemDefName.parsedRef.outStack[0].wsFrom.index : (elemDefName.parsedRef.outStack[0].ws ? elemDefName.parsedRef.outStack[0].ws.index : null); elemRange = elemValue.range.bbox ? elemValue.range.bbox : elemValue.bbox; if (defNameParentWsIndex && defNameParentWsIndex !== currentWsIndex) { // 3D is3D = true; isArea = false; } else if (elemRange.isOneCell()) { isArea = false; } sValue = elemValue.value; bPinCell = sValue.includes("$"); } else if (isTable) { let currentWsId = elem.ws.Id, elemWsId = elem.area.ws ? elem.area.ws.Id : elem.area.wsFrom.Id; // elem.area can be cRef and cArea is3D = currentWsId !== elemWsId; elemRange = elem.area.bbox ? elem.area.bbox : (elem.area.range ? elem.area.range.bbox : null); isArea = ref ? true : !elemRange.isOneCell(); } else { sValue = elem.value; bPinCell = sValue.includes("$"); elemRange = elem.range.bbox ? elem.range.bbox : elem.bbox; } if (!elemRange) { continue; } if (shared) { if (isTable) { let isRowMode = shared.ref.r1 === shared.ref.r2, isColumnMode = shared.ref.c1 === shared.ref.c2, diff = []; if ((isRowMode && (cellRange.c2 > elemRange.c2)) || (isColumnMode && (cellRange.r2 > elemRange.r2))) { // regular link to main table elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, elemRange.c1); } else { diff = elemRange.difference(shared.ref); if (diff.length > 0) { let res = diff[0].getSharedIntersect(elemRange, cellRange); if (res && (res.r1 === res.r2 && res.c1 === res.c2)) { elemCellIndex = AscCommonExcel.getCellIndex(res.r1, res.c1); } else { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1 + (row - base.nRow), elemRange.c1 + (col - base.nCol)); } } } } else if (bPinCell) { const FIRST_INDEX_VALUE = 0; let nIndex = sValue.indexOf("$"); if (nIndex === FIRST_INDEX_VALUE) { sValue = sValue.slice(nIndex + 1); let bStaticCell = sValue.includes("$"); if (bStaticCell) { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, elemRange.c1); } else { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1 + (row - base.nRow), elemRange.c1); } } else { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, elemRange.c1 + (col - base.nCol)); } } else { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1 + (row - base.nRow), elemRange.c1 + (col - base.nCol)); } } else { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, elemRange.c1); } // cross check for element: // if the element is Area and it doesn't have reference to another sheet, // and the element is not in the function, // and the formula is not CSE - we are checking for cross if (isArea && !ref && !is3D && !newOutStack[index].inFormulaRef) { if (elemRange.getWidth() > 1 && elemRange.getHeight() <= 1) { // check cols if (elemRange.containsCol(col)) { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, col); isArea = false; } } else if (elemRange.getWidth() <= 1 && elemRange.getHeight() > 1) { // check rows if (elemRange.containsRow(row)) { elemCellIndex = AscCommonExcel.getCellIndex(row, elemRange.c1); isArea = false; } } else { isArea = true; } } // if the area is on the same sheet - write to the array of areas for drawing if (isArea && !is3D) { let copyRange = elemRange.clone(); if (shared && !isTable) { const offset = { row: row - base.nRow, col: col - base.nCol }; // set offset according to base shift copyRange.setOffset(offset); } const areaRange = {}; areaName = copyRange.getName(); // areaName - unique key for areaRange areaRange[areaName] = {}; areaRange[areaName].range = copyRange; areaRange[areaName].isCalculated = null; areaRange[areaName].areaHeader = elemCellIndex; this._setPrecedentsAreas(areaRange); } if (is3D) { // external dependencies are stored in a separate object and rendered in a separate loop let elemIndex = elem.wsTo ? elem.wsTo.index : elem.ws.index; let elemWs = elem.wsFrom ? elem.wsTo : elem.ws; if (currentWsIndex !== elemIndex) { if (elem.externalLink != null) { elemIndex = "[" + elem.externalLink + "]"; } elemCellIndex += ";" + elemIndex; this.setPrecedentExternal(currentCellIndex, elemCellIndex, elemRange, elemWs, elem.externalLink); continue } this._setDependents(elemCellIndex, currentCellIndex); this._setPrecedents(currentCellIndex, elemCellIndex); } else { this._setPrecedents(currentCellIndex, elemCellIndex, false, areaName ? areaName : false); this._setDependents(elemCellIndex, currentCellIndex); if (areaName) { this._setPrecedentsAreaHeader(elemCellIndex, areaName); } } } } } } else { if (this.checkCircularReference(currentCellIndex, false)) { return; } if (this.checkPassedPrecedents(currentCellIndex)) { return; } this.setPrecedentsLoop(true); this.setPassedPrecedents(currentCellIndex); let isHavePrecedents = false; // check first level, then if function return false, check second, third and so on for (let i in this.precedents[currentCellIndex]) { let coords = AscCommonExcel.getFromCellIndex(i, true); this.calculatePrecedents(coords.row, coords.col, true); isHavePrecedents = true; } if (!isHavePrecedents && !isSecondCall) { this.ws.workbook.handlers.trigger("asc_onError", c_oAscError.ID.TracePrecedentsNoValidReference, c_oAscError.Level.NoCritical); } this.setPrecedentsLoop(false); if (!isSecondCall) { this.clearPassedPrecedents(); } } if (this.precedents && this.precedents[currentCellIndex] && Object.keys(this.precedents[currentCellIndex]).length === 0 && bCellHasNotTrace) { this.ws.workbook.handlers.trigger("asc_onError", c_oAscError.ID.TracePrecedentsNoValidReference, c_oAscError.Level.NoCritical); } }; TraceDependentsManager.prototype.addLineCoordinates = function (from, to, /*fromXY, toXY*/x1, y1, x2, y2) { /* from - cellIndex to - cellIndex fromXY - from coordinates toXY - to coordinates we are working with the precedents object, where [from] - this is the position of the line with the arrow pointing to the cell, [to] - this is the position of the line with a dot at the beginning */ if (from === undefined || to === undefined) { return } let traceLineInfo = {from : {x: x1, y: y1, cellRange: null, areaRange: null}, to: {x: x2, y: y2, cellRange: null, areaRange: null}}; let fromRowCol = AscCommonExcel.getFromCellIndex(from, true); let toRowCol = AscCommonExcel.getFromCellIndex(to, true); let toAreInfo = this.precedents[from] && this.precedents[from][to]; let fromAreaInfo = this.precedents[to] && this.precedents[to][from]; traceLineInfo.from.cellRange = new Asc.Range(fromRowCol.col, fromRowCol.row, fromRowCol.col, fromRowCol.row); traceLineInfo.to.cellRange = new Asc.Range(toRowCol.col, toRowCol.row, toRowCol.col, toRowCol.row); // areaCheck if (this.precedentsAreas) { if (toAreInfo && toAreInfo !== 1 && this.precedentsAreas[toAreInfo]) { traceLineInfo.to.areaRange = this.precedentsAreas[toAreInfo].range; } if (fromAreaInfo && fromAreaInfo !== 1 && this.precedentsAreas[fromAreaInfo]) { traceLineInfo.from.areaRange = this.precedentsAreas[fromAreaInfo].range; } } if (!this.tracesCoords) { this.tracesCoords = []; } this.tracesCoords.push(traceLineInfo); }; TraceDependentsManager.prototype.addExternalLineCoordinates = function (from, x1, y1, x2, y2) { /* from - cellIndex we are working with the precedentsExternal and dependentsExternal object, where [from] - this is the position of the line with an arrow indicating external dependencies */ if (from === undefined) { return } // in external we pass an array of strings like "[BookName.xlsx]SheetName!$A$1:$A$3" which we should pass to the goto window let traceLineInfo = {from : {x: x1, y: y1}, to: {x: x2, y: y2}, external: null}; // we go through all external dependencies and fill array of external dependencies for chosen line if (this.precedentsExternal && this.precedentsExternal[from]) { for (let i in this.precedentsExternal[from]) { let externalInfo = this.precedentsExternal[from][i]; if (!traceLineInfo.external) { traceLineInfo.external = []; } traceLineInfo.external.push(externalInfo); } } if (this.dependentsExternal && this.dependentsExternal[from]) { for (let i in this.dependentsExternal[from]) { let externalInfo = this.dependentsExternal[from][i]; if (!traceLineInfo.external) { traceLineInfo.external = []; } traceLineInfo.external.push(externalInfo); } } if (!this.tracesCoords) { this.tracesCoords = []; } this.tracesCoords.push(traceLineInfo); }; TraceDependentsManager.prototype.checkUnrecordedAndFormNewStack = function (cellIndex, formulaParsed) { let newOutStack = [], isHaveUnrecorded; if (formulaParsed && formulaParsed.outStack) { let currentWsIndex = formulaParsed.ws.index, ref = formulaParsed.ref, coords = AscCommonExcel.getFromCellIndex(cellIndex, true), row = coords.row, col = coords.col, shared, base, length = formulaParsed.outStack.length, isPartOfFunc, numberOfArgs, funcReturnType, funcArray; if (formulaParsed.shared !== null) { shared = formulaParsed.getShared(); base = shared.base; } for (let i = length - 1; i >= 0; i--) { let elem = formulaParsed.outStack[i]; if (!elem) { continue; } if (numberOfArgs <= 0) { funcArray = null; isPartOfFunc = null; } let elemTypeExist = elem.type !== undefined; let elemType = elem.type, inFormulaRef; if (isPartOfFunc && numberOfArgs > 0 && elemTypeExist) { if (cElementType.cellsRange === elemType || cElementType.name === elemType) { if (funcReturnType === AscCommonExcel.cReturnFormulaType.array) { // range refers to formula, add property inFormulaRef = true inFormulaRef = true; } else if (funcArray) { // if have no returnType check for arrayIndexes and if element pass in raw form(as array, range) to argument if (funcArray.getArrayIndex(numberOfArgs - 1)) { inFormulaRef = true; } } } numberOfArgs--; } if (elemType === cElementType.func) { isPartOfFunc = true; numberOfArgs = formulaParsed.outStack[i - 1]; funcReturnType = elem.returnValueType; funcArray = elem; } let is3D = elemType === cElementType.cell3D || elemType === cElementType.cellsRange3D || elemType === cElementType.name3D, isArea = elemType === cElementType.cellsRange || elemType === cElementType.name, isDefName = elemType === cElementType.name || elemType === cElementType.name3D, isTable = elemType === cElementType.table; if (elemType === cElementType.cell || isArea || is3D || isDefName || isTable) { // in any case, add the element to the array newOutStack.push({element: elem, inFormulaRef: inFormulaRef}); // if already know about unrealized entries, skip all checks if (isHaveUnrecorded) { continue } let elemRange, elemCellIndex; if (isDefName) { let elemDefName = elem.getDefName(), elemValue = elem.getValue(); if (!elemDefName) { continue } let defNameParentWsIndex = elemDefName.parsedRef.outStack[0].wsFrom ? elemDefName.parsedRef.outStack[0].wsFrom.index : (elemDefName.parsedRef.outStack[0].ws ? elemDefName.parsedRef.outStack[0].ws.index : null); elemRange = elemValue.range.bbox ? elemValue.range.bbox : elemValue.bbox; if (defNameParentWsIndex && defNameParentWsIndex !== currentWsIndex) { is3D = true; isArea = false; } else if (elemRange.isOneCell()) { isArea = false; } } else if (isTable) { let currentWsId = elem.ws.Id, elemWsId = elem.area.ws ? elem.area.ws.Id : elem.area.wsFrom.Id; is3D = currentWsId !== elemWsId; elemRange = elem.area.bbox ? elem.area.bbox : (elem.area.range ? elem.area.range.bbox : null); } else { elemRange = elem.range.bbox ? elem.range.bbox : elem.bbox; } if (!elemRange) { continue; } if (shared) { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1 + (row - base.nRow), elemRange.c1 + (col - base.nCol)); } else { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, elemRange.c1); } // cross check for cell if (isArea && !ref && !is3D) { if (elemRange.getWidth() > 1 && elemRange.getHeight() <= 1) { // check cols if (elemRange.containsCol(col)) { elemCellIndex = AscCommonExcel.getCellIndex(elemRange.r1, col); isArea = false; } } else if (elemRange.getWidth() <= 1 && elemRange.getHeight() > 1) { // check rows if (elemRange.containsRow(row)) { elemCellIndex = AscCommonExcel.getCellIndex(row, elemRange.c1); isArea = false; } } else { isArea = true; } } if (is3D) { // elemCellIndex += ";" + (elem.wsTo ? elem.wsTo.index : elem.ws.index); let elemIndex = elem.wsTo ? elem.wsTo.index : elem.ws.index; let elemWs = elem.wsFrom ? elem.wsTo : elem.ws; if (currentWsIndex !== elemIndex) { let hasExternalPrecedent = this.checkPrecedentExternal(cellIndex); elemCellIndex += ";" + elemIndex; if (!hasExternalPrecedent || (hasExternalPrecedent && !this.precedentsExternal[cellIndex][elemCellIndex])) { isHaveUnrecorded = true; } // this.setPrecedentExternal(currentCellIndex, elemCellIndex, elemRange, elemWs); continue } } if (!this._getPrecedents(cellIndex, elemCellIndex)) { isHaveUnrecorded = true; } } } return { isHaveUnrecorded: isHaveUnrecorded, newOutStack: newOutStack }; } return false; }; TraceDependentsManager.prototype.setPrecedentsLoop = function (inLoop) { this.inLoop = inLoop; }; TraceDependentsManager.prototype.getPrecedentsLoop = function () { return this.inLoop; }; TraceDependentsManager.prototype._getPrecedents = function (from, to) { return this.precedents[from] && this.precedents[from][to]; }; TraceDependentsManager.prototype._deleteDependent = function (from, to) { if (this.dependents[from] && this.dependents[from][to]) { delete this.dependents[from][to]; if (Object.keys(this.dependents[from]).length === 0) { delete this.dependents[from]; } } }; TraceDependentsManager.prototype._deletePrecedentFromArea = function (index) { if (this.dependents[index] && this.precedents) { for (let precedentIndex in this.precedents) { for (let i in this.precedents[precedentIndex]) { if (i == index) { this._deleteDependent(index, precedentIndex); this._deletePrecedent(precedentIndex, index); } } } } }; TraceDependentsManager.prototype._deletePrecedent = function (from, to) { if (this.precedents[from] && this.precedents[from][to]) { delete this.precedents[from][to]; if (Object.keys(this.precedents[from]).length === 0) { delete this.precedents[from]; } } }; TraceDependentsManager.prototype._setPrecedents = function (from, to, isDependent, areaName) { if (!this.precedents) { this.precedents = {}; } if (!this.precedents[from]) { this.precedents[from] = {}; } if (from === to) { return; } // TODO calculated: 1, not_calculated: 2 // TODO isAreaHeader: "A3:B4" // this.precedents[from][to] = isDependent ? 2 : 1; this.precedents[from][to] = areaName ? areaName : 1; }; TraceDependentsManager.prototype._setPrecedentsAreaHeader = function (headerCellIndex, areaName) { if (!this.precedentsAreasHeaders) { this.precedentsAreasHeaders = {}; } this.precedentsAreasHeaders[headerCellIndex] = areaName; }; TraceDependentsManager.prototype._setPrecedentsAreas = function (area) { if (!this.precedentsAreas) { this.precedentsAreas = {}; } Object.assign(this.precedentsAreas, area); }; TraceDependentsManager.prototype._getPrecedentsAreas = function () { return this.precedentsAreas; }; TraceDependentsManager.prototype.isHaveData = function () { return this.isHaveDependents() || this.isHavePrecedents(); }; TraceDependentsManager.prototype.isHaveDependents = function () { return !!this.dependents; }; TraceDependentsManager.prototype.isHavePrecedents = function () { return !!this.precedents; }; TraceDependentsManager.prototype.isHaveExternalPrecedents = function () { return !!this.precedentsExternal; }; TraceDependentsManager.prototype.isHaveExternalDependents = function () { return !!this.dependentsExternal; }; TraceDependentsManager.prototype.forEachDependents = function (callback) { for (let i in this.dependents) { callback(i, this.dependents[i], this.isPrecedentsCall); } }; TraceDependentsManager.prototype.forEachExternalPrecedent = function (callback) { for (let i in this.precedentsExternal) { callback(i); } }; TraceDependentsManager.prototype.clear = function (type) { if (Asc.c_oAscRemoveArrowsType.all === type) { this.clearAll(); } if (Asc.c_oAscRemoveArrowsType.dependent === type) { this.clearLastDependent(); } if (Asc.c_oAscRemoveArrowsType.precedent === type) { this.clearLastPrecedent(); } }; TraceDependentsManager.prototype.clearAll = function (needDraw) { if (needDraw && this.ws) { // need to call cleanSelection before any data is removed from the traceManager, because otherwise, isHaveData inside cleanSelection will return false, and the cleaning won't occur this.ws.cleanSelection(); } this.precedents = null; this.precedentsExternal = null; this.dependentsExternal = null; this.dependents = null; this.isDependetsCall = null; this.inLoop = null; this.isPrecedentsCall = null; this.precedentsAreas = null; this.currentCalculatedPrecedentAreas = null; this.precedentsAreasHeaders = null; this._setDefaultData(); this.clearCoordsData(); if (needDraw) { if (this.ws && this.ws.overlayCtx) { // on the other hand, drawSelection should be called after removing data from traceManager because there is a dependency drawing call inside it this.ws._drawSelection(); } } }; TraceDependentsManager.prototype.changeDocument = function (prop, arg1, arg2, fMergeCellIndex) { switch (prop) { case AscCommonExcel.docChangedType.cellValue: if (this._lockChangeDocument) { return; } if (arg1) { this.clearCellTraces(arg1.nRow, arg1.nCol); } break; case AscCommonExcel.docChangedType.rangeValues: break; case AscCommonExcel.docChangedType.sheetContent: if (this._lockChangeDocument) { return; } this.clearAll(); break; case AscCommonExcel.docChangedType.sheetRemove: break; case AscCommonExcel.docChangedType.sheetRename: break; case AscCommonExcel.docChangedType.sheetChangeIndex: break; case AscCommonExcel.docChangedType.markModifiedSearch: break; case AscCommonExcel.docChangedType.mergeRange: if (arg1 === true) { this._lockChangeDocument = true; } else { this._lockChangeDocument = null; const t = this; if (t.isHaveData() && arg2) { if (Asc.c_oAscSelectionType.RangeMax === arg2.getType()) { t.clearAll(); break; } let firstCellIndex = fMergeCellIndex !== undefined ? fMergeCellIndex : AscCommonExcel.getCellIndex(arg2.r1, arg2.c1); /* go through all existing dependencies and if they are in the merged range, delete them */ t.forEachDependents(function(i, precedents) { for (let precedent in precedents) { // delete everything except the first cell if (precedent == firstCellIndex) { continue; } let cell = AscCommonExcel.getFromCellIndex(precedent, true); if (arg2.contains2(cell)) { if (!(arg2.c1 === cell.col && arg2.r1 === cell.row)) { t.clearCellTraces(cell.row, cell.col); } } } }) } } break; } }; TraceDependentsManager.prototype.clearCellTraces = function (row, col) { let ws = this.ws && this.ws.model; if (!ws || row == null || col == null || !this.precedents || !this.dependents) { return; } let cellIndex = AscCommonExcel.getCellIndex(row, col); if (this.precedents[cellIndex]) { for (let i in this.precedents[cellIndex]) { this._deleteDependent(i, cellIndex); } delete this.precedents[cellIndex]; } // check the ranges for existence of dependencies on it this.checkAreas(); }; TraceDependentsManager.prototype.checkAreas = function () { if (!this.precedentsAreas) { return } for (let i in this.precedentsAreas) { let areaHeader = this.precedentsAreas[i].areaHeader; if (!this.dependents[areaHeader]) { delete this.precedentsAreas[i]; delete this.precedentsAreasHeaders[areaHeader]; } } }; TraceDependentsManager.prototype.clearCoordsData = function () { /* clear coordinatess data */ this.tracesCoords = null; }; /** * Sets passed precedents cells index for recursive calls * @memberof TraceDependentsManager * @param {number} currentCellIndex */ TraceDependentsManager.prototype.setPassedPrecedents = function (currentCellIndex) { if (!this.aPassedPrecedents) { this.aPassedPrecedents = []; } this.aPassedPrecedents.push(currentCellIndex); }; /** * Checks current cell index is already passed in recursive calls * @memberof TraceDependentsManager * @param {number} currentCellIndex * @returns {boolean} */ TraceDependentsManager.prototype.checkPassedPrecedents = function (currentCellIndex) { return !!(this.aPassedPrecedents && this.aPassedPrecedents.includes(currentCellIndex)); }; /** * Clears attribute of passed precedents * @memberof TraceDependentsManager */ TraceDependentsManager.prototype.clearPassedPrecedents = function () { this.aPassedPrecedents = null; }; /** * Sets passed dependents cells index for recursive calls * @memberof TraceDependentsManager * @param {number} currentCellIndex */ TraceDependentsManager.prototype.setPassedDependents = function (currentCellIndex) { if (!this.aPassedDependents) { this.aPassedDependents = []; } this.aPassedDependents.push(currentCellIndex); }; /** * Checks current cell index is already passed in recursive calls * @memberof TraceDependentsManager * @param {number} currentCellIndex * @returns {boolean} */ TraceDependentsManager.prototype.checkPassedDependents = function (currentCellIndex) { return !!(this.aPassedDependents && this.aPassedDependents.includes(currentCellIndex)); }; /** * Clears attribute of passed dependents */ TraceDependentsManager.prototype.clearPassedDependents = function () { this.aPassedDependents = null; }; //------------------------------------------------------------export--------------------------------------------------- window['AscCommonExcel'] = window['AscCommonExcel'] || {}; window["AscCommonExcel"].TraceDependentsManager = TraceDependentsManager; })(window);