From 0cc827145c9d03e9412e84177333063f0f4e8440 Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Thu, 12 Dec 2024 18:21:27 +0000 Subject: [PATCH 1/9] optimized: formula cached --- packages/core/src/events/paste.ts | 3 + packages/core/src/modules/cell.ts | 28 +- packages/core/src/modules/formula.ts | 458 ++++-------------- packages/core/src/modules/formulaHelper.ts | 326 +++++++++++++ packages/core/src/modules/refresh.ts | 2 + packages/core/src/modules/toolbar.ts | 9 + .../react/src/components/Workbook/index.tsx | 2 + 7 files changed, 439 insertions(+), 389 deletions(-) create mode 100644 packages/core/src/modules/formulaHelper.ts diff --git a/packages/core/src/events/paste.ts b/packages/core/src/events/paste.ts index 7401c595..1f3ea599 100644 --- a/packages/core/src/events/paste.ts +++ b/packages/core/src/events/paste.ts @@ -19,6 +19,7 @@ import { jfrefreshgrid } from "../modules/refresh"; import { setRowHeight } from "../api"; import { CFSplitRange } from "../modules"; import clipboard from "../modules/clipboard"; +import { setFormulaObject } from "../modules/formulaHelper"; function postPasteCut( ctx: Context, @@ -32,6 +33,7 @@ function postPasteCut( // clearTimeout(refreshCanvasTimeOut); for (let r = source.range.row[0]; r <= source.range.row[1]; r += 1) { for (let c = source.range.column[0]; c <= source.range.column[1]; c += 1) { + setFormulaObject(ctx, { r, c, id: source.sheetId }); if (`${r}_${c}_${source.sheetId}` in execF_rc) { continue; } @@ -43,6 +45,7 @@ function postPasteCut( for (let r = target.range.row[0]; r <= target.range.row[1]; r += 1) { for (let c = target.range.column[0]; c <= target.range.column[1]; c += 1) { + setFormulaObject(ctx, { r, c, id: source.sheetId }); if (`${r}_${c}_${target.sheetId}` in execF_rc) { continue; } diff --git a/packages/core/src/modules/cell.ts b/packages/core/src/modules/cell.ts index fa53e7a2..d67fa92c 100644 --- a/packages/core/src/modules/cell.ts +++ b/packages/core/src/modules/cell.ts @@ -21,6 +21,7 @@ import { } from "./inline-string"; import { isRealNull, isRealNum, valueIsError } from "./validation"; import { getCellTextInfo } from "./text"; +import { setFormulaObject } from "./formulaHelper"; // TODO put these in context ref // let rangestart = false; @@ -670,6 +671,10 @@ export function cancelNormalSelected(ctx: Context) { ctx.formulaCache.rangedrag_row_start = false; } +function isFormula(value: any) { + return _.isString(value) && value.slice(0, 1) === "=" && value.length > 1; +} + // formula.updatecell export function updateCell( ctx: Context, @@ -810,11 +815,11 @@ export function updateCell( curv.ct.fa !== "@" && !isRealNull(value) ) { - delete curv.m; // 更新时间m处理 , 会实际删除单元格数据的参数(flowdata时已删除) + delete curv.m; // Update time m processing will actually delete the parameters of the cell data (the flowdata has been deleted) if (curv.f) { - // 如果原来是公式,而更新的数据不是公式,则把公式删除 + // If it turns out to be a formula but the updated data is not a formula, delete the formula. delete curv.f; - delete curv.spl; // 删除单元格的sparklines的配置串 + delete curv.spl; // Delete the configuration string of sparklines of the cell } } } @@ -828,7 +833,7 @@ export function updateCell( if (_.isPlainObject(curv)) { if (!isCurInline) { - if (_.isString(value) && value.slice(0, 1) === "=" && value.length > 1) { + if (isFormula(value)) { const v = execfunction(ctx, value, r, c, undefined, undefined, true); isRunExecFunction = false; curv = _.cloneDeep(d?.[r]?.[c] || {}); @@ -854,11 +859,7 @@ export function updateCell( else if (_.isPlainObject(value)) { const valueFunction = value.f; - if ( - _.isString(valueFunction) && - valueFunction.slice(0, 1) === "=" && - valueFunction.length > 1 - ) { + if (isFormula(valueFunction)) { const v = execfunction( ctx, valueFunction, @@ -919,7 +920,7 @@ export function updateCell( } value = curv; } else { - if (_.isString(value) && value.slice(0, 1) === "=" && value.length > 1) { + if (isFormula(value)) { const v = execfunction(ctx, value, r, c, undefined, undefined, true); isRunExecFunction = false; value = { @@ -944,11 +945,7 @@ export function updateCell( else if (_.isPlainObject(value)) { const valueFunction = value.f; - if ( - _.isString(valueFunction) && - valueFunction.slice(0, 1) === "=" && - valueFunction.length > 1 - ) { + if (isFormula(valueFunction)) { const v = execfunction( ctx, valueFunction, @@ -1085,6 +1082,7 @@ export function updateCell( }); } + setFormulaObject(ctx, { r, c, id: ctx.currentSheetId }); ctx.formulaCache.execFunctionGlobalData = null; } diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index 9e4f8b8f..295f119f 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -1,7 +1,7 @@ import _ from "lodash"; // @ts-ignore import { Parser, ERROR_REF } from "@fortune-sheet/formula-parser"; -import type { Cell, Rect, Selection } from "../types"; +import type { Cell, CellMatrix, History, Rect, Selection } from "../types"; import { Context, getFlowdata } from "../context"; import { columnCharToIndex, @@ -11,18 +11,19 @@ import { getSheetIdByName, escapeHTMLTag, } from "../utils"; -import { - getcellFormula, - getRangetxt, - mergeMoveMain, - setCellValue, -} from "./cell"; +import { getRangetxt, mergeMoveMain, setCellValue } from "./cell"; import { error } from "./validation"; import { moveToEnd } from "./cursor"; import { locale } from "../locale"; import { colors } from "./color"; import { colLocation, mousePosition, rowLocation } from "./location"; import { cancelFunctionrangeSelected, seletedHighlistByindex } from "."; +import { + arrayMatch, + executeAffectedFormulas, + setFormulaObject, + getFormulaRunList, +} from "./formulaHelper"; let functionHTMLIndex = 0; let rangeIndexes: number[] = []; @@ -96,12 +97,18 @@ export class FormulaCache { execFunctionGlobalData: any; + formulaArrayCache: any; + + formulaObjects: any; + constructor() { const that = this; this.data_parm_index = 0; this.selectingRangeIndex = -1; this.functionlistMap = {}; this.execFunctionGlobalData = {}; + this.formulaArrayCache = {}; + this.formulaObjects = null; this.cellTextToIndexList = {}; this.parser = new Parser(); this.parser.on( @@ -178,6 +185,29 @@ export class FormulaCache { } return cell?.v; } + + updateFormulaCache(ctx: Context, history: History, data?: CellMatrix) { + function requestUpdate(value: any) { + if (value instanceof Object) { + if (value.r !== undefined && value.r !== undefined) { + setFormulaObject( + ctx, + { r: value.r, c: value.c, id: ctx.currentSheetId }, + data + ); + } + } + } + history.patches.forEach((patch) => { + if (Array.isArray(patch.value)) { + patch.value.forEach((value) => { + requestUpdate(value); + }); + } else { + requestUpdate(patch.value); + } + }); + } } function parseElement(eleString: string) { @@ -266,11 +296,16 @@ function addToCellIndexList(ctx: Context, txt: string, infoObj: any) { } } -export function getcellrange(ctx: Context, txt: string, formulaId?: string) { +export function getcellrange( + ctx: Context, + txt: string, + formulaId?: string, + data?: CellMatrix +) { if (_.isNil(txt) || txt.length === 0) { return null; } - const flowdata = getFlowdata(ctx, formulaId); + const flowdata = data || getFlowdata(ctx, formulaId); let sheettxt = ""; let rangetxt = ""; @@ -448,7 +483,7 @@ function checkSpecialFunctionRange( } } -function isFunctionRange( +export function isFunctionRange( ctx: Context, txt: string, r: number | null, @@ -854,7 +889,7 @@ export function delFunctionGroup( const { calcChain } = file; if (!_.isNil(calcChain)) { let modified = false; - const calcChainClone = _.cloneDeep(calcChain); + const calcChainClone = calcChain.slice(); for (let i = 0; i < calcChainClone.length; i += 1) { const calc = calcChainClone[i]; if (calc.r === r && calc.c === c && calc.id === id) { @@ -875,7 +910,7 @@ export function delFunctionGroup( const { dynamicArray } = file; if (!_.isNil(dynamicArray)) { let modified = false; - const dynamicArrayClone = _.cloneDeep(dynamicArray); + const dynamicArrayClone = dynamicArray.slice(); for (let i = 0; i < dynamicArrayClone.length; i += 1) { const calc = dynamicArrayClone[i]; if ( @@ -954,7 +989,7 @@ export function insertUpdateFunctionGroup( id = ctx.currentSheetId; } - // let func = getcellFormula(r, c, index); + // let func = getcellFormula(ctx, r, c, id); // if (_.isNil(func) || func.length==0) { // this.delFunctionGroup(r, c, index); // return; @@ -1274,6 +1309,17 @@ export function groupValuesRefresh(ctx: Context) { } } +function setFormulaObjectsCache( + ctx: Context, + calcChains: any, + data: CellMatrix +) { + for (let i = 0; i < calcChains.length; i += 1) { + const formulaCell = calcChains[i]; + setFormulaObject(ctx, formulaCell, data); + } +} + export function execFunctionGroup( ctx: Context, origin_r: number, @@ -1283,36 +1329,19 @@ export function execFunctionGroup( data?: any, isForce = false ) { + // 0. null checks if (_.isNil(data)) { data = getFlowdata(ctx); } - // if (!window.luckysheet_compareWith) { - // window.luckysheet_compareWith = luckysheet_compareWith; - // window.luckysheet_getarraydata = luckysheet_getarraydata; - // window.luckysheet_getcelldata = luckysheet_getcelldata; - // window.luckysheet_parseData = luckysheet_parseData; - // window.luckysheet_getValue = luckysheet_getValue; - // window.luckysheet_indirect_check = luckysheet_indirect_check; - // window.luckysheet_indirect_check_return = luckysheet_indirect_check_return; - // window.luckysheet_offset_check = luckysheet_offset_check; - // window.luckysheet_calcADPMM = luckysheet_calcADPMM; - // window.luckysheet_getSpecialReference = luckysheet_getSpecialReference; - // } - if (_.isNil(ctx.formulaCache.execFunctionGlobalData)) { ctx.formulaCache.execFunctionGlobalData = {}; } - // let luckysheetfile = getluckysheetfile(); - // let dynamicArray_compute = luckysheetfile[getSheetIndex(ctx.currentSheetId)_.isNil(]["dynamicArray_compute"]) ? {} : luckysheetfile[getSheetIndex(ctx.currentSheetId)]["dynamicArray_compute"]; - if (_.isNil(id)) { id = ctx.currentSheetId; } if (!_.isNil(value)) { - // 此处setcellvalue 中this.execFunctionGroupData会保存想要更新的值,本函数结尾不要设为null,以备后续函数使用 - // setcellvalue(origin_r, origin_c, _this.execFunctionGroupData, value); const cellCache: Cell[][] = [[{ v: undefined }]]; setCellValue(ctx, 0, 0, cellCache, value); [ @@ -1324,282 +1353,50 @@ export function execFunctionGroup( ] = cellCache; } - // { "r": r, "c": c, "id": id, "func": func} - const calcChains = getAllFunctionGroup(ctx); - const formulaObjects: any = {}; + // 1. get list of all functions in the sheet + const calcChains = getAllFunctionGroup(ctx); // { "r": r, "c": c, "id": id, "func": func} - const sheets = ctx.luckysheetfile; - const sheetData: any = {}; - for (let i = 0; i < sheets.length; i += 1) { - const sheet = sheets[i]; - sheetData[sheet.id!] = sheet.data; - } - - // 把修改涉及的单元格存储为对象 - const updateValueOjects: any = {}; - const updateValueArray: any = []; + // 2. Store the cells involved in the modification + const updateValueObjects: any = {}; if (_.isNil(ctx.formulaCache.execFunctionExist)) { const key = `r${origin_r}c${origin_c}i${id}`; - updateValueOjects[key] = 1; + updateValueObjects[key] = 1; } else { for (let x = 0; x < ctx.formulaCache.execFunctionExist.length; x += 1) { const cell = ctx.formulaCache.execFunctionExist[x] as any; const key = `r${cell.r}c${cell.c}i${cell.i}`; - updateValueOjects[key] = 1; + updateValueObjects[key] = 1; } } + // 3. formulaObjects: a cache of ALL formulas vs their ranges + if (!ctx.formulaCache.formulaObjects) { + ctx.formulaCache.formulaObjects = {}; + setFormulaObjectsCache(ctx, calcChains, data); + } + const { formulaObjects } = ctx.formulaCache; + + // 4. Form a graph structure of references between formulas + // basically fills parents and children in formulaObjects[i] + const updateValueArray: any = []; const arrayMatchCache: Record< string, { key: string; r: number; c: number; sheetId: string }[] > = {}; - const arrayMatch = ( - formulaArray: any, - _formulaObjects: any, - _updateValueOjects: any, - func: any - ) => { - for (let a = 0; a < formulaArray.length; a += 1) { - const range = formulaArray[a]; - const cacheKey = `r${range.row[0]}${range.row[1]}c${range.column[0]}${range.column[1]}id${range.sheetId}`; - if (cacheKey in arrayMatchCache) { - const amc = arrayMatchCache[cacheKey]; - // console.log(amc); - amc.forEach((item) => { - func(item.key, item.r, item.c, item.sheetId); - }); - } else { - const functionArr = []; - for (let r = range.row[0]; r <= range.row[1]; r += 1) { - for (let c = range.column[0]; c <= range.column[1]; c += 1) { - const key = `r${r}c${c}i${range.sheetId}`; - func(key, r, c, range.sheetId); - if ( - (_formulaObjects && key in _formulaObjects) || - (_updateValueOjects && key in _updateValueOjects) - ) { - functionArr.push({ - key, - r, - c, - sheetId: range.sheetId, - }); - } - } - } - - if (_formulaObjects || _updateValueOjects) { - arrayMatchCache[cacheKey] = functionArr; - } - } - } - }; - - // 创建公式缓存及其范围的缓存 - // console.time("1"); - for (let i = 0; i < calcChains.length; i += 1) { - const formulaCell = calcChains[i]; - const key = `r${formulaCell.r}c${formulaCell.c}i${formulaCell.id}`; - const calc_funcStr = getcellFormula( - ctx, - formulaCell.r, - formulaCell.c, - formulaCell.id - ); - if (_.isNil(calc_funcStr)) { - continue; - } - const txt1 = calc_funcStr.toUpperCase(); - const isOffsetFunc = - txt1.indexOf("INDIRECT(") > -1 || - txt1.indexOf("OFFSET(") > -1 || - txt1.indexOf("INDEX(") > -1; - const formulaArray = []; - - if (isOffsetFunc) { - isFunctionRange( - ctx, - calc_funcStr, - null, - null, - formulaCell.id, - null, - (str_nb: string) => { - const range = getcellrange(ctx, _.trim(str_nb), formulaCell.id); - if (!_.isNil(range)) { - formulaArray.push(range); - } - } - ); - } else if ( - !( - calc_funcStr.substring(0, 2) === '="' && - calc_funcStr.substring(calc_funcStr.length - 1, 1) === '"' - ) - ) { - // let formulaTextArray = calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g);//无法正确分割单引号或双引号之间有==、!=、-等运算符的情况。导致如='1-2'!A1公式中表名1-2的A1单元格内容更新后,公式的值不更新的bug - // 解决='1-2'!A1+5会被calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g)分割成["","'1","2'!A1",5]的错误情况 - let point = 0; // 指针 - let squote = -1; // 双引号 - let dquote = -1; // 单引号 - const formulaTextArray = []; - const sq_end_array = []; // 保存了配对的单引号在formulaTextArray的index索引。 - const calc_funcStr_length = calc_funcStr.length; - for (let j = 0; j < calc_funcStr_length; j += 1) { - const char = calc_funcStr.charAt(j); - if (char === "'" && dquote === -1) { - // 如果是单引号开始 - if (squote === -1) { - if (point !== j) { - formulaTextArray.push( - ...calc_funcStr - .substring(point, j) - .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) - ); - } - squote = j; - point = j; - } // 单引号结束 - else { - // if (squote === i - 1)//配对的单引号后第一个字符不能是单引号 - // { - // ;//到此处说明公式错误 - // } - // 如果是''代表着输出' - if ( - j < calc_funcStr_length - 1 && - calc_funcStr.charAt(j + 1) === "'" - ) { - j += 1; - } else { - // 如果下一个字符不是'代表单引号结束 - // if (calc_funcStr.charAt(i - 1) === "'") {//配对的单引号后最后一个字符不能是单引号 - // ;//到此处说明公式错误 - point = j + 1; - formulaTextArray.push(calc_funcStr.substring(squote, point)); - sq_end_array.push(formulaTextArray.length - 1); - squote = -1; - // } else { - // point = i + 1; - // formulaTextArray.push(calc_funcStr.substring(squote, point)); - // sq_end_array.push(formulaTextArray.length - 1); - // squote = -1; - // } - } - } - } - if (char === '"' && squote === -1) { - // 如果是双引号开始 - if (dquote === -1) { - if (point !== j) { - formulaTextArray.push( - ...calc_funcStr - .substring(point, j) - .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) - ); - } - dquote = j; - point = j; - } else { - // 如果是""代表着输出" - if ( - j < calc_funcStr_length - 1 && - calc_funcStr.charAt(j + 1) === '"' - ) { - j += 1; - } else { - // 双引号结束 - point = j + 1; - formulaTextArray.push(calc_funcStr.substring(dquote, point)); - dquote = -1; - } - } - } - } - if (point !== calc_funcStr_length) { - formulaTextArray.push( - ...calc_funcStr - .substring(point, calc_funcStr_length) - .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) - ); - } - // 拼接所有配对单引号及之后一个单元格内容,例如["'1-2'","!A1"]拼接为["'1-2'!A1"] - for (let j = sq_end_array.length - 1; j >= 0; j -= 1) { - if (sq_end_array[j] !== formulaTextArray.length - 1) { - formulaTextArray[sq_end_array[j]] += - formulaTextArray[sq_end_array[j] + 1]; - formulaTextArray.splice(sq_end_array[j] + 1, 1); - } - } - // 至此=SUM('1-2'!A1:A2&"'1-2'!A2")由原来的["","SUM","'1","2'!A1:A2","",""'1","2'!A2""]更正为["","SUM","","'1-2'!A1:A2","","",""'1-2'!A2""] - - for (let j = 0; j < formulaTextArray.length; j += 1) { - const t = formulaTextArray[j]; - if (t.length <= 1) { - continue; - } - - if ( - (t.substring(0, 1) === '"' && t.substring(t.length - 1, 1) === '"') || - !iscelldata(t) - ) { - continue; - } - - const range = getcellrange(ctx, _.trim(t), formulaCell.id); - - if (_.isNil(range)) { - continue; - } - - formulaArray.push(range); - } - } - - const item = { - formulaArray, - calc_funcStr, - key, - r: formulaCell.r, - c: formulaCell.c, - id: formulaCell.id, - parents: {}, - chidren: {}, - color: "w", - }; - - formulaObjects[key] = item; - - // if(isForce){ - // updateValueArray.push(item); - // } - // else{ - // arrayMatch(formulaArray, null, function(key){ - // if(key in updateValueOjects){ - // updateValueArray.push(item); - // } - // }); - // } - } - - // console.timeEnd("1"); - - // console.time("2"); - // 形成一个公式之间引用的图结构 Object.keys(formulaObjects).forEach((key) => { const formulaObject = formulaObjects[key]; arrayMatch( + arrayMatchCache, formulaObject.formulaArray, formulaObjects, - updateValueOjects, + updateValueObjects, (childKey: string) => { if (childKey in formulaObjects) { const childFormulaObject = formulaObjects[childKey]; - formulaObject.chidren[childKey] = 1; + // formulaObject.chidren[childKey] = 1; not needed childFormulaObject.parents[key] = 1; } - // console.log(childKey,formulaObject.formulaArray); - if (!isForce && childKey in updateValueOjects) { + if (!isForce && childKey in updateValueObjects) { updateValueArray.push(formulaObject); } } @@ -1610,98 +1407,11 @@ export function execFunctionGroup( } }); - // console.log(formulaObjects) - // console.timeEnd("2"); - - // console.time("3"); - const formulaRunList = []; - // 计算,采用深度优先遍历公式形成的图结构 - - // updateValueArray.forEach((key)=>{ - // let formulaObject = formulaObjects[key]; - - // }); - - let stack = updateValueArray; - const existsFormulaRunList: any = {}; - while (stack.length > 0) { - const formulaObject = stack.pop(); - - if (_.isNil(formulaObject) || formulaObject.key in existsFormulaRunList) { - continue; - } - - if (formulaObject.color === "b") { - formulaRunList.push(formulaObject); - existsFormulaRunList[formulaObject.key] = 1; - continue; - } - - const cacheStack: any = []; - Object.keys(formulaObject.parents).forEach((parentKey) => { - const parentFormulaObject = formulaObjects[parentKey]; - if (!_.isNil(parentFormulaObject)) { - cacheStack.push(parentFormulaObject); - } - }); - - if (cacheStack.length === 0) { - formulaRunList.push(formulaObject); - existsFormulaRunList[formulaObject.key] = 1; - } else { - formulaObject.color = "b"; - stack.push(formulaObject); - stack = stack.concat(cacheStack); - } - } - - formulaRunList.reverse(); - - const calcChainSet = new Set(); - calcChains.forEach((item) => { - calcChainSet.add(`${item.r}_${item.c}_${item.id}`); - }); - - // console.log(formulaObjects, ii) - // console.timeEnd("3"); - - // console.time("4"); - for (let i = 0; i < formulaRunList.length; i += 1) { - const formulaCell = formulaRunList[i]; - if (formulaCell.level === Math.max) { - continue; - } + // 5. Get list of affected formulas using the graph structure by depth-first traversal + const formulaRunList = getFormulaRunList(updateValueArray, formulaObjects); - const { calc_funcStr } = formulaCell; - - const v = execfunction( - ctx, - calc_funcStr, - formulaCell.r, - formulaCell.c, - formulaCell.id, - calcChainSet - ); - - ctx.groupValuesRefreshData.push({ - r: formulaCell.r, - c: formulaCell.c, - v: v[1], - f: v[2], - spe: v[3], - id: formulaCell.id, - }); - - // _this.execFunctionGroupData[u.r][u.c] = value; - ctx.formulaCache.execFunctionGlobalData[ - `${formulaCell.r}_${formulaCell.c}_${formulaCell.id}` - ] = { - v: v[1], - f: v[2], - }; - } - // console.log(formulaRunList); - // console.timeEnd("4"); + // 6. execute relevant formulas + executeAffectedFormulas(ctx, formulaRunList, calcChains); ctx.formulaCache.execFunctionExist = undefined; } diff --git a/packages/core/src/modules/formulaHelper.ts b/packages/core/src/modules/formulaHelper.ts new file mode 100644 index 00000000..d1cfdc07 --- /dev/null +++ b/packages/core/src/modules/formulaHelper.ts @@ -0,0 +1,326 @@ +import _ from "lodash"; +import { + CellMatrix, + Context, + execfunction, + getcellFormula, + getcellrange, + iscelldata, + isFunctionRange, +} from ".."; + +// make sure to place it *after* the value at cell[r][c] has been modified +export function setFormulaObject( + ctx: Context, + formulaCell: any, + data?: CellMatrix +) { + const key = `r${formulaCell.r}c${formulaCell.c}i${formulaCell.id}`; + const calc_funcStr = getcellFormula( + ctx, + formulaCell.r, + formulaCell.c, + formulaCell.id, + data + ); + if (_.isNil(calc_funcStr)) { + delete ctx.formulaCache.formulaObjects?.[key]; + return; + } + const txt1 = calc_funcStr.toUpperCase(); + const isOffsetFunc = + txt1.indexOf("INDIRECT(") > -1 || + txt1.indexOf("OFFSET(") > -1 || + txt1.indexOf("INDEX(") > -1; + + const formulaArray = ctx.formulaCache.formulaArrayCache[calc_funcStr] || []; + if (formulaArray.length === 0) { + if (isOffsetFunc) { + isFunctionRange( + ctx, + calc_funcStr, + null, + null, + formulaCell.id, + null, + (str_nb: string) => { + const range = getcellrange(ctx, _.trim(str_nb), formulaCell.id, data); + if (!_.isNil(range)) { + formulaArray.push(range); + } + } + ); + } else if ( + !( + calc_funcStr.substring(0, 2) === '="' && + calc_funcStr.substring(calc_funcStr.length - 1, 1) === '"' + ) + ) { + // let formulaTextArray = calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g);//无法正确分割单引号或双引号之间有==、!=、-等运算符的情况。导致如='1-2'!A1公式中表名1-2的A1单元格内容更新后,公式的值不更新的bug + // 解决='1-2'!A1+5会被calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g)分割成["","'1","2'!A1",5]的错误情况 + let point = 0; // pointer + let squote = -1; // single quote + let dquote = -1; // double quotes + const formulaTextArray = []; + const sq_end_array = []; // Saves the paired single quotes in the index of formulaTextArray. + const calc_funcStr_length = calc_funcStr.length; + for (let j = 0; j < calc_funcStr_length; j += 1) { + const char = calc_funcStr.charAt(j); + if (char === "'" && dquote === -1) { + // If it starts with a single quote + if (squote === -1) { + if (point !== j) { + formulaTextArray.push( + ...calc_funcStr + .substring(point, j) + .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); + } + squote = j; + point = j; + } // end single quote + else { + // if (squote === i - 1)//配对的单引号后第一个字符不能是单引号 + // { + // ;//到此处说明公式错误 + // } + // 如果是''代表着输出' + if ( + j < calc_funcStr_length - 1 && + calc_funcStr.charAt(j + 1) === "'" + ) { + j += 1; + } else { + // If the next character is not ', it means the end of a single quote + // if (calc_funcStr.charAt(i - 1) === "'") {//The last character after the paired single quote cannot be a single quote + // ;//Go here to explain the formula error + point = j + 1; + formulaTextArray.push(calc_funcStr.substring(squote, point)); + sq_end_array.push(formulaTextArray.length - 1); + squote = -1; + // } else { + // point = i + 1; + // formulaTextArray.push(calc_funcStr.substring(squote, point)); + // sq_end_array.push(formulaTextArray.length - 1); + // squote = -1; + // } + } + } + } else if (char === '"' && squote === -1) { + // If it starts with double quotes + if (dquote === -1) { + if (point !== j) { + formulaTextArray.push( + ...calc_funcStr + .substring(point, j) + .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); + } + dquote = j; + point = j; + } else { + // If "" represents output" + if ( + j < calc_funcStr_length - 1 && + calc_funcStr.charAt(j + 1) === '"' + ) { + j += 1; + } else { + // end with double quotes + point = j + 1; + formulaTextArray.push(calc_funcStr.substring(dquote, point)); + dquote = -1; + } + } + } + } + if (point !== calc_funcStr_length) { + formulaTextArray.push( + ...calc_funcStr + .substring(point, calc_funcStr_length) + .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); + } + // 拼接所有配对单引号及之后一个单元格内容,例如["'1-2'","!A1"]拼接为["'1-2'!A1"] + for (let j = sq_end_array.length - 1; j >= 0; j -= 1) { + if (sq_end_array[j] !== formulaTextArray.length - 1) { + formulaTextArray[sq_end_array[j]] += + formulaTextArray[sq_end_array[j] + 1]; + formulaTextArray.splice(sq_end_array[j] + 1, 1); + } + } + // 至此=SUM('1-2'!A1:A2&"'1-2'!A2")由原来的["","SUM","'1","2'!A1:A2","",""'1","2'!A2""]更正为["","SUM","","'1-2'!A1:A2","","",""'1-2'!A2""] + + for (let j = 0; j < formulaTextArray.length; j += 1) { + const t = formulaTextArray[j]; + if (t.length <= 1) { + continue; + } + + if ( + (t.substring(0, 1) === '"' && t.substring(t.length - 1, 1) === '"') || + !iscelldata(t) + ) { + continue; + } + + const range = getcellrange(ctx, _.trim(t), formulaCell.id, data); + + if (_.isNil(range)) { + continue; + } + + formulaArray.push(range); + } + } + } + if (!ctx.formulaCache.formulaArrayCache[calc_funcStr]) + ctx.formulaCache.formulaArrayCache[calc_funcStr] = formulaArray; + + const item = { + formulaArray, + calc_funcStr, + key, + r: formulaCell.r, + c: formulaCell.c, + id: formulaCell.id, + parents: {}, + chidren: {}, + color: "w", + }; + + if (!ctx.formulaCache.formulaObjects) ctx.formulaCache.formulaObjects = {}; + ctx.formulaCache.formulaObjects[key] = item; +} + +export function executeAffectedFormulas( + ctx: Context, + formulaRunList: any[], + calcChains: any +) { + const calcChainSet = new Set(); + calcChains.forEach((item: any) => { + calcChainSet.add(`${item.r}_${item.c}_${item.id}`); + }); + + for (let i = 0; i < formulaRunList.length; i += 1) { + const formulaCell = formulaRunList[i]; + if (formulaCell.level === Math.max) { + continue; + } + + const { calc_funcStr } = formulaCell; + + const v = execfunction( + ctx, + calc_funcStr, + formulaCell.r, + formulaCell.c, + formulaCell.id, + calcChainSet + ); + + ctx.groupValuesRefreshData.push({ + r: formulaCell.r, + c: formulaCell.c, + v: v[1], + f: v[2], + spe: v[3], + id: formulaCell.id, + }); + + ctx.formulaCache.execFunctionGlobalData[ + `${formulaCell.r}_${formulaCell.c}_${formulaCell.id}` + ] = { + v: v[1], + f: v[2], + }; + } +} + +export function getFormulaRunList( + updateValueArray: any[], + formulaObjects: any +) { + const formulaRunList = []; + let stack = updateValueArray; + const existsFormulaRunList: any = {}; + while (stack.length > 0) { + const formulaObject = stack.pop(); + + if (_.isNil(formulaObject) || formulaObject.key in existsFormulaRunList) { + continue; + } + + if (formulaObject.color === "b") { + formulaRunList.push(formulaObject); + existsFormulaRunList[formulaObject.key] = 1; + continue; + } + + const cacheStack: any = []; + Object.keys(formulaObject.parents).forEach((parentKey) => { + const parentFormulaObject = formulaObjects[parentKey]; + if (!_.isNil(parentFormulaObject)) { + cacheStack.push(parentFormulaObject); + } + }); + + if (cacheStack.length === 0) { + formulaRunList.push(formulaObject); + existsFormulaRunList[formulaObject.key] = 1; + } else { + formulaObject.color = "b"; + stack.push(formulaObject); + stack = stack.concat(cacheStack); + } + } + + formulaRunList.reverse(); + return formulaRunList; +} + +export const arrayMatch = ( + arrayMatchCache: any, + formulaArray: any, + _formulaObjects: any, + _updateValueObjects: any, + func: any +) => { + // OPTIMIZE_TODO: _updateValueObjects contains the currently edited rows and cols right now. + // we can execute it for all cells and store the graphs somewhere????? + for (let a = 0; a < formulaArray.length; a += 1) { + const range = formulaArray[a]; + const cacheKey = `r${range.row[0]}${range.row[1]}c${range.column[0]}${range.column[1]}id${range.sheetId}`; + if (cacheKey in arrayMatchCache) { + const amc: any[] = arrayMatchCache[cacheKey]; + // console.log(amc); + amc.forEach((item) => { + func(item.key, item.r, item.c, item.sheetId); + }); + } else { + const functionArr = []; + for (let r = range.row[0]; r <= range.row[1]; r += 1) { + for (let c = range.column[0]; c <= range.column[1]; c += 1) { + const key = `r${r}c${c}i${range.sheetId}`; + func(key, r, c, range.sheetId); + if ( + (_formulaObjects && key in _formulaObjects) || + (_updateValueObjects && key in _updateValueObjects) + ) { + functionArr.push({ + key, + r, + c, + sheetId: range.sheetId, + }); + } + } + } + + if (_formulaObjects || _updateValueObjects) { + arrayMatchCache[cacheKey] = functionArr; + } + } + } +}; diff --git a/packages/core/src/modules/refresh.ts b/packages/core/src/modules/refresh.ts index b8d2de4e..0ed35df2 100644 --- a/packages/core/src/modules/refresh.ts +++ b/packages/core/src/modules/refresh.ts @@ -1,6 +1,7 @@ import { Context, getFlowdata } from "../context"; import { CellMatrix, Selection } from "../types"; import { execFunctionGroup } from "./formula"; +import { setFormulaObject } from "./formulaHelper"; function runExecFunction( ctx: Context, @@ -12,6 +13,7 @@ function runExecFunction( for (let s = 0; s < range.length; s += 1) { for (let r = range[s].row[0]; r <= range[s].row[1]; r += 1) { for (let c = range[s].column[0]; c <= range[s].column[1]; c += 1) { + setFormulaObject(ctx, { r, c, id: index }, data); ctx.formulaCache.execFunctionExist.push({ r, c, i: index }); } } diff --git a/packages/core/src/modules/toolbar.ts b/packages/core/src/modules/toolbar.ts index 00435eba..b5d178b7 100644 --- a/packages/core/src/modules/toolbar.ts +++ b/packages/core/src/modules/toolbar.ts @@ -41,6 +41,7 @@ import { import { showLinkCard } from "./hyperlink"; import { cfSplitRange } from "./conditionalFormat"; import { getCellTextInfo } from "./text"; +import { setFormulaObject } from "./formulaHelper"; type ToolbarItemClickHandler = ( ctx: Context, @@ -901,6 +902,14 @@ export function autoSelectionFormula( if (!isfalse) { ctx.formulaCache.execFunctionExist.reverse(); + ctx.formulaCache.execFunctionExist.forEach((formulaCell) => { + setFormulaObject( + ctx, + { r: formulaCell.r, c: formulaCell.c, id: ctx.currentSheetId }, + flowdata + ); + }); + // @ts-ignore execFunctionGroup(ctx, null, null, null, null, flowdata); ctx.formulaCache.execFunctionGlobalData = null; diff --git a/packages/react/src/components/Workbook/index.tsx b/packages/react/src/components/Workbook/index.tsx index 807e0b36..df40b8fa 100644 --- a/packages/react/src/components/Workbook/index.tsx +++ b/packages/react/src/components/Workbook/index.tsx @@ -369,6 +369,7 @@ const Workbook = React.forwardRef( delete inversedOptions!.addSheet!.value!.data; } emitOp(newContext, history.inversePatches, inversedOptions, true); + newContext.formulaCache.updateFormulaCache(newContext, history); return newContext; }); } @@ -381,6 +382,7 @@ const Workbook = React.forwardRef( const newContext = applyPatches(ctx_, history.patches); globalCache.current.undoList.push(history); emitOp(newContext, history.patches, history.options); + newContext.formulaCache.updateFormulaCache(newContext, history); return newContext; }); } From b1beffaba73391b9cfa5ce0161ac65be71bbeedf Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Fri, 13 Dec 2024 06:47:57 +0000 Subject: [PATCH 2/9] removed: comments --- packages/core/src/modules/formula.ts | 126 +-------------------- packages/core/src/modules/formulaHelper.ts | 5 +- 2 files changed, 3 insertions(+), 128 deletions(-) diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index 295f119f..2ff238b9 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -97,6 +97,7 @@ export class FormulaCache { execFunctionGlobalData: any; + // useful in cut-paste operation where several cells may be affected but the formulas remains the same formulaArrayCache: any; formulaObjects: any; @@ -1061,129 +1062,6 @@ export function execfunction( ctx.calculateSheetId = id; - /* - const fp = _.trim(functionParserExe(txt)); - if ( - fp.substring(0, 20) === "luckysheet_function." || - fp.substring(0, 22) === "luckysheet_compareWith" - ) { - functionHTMLIndex = 0; - } - - if (!testFunction(txt) || fp === "") { - // TODO tooltip.info("", locale_formulaMore.execfunctionError); - return [false, error.n, txt]; - } - - let result = null; - window.luckysheetCurrentRow = r; - window.luckysheetCurrentColumn = c; - window.luckysheetCurrentIndex = index; - window.luckysheetCurrentFunction = txt; - - let sparklines = null; - - try { - if (fp.indexOf("luckysheet_getcelldata") > -1) { - const funcg = fp.split("luckysheet_getcelldata('"); - - for (let i = 1; i < funcg.length; i += 1) { - const funcgStr = funcg[i].split("')")[0]; - const funcgRange = getcellrange(ctx, funcgStr); - - if (funcgRange.row[0] < 0 || funcgRange.column[0] < 0) { - return [true, error.r, txt]; - } - - if ( - funcgRange.sheetId === ctx.calculateSheetId && - r >= funcgRange.row[0] && - r <= funcgRange.row[1] && - c >= funcgRange.column[0] && - c <= funcgRange.column[1] - ) { - // TODO if (isEditMode()) { - // alert(locale_formulaMore.execfunctionSelfError); - // } else { - // tooltip.info("", locale_formulaMore.execfunctionSelfErrorResult); - // } - - return [false, 0, txt]; - } - } - } - - result = new Function(`return ${fp}`)(); - if (typeof result === "string") { - // 把之前的非打印控制字符DEL替换回一个双引号。 - result = result.replace(/\x7F/g, '"'); - } - - // 加入sparklines的参数项目 - if (fp.indexOf("SPLINES") > -1) { - sparklines = result; - result = ""; - } - } catch (e) { - const err = e; - // err错误提示处理 - console.log(e, fp); - result = [error.n, err]; - } - - // 公式结果是对象,则表示只是选区。如果是单个单元格,则返回其值;如果是多个单元格,则返回 #VALUE!。 - if (_.isPlainObject(result) && !_.isNil(result.startCell)) { - if (_.isArray(result.data)) { - result = error.v; - } else { - if (_.isPlainObject(result.data) && !_.isEmpty(result.data.v)) { - result = result.data.v; - } else if (!_.isEmpty(result.data)) { - // 只有data长或宽大于1才可能是选区 - if (result.cell > 1 || result.rowl > 1) { - result = result.data; - } // 否则就是单个不为null的没有值v的单元格 - else { - result = 0; - } - } else { - result = 0; - } - } - } - - // 公式结果是数组,分错误值 和 动态数组 两种情况 - let dynamicArrayItem = null; - - if (_.isArray(result)) { - let isErr = false; - - if (!_.isArray(result[0]) && result.length === 2) { - isErr = valueIsError(result[0]); - } - - if (!isErr) { - if ( - _.isArray(result[0]) && - result.length === 1 && - result[0].length === 1 - ) { - result = result[0][0]; - } else { - dynamicArrayItem = { r, c, f: txt, id, data: result }; - result = ""; - } - } else { - result = result[0]; - } - } - - window.luckysheetCurrentRow = null; - window.luckysheetCurrentColumn = null; - window.luckysheetCurrentIndex = null; - window.luckysheetCurrentFunction = null; - */ - ctx.formulaCache.parser.context = ctx; const parsedResponse = ctx.formulaCache.parser.parse(txt.substring(1), { sheetId: id || ctx.currentSheetId, @@ -1377,7 +1255,7 @@ export function execFunctionGroup( const { formulaObjects } = ctx.formulaCache; // 4. Form a graph structure of references between formulas - // basically fills parents and children in formulaObjects[i] + // basically fills parents in formulaObjects[i] const updateValueArray: any = []; const arrayMatchCache: Record< string, diff --git a/packages/core/src/modules/formulaHelper.ts b/packages/core/src/modules/formulaHelper.ts index d1cfdc07..3fa84283 100644 --- a/packages/core/src/modules/formulaHelper.ts +++ b/packages/core/src/modules/formulaHelper.ts @@ -9,7 +9,7 @@ import { isFunctionRange, } from ".."; -// make sure to place it *after* the value at cell[r][c] has been modified +// Make sure setFormulaObject() is executed *after* the cell modifications export function setFormulaObject( ctx: Context, formulaCell: any, @@ -287,14 +287,11 @@ export const arrayMatch = ( _updateValueObjects: any, func: any ) => { - // OPTIMIZE_TODO: _updateValueObjects contains the currently edited rows and cols right now. - // we can execute it for all cells and store the graphs somewhere????? for (let a = 0; a < formulaArray.length; a += 1) { const range = formulaArray[a]; const cacheKey = `r${range.row[0]}${range.row[1]}c${range.column[0]}${range.column[1]}id${range.sheetId}`; if (cacheKey in arrayMatchCache) { const amc: any[] = arrayMatchCache[cacheKey]; - // console.log(amc); amc.forEach((item) => { func(item.key, item.r, item.c, item.sheetId); }); From d36e50e72b5890051a27f2fbd6cd98653d004718 Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:27:46 +0000 Subject: [PATCH 3/9] fixed: skipping formula error --- packages/core/src/modules/formulaHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/modules/formulaHelper.ts b/packages/core/src/modules/formulaHelper.ts index 3fa84283..a675bd86 100644 --- a/packages/core/src/modules/formulaHelper.ts +++ b/packages/core/src/modules/formulaHelper.ts @@ -243,7 +243,7 @@ export function getFormulaRunList( formulaObjects: any ) { const formulaRunList = []; - let stack = updateValueArray; + let stack = _.cloneDeep(updateValueArray); const existsFormulaRunList: any = {}; while (stack.length > 0) { const formulaObject = stack.pop(); From 12c2c0c2f1a6bff41f0df0c3363dcca9719522b0 Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Mon, 16 Dec 2024 06:36:47 +0000 Subject: [PATCH 4/9] fixed: formula optimization bugfixes, renaming variables, defining types --- packages/core/src/events/paste.ts | 6 +- packages/core/src/modules/ConditionFormat.ts | 42 ++++----- packages/core/src/modules/cell.ts | 15 ++- packages/core/src/modules/dataVerification.ts | 25 +++-- packages/core/src/modules/formula.ts | 94 ++++++++++--------- packages/core/src/modules/formulaHelper.ts | 46 ++++----- packages/core/src/modules/hyperlink.ts | 2 +- packages/core/src/modules/refresh.ts | 4 +- packages/core/src/modules/rowcol.ts | 2 + packages/core/src/modules/sheet.ts | 12 ++- packages/core/src/modules/toolbar.ts | 4 +- packages/core/src/types.ts | 42 +++++++++ .../src/components/DataVerification/index.tsx | 28 ++++-- .../react/src/components/Workbook/index.tsx | 8 +- 14 files changed, 209 insertions(+), 121 deletions(-) diff --git a/packages/core/src/events/paste.ts b/packages/core/src/events/paste.ts index 1f3ea599..144d0a56 100644 --- a/packages/core/src/events/paste.ts +++ b/packages/core/src/events/paste.ts @@ -19,7 +19,7 @@ import { jfrefreshgrid } from "../modules/refresh"; import { setRowHeight } from "../api"; import { CFSplitRange } from "../modules"; import clipboard from "../modules/clipboard"; -import { setFormulaObject } from "../modules/formulaHelper"; +import { setFormulaCellInfo } from "../modules/formulaHelper"; function postPasteCut( ctx: Context, @@ -33,7 +33,7 @@ function postPasteCut( // clearTimeout(refreshCanvasTimeOut); for (let r = source.range.row[0]; r <= source.range.row[1]; r += 1) { for (let c = source.range.column[0]; c <= source.range.column[1]; c += 1) { - setFormulaObject(ctx, { r, c, id: source.sheetId }); + setFormulaCellInfo(ctx, { r, c, id: source.sheetId }); if (`${r}_${c}_${source.sheetId}` in execF_rc) { continue; } @@ -45,7 +45,7 @@ function postPasteCut( for (let r = target.range.row[0]; r <= target.range.row[1]; r += 1) { for (let c = target.range.column[0]; c <= target.range.column[1]; c += 1) { - setFormulaObject(ctx, { r, c, id: source.sheetId }); + setFormulaCellInfo(ctx, { r, c, id: source.sheetId }); if (`${r}_${c}_${target.sheetId}` in execF_rc) { continue; } diff --git a/packages/core/src/modules/ConditionFormat.ts b/packages/core/src/modules/ConditionFormat.ts index 592994dd..5a371139 100644 --- a/packages/core/src/modules/ConditionFormat.ts +++ b/packages/core/src/modules/ConditionFormat.ts @@ -63,17 +63,17 @@ export function setConditionRules( const rangeArr = getRangeByTxt(ctx, v); // 判断条件值是不是选区 if (rangeArr.length > 1) { - const r1 = rangeArr[0].row[0]; - const r2 = rangeArr[0].row[1]; - const c1 = rangeArr[0].column[0]; - const c2 = rangeArr[0].column[1]; + const r1 = rangeArr?.[0]?.row[0]; + const r2 = rangeArr?.[0]?.row[1]; + const c1 = rangeArr?.[0]?.column[0]; + const c2 = rangeArr?.[0]?.column[1]; if (r1 === r2 && c1 === c2) { const d = getFlowdata(ctx); - if (!d) return; + if (!d || _.isNil(r1) || _.isNil(c1)) return; v = getCellValue(r1, c1, d); conditionRange.push({ - row: rangeArr[0].row, - column: rangeArr[0].column, + row: rangeArr?.[0]?.row, + column: rangeArr?.[0]?.column, }); conditionValue.push(v); } else { @@ -97,17 +97,17 @@ export function setConditionRules( return; } if (rangeArr1.length === 1) { - const r1 = rangeArr1[0].row[0]; - const r2 = rangeArr1[0].row[1]; - const c1 = rangeArr1[0].column[0]; - const c2 = rangeArr1[0].column[1]; + const r1 = rangeArr1?.[0]?.row[0]; + const r2 = rangeArr1?.[0]?.row[1]; + const c1 = rangeArr1?.[0]?.column[0]; + const c2 = rangeArr1?.[0]?.column[1]; if (r1 === r2 && c1 === c2) { const d = getFlowdata(ctx); - if (!d) return; + if (!d || _.isNil(r1) || _.isNil(c1)) return; v1 = getCellValue(r1, c1, d); conditionRange.push({ - row: rangeArr1[0].row, - column: rangeArr1[0].column, + row: rangeArr1?.[0]?.row, + column: rangeArr1?.[0]?.column, }); conditionValue.push(v1); } else { @@ -127,17 +127,17 @@ export function setConditionRules( return; } if (rangeArr2.length === 1) { - const r1 = rangeArr2[0].row[0]; - const r2 = rangeArr2[0].row[1]; - const c1 = rangeArr2[0].column[0]; - const c2 = rangeArr2[0].column[1]; + const r1 = rangeArr2?.[0]?.row[0]; + const r2 = rangeArr2?.[0]?.row[1]; + const c1 = rangeArr2?.[0]?.column[0]; + const c2 = rangeArr2?.[0]?.column[1]; if (r1 === r2 && c1 === c2) { const d = getFlowdata(ctx); - if (!d) return; + if (!d || _.isNil(r1) || _.isNil(c1)) return; v2 = getCellValue(r1, c1, d); conditionRange.push({ - row: rangeArr2[0].row, - column: rangeArr2[0].column, + row: rangeArr2?.[0]?.row, + column: rangeArr2?.[0]?.column, }); } else { ctx.warnDialog = conditionformat.onlySingleCell; diff --git a/packages/core/src/modules/cell.ts b/packages/core/src/modules/cell.ts index d67fa92c..f18db856 100644 --- a/packages/core/src/modules/cell.ts +++ b/packages/core/src/modules/cell.ts @@ -1,6 +1,13 @@ import _ from "lodash"; import { Context, getFlowdata } from "../context"; -import { Cell, CellMatrix, Range, Selection, SingleRange } from "../types"; +import { + Cell, + CellMatrix, + FormulaDependency, + Range, + Selection, + SingleRange, +} from "../types"; import { getSheetIndex, indexToColumnChar, rgbToHex } from "../utils"; import { checkCF, getComputeMap } from "./ConditionFormat"; import { getFailureText, validateCellData } from "./dataVerification"; @@ -21,7 +28,7 @@ import { } from "./inline-string"; import { isRealNull, isRealNum, valueIsError } from "./validation"; import { getCellTextInfo } from "./text"; -import { setFormulaObject } from "./formulaHelper"; +import { setFormulaCellInfo } from "./formulaHelper"; // TODO put these in context ref // let rangestart = false; @@ -1082,7 +1089,7 @@ export function updateCell( }); } - setFormulaObject(ctx, { r, c, id: ctx.currentSheetId }); + setFormulaCellInfo(ctx, { r, c, id: ctx.currentSheetId }); ctx.formulaCache.execFunctionGlobalData = null; } @@ -1210,7 +1217,7 @@ export function getRangetxt( // 把string A1:A2转为选区数组 export function getRangeByTxt(ctx: Context, txt: string) { - let range = []; + let range: (FormulaDependency | null)[] = []; if (txt.indexOf(",") !== -1) { const arr = txt.split(","); for (let i = 0; i < arr.length; i += 1) { diff --git a/packages/core/src/modules/dataVerification.ts b/packages/core/src/modules/dataVerification.ts index 7dcfc39c..7aebb97f 100644 --- a/packages/core/src/modules/dataVerification.ts +++ b/packages/core/src/modules/dataVerification.ts @@ -1,3 +1,4 @@ +import _ from "lodash"; import { colLocationByIndex, Context, @@ -39,9 +40,9 @@ export function dataRangeSelection( ctx.luckysheetCellUpdate = [row_index, col_index]; const range = getRangeByTxt(ctx, rangT); - const r = range[0].row; - const c = range[0].column; - + const r = range?.[0]?.row; + const c = range?.[0]?.column; + if (_.isNil(r) || _.isNil(c)) return; const row_pre = rowLocationByIndex(r[0], ctx.visibledatarow)[0]; const row = rowLocationByIndex(r[1], ctx.visibledatarow)[1]; const col_pre = colLocationByIndex(c[0], ctx.visibledatacolumn)[0]; @@ -69,9 +70,12 @@ export function getDropdownList(ctx: Context, txt: string) { const list: (string | number | boolean)[] = []; if (iscelldata(txt)) { const range = getcellrange(ctx, txt); - const index = getSheetIndex(ctx, range.sheetId) as number; + const index = getSheetIndex( + ctx, + range?.sheetId || ctx.currentSheetId + ) as number; const d = ctx.luckysheetfile[index].data; - if (!d) return []; + if (!d || !range) return []; for (let r = range.row[0]; r <= range.row[1]; r += 1) { for (let c = range.column[0]; c <= range.column[1]; c += 1) { if (!d[r]) { @@ -854,12 +858,13 @@ export function confirmMessage( ctx.warnDialog = generalDialog.noSeletionError; return false; } - let str = range[range.length - 1].row[0]; - let edr = range[range.length - 1].row[1]; - let stc = range[range.length - 1].column[0]; - let edc = range[range.length - 1].column[1]; + let str = range?.[range.length - 1]?.row[0]; + let edr = range?.[range.length - 1]?.row[1]; + let stc = range?.[range.length - 1]?.column[0]; + let edc = range?.[range.length - 1]?.column[1]; const d = getFlowdata(ctx); - if (!d) return false; + if (!d || _.isNil(str) || _.isNil(edr) || _.isNil(stc) || _.isNil(edc)) + return false; if (str < 0) { str = 0; } diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index 2ff238b9..9e7feef4 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -1,7 +1,17 @@ import _ from "lodash"; // @ts-ignore import { Parser, ERROR_REF } from "@fortune-sheet/formula-parser"; -import type { Cell, CellMatrix, History, Rect, Selection } from "../types"; +import type { + Cell, + CellMatrix, + FormulaDependency, + FormulaDependenciesMap, + FormulaCell, + FormulaCellInfoMap, + History, + Rect, + Selection, +} from "../types"; import { Context, getFlowdata } from "../context"; import { columnCharToIndex, @@ -21,7 +31,7 @@ import { cancelFunctionrangeSelected, seletedHighlistByindex } from "."; import { arrayMatch, executeAffectedFormulas, - setFormulaObject, + setFormulaCellInfo, getFormulaRunList, } from "./formulaHelper"; @@ -98,9 +108,9 @@ export class FormulaCache { execFunctionGlobalData: any; // useful in cut-paste operation where several cells may be affected but the formulas remains the same - formulaArrayCache: any; + formulaDependenciesMap: FormulaDependenciesMap; - formulaObjects: any; + formulaCellInfoMap: FormulaCellInfoMap | null; constructor() { const that = this; @@ -108,8 +118,8 @@ export class FormulaCache { this.selectingRangeIndex = -1; this.functionlistMap = {}; this.execFunctionGlobalData = {}; - this.formulaArrayCache = {}; - this.formulaObjects = null; + this.formulaDependenciesMap = {}; + this.formulaCellInfoMap = null; this.cellTextToIndexList = {}; this.parser = new Parser(); this.parser.on( @@ -191,7 +201,7 @@ export class FormulaCache { function requestUpdate(value: any) { if (value instanceof Object) { if (value.r !== undefined && value.r !== undefined) { - setFormulaObject( + setFormulaCellInfo( ctx, { r: value.r, c: value.c, id: ctx.currentSheetId }, data @@ -302,7 +312,7 @@ export function getcellrange( txt: string, formulaId?: string, data?: CellMatrix -) { +): FormulaDependency | null { if (_.isNil(txt) || txt.length === 0) { return null; } @@ -310,7 +320,7 @@ export function getcellrange( let sheettxt = ""; let rangetxt = ""; - let sheetId = null; + let sheetId; let sheetdata = null; const { luckysheetfile } = ctx; @@ -369,7 +379,7 @@ export function getcellrange( const col = columnCharToIndex(rangetxt.replace(/[^A-Za-z]/g, "")); if (!Number.isNaN(row) && !Number.isNaN(col)) { - const item = { + const item: FormulaDependency = { row: [row, row], column: [col, col], sheetId, @@ -380,8 +390,8 @@ export function getcellrange( return null; } const rangetxtArr = rangetxt.split(":"); - const row = []; - const col = []; + const row: [number, number] = [-1, -1]; + const col: [number, number] = [-1, -1]; row[0] = parseInt(rangetxtArr[0].replace(/[^0-9]/g, ""), 10) - 1; row[1] = parseInt(rangetxtArr[1].replace(/[^0-9]/g, ""), 10) - 1; if (Number.isNaN(row[0])) { @@ -405,7 +415,7 @@ export function getcellrange( return null; } - const item = { + const item: FormulaDependency = { row, column: col, sheetId, @@ -832,25 +842,11 @@ export function isFunctionRange( export function getAllFunctionGroup(ctx: Context) { const { luckysheetfile } = ctx; - let ret: any[] = []; + let ret: FormulaCell[] = []; for (let i = 0; i < luckysheetfile.length; i += 1) { const file = luckysheetfile[i]; let { calcChain } = file; - /* 备注:再次加载表格获取的数据可能是JSON字符串格式(需要进行发序列化处理) */ - // if (calcChain) { - // const tempCalcChain: any[] = []; - // calcChain.forEach((item) => { - // if (typeof item === "string") { - // tempCalcChain.push(JSON.parse(item)); - // } else { - // tempCalcChain.push(item); - // } - // }); - // calcChain = tempCalcChain; - // file.calcChain = tempCalcChain; - // } - let { dynamicArray_compute } = file; if (_.isNil(calcChain)) { calcChain = []; @@ -1187,14 +1183,14 @@ export function groupValuesRefresh(ctx: Context) { } } -function setFormulaObjectsCache( +export function setFormulaCellInfoMap( ctx: Context, calcChains: any, - data: CellMatrix + data?: CellMatrix ) { for (let i = 0; i < calcChains.length; i += 1) { const formulaCell = calcChains[i]; - setFormulaObject(ctx, formulaCell, data); + setFormulaCellInfo(ctx, formulaCell, data); } } @@ -1232,7 +1228,7 @@ export function execFunctionGroup( } // 1. get list of all functions in the sheet - const calcChains = getAllFunctionGroup(ctx); // { "r": r, "c": c, "id": id, "func": func} + const calcChains: FormulaCell[] = getAllFunctionGroup(ctx); // 2. Store the cells involved in the modification const updateValueObjects: any = {}; @@ -1247,30 +1243,33 @@ export function execFunctionGroup( } } - // 3. formulaObjects: a cache of ALL formulas vs their ranges - if (!ctx.formulaCache.formulaObjects) { - ctx.formulaCache.formulaObjects = {}; - setFormulaObjectsCache(ctx, calcChains, data); + // 3. formulaCellInfoMap: a cache of ALL formulas vs their ranges + if ( + !ctx.formulaCache.formulaCellInfoMap || + _.isEmpty(ctx.formulaCache.formulaCellInfoMap) + ) { + ctx.formulaCache.formulaCellInfoMap = {}; + setFormulaCellInfoMap(ctx, calcChains, data); } - const { formulaObjects } = ctx.formulaCache; + const { formulaCellInfoMap } = ctx.formulaCache; // 4. Form a graph structure of references between formulas - // basically fills parents in formulaObjects[i] + // basically fills parents in formulaCellInfoMap[i] const updateValueArray: any = []; const arrayMatchCache: Record< string, { key: string; r: number; c: number; sheetId: string }[] > = {}; - Object.keys(formulaObjects).forEach((key) => { - const formulaObject = formulaObjects[key]; + Object.keys(formulaCellInfoMap).forEach((key) => { + const formulaObject = formulaCellInfoMap[key]; arrayMatch( arrayMatchCache, - formulaObject.formulaArray, - formulaObjects, + formulaObject.formulaDependency, + formulaCellInfoMap, updateValueObjects, (childKey: string) => { - if (childKey in formulaObjects) { - const childFormulaObject = formulaObjects[childKey]; + if (childKey in formulaCellInfoMap) { + const childFormulaObject = formulaCellInfoMap[childKey]; // formulaObject.chidren[childKey] = 1; not needed childFormulaObject.parents[key] = 1; } @@ -1286,7 +1285,10 @@ export function execFunctionGroup( }); // 5. Get list of affected formulas using the graph structure by depth-first traversal - const formulaRunList = getFormulaRunList(updateValueArray, formulaObjects); + const formulaRunList = getFormulaRunList( + updateValueArray, + formulaCellInfoMap + ); // 6. execute relevant formulas executeAffectedFormulas(ctx, formulaRunList, calcChains); @@ -1540,7 +1542,7 @@ export function createRangeHightlight( return; if ( cellrange.sheetId === ctx.currentSheetId || - (cellrange.sheetId === -1 && + (!cellrange.sheetId && ctx.formulaCache.rangetosheet === ctx.currentSheetId) ) { const rect = seletedHighlistByindex( diff --git a/packages/core/src/modules/formulaHelper.ts b/packages/core/src/modules/formulaHelper.ts index a675bd86..5f356433 100644 --- a/packages/core/src/modules/formulaHelper.ts +++ b/packages/core/src/modules/formulaHelper.ts @@ -3,6 +3,8 @@ import { CellMatrix, Context, execfunction, + FormulaCell, + FormulaCellInfo, getcellFormula, getcellrange, iscelldata, @@ -10,9 +12,9 @@ import { } from ".."; // Make sure setFormulaObject() is executed *after* the cell modifications -export function setFormulaObject( +export function setFormulaCellInfo( ctx: Context, - formulaCell: any, + formulaCell: FormulaCell, data?: CellMatrix ) { const key = `r${formulaCell.r}c${formulaCell.c}i${formulaCell.id}`; @@ -24,7 +26,7 @@ export function setFormulaObject( data ); if (_.isNil(calc_funcStr)) { - delete ctx.formulaCache.formulaObjects?.[key]; + delete ctx.formulaCache.formulaCellInfoMap?.[key]; return; } const txt1 = calc_funcStr.toUpperCase(); @@ -33,8 +35,9 @@ export function setFormulaObject( txt1.indexOf("OFFSET(") > -1 || txt1.indexOf("INDEX(") > -1; - const formulaArray = ctx.formulaCache.formulaArrayCache[calc_funcStr] || []; - if (formulaArray.length === 0) { + const formulaDependency = + ctx.formulaCache.formulaDependenciesMap[calc_funcStr] || []; + if (formulaDependency.length === 0) { if (isOffsetFunc) { isFunctionRange( ctx, @@ -46,7 +49,7 @@ export function setFormulaObject( (str_nb: string) => { const range = getcellrange(ctx, _.trim(str_nb), formulaCell.id, data); if (!_.isNil(range)) { - formulaArray.push(range); + formulaDependency.push(range); } } ); @@ -170,15 +173,15 @@ export function setFormulaObject( continue; } - formulaArray.push(range); + formulaDependency.push(range); } } } - if (!ctx.formulaCache.formulaArrayCache[calc_funcStr]) - ctx.formulaCache.formulaArrayCache[calc_funcStr] = formulaArray; + if (!ctx.formulaCache.formulaDependenciesMap[calc_funcStr]) + ctx.formulaCache.formulaDependenciesMap[calc_funcStr] = formulaDependency; - const item = { - formulaArray, + const item: FormulaCellInfo = { + formulaDependency, calc_funcStr, key, r: formulaCell.r, @@ -189,8 +192,9 @@ export function setFormulaObject( color: "w", }; - if (!ctx.formulaCache.formulaObjects) ctx.formulaCache.formulaObjects = {}; - ctx.formulaCache.formulaObjects[key] = item; + if (!ctx.formulaCache.formulaCellInfoMap) + ctx.formulaCache.formulaCellInfoMap = {}; + ctx.formulaCache.formulaCellInfoMap[key] = item; } export function executeAffectedFormulas( @@ -240,7 +244,7 @@ export function executeAffectedFormulas( export function getFormulaRunList( updateValueArray: any[], - formulaObjects: any + formulaCellInfoMap: any ) { const formulaRunList = []; let stack = _.cloneDeep(updateValueArray); @@ -260,7 +264,7 @@ export function getFormulaRunList( const cacheStack: any = []; Object.keys(formulaObject.parents).forEach((parentKey) => { - const parentFormulaObject = formulaObjects[parentKey]; + const parentFormulaObject = formulaCellInfoMap[parentKey]; if (!_.isNil(parentFormulaObject)) { cacheStack.push(parentFormulaObject); } @@ -282,13 +286,13 @@ export function getFormulaRunList( export const arrayMatch = ( arrayMatchCache: any, - formulaArray: any, - _formulaObjects: any, + formulaDependency: any, + _formulaCellInfoMap: any, _updateValueObjects: any, func: any ) => { - for (let a = 0; a < formulaArray.length; a += 1) { - const range = formulaArray[a]; + for (let a = 0; a < formulaDependency.length; a += 1) { + const range = formulaDependency[a]; const cacheKey = `r${range.row[0]}${range.row[1]}c${range.column[0]}${range.column[1]}id${range.sheetId}`; if (cacheKey in arrayMatchCache) { const amc: any[] = arrayMatchCache[cacheKey]; @@ -302,7 +306,7 @@ export const arrayMatch = ( const key = `r${r}c${c}i${range.sheetId}`; func(key, r, c, range.sheetId); if ( - (_formulaObjects && key in _formulaObjects) || + (_formulaCellInfoMap && key in _formulaCellInfoMap) || (_updateValueObjects && key in _updateValueObjects) ) { functionArr.push({ @@ -315,7 +319,7 @@ export const arrayMatch = ( } } - if (_formulaObjects || _updateValueObjects) { + if (_formulaCellInfoMap || _updateValueObjects) { arrayMatchCache[cacheKey] = functionArr; } } diff --git a/packages/core/src/modules/hyperlink.ts b/packages/core/src/modules/hyperlink.ts index 6b751ceb..b9f77072 100644 --- a/packages/core/src/modules/hyperlink.ts +++ b/packages/core/src/modules/hyperlink.ts @@ -175,7 +175,7 @@ export function goToLink( scrollbarX.scrollLeft = col_pre; scrollbarY.scrollLeft = row_pre; ctx.luckysheet_select_save = normalizeSelection(ctx, [range]); - changeSheet(ctx, range.sheetId); + changeSheet(ctx, range.sheetId || ctx.currentSheetId); } ctx.linkCard = undefined; } diff --git a/packages/core/src/modules/refresh.ts b/packages/core/src/modules/refresh.ts index 0ed35df2..abb575c1 100644 --- a/packages/core/src/modules/refresh.ts +++ b/packages/core/src/modules/refresh.ts @@ -1,7 +1,7 @@ import { Context, getFlowdata } from "../context"; import { CellMatrix, Selection } from "../types"; import { execFunctionGroup } from "./formula"; -import { setFormulaObject } from "./formulaHelper"; +import { setFormulaCellInfo } from "./formulaHelper"; function runExecFunction( ctx: Context, @@ -13,7 +13,7 @@ function runExecFunction( for (let s = 0; s < range.length; s += 1) { for (let r = range[s].row[0]; r <= range[s].row[1]; r += 1) { for (let c = range[s].column[0]; c <= range[s].column[1]; c += 1) { - setFormulaObject(ctx, { r, c, id: index }, data); + setFormulaCellInfo(ctx, { r, c, id: index }, data); ctx.formulaCache.execFunctionExist.push({ r, c, i: index }); } } diff --git a/packages/core/src/modules/rowcol.ts b/packages/core/src/modules/rowcol.ts index 3a7db5ef..9f68526e 100644 --- a/packages/core/src/modules/rowcol.ts +++ b/packages/core/src/modules/rowcol.ts @@ -1152,6 +1152,7 @@ export function insertRowCol( } refreshLocalMergeData(merge_new, file); + ctx.formulaCache.formulaCellInfoMap = null; // if (type === "row") { // const scrollLeft = $("#luckysheet-cell-main").scrollLeft(); @@ -2062,6 +2063,7 @@ export function deleteRowCol( file.hyperlink = newHyperlink; refreshLocalMergeData(merge_new, file); + ctx.formulaCache.formulaCellInfoMap = null; if (file.id === ctx.currentSheetId) { ctx.config = cfg; diff --git a/packages/core/src/modules/sheet.ts b/packages/core/src/modules/sheet.ts index aa1cf5b3..5d73f173 100644 --- a/packages/core/src/modules/sheet.ts +++ b/packages/core/src/modules/sheet.ts @@ -6,6 +6,7 @@ import { locale } from "../locale"; import { Settings } from "../settings"; import { CellMatrix, Sheet } from "../types"; import { generateRandomSheetName, getSheetIndex } from "../utils"; +import { setFormulaCellInfo } from "./formulaHelper"; function storeSheetParam(ctx: Context) { const index = getSheetIndex(ctx, ctx.currentSheetId); @@ -194,7 +195,7 @@ export function updateSheet(ctx: Context, newData: Sheet[]) { const { data, row, column } = newDatum; const index = getSheetIndex(ctx, newDatum.id!) as number; if (data != null) { - // 如果row和column存在的话则进行row和column和data进行比较,如果row和column不存在的话则进行data和default进行比较。 + // If row and column exist, compare row and column with data. If row and column do not exist, compare data with default. let lastRowNum = data.length; let lastColNum = data[0].length; if (row != null && column != null && row > 0 && column > 0) { @@ -210,6 +211,7 @@ export function updateSheet(ctx: Context, newData: Sheet[]) { for (let i = 0; i < data.length; i += 1) { for (let j = 0; j < data[i].length; j += 1) { expandedData[i][j] = data[i][j]; + setFormulaCellInfo(ctx, { r: i, c: j, id: newDatum.id! }, data); } } newDatum.data = expandedData; @@ -220,6 +222,14 @@ export function updateSheet(ctx: Context, newData: Sheet[]) { } } else if (newDatum.celldata != null) { initSheetData(ctx, index, newDatum); + const _index = getSheetIndex(ctx, newDatum.id!) as number; + newDatum.celldata?.forEach((d) => { + setFormulaCellInfo( + ctx, + { r: d.r, c: d.c, id: newDatum.id! }, + ctx.luckysheetfile[_index].data + ); + }); } }); } diff --git a/packages/core/src/modules/toolbar.ts b/packages/core/src/modules/toolbar.ts index b5d178b7..a681ac77 100644 --- a/packages/core/src/modules/toolbar.ts +++ b/packages/core/src/modules/toolbar.ts @@ -41,7 +41,7 @@ import { import { showLinkCard } from "./hyperlink"; import { cfSplitRange } from "./conditionalFormat"; import { getCellTextInfo } from "./text"; -import { setFormulaObject } from "./formulaHelper"; +import { setFormulaCellInfo } from "./formulaHelper"; type ToolbarItemClickHandler = ( ctx: Context, @@ -903,7 +903,7 @@ export function autoSelectionFormula( if (!isfalse) { ctx.formulaCache.execFunctionExist.reverse(); ctx.formulaCache.execFunctionExist.forEach((formulaCell) => { - setFormulaObject( + setFormulaCellInfo( ctx, { r: formulaCell.r, c: formulaCell.c, id: ctx.currentSheetId }, flowdata diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f7faecc6..aaf675d8 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -314,3 +314,45 @@ export type GlobalCache = { export type SingleRange = { row: number[]; column: number[] }; export type Range = SingleRange[]; + +// FORMULA +export type FormulaDependency = { + row: [number, number]; + column: [number, number]; + sheetId: string | undefined; +}; + +export type FormulaDependenciesMap = { + [formula: string]: FormulaDependency[]; +}; + +type AncestorFormulaCell = { + [rxcxix: string]: number; +}; + +export type FormulaCellInfo = { + formulaDependency: FormulaDependency[]; + calc_funcStr: string; + key: string; + r: number; + c: number; + id: string; + parents: AncestorFormulaCell; + chidren: AncestorFormulaCell; + color: string; +}; + +export type FormulaCellInfoMap = { + [rxcxix: string]: FormulaCellInfo; +}; + +export type FormulaCell = { + r: number; + c: number; + id: string; + parent?: AncestorFormulaCell; + func?: [boolean, number, string]; + color?: string; + chidren?: AncestorFormulaCell; + times?: number; +}; diff --git a/packages/react/src/components/DataVerification/index.tsx b/packages/react/src/components/DataVerification/index.tsx index ea826344..7b452b76 100644 --- a/packages/react/src/components/DataVerification/index.tsx +++ b/packages/react/src/components/DataVerification/index.tsx @@ -1,3 +1,4 @@ +import _ from "lodash"; import { getDropdownList, getFlowdata, @@ -83,12 +84,19 @@ const DataVerification: React.FC = () => { getSheetIndex(ctx, ctx.currentSheetId) as number ].dataVerification ?? {}; - const str = range[range.length - 1].row[0]; - const edr = range[range.length - 1].row[1]; - const stc = range[range.length - 1].column[0]; - const edc = range[range.length - 1].column[1]; + const str = range?.[range.length - 1]?.row[0]; + const edr = range?.[range.length - 1]?.row[1]; + const stc = range?.[range.length - 1]?.column[0]; + const edc = range?.[range.length - 1]?.column[1]; const d = getFlowdata(ctx); - if (!d) return; + if ( + !d || + _.isNil(str) || + _.isNil(stc) || + _.isNil(edr) || + _.isNil(edc) + ) + return; for (let r = str; r <= edr; r += 1) { for (let c = stc; c <= edc; c += 1) { const key = `${r}_${c}`; @@ -116,10 +124,12 @@ const DataVerification: React.FC = () => { const currentDataVerification = ctx.luckysheetfile[getSheetIndex(ctx, ctx.currentSheetId) as number] .dataVerification ?? {}; - const str = range[range.length - 1].row[0]; - const edr = range[range.length - 1].row[1]; - const stc = range[range.length - 1].column[0]; - const edc = range[range.length - 1].column[1]; + const str = range?.[range.length - 1]?.row[0]; + const edr = range?.[range.length - 1]?.row[1]; + const stc = range?.[range.length - 1]?.column[0]; + const edc = range?.[range.length - 1]?.column[1]; + if (_.isNil(str) || _.isNil(stc) || _.isNil(edr) || _.isNil(edc)) + return; for (let r = str; r <= edr; r += 1) { for (let c = stc; c <= edc; c += 1) { delete currentDataVerification[`${r}_${c}`]; diff --git a/packages/react/src/components/Workbook/index.tsx b/packages/react/src/components/Workbook/index.tsx index df40b8fa..fef52db5 100644 --- a/packages/react/src/components/Workbook/index.tsx +++ b/packages/react/src/components/Workbook/index.tsx @@ -20,6 +20,7 @@ import { locale, calcSelectionInfo, groupValuesRefresh, + setFormulaCellInfoMap, } from "@fortune-sheet/core"; import React, { useMemo, @@ -440,7 +441,12 @@ const Workbook = React.forwardRef( newData.forEach((newDatum) => { const index = getSheetIndex(draftCtx, newDatum.id!) as number; const sheet = draftCtx.luckysheetfile?.[index]; - initSheetData(draftCtx, sheet, index); + const cellMatrixData = initSheetData(draftCtx, sheet, index); + setFormulaCellInfoMap( + draftCtx, + sheet.calcChain, + cellMatrixData || undefined + ); }); } if (mergedSettings.devicePixelRatio > 0) { From bcb01ba48e8bd1883218b00cf4c19c62dd25f87f Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Mon, 16 Dec 2024 06:50:12 +0000 Subject: [PATCH 5/9] fixed: handle undefined --- packages/core/src/modules/formula.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index 9e7feef4..852d3823 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -1185,9 +1185,10 @@ export function groupValuesRefresh(ctx: Context) { export function setFormulaCellInfoMap( ctx: Context, - calcChains: any, + calcChains?: any[], data?: CellMatrix ) { + if (_.isNil(calcChains)) return; for (let i = 0; i < calcChains.length; i += 1) { const formulaCell = calcChains[i]; setFormulaCellInfo(ctx, formulaCell, data); From 44be382ef5c6b5659baf2b77699a586f5b028ad3 Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Mon, 16 Dec 2024 07:17:45 +0000 Subject: [PATCH 6/9] fixed: row col nil check --- packages/core/src/modules/formula.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index 852d3823..f8fe102c 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -200,7 +200,7 @@ export class FormulaCache { updateFormulaCache(ctx: Context, history: History, data?: CellMatrix) { function requestUpdate(value: any) { if (value instanceof Object) { - if (value.r !== undefined && value.r !== undefined) { + if (!_.isNil(value.r) && !_.isNil(value.c)) { setFormulaCellInfo( ctx, { r: value.r, c: value.c, id: ctx.currentSheetId }, From 02df819ca12bf0512e3575fb72882bec0c6863cb Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Mon, 16 Dec 2024 08:12:43 +0000 Subject: [PATCH 7/9] fixed: delete formula > undo - not reflecting changes --- packages/core/src/modules/formula.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index f8fe102c..b647dd9f 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -203,14 +203,20 @@ export class FormulaCache { if (!_.isNil(value.r) && !_.isNil(value.c)) { setFormulaCellInfo( ctx, - { r: value.r, c: value.c, id: ctx.currentSheetId }, + { + r: value.r, + c: value.c, + id: value.id || history.options?.id || ctx.currentSheetId, + }, data ); } } } history.patches.forEach((patch) => { - if (Array.isArray(patch.value)) { + if (patch.path[5] === "f") { + requestUpdate({ r: patch.path[3], c: patch.path[4] }); + } else if (Array.isArray(patch.value)) { patch.value.forEach((value) => { requestUpdate(value); }); From 20f3b666192c2ebbb982666e68ebe8e513fad28a Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:43:01 +0000 Subject: [PATCH 8/9] removed: FormulaDependenciesMap, fixed: undo-redo bugs with formula cache --- packages/core/src/modules/cell.ts | 5 +- packages/core/src/modules/formula.ts | 26 +- packages/core/src/modules/formulaHelper.ts | 256 +++++++++--------- packages/core/src/types.ts | 4 - .../react/src/components/Workbook/index.tsx | 12 +- 5 files changed, 155 insertions(+), 148 deletions(-) diff --git a/packages/core/src/modules/cell.ts b/packages/core/src/modules/cell.ts index f18db856..c6f9f0e0 100644 --- a/packages/core/src/modules/cell.ts +++ b/packages/core/src/modules/cell.ts @@ -19,6 +19,7 @@ import { functionHTMLGenerate, getcellrange, iscelldata, + isFormula, } from "./formula"; import { attrToCssName, @@ -678,10 +679,6 @@ export function cancelNormalSelected(ctx: Context) { ctx.formulaCache.rangedrag_row_start = false; } -function isFormula(value: any) { - return _.isString(value) && value.slice(0, 1) === "=" && value.length > 1; -} - // formula.updatecell export function updateCell( ctx: Context, diff --git a/packages/core/src/modules/formula.ts b/packages/core/src/modules/formula.ts index b647dd9f..01918bed 100644 --- a/packages/core/src/modules/formula.ts +++ b/packages/core/src/modules/formula.ts @@ -5,7 +5,6 @@ import type { Cell, CellMatrix, FormulaDependency, - FormulaDependenciesMap, FormulaCell, FormulaCellInfoMap, History, @@ -59,6 +58,10 @@ const LABEL_EXTRACT_REGEXP = new RegExp( `^${rowColumnWithSheetName}(?:[:]${rowColumnWithSheetName})?$` ); +export function isFormula(value: any) { + return _.isString(value) && value.slice(0, 1) === "=" && value.length > 1; +} + // FormulaCache is defined as class to avoid being frozen by immer export class FormulaCache { parser: any; @@ -107,9 +110,6 @@ export class FormulaCache { execFunctionGlobalData: any; - // useful in cut-paste operation where several cells may be affected but the formulas remains the same - formulaDependenciesMap: FormulaDependenciesMap; - formulaCellInfoMap: FormulaCellInfoMap | null; constructor() { @@ -118,7 +118,6 @@ export class FormulaCache { this.selectingRangeIndex = -1; this.functionlistMap = {}; this.execFunctionGlobalData = {}; - this.formulaDependenciesMap = {}; this.formulaCellInfoMap = null; this.cellTextToIndexList = {}; this.parser = new Parser(); @@ -197,7 +196,12 @@ export class FormulaCache { return cell?.v; } - updateFormulaCache(ctx: Context, history: History, data?: CellMatrix) { + updateFormulaCache( + ctx: Context, + history: History, + type: "undo" | "redo", + data?: CellMatrix + ) { function requestUpdate(value: any) { if (value instanceof Object) { if (!_.isNil(value.r) && !_.isNil(value.c)) { @@ -213,8 +217,14 @@ export class FormulaCache { } } } - history.patches.forEach((patch) => { - if (patch.path[5] === "f") { + const changesHistory = + type === "undo" ? history.inversePatches : history.patches; + changesHistory.forEach((patch) => { + if ( + isFormula(patch.value?.f) || + patch.value === null || + patch.path[5] === "f" + ) { requestUpdate({ r: patch.path[3], c: patch.path[4] }); } else if (Array.isArray(patch.value)) { patch.value.forEach((value) => { diff --git a/packages/core/src/modules/formulaHelper.ts b/packages/core/src/modules/formulaHelper.ts index 5f356433..661e1d00 100644 --- a/packages/core/src/modules/formulaHelper.ts +++ b/packages/core/src/modules/formulaHelper.ts @@ -5,6 +5,7 @@ import { execfunction, FormulaCell, FormulaCellInfo, + FormulaDependency, getcellFormula, getcellrange, iscelldata, @@ -18,7 +19,7 @@ export function setFormulaCellInfo( data?: CellMatrix ) { const key = `r${formulaCell.r}c${formulaCell.c}i${formulaCell.id}`; - const calc_funcStr = getcellFormula( + const calc_funcStr: string | undefined = getcellFormula( ctx, formulaCell.r, formulaCell.c, @@ -35,150 +36,145 @@ export function setFormulaCellInfo( txt1.indexOf("OFFSET(") > -1 || txt1.indexOf("INDEX(") > -1; - const formulaDependency = - ctx.formulaCache.formulaDependenciesMap[calc_funcStr] || []; - if (formulaDependency.length === 0) { - if (isOffsetFunc) { - isFunctionRange( - ctx, - calc_funcStr, - null, - null, - formulaCell.id, - null, - (str_nb: string) => { - const range = getcellrange(ctx, _.trim(str_nb), formulaCell.id, data); - if (!_.isNil(range)) { - formulaDependency.push(range); - } + const formulaDependency: FormulaDependency[] = []; + if (isOffsetFunc) { + isFunctionRange( + ctx, + calc_funcStr, + null, + null, + formulaCell.id, + null, + (str_nb: string) => { + const range = getcellrange(ctx, _.trim(str_nb), formulaCell.id, data); + if (!_.isNil(range)) { + formulaDependency.push(range); } - ); - } else if ( - !( - calc_funcStr.substring(0, 2) === '="' && - calc_funcStr.substring(calc_funcStr.length - 1, 1) === '"' - ) - ) { - // let formulaTextArray = calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g);//无法正确分割单引号或双引号之间有==、!=、-等运算符的情况。导致如='1-2'!A1公式中表名1-2的A1单元格内容更新后,公式的值不更新的bug - // 解决='1-2'!A1+5会被calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g)分割成["","'1","2'!A1",5]的错误情况 - let point = 0; // pointer - let squote = -1; // single quote - let dquote = -1; // double quotes - const formulaTextArray = []; - const sq_end_array = []; // Saves the paired single quotes in the index of formulaTextArray. - const calc_funcStr_length = calc_funcStr.length; - for (let j = 0; j < calc_funcStr_length; j += 1) { - const char = calc_funcStr.charAt(j); - if (char === "'" && dquote === -1) { - // If it starts with a single quote - if (squote === -1) { - if (point !== j) { - formulaTextArray.push( - ...calc_funcStr - .substring(point, j) - .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) - ); - } - squote = j; - point = j; - } // end single quote - else { - // if (squote === i - 1)//配对的单引号后第一个字符不能是单引号 - // { - // ;//到此处说明公式错误 + } + ); + } else if ( + !( + calc_funcStr.substring(0, 2) === '="' && + calc_funcStr.substring(calc_funcStr.length - 1, 1) === '"' + ) + ) { + // let formulaTextArray = calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g);//无法正确分割单引号或双引号之间有==、!=、-等运算符的情况。导致如='1-2'!A1公式中表名1-2的A1单元格内容更新后,公式的值不更新的bug + // 解决='1-2'!A1+5会被calc_funcStr.split(/==|!=|<>|<=|>=|[,()=+-\/*%&^><]/g)分割成["","'1","2'!A1",5]的错误情况 + let point = 0; // pointer + let squote = -1; // single quote + let dquote = -1; // double quotes + const formulaTextArray = []; + const sq_end_array = []; // Saves the paired single quotes in the index of formulaTextArray. + const calc_funcStr_length = calc_funcStr.length; + for (let j = 0; j < calc_funcStr_length; j += 1) { + const char = calc_funcStr.charAt(j); + if (char === "'" && dquote === -1) { + // If it starts with a single quote + if (squote === -1) { + if (point !== j) { + formulaTextArray.push( + ...calc_funcStr + .substring(point, j) + .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); + } + squote = j; + point = j; + } // end single quote + else { + // if (squote === i - 1)//配对的单引号后第一个字符不能是单引号 + // { + // ;//到此处说明公式错误 + // } + // 如果是''代表着输出' + if ( + j < calc_funcStr_length - 1 && + calc_funcStr.charAt(j + 1) === "'" + ) { + j += 1; + } else { + // If the next character is not ', it means the end of a single quote + // if (calc_funcStr.charAt(i - 1) === "'") {//The last character after the paired single quote cannot be a single quote + // ;//Go here to explain the formula error + point = j + 1; + formulaTextArray.push(calc_funcStr.substring(squote, point)); + sq_end_array.push(formulaTextArray.length - 1); + squote = -1; + // } else { + // point = i + 1; + // formulaTextArray.push(calc_funcStr.substring(squote, point)); + // sq_end_array.push(formulaTextArray.length - 1); + // squote = -1; // } - // 如果是''代表着输出' - if ( - j < calc_funcStr_length - 1 && - calc_funcStr.charAt(j + 1) === "'" - ) { - j += 1; - } else { - // If the next character is not ', it means the end of a single quote - // if (calc_funcStr.charAt(i - 1) === "'") {//The last character after the paired single quote cannot be a single quote - // ;//Go here to explain the formula error - point = j + 1; - formulaTextArray.push(calc_funcStr.substring(squote, point)); - sq_end_array.push(formulaTextArray.length - 1); - squote = -1; - // } else { - // point = i + 1; - // formulaTextArray.push(calc_funcStr.substring(squote, point)); - // sq_end_array.push(formulaTextArray.length - 1); - // squote = -1; - // } - } } - } else if (char === '"' && squote === -1) { - // If it starts with double quotes - if (dquote === -1) { - if (point !== j) { - formulaTextArray.push( - ...calc_funcStr - .substring(point, j) - .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) - ); - } - dquote = j; - point = j; + } + } else if (char === '"' && squote === -1) { + // If it starts with double quotes + if (dquote === -1) { + if (point !== j) { + formulaTextArray.push( + ...calc_funcStr + .substring(point, j) + .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); + } + dquote = j; + point = j; + } else { + // If "" represents output" + if ( + j < calc_funcStr_length - 1 && + calc_funcStr.charAt(j + 1) === '"' + ) { + j += 1; } else { - // If "" represents output" - if ( - j < calc_funcStr_length - 1 && - calc_funcStr.charAt(j + 1) === '"' - ) { - j += 1; - } else { - // end with double quotes - point = j + 1; - formulaTextArray.push(calc_funcStr.substring(dquote, point)); - dquote = -1; - } + // end with double quotes + point = j + 1; + formulaTextArray.push(calc_funcStr.substring(dquote, point)); + dquote = -1; } } } - if (point !== calc_funcStr_length) { - formulaTextArray.push( - ...calc_funcStr - .substring(point, calc_funcStr_length) - .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) - ); - } - // 拼接所有配对单引号及之后一个单元格内容,例如["'1-2'","!A1"]拼接为["'1-2'!A1"] - for (let j = sq_end_array.length - 1; j >= 0; j -= 1) { - if (sq_end_array[j] !== formulaTextArray.length - 1) { - formulaTextArray[sq_end_array[j]] += - formulaTextArray[sq_end_array[j] + 1]; - formulaTextArray.splice(sq_end_array[j] + 1, 1); - } + } + if (point !== calc_funcStr_length) { + formulaTextArray.push( + ...calc_funcStr + .substring(point, calc_funcStr_length) + .split(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); + } + // 拼接所有配对单引号及之后一个单元格内容,例如["'1-2'","!A1"]拼接为["'1-2'!A1"] + for (let j = sq_end_array.length - 1; j >= 0; j -= 1) { + if (sq_end_array[j] !== formulaTextArray.length - 1) { + formulaTextArray[sq_end_array[j]] += + formulaTextArray[sq_end_array[j] + 1]; + formulaTextArray.splice(sq_end_array[j] + 1, 1); } - // 至此=SUM('1-2'!A1:A2&"'1-2'!A2")由原来的["","SUM","'1","2'!A1:A2","",""'1","2'!A2""]更正为["","SUM","","'1-2'!A1:A2","","",""'1-2'!A2""] - - for (let j = 0; j < formulaTextArray.length; j += 1) { - const t = formulaTextArray[j]; - if (t.length <= 1) { - continue; - } + } + // 至此=SUM('1-2'!A1:A2&"'1-2'!A2")由原来的["","SUM","'1","2'!A1:A2","",""'1","2'!A2""]更正为["","SUM","","'1-2'!A1:A2","","",""'1-2'!A2""] - if ( - (t.substring(0, 1) === '"' && t.substring(t.length - 1, 1) === '"') || - !iscelldata(t) - ) { - continue; - } + for (let j = 0; j < formulaTextArray.length; j += 1) { + const t = formulaTextArray[j]; + if (t.length <= 1) { + continue; + } - const range = getcellrange(ctx, _.trim(t), formulaCell.id, data); + if ( + (t.substring(0, 1) === '"' && t.substring(t.length - 1, 1) === '"') || + !iscelldata(t) + ) { + continue; + } - if (_.isNil(range)) { - continue; - } + const range = getcellrange(ctx, _.trim(t), formulaCell.id, data); - formulaDependency.push(range); + if (_.isNil(range)) { + continue; } + + formulaDependency.push(range); } } - if (!ctx.formulaCache.formulaDependenciesMap[calc_funcStr]) - ctx.formulaCache.formulaDependenciesMap[calc_funcStr] = formulaDependency; const item: FormulaCellInfo = { formulaDependency, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index aaf675d8..cc694b7a 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -322,10 +322,6 @@ export type FormulaDependency = { sheetId: string | undefined; }; -export type FormulaDependenciesMap = { - [formula: string]: FormulaDependency[]; -}; - type AncestorFormulaCell = { [rxcxix: string]: number; }; diff --git a/packages/react/src/components/Workbook/index.tsx b/packages/react/src/components/Workbook/index.tsx index fef52db5..46745e17 100644 --- a/packages/react/src/components/Workbook/index.tsx +++ b/packages/react/src/components/Workbook/index.tsx @@ -370,7 +370,11 @@ const Workbook = React.forwardRef( delete inversedOptions!.addSheet!.value!.data; } emitOp(newContext, history.inversePatches, inversedOptions, true); - newContext.formulaCache.updateFormulaCache(newContext, history); + newContext.formulaCache.updateFormulaCache( + newContext, + history, + "undo" + ); return newContext; }); } @@ -383,7 +387,11 @@ const Workbook = React.forwardRef( const newContext = applyPatches(ctx_, history.patches); globalCache.current.undoList.push(history); emitOp(newContext, history.patches, history.options); - newContext.formulaCache.updateFormulaCache(newContext, history); + newContext.formulaCache.updateFormulaCache( + newContext, + history, + "redo" + ); return newContext; }); } From e068bf043c9a44c83d996cb1dfc0e7415c054f7b Mon Sep 17 00:00:00 2001 From: Shashank Agarwal <53386582+Corbe30@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:02:29 +0000 Subject: [PATCH 9/9] removed: unnecessary null checks --- packages/core/src/modules/ConditionFormat.ts | 24 +++++++++---------- packages/core/src/modules/dataVerification.ts | 12 +++++----- .../src/components/DataVerification/index.tsx | 16 ++++++------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/core/src/modules/ConditionFormat.ts b/packages/core/src/modules/ConditionFormat.ts index 5a371139..8b93bd79 100644 --- a/packages/core/src/modules/ConditionFormat.ts +++ b/packages/core/src/modules/ConditionFormat.ts @@ -63,10 +63,10 @@ export function setConditionRules( const rangeArr = getRangeByTxt(ctx, v); // 判断条件值是不是选区 if (rangeArr.length > 1) { - const r1 = rangeArr?.[0]?.row[0]; - const r2 = rangeArr?.[0]?.row[1]; - const c1 = rangeArr?.[0]?.column[0]; - const c2 = rangeArr?.[0]?.column[1]; + const r1 = rangeArr[0]?.row[0]; + const r2 = rangeArr[0]?.row[1]; + const c1 = rangeArr[0]?.column[0]; + const c2 = rangeArr[0]?.column[1]; if (r1 === r2 && c1 === c2) { const d = getFlowdata(ctx); if (!d || _.isNil(r1) || _.isNil(c1)) return; @@ -97,10 +97,10 @@ export function setConditionRules( return; } if (rangeArr1.length === 1) { - const r1 = rangeArr1?.[0]?.row[0]; - const r2 = rangeArr1?.[0]?.row[1]; - const c1 = rangeArr1?.[0]?.column[0]; - const c2 = rangeArr1?.[0]?.column[1]; + const r1 = rangeArr1[0]?.row[0]; + const r2 = rangeArr1[0]?.row[1]; + const c1 = rangeArr1[0]?.column[0]; + const c2 = rangeArr1[0]?.column[1]; if (r1 === r2 && c1 === c2) { const d = getFlowdata(ctx); if (!d || _.isNil(r1) || _.isNil(c1)) return; @@ -127,10 +127,10 @@ export function setConditionRules( return; } if (rangeArr2.length === 1) { - const r1 = rangeArr2?.[0]?.row[0]; - const r2 = rangeArr2?.[0]?.row[1]; - const c1 = rangeArr2?.[0]?.column[0]; - const c2 = rangeArr2?.[0]?.column[1]; + const r1 = rangeArr2[0]?.row[0]; + const r2 = rangeArr2[0]?.row[1]; + const c1 = rangeArr2[0]?.column[0]; + const c2 = rangeArr2[0]?.column[1]; if (r1 === r2 && c1 === c2) { const d = getFlowdata(ctx); if (!d || _.isNil(r1) || _.isNil(c1)) return; diff --git a/packages/core/src/modules/dataVerification.ts b/packages/core/src/modules/dataVerification.ts index 7aebb97f..689f6bf9 100644 --- a/packages/core/src/modules/dataVerification.ts +++ b/packages/core/src/modules/dataVerification.ts @@ -40,8 +40,8 @@ export function dataRangeSelection( ctx.luckysheetCellUpdate = [row_index, col_index]; const range = getRangeByTxt(ctx, rangT); - const r = range?.[0]?.row; - const c = range?.[0]?.column; + const r = range[0]?.row; + const c = range[0]?.column; if (_.isNil(r) || _.isNil(c)) return; const row_pre = rowLocationByIndex(r[0], ctx.visibledatarow)[0]; const row = rowLocationByIndex(r[1], ctx.visibledatarow)[1]; @@ -858,10 +858,10 @@ export function confirmMessage( ctx.warnDialog = generalDialog.noSeletionError; return false; } - let str = range?.[range.length - 1]?.row[0]; - let edr = range?.[range.length - 1]?.row[1]; - let stc = range?.[range.length - 1]?.column[0]; - let edc = range?.[range.length - 1]?.column[1]; + let str = range[range.length - 1]?.row[0]; + let edr = range[range.length - 1]?.row[1]; + let stc = range[range.length - 1]?.column[0]; + let edc = range[range.length - 1]?.column[1]; const d = getFlowdata(ctx); if (!d || _.isNil(str) || _.isNil(edr) || _.isNil(stc) || _.isNil(edc)) return false; diff --git a/packages/react/src/components/DataVerification/index.tsx b/packages/react/src/components/DataVerification/index.tsx index 7b452b76..0ae2dee6 100644 --- a/packages/react/src/components/DataVerification/index.tsx +++ b/packages/react/src/components/DataVerification/index.tsx @@ -84,10 +84,10 @@ const DataVerification: React.FC = () => { getSheetIndex(ctx, ctx.currentSheetId) as number ].dataVerification ?? {}; - const str = range?.[range.length - 1]?.row[0]; - const edr = range?.[range.length - 1]?.row[1]; - const stc = range?.[range.length - 1]?.column[0]; - const edc = range?.[range.length - 1]?.column[1]; + const str = range[range.length - 1]?.row[0]; + const edr = range[range.length - 1]?.row[1]; + const stc = range[range.length - 1]?.column[0]; + const edc = range[range.length - 1]?.column[1]; const d = getFlowdata(ctx); if ( !d || @@ -124,10 +124,10 @@ const DataVerification: React.FC = () => { const currentDataVerification = ctx.luckysheetfile[getSheetIndex(ctx, ctx.currentSheetId) as number] .dataVerification ?? {}; - const str = range?.[range.length - 1]?.row[0]; - const edr = range?.[range.length - 1]?.row[1]; - const stc = range?.[range.length - 1]?.column[0]; - const edc = range?.[range.length - 1]?.column[1]; + const str = range[range.length - 1]?.row[0]; + const edr = range[range.length - 1]?.row[1]; + const stc = range[range.length - 1]?.column[0]; + const edc = range[range.length - 1]?.column[1]; if (_.isNil(str) || _.isNil(stc) || _.isNil(edr) || _.isNil(edc)) return; for (let r = str; r <= edr; r += 1) {