From 2512d3c1ac590e52d809f3bd8f39f3cf8272f7f9 Mon Sep 17 00:00:00 2001 From: DengY11 <212294929@qq.com> Date: Sun, 25 May 2025 21:30:50 +0800 Subject: [PATCH 1/3] add calc cache --- calc.go | 32 ++++++++ calc_test.go | 206 +++++++++++++++++++++++++++++++++++++++------------ cell.go | 12 +++ excelize.go | 2 + 4 files changed, 203 insertions(+), 49 deletions(-) diff --git a/calc.go b/calc.go index c36e500942..2aff20bef4 100644 --- a/calc.go +++ b/calc.go @@ -814,6 +814,14 @@ type formulaFuncs struct { // Z.TEST // ZTEST func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string, err error) { + cacheKey := fmt.Sprintf("%s!%s", sheet, cell) + f.calcCacheMu.RLock() + if cachedResult, found := f.calcCache.Load(cacheKey); found { + f.calcCacheMu.RUnlock() + return cachedResult.(string), nil + } + f.calcCacheMu.RUnlock() + options := f.getOptions(opts...) var ( rawCellValue = options.RawCellValue @@ -836,14 +844,29 @@ func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string _, precision, decimal := isNumeric(token.Value()) if precision > 15 { result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'G', 15, 64))}, rawCellValue, CellTypeNumber) + if err == nil { + f.calcCacheMu.Lock() + f.calcCache.Store(cacheKey, result) + f.calcCacheMu.Unlock() + } return } if !strings.HasPrefix(result, "0") { result, err = f.formattedValue(&xlsxC{S: styleIdx, V: strings.ToUpper(strconv.FormatFloat(decimal, 'f', -1, 64))}, rawCellValue, CellTypeNumber) } + if err == nil { + f.calcCacheMu.Lock() + f.calcCache.Store(cacheKey, result) + f.calcCacheMu.Unlock() + } return } result, err = f.formattedValue(&xlsxC{S: styleIdx, V: token.Value()}, rawCellValue, CellTypeInlineString) + if err == nil { + f.calcCacheMu.Lock() + f.calcCache.Store(cacheKey, result) + f.calcCacheMu.Unlock() + } return } @@ -18842,3 +18865,12 @@ func (fn *formulaFuncs) DISPIMG(argsList *list.List) formulaArg { } return argsList.Front().Value.(formulaArg) } + +func (f *File) clearCalcCache() { + f.calcCacheMu.Lock() + defer f.calcCacheMu.Unlock() + f.calcCache.Range(func(key, value interface{}) bool { + f.calcCache.Delete(key) + return true + }) +} diff --git a/calc_test.go b/calc_test.go index 5f24cd3544..8b6b1e6f9d 100644 --- a/calc_test.go +++ b/calc_test.go @@ -777,25 +777,25 @@ func TestCalcCellValue(t *testing.T) { "=ROUNDUP(-11.111,-1)": "-20", "=ROUNDUP(ROUNDUP(100,1),-1)": "100", // SEARCH - "=SEARCH(\"s\",F1)": "1", - "=SEARCH(\"s\",F1,2)": "5", - "=SEARCH(\"e\",F1)": "4", - "=SEARCH(\"e*\",F1)": "4", - "=SEARCH(\"?e\",F1)": "3", - "=SEARCH(\"??e\",F1)": "2", - "=SEARCH(6,F2)": "2", + "=SEARCH(\"s\",F1)": "1", + "=SEARCH(\"s\",F1,2)": "5", + "=SEARCH(\"e\",F1)": "4", + "=SEARCH(\"e*\",F1)": "4", + "=SEARCH(\"?e\",F1)": "3", + "=SEARCH(\"??e\",F1)": "2", + "=SEARCH(6,F2)": "2", "=SEARCH(\"?\",\"你好world\")": "1", "=SEARCH(\"?l\",\"你好world\")": "5", "=SEARCH(\"?+\",\"你好 1+2\")": "4", "=SEARCH(\" ?+\",\"你好 1+2\")": "3", // SEARCHB - "=SEARCHB(\"s\",F1)": "1", - "=SEARCHB(\"s\",F1,2)": "5", - "=SEARCHB(\"e\",F1)": "4", - "=SEARCHB(\"e*\",F1)": "4", - "=SEARCHB(\"?e\",F1)": "3", - "=SEARCHB(\"??e\",F1)": "2", - "=SEARCHB(6,F2)": "2", + "=SEARCHB(\"s\",F1)": "1", + "=SEARCHB(\"s\",F1,2)": "5", + "=SEARCHB(\"e\",F1)": "4", + "=SEARCHB(\"e*\",F1)": "4", + "=SEARCHB(\"?e\",F1)": "3", + "=SEARCHB(\"??e\",F1)": "2", + "=SEARCHB(6,F2)": "2", "=SEARCHB(\"?\",\"你好world\")": "5", "=SEARCHB(\"?l\",\"你好world\")": "7", "=SEARCHB(\"?+\",\"你好 1+2\")": "6", @@ -1764,16 +1764,16 @@ func TestCalcCellValue(t *testing.T) { "=FINDB(\"\",\"Original Text\",2)": "2", "=FINDB(\"s\",\"Sales\",2)": "5", // LEFT - "=LEFT(\"Original Text\")": "O", - "=LEFT(\"Original Text\",4)": "Orig", - "=LEFT(\"Original Text\",0)": "", - "=LEFT(\"Original Text\",13)": "Original Text", - "=LEFT(\"Original Text\",20)": "Original Text", - "=LEFT(\"オリジナルテキスト\")": "オ", - "=LEFT(\"オリジナルテキスト\",2)": "オリ", - "=LEFT(\"オリジナルテキスト\",5)": "オリジナル", - "=LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ", - "=LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト", + "=LEFT(\"Original Text\")": "O", + "=LEFT(\"Original Text\",4)": "Orig", + "=LEFT(\"Original Text\",0)": "", + "=LEFT(\"Original Text\",13)": "Original Text", + "=LEFT(\"Original Text\",20)": "Original Text", + "=LEFT(\"オリジナルテキスト\")": "オ", + "=LEFT(\"オリジナルテキスト\",2)": "オリ", + "=LEFT(\"オリジナルテキスト\",5)": "オリジナル", + "=LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ", + "=LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト", // LEFTB "=LEFTB(\"Original Text\")": "O", "=LEFTB(\"Original Text\",4)": "Orig", @@ -1781,16 +1781,16 @@ func TestCalcCellValue(t *testing.T) { "=LEFTB(\"Original Text\",13)": "Original Text", "=LEFTB(\"Original Text\",20)": "Original Text", // LEN - "=LEN(\"\")": "0", - "=LEN(D1)": "5", - "=LEN(\"テキスト\")": "4", - "=LEN(\"オリジナルテキスト\")": "9", - "=LEN(7+LEN(A1&B1&C1))": "1", - "=LEN(8+LEN(A1+(C1-B1)))": "2", + "=LEN(\"\")": "0", + "=LEN(D1)": "5", + "=LEN(\"テキスト\")": "4", + "=LEN(\"オリジナルテキスト\")": "9", + "=LEN(7+LEN(A1&B1&C1))": "1", + "=LEN(8+LEN(A1+(C1-B1)))": "2", // LENB - "=LENB(\"\")": "0", - "=LENB(D1)": "5", - "=LENB(\"テキスト\")": "8", + "=LENB(\"\")": "0", + "=LENB(D1)": "5", + "=LENB(\"テキスト\")": "8", "=LENB(\"オリジナルテキスト\")": "18", // LOWER "=LOWER(\"test\")": "test", @@ -1803,7 +1803,7 @@ func TestCalcCellValue(t *testing.T) { "=MID(\"255 years\",3,1)": "5", "=MID(\"text\",3,6)": "xt", "=MID(\"text\",6,0)": "", - "=MID(\"你好World\",5,1)": "r", + "=MID(\"你好World\",5,1)": "r", "=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30C6\u30AD\u30B9\u30C8", "=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30B8\u30CA\u30EB\u30C6\u30AD", // MIDB @@ -1812,7 +1812,7 @@ func TestCalcCellValue(t *testing.T) { "=MIDB(\"255 years\",3,1)": "5", "=MIDB(\"text\",3,6)": "xt", "=MIDB(\"text\",6,0)": "", - "=MIDB(\"你好World\",5,1)": "W", + "=MIDB(\"你好World\",5,1)": "W", "=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30B8\u30CA", "=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30EA\u30B8\xe3", // PROPER @@ -1835,16 +1835,16 @@ func TestCalcCellValue(t *testing.T) { "=REPT(\"*\",1)": "*", "=REPT(\"**\",2)": "****", // RIGHT - "=RIGHT(\"Original Text\")": "t", - "=RIGHT(\"Original Text\",4)": "Text", - "=RIGHT(\"Original Text\",0)": "", - "=RIGHT(\"Original Text\",13)": "Original Text", - "=RIGHT(\"Original Text\",20)": "Original Text", - "=RIGHT(\"オリジナルテキスト\")": "ト", - "=RIGHT(\"オリジナルテキスト\",2)": "スト", - "=RIGHT(\"オリジナルテキスト\",4)": "テキスト", - "=RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト", - "=RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト", + "=RIGHT(\"Original Text\")": "t", + "=RIGHT(\"Original Text\",4)": "Text", + "=RIGHT(\"Original Text\",0)": "", + "=RIGHT(\"Original Text\",13)": "Original Text", + "=RIGHT(\"Original Text\",20)": "Original Text", + "=RIGHT(\"オリジナルテキスト\")": "ト", + "=RIGHT(\"オリジナルテキスト\",2)": "スト", + "=RIGHT(\"オリジナルテキスト\",4)": "テキスト", + "=RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト", + "=RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト", // RIGHTB "=RIGHTB(\"Original Text\")": "t", "=RIGHTB(\"Original Text\",4)": "Text", @@ -2806,11 +2806,11 @@ func TestCalcCellValue(t *testing.T) { "=SEARCH(2,A1)": {"#VALUE!", "#VALUE!"}, "=SEARCH(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // SEARCHB - "=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"}, - "=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"}, - "=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"}, + "=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"}, + "=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"}, + "=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"}, "=SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"}, - "=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, + "=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // SEC "=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"}, "=_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, @@ -6502,3 +6502,111 @@ func TestParseToken(t *testing.T) { efp.Token{TSubType: efp.TokenSubTypeRange, TValue: "1A"}, nil, nil, ).Error()) } + +// TestCalcCellValueCache tests the calculation cache functionality +func TestCalcCellValueCache(t *testing.T) { + f := NewFile() + + assert.NoError(t, f.SetCellValue("Sheet1", "A1", 40)) + assert.NoError(t, f.SetCellValue("Sheet1", "A2", 50)) + assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1+A2")) + + result1, err := f.CalcCellValue("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, "90", result1) + + result2, err := f.CalcCellValue("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, result1, result2, "cached result should be consistent") + + assert.NoError(t, f.SetCellValue("Sheet1", "A1", 60)) + + result3, err := f.CalcCellValue("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, "110", result3) + assert.NotEqual(t, result1, result3, "result should be updated after cache clear") +} + +// TestCalcCacheMultipleCells tests cache functionality with multiple dependent cells +func TestCalcCacheMultipleCells(t *testing.T) { + f := NewFile() + + assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10)) + assert.NoError(t, f.SetCellValue("Sheet1", "A2", 10)) + assert.NoError(t, f.SetCellFormula("Sheet1", "A3", "A1+A2")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A4", "A3*3")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A5", "A3+A4")) + + result3, err := f.CalcCellValue("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, "20", result3) + + result4, err := f.CalcCellValue("Sheet1", "A4") + assert.NoError(t, err) + assert.Equal(t, "60", result4) + + result5, err := f.CalcCellValue("Sheet1", "A5") + assert.NoError(t, err) + assert.Equal(t, "80", result5) + + assert.NoError(t, f.SetCellValue("Sheet1", "A1", 20)) + + newResult3, err := f.CalcCellValue("Sheet1", "A3") + assert.NoError(t, err) + assert.Equal(t, "30", newResult3) + assert.NotEqual(t, result3, newResult3, "A3 should be updated") + + newResult5, err := f.CalcCellValue("Sheet1", "A5") + assert.NoError(t, err) + assert.Equal(t, "120", newResult5) + assert.NotEqual(t, result5, newResult5, "A5 should be updated") +} + +// TestSetFunctionsClearCache tests that all Set functions properly clear the cache +func TestSetFunctionsClearCache(t *testing.T) { + f := NewFile() + + assert.NoError(t, f.SetCellValue("Sheet1", "A1", 10)) + assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "A1*2")) + + result1, err := f.CalcCellValue("Sheet1", "A2") + assert.NoError(t, err) + assert.Equal(t, "20", result1) + + result2, err := f.CalcCellValue("Sheet1", "A2") + assert.NoError(t, err) + assert.Equal(t, result1, result2, "results should be consistent from cache") + + testCases := []struct { + name string + setFunc func() error + }{ + {"SetCellValue", func() error { return f.SetCellValue("Sheet1", "B1", 100) }}, + {"SetCellInt", func() error { return f.SetCellInt("Sheet1", "B2", 200) }}, + {"SetCellUint", func() error { return f.SetCellUint("Sheet1", "B3", 300) }}, + {"SetCellFloat", func() error { return f.SetCellFloat("Sheet1", "B4", 3.14, 2, 64) }}, + {"SetCellStr", func() error { return f.SetCellStr("Sheet1", "B5", "test") }}, + {"SetCellBool", func() error { return f.SetCellBool("Sheet1", "B6", true) }}, + {"SetCellDefault", func() error { return f.SetCellDefault("Sheet1", "B7", "default") }}, + {"SetCellFormula", func() error { return f.SetCellFormula("Sheet1", "B8", "=1+1") }}, + {"SetCellHyperLink", func() error { return f.SetCellHyperLink("Sheet1", "B9", "http://example.com", "External") }}, + {"SetCellRichText", func() error { + runs := []RichTextRun{{Text: "Rich", Font: &Font{Bold: true}}} + return f.SetCellRichText("Sheet1", "B10", runs) + }}, + {"SetSheetRow", func() error { return f.SetSheetRow("Sheet1", "C1", &[]interface{}{1, 2, 3}) }}, + {"SetSheetCol", func() error { return f.SetSheetCol("Sheet1", "D1", &[]interface{}{4, 5, 6}) }}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Ensure cache is built + _, err := f.CalcCellValue("Sheet1", "A2") + assert.NoError(t, err) + assert.NoError(t, tc.setFunc()) + result, err := f.CalcCellValue("Sheet1", "A2") + assert.NoError(t, err) + assert.Equal(t, "20", result, "calculation should still work after cache clear") + }) + } +} diff --git a/cell.go b/cell.go index b389f73c7b..a51fac9ee3 100644 --- a/cell.go +++ b/cell.go @@ -128,6 +128,7 @@ func (f *File) GetCellType(sheet, cell string) (CellType, error) { // the cell value as number 0 or 60, then create and bind the date-time number // format style for the cell. func (f *File) SetCellValue(sheet, cell string, value interface{}) error { + f.clearCalcCache() var err error switch v := value.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: @@ -292,6 +293,7 @@ func setCellDuration(value time.Duration) (t string, v string) { // SetCellInt provides a function to set int type value of a cell by given // worksheet name, cell reference and cell value. func (f *File) SetCellInt(sheet, cell string, value int64) error { + f.clearCalcCache() f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { @@ -320,6 +322,7 @@ func setCellInt(value int64) (t string, v string) { // SetCellUint provides a function to set uint type value of a cell by given // worksheet name, cell reference and cell value. func (f *File) SetCellUint(sheet, cell string, value uint64) error { + f.clearCalcCache() f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { @@ -349,6 +352,7 @@ func setCellUint(value uint64) (t string, v string) { // SetCellBool provides a function to set bool type value of a cell by given // worksheet name, cell reference and cell value. func (f *File) SetCellBool(sheet, cell string, value bool) error { + f.clearCalcCache() f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { @@ -389,6 +393,7 @@ func setCellBool(value bool) (t string, v string) { // var x float32 = 1.325 // f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32) func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSize int) error { + f.clearCalcCache() if math.IsNaN(value) || math.IsInf(value, 0) { return f.SetCellStr(sheet, cell, fmt.Sprint(value)) } @@ -424,6 +429,7 @@ func (c *xlsxC) setCellFloat(value float64, precision, bitSize int) { // SetCellStr provides a function to set string type value of a cell. Total // number of characters that a cell can contain 32767 characters. func (f *File) SetCellStr(sheet, cell, value string) error { + f.clearCalcCache() f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { @@ -650,6 +656,7 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { // SetCellDefault provides a function to set string type value of a cell as // default format without escaping the cell. func (f *File) SetCellDefault(sheet, cell, value string) error { + f.clearCalcCache() f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { @@ -787,6 +794,7 @@ type FormulaOpts struct { // } // } func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts) error { + f.clearCalcCache() ws, err := f.workSheetReader(sheet) if err != nil { return err @@ -1044,6 +1052,7 @@ func (f *File) removeHyperLink(ws *xlsxWorksheet, sheet, cell string) error { // // err := f.SetCellHyperLink("Sheet1", "A3", "Sheet1!A40", "Location") func (f *File) SetCellHyperLink(sheet, cell, link, linkType string, opts ...HyperlinkOpts) error { + f.clearCalcCache() // Check for correct cell name if _, _, err := SplitCellName(cell); err != nil { return err @@ -1350,6 +1359,7 @@ func setRichText(runs []RichTextRun) ([]xlsxR, error) { // } // } func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { + f.clearCalcCache() ws, err := f.workSheetReader(sheet) if err != nil { return err @@ -1390,6 +1400,7 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error { // // err := f.SetSheetRow("Sheet1", "B6", &[]interface{}{"1", nil, 2}) func (f *File) SetSheetRow(sheet, cell string, slice interface{}) error { + f.clearCalcCache() return f.setSheetCells(sheet, cell, slice, rows) } @@ -1399,6 +1410,7 @@ func (f *File) SetSheetRow(sheet, cell string, slice interface{}) error { // // err := f.SetSheetCol("Sheet1", "B6", &[]interface{}{"1", nil, 2}) func (f *File) SetSheetCol(sheet, cell string, slice interface{}) error { + f.clearCalcCache() return f.setSheetCells(sheet, cell, slice, columns) } diff --git a/excelize.go b/excelize.go index 61bb6d3489..6b978e3919 100644 --- a/excelize.go +++ b/excelize.go @@ -40,6 +40,8 @@ type File struct { streams map[string]*StreamWriter tempFiles sync.Map xmlAttr sync.Map + calcCache sync.Map + calcCacheMu sync.RWMutex CalcChain *xlsxCalcChain CharsetReader charsetTranscoderFn Comments map[string]*xlsxComments From 93c378ed587ffb236030764a57f962074728c63e Mon Sep 17 00:00:00 2001 From: DengY11 <212294929@qq.com> Date: Tue, 27 May 2025 21:29:35 +0800 Subject: [PATCH 2/3] consider more scenario needed del cache --- col.go | 2 ++ merge.go | 2 ++ rows.go | 4 ++++ sheet.go | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/col.go b/col.go index d8c3d0dbe9..9ffc0fceaf 100644 --- a/col.go +++ b/col.go @@ -748,6 +748,7 @@ func (f *File) GetColWidth(sheet, col string) (float64, error) { // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) InsertCols(sheet, col string, n int) error { + f.clearCalcCache() num, err := ColumnNameToNumber(col) if err != nil { return err @@ -768,6 +769,7 @@ func (f *File) InsertCols(sheet, col string, n int) error { // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) RemoveCol(sheet, col string) error { + f.clearCalcCache() num, err := ColumnNameToNumber(col) if err != nil { return err diff --git a/merge.go b/merge.go index 9f2d25b04d..73bbb6695b 100644 --- a/merge.go +++ b/merge.go @@ -50,6 +50,7 @@ func (mc *xlsxMergeCell) Rect() ([]int, error) { // |A8(x3,y4) C8(x4,y4)| // +------------------------+ func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error { + f.clearCalcCache() rect, err := rangeRefToCoordinates(topLeftCell + ":" + bottomRightCell) if err != nil { return err @@ -94,6 +95,7 @@ func (f *File) MergeCell(sheet, topLeftCell, bottomRightCell string) error { // // Attention: overlapped range will also be unmerged. func (f *File) UnmergeCell(sheet, topLeftCell, bottomRightCell string) error { + f.clearCalcCache() ws, err := f.workSheetReader(sheet) if err != nil { return err diff --git a/rows.go b/rows.go index 5dbfaf86ea..f652ba2915 100644 --- a/rows.go +++ b/rows.go @@ -631,6 +631,7 @@ func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) { // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) RemoveRow(sheet string, row int) error { + f.clearCalcCache() if row < 1 { return newInvalidRowNumberError(row) } @@ -676,6 +677,7 @@ func (f *File) RemoveRow(sheet string, row int) error { // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) InsertRows(sheet string, row, n int) error { + f.clearCalcCache() if row < 1 { return newInvalidRowNumberError(row) } @@ -697,6 +699,7 @@ func (f *File) InsertRows(sheet string, row, n int) error { // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) DuplicateRow(sheet string, row int) error { + f.clearCalcCache() return f.DuplicateRowTo(sheet, row, row+1) } @@ -710,6 +713,7 @@ func (f *File) DuplicateRow(sheet string, row int) error { // worksheet, it will cause a file error when you open it. The excelize only // partially updates these references currently. func (f *File) DuplicateRowTo(sheet string, row, row2 int) error { + f.clearCalcCache() if row < 1 { return newInvalidRowNumberError(row) } diff --git a/sheet.go b/sheet.go index 65f2a4b008..8fc10d48a9 100644 --- a/sheet.go +++ b/sheet.go @@ -373,6 +373,7 @@ func (f *File) getActiveSheetID() int { // sheet name in the formula or reference associated with the cell. So there // may be problem formula error or reference missing. func (f *File) SetSheetName(source, target string) error { + f.clearCalcCache() var err error if err = checkSheetName(source); err != nil { return err @@ -572,6 +573,7 @@ func (f *File) setSheetBackground(sheet, extension string, file []byte) error { // value of the deleted worksheet, it will cause a file error when you open // it. This function will be invalid when only one worksheet is left. func (f *File) DeleteSheet(sheet string) error { + f.clearCalcCache() if err := checkSheetName(sheet); err != nil { return err } @@ -626,6 +628,7 @@ func (f *File) DeleteSheet(sheet string) error { // // err := f.MoveSheet("Sheet2", "Sheet1") func (f *File) MoveSheet(source, target string) error { + f.clearCalcCache() if strings.EqualFold(source, target) { return nil } @@ -753,6 +756,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { // } // err := f.CopySheet(1, index) func (f *File) CopySheet(from, to int) error { + f.clearCalcCache() if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" { return ErrSheetIdx } From 840ad9c3b046e6b6fe32547c6957ba602dfc0611 Mon Sep 17 00:00:00 2001 From: DengY11 <212294929@qq.com> Date: Mon, 2 Jun 2025 20:02:05 +0800 Subject: [PATCH 3/3] resolve conflict --- calc_test.go | 98 ++++++++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/calc_test.go b/calc_test.go index 8b6b1e6f9d..70d6f0aaef 100644 --- a/calc_test.go +++ b/calc_test.go @@ -777,25 +777,25 @@ func TestCalcCellValue(t *testing.T) { "=ROUNDUP(-11.111,-1)": "-20", "=ROUNDUP(ROUNDUP(100,1),-1)": "100", // SEARCH - "=SEARCH(\"s\",F1)": "1", - "=SEARCH(\"s\",F1,2)": "5", - "=SEARCH(\"e\",F1)": "4", - "=SEARCH(\"e*\",F1)": "4", - "=SEARCH(\"?e\",F1)": "3", - "=SEARCH(\"??e\",F1)": "2", - "=SEARCH(6,F2)": "2", + "=SEARCH(\"s\",F1)": "1", + "=SEARCH(\"s\",F1,2)": "5", + "=SEARCH(\"e\",F1)": "4", + "=SEARCH(\"e*\",F1)": "4", + "=SEARCH(\"?e\",F1)": "3", + "=SEARCH(\"??e\",F1)": "2", + "=SEARCH(6,F2)": "2", "=SEARCH(\"?\",\"你好world\")": "1", "=SEARCH(\"?l\",\"你好world\")": "5", "=SEARCH(\"?+\",\"你好 1+2\")": "4", "=SEARCH(\" ?+\",\"你好 1+2\")": "3", // SEARCHB - "=SEARCHB(\"s\",F1)": "1", - "=SEARCHB(\"s\",F1,2)": "5", - "=SEARCHB(\"e\",F1)": "4", - "=SEARCHB(\"e*\",F1)": "4", - "=SEARCHB(\"?e\",F1)": "3", - "=SEARCHB(\"??e\",F1)": "2", - "=SEARCHB(6,F2)": "2", + "=SEARCHB(\"s\",F1)": "1", + "=SEARCHB(\"s\",F1,2)": "5", + "=SEARCHB(\"e\",F1)": "4", + "=SEARCHB(\"e*\",F1)": "4", + "=SEARCHB(\"?e\",F1)": "3", + "=SEARCHB(\"??e\",F1)": "2", + "=SEARCHB(6,F2)": "2", "=SEARCHB(\"?\",\"你好world\")": "5", "=SEARCHB(\"?l\",\"你好world\")": "7", "=SEARCHB(\"?+\",\"你好 1+2\")": "6", @@ -1764,16 +1764,16 @@ func TestCalcCellValue(t *testing.T) { "=FINDB(\"\",\"Original Text\",2)": "2", "=FINDB(\"s\",\"Sales\",2)": "5", // LEFT - "=LEFT(\"Original Text\")": "O", - "=LEFT(\"Original Text\",4)": "Orig", - "=LEFT(\"Original Text\",0)": "", - "=LEFT(\"Original Text\",13)": "Original Text", - "=LEFT(\"Original Text\",20)": "Original Text", - "=LEFT(\"オリジナルテキスト\")": "オ", - "=LEFT(\"オリジナルテキスト\",2)": "オリ", - "=LEFT(\"オリジナルテキスト\",5)": "オリジナル", - "=LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ", - "=LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト", + "=LEFT(\"Original Text\")": "O", + "=LEFT(\"Original Text\",4)": "Orig", + "=LEFT(\"Original Text\",0)": "", + "=LEFT(\"Original Text\",13)": "Original Text", + "=LEFT(\"Original Text\",20)": "Original Text", + "=LEFT(\"オリジナルテキスト\")": "オ", + "=LEFT(\"オリジナルテキスト\",2)": "オリ", + "=LEFT(\"オリジナルテキスト\",5)": "オリジナル", + "=LEFT(\"オリジナルテキスト\",7)": "オリジナルテキ", + "=LEFT(\"オリジナルテキスト\",20)": "オリジナルテキスト", // LEFTB "=LEFTB(\"Original Text\")": "O", "=LEFTB(\"Original Text\",4)": "Orig", @@ -1781,16 +1781,16 @@ func TestCalcCellValue(t *testing.T) { "=LEFTB(\"Original Text\",13)": "Original Text", "=LEFTB(\"Original Text\",20)": "Original Text", // LEN - "=LEN(\"\")": "0", - "=LEN(D1)": "5", - "=LEN(\"テキスト\")": "4", - "=LEN(\"オリジナルテキスト\")": "9", - "=LEN(7+LEN(A1&B1&C1))": "1", - "=LEN(8+LEN(A1+(C1-B1)))": "2", + "=LEN(\"\")": "0", + "=LEN(D1)": "5", + "=LEN(\"テキスト\")": "4", + "=LEN(\"オリジナルテキスト\")": "9", + "=LEN(7+LEN(A1&B1&C1))": "1", + "=LEN(8+LEN(A1+(C1-B1)))": "2", // LENB - "=LENB(\"\")": "0", - "=LENB(D1)": "5", - "=LENB(\"テキスト\")": "8", + "=LENB(\"\")": "0", + "=LENB(D1)": "5", + "=LENB(\"テキスト\")": "8", "=LENB(\"オリジナルテキスト\")": "18", // LOWER "=LOWER(\"test\")": "test", @@ -1803,7 +1803,7 @@ func TestCalcCellValue(t *testing.T) { "=MID(\"255 years\",3,1)": "5", "=MID(\"text\",3,6)": "xt", "=MID(\"text\",6,0)": "", - "=MID(\"你好World\",5,1)": "r", + "=MID(\"你好World\",5,1)": "r", "=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30C6\u30AD\u30B9\u30C8", "=MID(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30B8\u30CA\u30EB\u30C6\u30AD", // MIDB @@ -1812,7 +1812,7 @@ func TestCalcCellValue(t *testing.T) { "=MIDB(\"255 years\",3,1)": "5", "=MIDB(\"text\",3,6)": "xt", "=MIDB(\"text\",6,0)": "", - "=MIDB(\"你好World\",5,1)": "W", + "=MIDB(\"你好World\",5,1)": "W", "=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",6,4)": "\u30B8\u30CA", "=MIDB(\"\u30AA\u30EA\u30B8\u30CA\u30EB\u30C6\u30AD\u30B9\u30C8\",3,5)": "\u30EA\u30B8\xe3", // PROPER @@ -1835,16 +1835,16 @@ func TestCalcCellValue(t *testing.T) { "=REPT(\"*\",1)": "*", "=REPT(\"**\",2)": "****", // RIGHT - "=RIGHT(\"Original Text\")": "t", - "=RIGHT(\"Original Text\",4)": "Text", - "=RIGHT(\"Original Text\",0)": "", - "=RIGHT(\"Original Text\",13)": "Original Text", - "=RIGHT(\"Original Text\",20)": "Original Text", - "=RIGHT(\"オリジナルテキスト\")": "ト", - "=RIGHT(\"オリジナルテキスト\",2)": "スト", - "=RIGHT(\"オリジナルテキスト\",4)": "テキスト", - "=RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト", - "=RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト", + "=RIGHT(\"Original Text\")": "t", + "=RIGHT(\"Original Text\",4)": "Text", + "=RIGHT(\"Original Text\",0)": "", + "=RIGHT(\"Original Text\",13)": "Original Text", + "=RIGHT(\"Original Text\",20)": "Original Text", + "=RIGHT(\"オリジナルテキスト\")": "ト", + "=RIGHT(\"オリジナルテキスト\",2)": "スト", + "=RIGHT(\"オリジナルテキスト\",4)": "テキスト", + "=RIGHT(\"オリジナルテキスト\",7)": "ジナルテキスト", + "=RIGHT(\"オリジナルテキスト\",20)": "オリジナルテキスト", // RIGHTB "=RIGHTB(\"Original Text\")": "t", "=RIGHTB(\"Original Text\",4)": "Text", @@ -2806,11 +2806,11 @@ func TestCalcCellValue(t *testing.T) { "=SEARCH(2,A1)": {"#VALUE!", "#VALUE!"}, "=SEARCH(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // SEARCHB - "=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"}, - "=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"}, - "=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"}, + "=SEARCHB()": {"#VALUE!", "SEARCHB requires at least 2 arguments"}, + "=SEARCHB(1,A1,1,1)": {"#VALUE!", "SEARCHB allows at most 3 arguments"}, + "=SEARCHB(2,A1)": {"#VALUE!", "#VALUE!"}, "=SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"}, - "=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, + "=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, // SEC "=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"}, "=_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},