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

1723 lines
58 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) {
/*
* 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);