From ad90cea78bc75f1407fdc3e7730fda26fc718040 Mon Sep 17 00:00:00 2001 From: jaby <97000+jaby@users.noreply.github.com> Date: Wed, 15 Feb 2023 14:38:11 +0100 Subject: [PATCH] This closes #1469, fix cell resolver caused incorrect calculation result (#1470) --- calc.go | 44 +++++++++++++++++++++++++++----------------- calc_test.go | 19 ++++++++++++++++++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/calc.go b/calc.go index eff012fd..ddc07a74 100644 --- a/calc.go +++ b/calc.go @@ -197,8 +197,10 @@ var ( // calcContext defines the formula execution context. type calcContext struct { sync.Mutex - entry string - iterations map[string]uint + entry string + maxCalcIterations uint + iterations map[string]uint + iterationsCache map[string]formulaArg } // cellRef defines the structure of a cell reference. @@ -774,8 +776,10 @@ func (f *File) CalcCellValue(sheet, cell string, opts ...Options) (result string token formulaArg ) if token, err = f.calcCellValue(&calcContext{ - entry: fmt.Sprintf("%s!%s", sheet, cell), - iterations: make(map[string]uint), + entry: fmt.Sprintf("%s!%s", sheet, cell), + maxCalcIterations: getOptions(opts...).MaxCalcIterations, + iterations: make(map[string]uint), + iterationsCache: make(map[string]formulaArg), }, sheet, cell); err != nil { return } @@ -1527,17 +1531,6 @@ func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, e value string err error ) - ref := fmt.Sprintf("%s!%s", sheet, cell) - if formula, _ := f.GetCellFormula(sheet, cell); len(formula) != 0 { - ctx.Lock() - if ctx.entry != ref && ctx.iterations[ref] <= f.options.MaxCalcIterations { - ctx.iterations[ref]++ - ctx.Unlock() - arg, _ = f.calcCellValue(ctx, sheet, cell) - return arg, nil - } - ctx.Unlock() - } if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil { return arg, err } @@ -1551,8 +1544,25 @@ func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, e return newEmptyFormulaArg(), err } return arg.ToNumber(), err - default: + case CellTypeInlineString, CellTypeSharedString: return arg, err + case CellTypeFormula: + ref := fmt.Sprintf("%s!%s", sheet, cell) + if ctx.entry != ref { + ctx.Lock() + if ctx.iterations[ref] <= ctx.maxCalcIterations { + ctx.iterations[ref]++ + ctx.Unlock() + arg, _ = f.calcCellValue(ctx, sheet, cell) + ctx.iterationsCache[ref] = arg + return arg, nil + } + ctx.Unlock() + return ctx.iterationsCache[ref], nil + } + fallthrough + default: + return newEmptyFormulaArg(), err } } @@ -7746,7 +7756,7 @@ func (fn *formulaFuncs) COUNTBLANK(argsList *list.List) formulaArg { } var count float64 for _, cell := range argsList.Front().Value.(formulaArg).ToList() { - if cell.Value() == "" { + if cell.Type == ArgEmpty { count++ } } diff --git a/calc_test.go b/calc_test.go index 5e87763e..c740e6b4 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1023,7 +1023,7 @@ func TestCalcCellValue(t *testing.T) { "=COUNTBLANK(MUNIT(1))": "0", "=COUNTBLANK(1)": "0", "=COUNTBLANK(B1:C1)": "1", - "=COUNTBLANK(C1)": "1", + "=COUNTBLANK(C1)": "0", // COUNTIF "=COUNTIF(D1:D9,\"Jan\")": "4", "=COUNTIF(D1:D9,\"<>Jan\")": "5", @@ -5871,3 +5871,20 @@ func TestCalcColRowQRDecomposition(t *testing.T) { assert.False(t, calcRowQRDecomposition([][]float64{{0, 0}, {0, 0}}, []float64{0, 0}, 1, 0)) assert.False(t, calcColQRDecomposition([][]float64{{0, 0}, {0, 0}}, []float64{0, 0}, 1, 0)) } + +func TestCalcCellResolver(t *testing.T) { + f := NewFile() + // Test reference a cell multiple times in a formula + assert.NoError(t, f.SetCellValue("Sheet1", "A1", "VALUE1")) + assert.NoError(t, f.SetCellFormula("Sheet1", "A2", "=A1")) + for formula, expected := range map[string]string{ + "=CONCATENATE(A1,\"_\",A1)": "VALUE1_VALUE1", + "=CONCATENATE(A1,\"_\",A2)": "VALUE1_VALUE1", + "=CONCATENATE(A2,\"_\",A2)": "VALUE1_VALUE1", + } { + assert.NoError(t, f.SetCellFormula("Sheet1", "A3", formula)) + result, err := f.CalcCellValue("Sheet1", "A3") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } +}