fix/bug-46893 (#1159)

[se] Fix bug 46893

Co-authored-by: GoshaZotov <Igor.Zotov@onlyoffice.com>
Co-committed-by: GoshaZotov <Igor.Zotov@onlyoffice.com>
This commit is contained in:
GoshaZotov
2025-06-27 09:40:30 +00:00
committed by Igor Zotov
parent 794fd6f645
commit 52afffeb72
3 changed files with 242 additions and 3 deletions

View File

@ -1865,6 +1865,114 @@
});
};
MultiplyRange.prototype.getUnionRanges = function() {
let ranges = this.ranges;
if (!ranges || ranges.length === 0) {
return [];
}
if (ranges.length === 1) {
return [ranges[0].clone()];
}
const rangesByRow = new Array(ranges.length);
for (let i = 0; i < ranges.length; i++) {
rangesByRow[i] = ranges[i].clone(true);
}
// First sorting - by row starting position
rangesByRow.sort(function(a, b) { return a.r1 - b.r1; });
// First merging pass - combine vertically aligned ranges
const result = [rangesByRow[0]];
let currentRange = result[0];
for (let i = 1; i < rangesByRow.length; i++) {
const range = rangesByRow[i];
// Check if ranges have same columns and are adjacent or overlapping rows
if (range.c1 === currentRange.c1 && range.c2 === currentRange.c2 &&
range.r1 <= currentRange.r2 + 1) {
// Extend current range down
currentRange.r2 = Math.max(currentRange.r2, range.r2);
} else {
// Start new range if can't merge
result.push(range);
currentRange = range;
}
}
// Second sorting - by column starting position
result.sort(function(a, b) { return a.c1 - b.c1; });
// Second merging pass - combine horizontally aligned ranges
const finalResult = [result[0]];
let currentHorRange = finalResult[0];
for (let i = 1; i < result.length; i++) {
const range = result[i];
// Check if ranges have same rows and are adjacent or overlapping columns
if (range.r1 === currentHorRange.r1 && range.r2 === currentHorRange.r2 &&
range.c1 <= currentHorRange.c2 + 1) {
// Extend current range horizontally
currentHorRange.c2 = Math.max(currentHorRange.c2, range.c2);
} else {
// Start new range if can't merge
finalResult.push(range);
currentHorRange = range;
}
}
// Final merging pass - handle more complex overlapping cases
let changed = true;
let iterationRanges = finalResult.slice();
// Limit iterations to avoid excessive processing
for (let iteration = 0; iteration < 3 && changed && iterationRanges.length > 1; iteration++) {
changed = false;
const tempRanges = [];
for (let i = 0; i < iterationRanges.length; i++) {
const currentRange = iterationRanges[i];
let merged = false;
// Try to merge with ranges already in result
for (let j = 0; j < tempRanges.length; j++) {
const tempRange = tempRanges[j];
// Check for possible merging cases (same columns or same rows with adjacency)
if ((tempRange.c1 === currentRange.c1 && tempRange.c2 === currentRange.c2 &&
(tempRange.r2 + 1 === currentRange.r1 || currentRange.r2 + 1 === tempRange.r1)) ||
(tempRange.r1 === currentRange.r1 && tempRange.r2 === currentRange.r2 &&
(tempRange.c2 + 1 === currentRange.c1 || currentRange.c2 + 1 === tempRange.c1))) {
// Merge ranges by taking the outer boundaries
tempRange.c1 = Math.min(tempRange.c1, currentRange.c1);
tempRange.r1 = Math.min(tempRange.r1, currentRange.r1);
tempRange.c2 = Math.max(tempRange.c2, currentRange.c2);
tempRange.r2 = Math.max(tempRange.r2, currentRange.r2);
merged = true;
changed = true;
break; // Exit inner loop once merged
}
}
// Add to result if couldn't merge
if (!merged) {
tempRanges.push(currentRange);
}
}
// Update working set for next iteration
iterationRanges = tempRanges;
}
return iterationRanges;
};
MultiplyRange.prototype.isNull = function () {
if (!this.ranges || 0 === this.ranges.length || (1 === this.ranges.length && this.ranges[0] == null)) {
return true;

View File

@ -7320,6 +7320,9 @@
return this.props && this.props.specificRange;
};
CDocumentSearchExcel.prototype.checkMaxReplacedCells = function () {
return this.Count > 1000;
};
/**

View File

@ -553,6 +553,8 @@ function isAllowPasteLink(pastedWb) {
this.vScrollPxStep = null;
this.hScrollPxStep = null;
this._replaceCellTextManager = null;
this._init();
return this;
@ -18425,15 +18427,33 @@ function isAllowPasteLink(pastedWb) {
}
options.countReplace = 0;
if (options.isReplaceAll && false === this.collaborativeEditing.getCollaborativeEditing()) {
this._isLockedCells(aReplaceCells, /*subType*/null, function () {
t._replaceCellText(aReplaceCells, options, lockDraw, callback, true);
//lock all if large replaced range
let bCollaborativeEditing = this.collaborativeEditing.getCollaborativeEditing();
if (options.isReplaceAll && (false === bCollaborativeEditing || (this.workbook.SearchEngine && this.workbook.SearchEngine.checkMaxReplacedCells()))) {
let aReplaceCellsUnion = false === bCollaborativeEditing ? aReplaceCells : new AscCommonExcel.MultiplyRange(aReplaceCells).getUnionRanges();
this._isLockedCells(aReplaceCellsUnion, /*subType*/null, function (success) {
if (!success) {
callback && callback();
return;
}
t._replaceCellTextFast(aReplaceCells, options, lockDraw, callback, true);
});
} else {
this._replaceCellText(aReplaceCells, options, lockDraw, callback, false);
}
};
WorksheetView.prototype._replaceCellTextFast = function (aReplaceCells, options, lockDraw, callback, oneUser) {
// Use CReplaceCellTextManager for asynchronous text replacement processing
if (!this._replaceCellTextManager) {
this._replaceCellTextManager = new CReplaceCellTextManager();
}
// Start asynchronous processing using timer
this._replaceCellTextManager.Begin(this, aReplaceCells, options, lockDraw, callback, oneUser);
};
WorksheetView.prototype._replaceCellText = function (aReplaceCells, options, lockDraw, callback, oneUser) {
var t = this;
var needLockCell = !oneUser;
@ -30061,6 +30081,114 @@ function isAllowPasteLink(pastedWb) {
};
/**
* Class for asynchronous cell text replacement using timer
* @constructor
*/
function CReplaceCellTextManager() {
AscCommon.CActionOnTimerBase.call(this);
this.ws = null;
this.replaceCells = [];
this.options = null;
this.lockDraw = false;
this.callback = null;
this.oneUser = false;
this.needLockCell = false;
this.isSC = false;
this.FirstActionOnTimer = true;
this.Index = 0;
}
CReplaceCellTextManager.prototype = Object.create(AscCommon.CActionOnTimerBase.prototype);
CReplaceCellTextManager.prototype.constructor = CReplaceCellTextManager;
CReplaceCellTextManager.prototype.OnBegin = function(ws, aReplaceCells, options, lockDraw, callback, oneUser) {
this.ws = ws;
this.replaceCells = aReplaceCells;
this.options = options;
this.lockDraw = lockDraw;
this.callback = callback;
this.oneUser = oneUser;
this.needLockCell = !oneUser;
this.isSC = options.isSpellCheck;
this.Index = options.indexInArray || 0;
};
CReplaceCellTextManager.prototype.OnEnd = function() {
// After processing all cells, unlock calculation and draw
this.ws.model.workbook.dependencyFormulas.unlockRecal();
this.ws.draw(this.lockDraw);
if (this.callback) {
this.callback(this.options);
}
};
CReplaceCellTextManager.prototype.IsContinue = function() {
return (this.Index < this.replaceCells.length);
};
CReplaceCellTextManager.prototype.DoAction = function() {
const cell = this.replaceCells[this.Index];
const t = this.ws;
this.Index++;
this.options.indexInArray = this.Index;
// Check for protected ranges
if (cell && t.model.isUserProtectedRangesIntersection(cell)) {
t.model.workbook.handlers.trigger("asc_onError", c_oAscError.ID.ProtectedRangeByOtherUser, c_oAscError.Level.NoCritical);
this.options.error = true;
this.End();
return;
}
// Check cell locking or perform replacement
const isSuccess = !this.needLockCell || t._isLockedCells(cell, /*subType*/null, function(){});
if (isSuccess) {
const c = t._getVisibleCell(cell.c1, cell.r1);
let cellValue = c.getValueForEdit();
let v, newValue;
const oldCellValue = cellValue;
// Replace text depending on the mode
if (!this.isSC) {
cellValue = cellValue.replace(this.options.findRegExp, () => {
++this.options.countReplace;
return this.options.replaceWith;
});
} else {
cellValue = AscCommonExcel.replaceSpellCheckWords(cellValue, this.options);
}
const isNeedToSave = oldCellValue !== cellValue;
if (isNeedToSave) {
// Create new fragments with replaced text
v = c.getValueForEdit2().slice(0, 1);
newValue = [];
newValue[0] = new AscCommonExcel.Fragment({text: cellValue, format: v[0].format.clone()});
// Save new value
if (!t._saveCellValueAfterEdit(c, newValue, /*flags*/undefined, /*isNotHistory*/true, /*lockDraw*/true)) {
this.options.error = true;
this.End();
return;
}
// Update search elements
if (t.workbook.SearchEngine) {
t.workbook.SearchEngine.removeFromSearchElems(cell.c1, cell.r1, t.model);
}
}
} else {
// If cell locking failed, stop processing
this.options.error = true;
this.End();
}
};
//------------------------------------------------------------export---------------------------------------------------
window['AscCommonExcel'] = window['AscCommonExcel'] || {};