diff --git a/calc.go b/calc.go index 4b17d84e..7b8dcf52 100644 --- a/calc.go +++ b/calc.go @@ -1532,6 +1532,22 @@ 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 { + if ctx.iterations[ref] <= f.options.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 + } + ctx.Unlock() + } if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil { return arg, err } @@ -1547,21 +1563,6 @@ func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, e return arg.ToNumber(), err 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 } diff --git a/datavalidation.go b/datavalidation.go index 1201b4fa..ac4aaec5 100644 --- a/datavalidation.go +++ b/datavalidation.go @@ -88,9 +88,9 @@ func NewDataValidation(allowBlank bool) *DataValidation { } // SetError set error notice. -func (dd *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) { - dd.Error = &msg - dd.ErrorTitle = &title +func (dv *DataValidation) SetError(style DataValidationErrorStyle, title, msg string) { + dv.Error = &msg + dv.ErrorTitle = &title strStyle := styleStop switch style { case DataValidationErrorStyleStop: @@ -101,31 +101,31 @@ func (dd *DataValidation) SetError(style DataValidationErrorStyle, title, msg st strStyle = styleInformation } - dd.ShowErrorMessage = true - dd.ErrorStyle = &strStyle + dv.ShowErrorMessage = true + dv.ErrorStyle = &strStyle } // SetInput set prompt notice. -func (dd *DataValidation) SetInput(title, msg string) { - dd.ShowInputMessage = true - dd.PromptTitle = &title - dd.Prompt = &msg +func (dv *DataValidation) SetInput(title, msg string) { + dv.ShowInputMessage = true + dv.PromptTitle = &title + dv.Prompt = &msg } // SetDropList data validation list. -func (dd *DataValidation) SetDropList(keys []string) error { +func (dv *DataValidation) SetDropList(keys []string) error { formula := strings.Join(keys, ",") if MaxFieldLength < len(utf16.Encode([]rune(formula))) { return ErrDataValidationFormulaLength } - dd.Formula1 = fmt.Sprintf(`"%s"`, formulaEscaper.Replace(formula)) - dd.Type = convDataValidationType(typeList) + dv.Formula1 = fmt.Sprintf(`"%s"`, formulaEscaper.Replace(formula)) + dv.Type = convDataValidationType(typeList) return nil } // SetRange provides function to set data validation range in drop list, only // accepts int, float64, or string data type formula argument. -func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error { +func (dv *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o DataValidationOperator) error { var formula1, formula2 string switch v := f1.(type) { case int: @@ -153,9 +153,9 @@ func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o D default: return ErrParameterInvalid } - dd.Formula1, dd.Formula2 = formula1, formula2 - dd.Type = convDataValidationType(t) - dd.Operator = convDataValidationOperator(o) + dv.Formula1, dv.Formula2 = formula1, formula2 + dv.Type = convDataValidationType(t) + dv.Operator = convDataValidationOperator(o) return nil } @@ -166,21 +166,21 @@ func (dd *DataValidation) SetRange(f1, f2 interface{}, t DataValidationType, o D // Sheet1!A7:B8 with validation criteria source Sheet1!E1:E3 settings, create // in-cell dropdown by allowing list source: // -// dvRange := excelize.NewDataValidation(true) -// dvRange.Sqref = "A7:B8" -// dvRange.SetSqrefDropList("$E$1:$E$3") -// f.AddDataValidation("Sheet1", dvRange) -func (dd *DataValidation) SetSqrefDropList(sqref string) { - dd.Formula1 = fmt.Sprintf("%s", sqref) - dd.Type = convDataValidationType(typeList) +// dv := excelize.NewDataValidation(true) +// dv.Sqref = "A7:B8" +// dv.SetSqrefDropList("$E$1:$E$3") +// err := f.AddDataValidation("Sheet1", dv) +func (dv *DataValidation) SetSqrefDropList(sqref string) { + dv.Formula1 = fmt.Sprintf("%s", sqref) + dv.Type = convDataValidationType(typeList) } // SetSqref provides function to set data validation range in drop list. -func (dd *DataValidation) SetSqref(sqref string) { - if dd.Sqref == "" { - dd.Sqref = sqref +func (dv *DataValidation) SetSqref(sqref string) { + if dv.Sqref == "" { + dv.Sqref = sqref } else { - dd.Sqref = fmt.Sprintf("%s %s", dd.Sqref, sqref) + dv.Sqref = fmt.Sprintf("%s %s", dv.Sqref, sqref) } } @@ -224,28 +224,28 @@ func convDataValidationOperator(o DataValidationOperator) string { // settings, show error alert after invalid data is entered with "Stop" style // and custom title "error body": // -// dvRange := excelize.NewDataValidation(true) -// dvRange.Sqref = "A1:B2" -// dvRange.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween) -// dvRange.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body") -// err := f.AddDataValidation("Sheet1", dvRange) +// dv := excelize.NewDataValidation(true) +// dv.Sqref = "A1:B2" +// dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorBetween) +// dv.SetError(excelize.DataValidationErrorStyleStop, "error title", "error body") +// err := f.AddDataValidation("Sheet1", dv) // // Example 2, set data validation on Sheet1!A3:B4 with validation criteria // settings, and show input message when cell is selected: // -// dvRange = excelize.NewDataValidation(true) -// dvRange.Sqref = "A3:B4" -// dvRange.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan) -// dvRange.SetInput("input title", "input body") -// err = f.AddDataValidation("Sheet1", dvRange) +// dv = excelize.NewDataValidation(true) +// dv.Sqref = "A3:B4" +// dv.SetRange(10, 20, excelize.DataValidationTypeWhole, excelize.DataValidationOperatorGreaterThan) +// dv.SetInput("input title", "input body") +// err = f.AddDataValidation("Sheet1", dv) // // Example 3, set data validation on Sheet1!A5:B6 with validation criteria // settings, create in-cell dropdown by allowing list source: // -// dvRange = excelize.NewDataValidation(true) -// dvRange.Sqref = "A5:B6" -// dvRange.SetDropList([]string{"1", "2", "3"}) -// err = f.AddDataValidation("Sheet1", dvRange) +// dv = excelize.NewDataValidation(true) +// dv.Sqref = "A5:B6" +// dv.SetDropList([]string{"1", "2", "3"}) +// err = f.AddDataValidation("Sheet1", dv) func (f *File) AddDataValidation(sheet string, dv *DataValidation) error { ws, err := f.workSheetReader(sheet) if err != nil { diff --git a/datavalidation_test.go b/datavalidation_test.go index 66855f74..4987f818 100644 --- a/datavalidation_test.go +++ b/datavalidation_test.go @@ -25,13 +25,13 @@ func TestDataValidation(t *testing.T) { f := NewFile() - dvRange := NewDataValidation(true) - dvRange.Sqref = "A1:B2" - assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) - dvRange.SetError(DataValidationErrorStyleStop, "error title", "error body") - dvRange.SetError(DataValidationErrorStyleWarning, "error title", "error body") - dvRange.SetError(DataValidationErrorStyleInformation, "error title", "error body") - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv := NewDataValidation(true) + dv.Sqref = "A1:B2" + assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) + dv.SetError(DataValidationErrorStyleStop, "error title", "error body") + dv.SetError(DataValidationErrorStyleWarning, "error title", "error body") + dv.SetError(DataValidationErrorStyleInformation, "error title", "error body") + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dataValidations, err := f.GetDataValidations("Sheet1") assert.NoError(t, err) @@ -39,11 +39,11 @@ func TestDataValidation(t *testing.T) { assert.NoError(t, f.SaveAs(resultFile)) - dvRange = NewDataValidation(true) - dvRange.Sqref = "A3:B4" - assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) - dvRange.SetInput("input title", "input body") - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv = NewDataValidation(true) + dv.Sqref = "A3:B4" + assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) + dv.SetInput("input title", "input body") + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) @@ -55,11 +55,11 @@ func TestDataValidation(t *testing.T) { assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet2", "A2", &[]interface{}{"B2", 1})) assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3})) - dvRange = NewDataValidation(true) - dvRange.Sqref = "A1:B1" - assert.NoError(t, dvRange.SetRange("INDIRECT($A$2)", "INDIRECT($A$3)", DataValidationTypeWhole, DataValidationOperatorBetween)) - dvRange.SetError(DataValidationErrorStyleStop, "error title", "error body") - assert.NoError(t, f.AddDataValidation("Sheet2", dvRange)) + dv = NewDataValidation(true) + dv.Sqref = "A1:B1" + assert.NoError(t, dv.SetRange("INDIRECT($A$2)", "INDIRECT($A$3)", DataValidationTypeWhole, DataValidationOperatorBetween)) + dv.SetError(DataValidationErrorStyleStop, "error title", "error body") + assert.NoError(t, f.AddDataValidation("Sheet2", dv)) dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) assert.Equal(t, len(dataValidations), 2) @@ -67,8 +67,8 @@ func TestDataValidation(t *testing.T) { assert.NoError(t, err) assert.Equal(t, len(dataValidations), 1) - dvRange = NewDataValidation(true) - dvRange.Sqref = "A5:B6" + dv = NewDataValidation(true) + dv.Sqref = "A5:B6" for _, listValid := range [][]string{ {"1", "2", "3"}, {strings.Repeat("&", MaxFieldLength)}, @@ -76,14 +76,14 @@ func TestDataValidation(t *testing.T) { {strings.Repeat("\U0001F600", 100), strings.Repeat("\u4E01", 50), "<&>"}, {`A<`, `B>`, `C"`, "D\t", `E'`, `F`}, } { - dvRange.Formula1 = "" - assert.NoError(t, dvRange.SetDropList(listValid), + dv.Formula1 = "" + assert.NoError(t, dv.SetDropList(listValid), "SetDropList failed for valid input %v", listValid) - assert.NotEqual(t, "", dvRange.Formula1, + assert.NotEqual(t, "", dv.Formula1, "Formula1 should not be empty for valid input %v", listValid) } - assert.Equal(t, `"A<,B>,C"",D ,E',F"`, dvRange.Formula1) - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.Equal(t, `"A<,B>,C"",D ,E',F"`, dv.Formula1) + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) dataValidations, err = f.GetDataValidations("Sheet1") assert.NoError(t, err) @@ -113,29 +113,29 @@ func TestDataValidationError(t *testing.T) { assert.NoError(t, f.SetCellStr("Sheet1", "E2", "E2")) assert.NoError(t, f.SetCellStr("Sheet1", "E3", "E3")) - dvRange := NewDataValidation(true) - dvRange.SetSqref("A7:B8") - dvRange.SetSqref("A7:B8") - dvRange.SetSqrefDropList("$E$1:$E$3") + dv := NewDataValidation(true) + dv.SetSqref("A7:B8") + dv.SetSqref("A7:B8") + dv.SetSqrefDropList("$E$1:$E$3") - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) - dvRange = NewDataValidation(true) - err := dvRange.SetDropList(make([]string, 258)) - if dvRange.Formula1 != "" { + dv = NewDataValidation(true) + err := dv.SetDropList(make([]string, 258)) + if dv.Formula1 != "" { t.Errorf("data validation error. Formula1 must be empty!") return } assert.EqualError(t, err, ErrDataValidationFormulaLength.Error()) - assert.EqualError(t, dvRange.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error()) - assert.EqualError(t, dvRange.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error()) - assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) - dvRange.SetSqref("A9:B10") + assert.EqualError(t, dv.SetRange(nil, 20, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error()) + assert.EqualError(t, dv.SetRange(10, nil, DataValidationTypeWhole, DataValidationOperatorBetween), ErrParameterInvalid.Error()) + assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) + dv.SetSqref("A9:B10") - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) // Test width invalid data validation formula - prevFormula1 := dvRange.Formula1 + prevFormula1 := dv.Formula1 for _, keys := range [][]string{ make([]string, 257), {strings.Repeat("s", 256)}, @@ -143,19 +143,19 @@ func TestDataValidationError(t *testing.T) { {strings.Repeat("\U0001F600", 128)}, {strings.Repeat("\U0001F600", 127), "s"}, } { - err = dvRange.SetDropList(keys) - assert.Equal(t, prevFormula1, dvRange.Formula1, + err = dv.SetDropList(keys) + assert.Equal(t, prevFormula1, dv.Formula1, "Formula1 should be unchanged for invalid input %v", keys) assert.EqualError(t, err, ErrDataValidationFormulaLength.Error()) } - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) - assert.NoError(t, dvRange.SetRange( + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) + assert.NoError(t, dv.SetRange( -math.MaxFloat32, math.MaxFloat32, DataValidationTypeWhole, DataValidationOperatorGreaterThan)) - assert.EqualError(t, dvRange.SetRange( + assert.EqualError(t, dv.SetRange( -math.MaxFloat64, math.MaxFloat32, DataValidationTypeWhole, DataValidationOperatorGreaterThan), ErrDataValidationRange.Error()) - assert.EqualError(t, dvRange.SetRange( + assert.EqualError(t, dv.SetRange( math.SmallestNonzeroFloat64, math.MaxFloat64, DataValidationTypeWhole, DataValidationOperatorGreaterThan), ErrDataValidationRange.Error()) assert.NoError(t, f.SaveAs(resultFile)) @@ -173,33 +173,33 @@ func TestDeleteDataValidation(t *testing.T) { f := NewFile() assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2")) - dvRange := NewDataValidation(true) - dvRange.Sqref = "A1:B2" - assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) - dvRange.SetInput("input title", "input body") - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv := NewDataValidation(true) + dv.Sqref = "A1:B2" + assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) + dv.SetInput("input title", "input body") + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1:B2")) - dvRange.Sqref = "A1" - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv.Sqref = "A1" + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "B1")) assert.NoError(t, f.DeleteDataValidation("Sheet1", "A1")) - dvRange.Sqref = "C2:C5" - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv.Sqref = "C2:C5" + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "C4")) - dvRange = NewDataValidation(true) - dvRange.Sqref = "D2:D2 D3 D4" - assert.NoError(t, dvRange.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) - dvRange.SetInput("input title", "input body") - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv = NewDataValidation(true) + dv.Sqref = "D2:D2 D3 D4" + assert.NoError(t, dv.SetRange(10, 20, DataValidationTypeWhole, DataValidationOperatorBetween)) + dv.SetInput("input title", "input body") + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.NoError(t, f.DeleteDataValidation("Sheet1", "D3")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteDataValidation.xlsx"))) - dvRange.Sqref = "A" - assert.NoError(t, f.AddDataValidation("Sheet1", dvRange)) + dv.Sqref = "A" + assert.NoError(t, f.AddDataValidation("Sheet1", dv)) assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.DeleteDataValidation("Sheet1", "A1:A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())