mirror of
https://github.com/ONLYOFFICE/sdkjs.git
synced 2026-04-07 14:09:12 +08:00
Fix/trace (#4643)
* [se] Fix dependents and precedents trace - Fix dependents and precedents trace for cells with circular reference. * [se] Fix bug with overflow call stack * [se] Fix bug with recursion dep. trace - Fix bug with iterative drawing traces for dependents * [se] For trace - Fix incorrect drawing traces for pinned cells in formula. --------- Co-authored-by: Dmitry Gvozdev <dmitriy.gvozdev@onlyoffice.com>
This commit is contained in:
@ -72,6 +72,8 @@ function (window, undefined) {
|
||||
// isCalculated: null
|
||||
// }
|
||||
};
|
||||
this.aPassedPrecedents = null;
|
||||
this.aPassedDependents = null;
|
||||
|
||||
this._lockChangeDocument = null;
|
||||
}
|
||||
@ -513,6 +515,7 @@ function (window, undefined) {
|
||||
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;
|
||||
@ -564,7 +567,7 @@ function (window, undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
let parentCellIndex = getParentIndex(parent);
|
||||
parentCellIndex = getParentIndex(parent);
|
||||
if (parentCellIndex === null) {
|
||||
//if (parentCellIndex === null || (typeof(parentCellIndex) === "number" && isNaN(parentCellIndex))) {
|
||||
continue;
|
||||
@ -573,7 +576,7 @@ function (window, undefined) {
|
||||
this._setPrecedents(parentCellIndex, cellIndex, true);
|
||||
}
|
||||
}
|
||||
if (Object.keys(this.dependents[cellIndex]).length === 0) {
|
||||
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);
|
||||
}
|
||||
@ -581,9 +584,16 @@ function (window, undefined) {
|
||||
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;
|
||||
@ -611,7 +621,7 @@ function (window, undefined) {
|
||||
}
|
||||
|
||||
// if the child cell does not yet have a dependency with listeners, create it
|
||||
if (!this._getDependents(cellIndex, elemCellIndex)) {
|
||||
if (!this._getDependents(cellIndex, elemCellIndex) && cellIndex !== elemCellIndex) {
|
||||
this._setDependents(cellIndex, elemCellIndex);
|
||||
this._setPrecedents(elemCellIndex, cellIndex, true);
|
||||
isUpdated = true;
|
||||
@ -620,11 +630,18 @@ function (window, undefined) {
|
||||
}
|
||||
|
||||
if (!isUpdated) {
|
||||
this.setPassedDependents(cellIndex);
|
||||
for (let i in this.dependents[cellIndex]) {
|
||||
if (this.dependents[cellIndex].hasOwnProperty(i)) {
|
||||
this._calculateDependents(i, curListener, true);
|
||||
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) {
|
||||
@ -641,6 +658,9 @@ function (window, undefined) {
|
||||
if (!this.dependents[from]) {
|
||||
this.dependents[from] = {};
|
||||
}
|
||||
if (from === to) {
|
||||
return;
|
||||
}
|
||||
this.dependents[from][to] = 1;
|
||||
};
|
||||
TraceDependentsManager.prototype._setDefaultData = function () {
|
||||
@ -907,7 +927,11 @@ function (window, undefined) {
|
||||
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;
|
||||
@ -935,6 +959,8 @@ function (window, undefined) {
|
||||
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;
|
||||
@ -954,6 +980,8 @@ function (window, undefined) {
|
||||
} 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;
|
||||
@ -962,6 +990,8 @@ function (window, undefined) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -989,6 +1019,20 @@ function (window, undefined) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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));
|
||||
}
|
||||
@ -1060,7 +1104,11 @@ function (window, undefined) {
|
||||
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]) {
|
||||
@ -1074,6 +1122,12 @@ function (window, undefined) {
|
||||
}
|
||||
|
||||
this.setPrecedentsLoop(false);
|
||||
if (!isSecondCall) {
|
||||
this.clearPassedPrecedents();
|
||||
}
|
||||
}
|
||||
if (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.checkUnrecordedAndFormNewStack = function (cellIndex, formulaParsed) {
|
||||
@ -1252,6 +1306,9 @@ function (window, undefined) {
|
||||
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;
|
||||
@ -1399,6 +1456,59 @@ function (window, undefined) {
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 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---------------------------------------------------
|
||||
|
||||
@ -2323,6 +2323,39 @@ $(function() {
|
||||
// // asc_Paste -> false
|
||||
// // asc_PasteData -> true*
|
||||
// });
|
||||
QUnit.test("Test: \"Recursive formulas\"", function (assert) {
|
||||
// Case #1: Precedent trace for formula - =A100+1. A dot or line shouldn't be traced
|
||||
ws.getRange2("A100").setValue("=A100+1");
|
||||
let A100Index = AscCommonExcel.getCellIndex(ws.getRange2("A100").bbox.r1, ws.getRange2("A100").bbox.c1);
|
||||
ws.selectionRange.ranges = [ws.getRange2("A100").getBBox0()];
|
||||
ws.selectionRange.setActiveCell(ws.getRange2("A100").getBBox0().r1, ws.getRange2("C1").getBBox0().c1);
|
||||
|
||||
api.asc_TracePrecedents();
|
||||
assert.strictEqual(traceManager._getPrecedents(A100Index, A100Index), undefined, "Precedent trace. Formula A100+1. A100. Dot shouldn't be show");
|
||||
|
||||
// clear traces
|
||||
api.asc_RemoveTraceArrows(Asc.c_oAscRemoveArrowsType.all);
|
||||
// Case #2: Dependent trace for formula - =A100+1. A dot or line shouldn't be traced
|
||||
api.asc_TraceDependents();
|
||||
assert.strictEqual(traceManager._getDependents(A100Index, A100Index), undefined, "Dependent trace. Formula A100+1. A100. Dot shouldn't be show");
|
||||
// Case #3: Precedent trace for formula - A100: A100+B100, B100: B100+C100. A100<-B100. Arrow without dot in A100 cell.
|
||||
ws.getRange2("A100").setValue("=A100+B100");
|
||||
ws.getRange2("B100").setValue("=B100+C100");
|
||||
A100Index = AscCommonExcel.getCellIndex(ws.getRange2("A100").bbox.r1, ws.getRange2("A100").bbox.c1);
|
||||
let B100Index = AscCommonExcel.getCellIndex(ws.getRange2("B100").bbox.r1, ws.getRange2("B100").bbox.c1);
|
||||
ws.selectionRange.ranges = [ws.getRange2("A100").getBBox0()];
|
||||
ws.selectionRange.setActiveCell(ws.getRange2("A100").getBBox0().r1, ws.getRange2("C1").getBBox0().c1);
|
||||
|
||||
api.asc_TracePrecedents();
|
||||
assert.strictEqual(traceManager._getPrecedents(A100Index, B100Index), 1, "Precedent trace. Formula: A100: A100+B100, B100: B100+C100. A100<-B100. Arrow to A100 without dot in A100 cell.");
|
||||
assert.strictEqual(traceManager._getPrecedents(A100Index, A100Index), undefined, "Precedent trace. Formula: A100: A100+B100, B100: B100+C100. A100. Dot shouldn't be show");
|
||||
|
||||
// clear traces
|
||||
api.asc_RemoveTraceArrows(Asc.c_oAscRemoveArrowsType.all);
|
||||
// Case #4: Dependent trace for formula - A100: A100+B100, B100: B100+C100. A100->B100. Arrow without dot in B100 cell.
|
||||
api.asc_TraceDependents();
|
||||
assert.strictEqual(traceManager._getDependents(A100Index, A100Index), undefined, "Dependent trace. Formula: A100: A100+B100, B100: B100+C100. A100. Dot shouldn't be show");
|
||||
});
|
||||
}
|
||||
|
||||
QUnit.module("FormulaTrace");
|
||||
|
||||
Reference in New Issue
Block a user