diff --git a/packages/core/src/modules/cell.ts b/packages/core/src/modules/cell.ts index f18db85..c6f9f0e 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 b647dd9..01918be 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 5f35643..661e1d0 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)//配对的单引号后第一个字符不能是单引号 - // { - // ;//到此处说明公式错误 - // } - // 如果是''代表着输出' - 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 ( + !( + 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(/==|!=|<>|<=|>=|[,()=+-/*%&^><]/) + ); } - } 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; + 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 "" 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 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 (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); + } 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; + } } } - // 至此=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; - } - - formulaDependency.push(range); - } + } + 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; + } + + 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 aaf675d..cc694b7 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 fef52db..46745e1 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; }); }