From ec45d67e59318ad876b38d6ef96402732b601071 Mon Sep 17 00:00:00 2001 From: xuri Date: Sat, 13 Feb 2021 00:07:52 +0800 Subject: [PATCH] binary search in range lookup and new formula function: LOOKUP --- LICENSE | 2 +- calc.go | 215 ++++++++++++++++++++++++++++++++++++++++++--------- calc_test.go | 187 +++++++++++++++++++++++++++++++------------- 3 files changed, 315 insertions(+), 89 deletions(-) diff --git a/LICENSE b/LICENSE index e0f34bb..17591f2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2016-2020 The excelize Authors. +Copyright (c) 2016-2021 The excelize Authors. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/calc.go b/calc.go index df2230e..450a788 100644 --- a/calc.go +++ b/calc.go @@ -223,6 +223,7 @@ var tokenPriority = map[string]int{ // FLOOR.MATH // FLOOR.PRECISE // GCD +// HLOOKUP // IF // INT // ISBLANK @@ -239,6 +240,7 @@ var tokenPriority = map[string]int{ // LN // LOG // LOG10 +// LOOKUP // LOWER // MDETERM // MEDIAN @@ -275,6 +277,7 @@ var tokenPriority = map[string]int{ // TRIM // TRUNC // UPPER +// VLOOKUP // func (f *File) CalcCellValue(sheet, cell string) (result string, err error) { var ( @@ -2335,8 +2338,8 @@ func (fn *formulaFuncs) MUNIT(argsList *list.List) (result formulaArg) { return newErrorFormulaArg(formulaErrorVALUE, "MUNIT requires 1 numeric argument") } dimension := argsList.Back().Value.(formulaArg).ToNumber() - if dimension.Type == ArgError { - return dimension + if dimension.Type == ArgError || dimension.Number < 0 { + return newErrorFormulaArg(formulaErrorVALUE, dimension.Error) } matrix := make([][]formulaArg, 0, int(dimension.Number)) for i := 0; i < int(dimension.Number); i++ { @@ -3607,8 +3610,7 @@ func matchPattern(pattern, name string) (matched bool) { if pattern == "*" { return true } - rname := make([]rune, 0, len(name)) - rpattern := make([]rune, 0, len(pattern)) + rname, rpattern := make([]rune, 0, len(name)), make([]rune, 0, len(pattern)) for _, r := range name { rname = append(rname, r) } @@ -3636,11 +3638,9 @@ func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte } return criteriaG case ArgString: - ls := lhs.String - rs := rhs.String + ls, rs := lhs.String, rhs.String if !caseSensitive { - ls = strings.ToLower(ls) - rs = strings.ToLower(rs) + ls, rs = strings.ToLower(ls), strings.ToLower(rs) } if exactMatch { match := matchPattern(rs, ls) @@ -3649,7 +3649,15 @@ func compareFormulaArg(lhs, rhs formulaArg, caseSensitive, exactMatch bool) byte } return criteriaG } - return byte(strings.Compare(ls, rs)) + switch strings.Compare(ls, rs) { + case 1: + return criteriaG + case -1: + return criteriaL + case 0: + return criteriaEq + } + return criteriaErr case ArgEmpty: return criteriaEq case ArgList: @@ -3739,16 +3747,29 @@ func (fn *formulaFuncs) HLOOKUP(argsList *list.List) formulaArg { } } row := tableArray.Matrix[0] -start: - for idx, mtx := range row { - switch compareFormulaArg(mtx, lookupValue, false, exactMatch) { - case criteriaL: - matchIdx = idx - case criteriaEq: - matchIdx = idx - wasExact = true - break start + if exactMatch || len(tableArray.Matrix) == TotalRows { + start: + for idx, mtx := range row { + lhs := mtx + switch lookupValue.Type { + case ArgNumber: + if !lookupValue.Boolean { + lhs = mtx.ToNumber() + if lhs.Type == ArgError { + lhs = mtx + } + } + case ArgMatrix: + lhs = tableArray + } + if compareFormulaArg(lhs, lookupValue, false, exactMatch) == criteriaEq { + matchIdx = idx + wasExact = true + break start + } } + } else { + matchIdx, wasExact = hlookupBinarySearch(row, lookupValue) } if matchIdx == -1 { return newErrorFormulaArg(formulaErrorNA, "HLOOKUP no result found") @@ -3795,11 +3816,51 @@ func (fn *formulaFuncs) VLOOKUP(argsList *list.List) formulaArg { exactMatch = true } } -start: - for idx, mtx := range tableArray.Matrix { - if len(mtx) == 0 { - continue + if exactMatch || len(tableArray.Matrix) == TotalRows { + start: + for idx, mtx := range tableArray.Matrix { + lhs := mtx[0] + switch lookupValue.Type { + case ArgNumber: + if !lookupValue.Boolean { + lhs = mtx[0].ToNumber() + if lhs.Type == ArgError { + lhs = mtx[0] + } + } + case ArgMatrix: + lhs = tableArray + } + if compareFormulaArg(lhs, lookupValue, false, exactMatch) == criteriaEq { + matchIdx = idx + wasExact = true + break start + } } + } else { + matchIdx, wasExact = vlookupBinarySearch(tableArray, lookupValue) + } + if matchIdx == -1 { + return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") + } + mtx := tableArray.Matrix[matchIdx] + if col < 0 || col >= len(mtx) { + return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index") + } + if wasExact || !exactMatch { + return mtx[col] + } + return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") +} + +// vlookupBinarySearch finds the position of a target value when range lookup +// is TRUE, if the data of table array can't guarantee be sorted, it will +// return wrong result. +func vlookupBinarySearch(tableArray, lookupValue formulaArg) (matchIdx int, wasExact bool) { + var low, high, lastMatchIdx int = 0, len(tableArray.Matrix) - 1, -1 + for low <= high { + var mid int = low + (high-low)/2 + mtx := tableArray.Matrix[mid] lhs := mtx[0] switch lookupValue.Type { case ArgNumber: @@ -3812,24 +3873,106 @@ start: case ArgMatrix: lhs = tableArray } - switch compareFormulaArg(lhs, lookupValue, false, exactMatch) { - case criteriaL: - matchIdx = idx - case criteriaEq: + result := compareFormulaArg(lhs, lookupValue, false, false) + if result == criteriaEq { + matchIdx, wasExact = mid, true + return + } else if result == criteriaG { + high = mid - 1 + } else if result == criteriaL { + matchIdx, low = mid, mid+1 + if lhs.Value() != "" { + lastMatchIdx = matchIdx + } + } else { + return -1, false + } + } + matchIdx, wasExact = lastMatchIdx, true + return +} + +// vlookupBinarySearch finds the position of a target value when range lookup +// is TRUE, if the data of table array can't guarantee be sorted, it will +// return wrong result. +func hlookupBinarySearch(row []formulaArg, lookupValue formulaArg) (matchIdx int, wasExact bool) { + var low, high, lastMatchIdx int = 0, len(row) - 1, -1 + for low <= high { + var mid int = low + (high-low)/2 + mtx := row[mid] + result := compareFormulaArg(mtx, lookupValue, false, false) + if result == criteriaEq { + matchIdx, wasExact = mid, true + return + } else if result == criteriaG { + high = mid - 1 + } else if result == criteriaL { + low, lastMatchIdx = mid+1, mid + } else { + return -1, false + } + } + matchIdx, wasExact = lastMatchIdx, true + return +} + +// LOOKUP function performs an approximate match lookup in a one-column or +// one-row range, and returns the corresponding value from another one-column +// or one-row range. The syntax of the function is: +// +// LOOKUP(lookup_value,lookup_vector,[result_vector]) +// +func (fn *formulaFuncs) LOOKUP(argsList *list.List) formulaArg { + if argsList.Len() < 2 { + return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at least 2 arguments") + } + if argsList.Len() > 3 { + return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments") + } + lookupValue := argsList.Front().Value.(formulaArg) + lookupVector := argsList.Front().Next().Value.(formulaArg) + if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList { + return newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array") + } + cols, matchIdx := lookupCol(lookupVector), -1 + for idx, col := range cols { + lhs := lookupValue + switch col.Type { + case ArgNumber: + lhs = lhs.ToNumber() + if !col.Boolean { + if lhs.Type == ArgError { + lhs = lookupValue + } + } + } + if compareFormulaArg(lhs, col, false, false) == criteriaEq { matchIdx = idx - wasExact = true - break start + break } } - if matchIdx == -1 { - return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") + column := cols + if argsList.Len() == 3 { + column = lookupCol(argsList.Back().Value.(formulaArg)) } - mtx := tableArray.Matrix[matchIdx] - if col < 0 || col >= len(mtx) { - return newErrorFormulaArg(formulaErrorNA, "VLOOKUP has invalid column index") + if matchIdx < 0 || matchIdx >= len(column) { + return newErrorFormulaArg(formulaErrorNA, "LOOKUP no result found") } - if wasExact || !exactMatch { - return mtx[col] + return column[matchIdx] +} + +// lookupCol extract columns for LOOKUP. +func lookupCol(arr formulaArg) []formulaArg { + col := arr.List + if arr.Type == ArgMatrix { + col = nil + for _, r := range arr.Matrix { + if len(r) > 0 { + col = append(col, r[0]) + continue + } + col = append(col, newEmptyFormulaArg()) + } } - return newErrorFormulaArg(formulaErrorNA, "VLOOKUP no result found") + return col } diff --git a/calc_test.go b/calc_test.go index c72d78b..b71d822 100644 --- a/calc_test.go +++ b/calc_test.go @@ -10,6 +10,17 @@ import ( "github.com/xuri/efp" ) +func prepareCalcData(cellData [][]interface{}) *File { + f := NewFile() + for r, row := range cellData { + for c, value := range row { + cell, _ := CoordinatesToCellName(c+1, r+1) + f.SetCellValue("Sheet1", cell, value) + } + } + return f +} + func TestCalcCellValue(t *testing.T) { cellData := [][]interface{}{ {1, 4, nil, "Month", "Team", "Sales"}, @@ -22,17 +33,6 @@ func TestCalcCellValue(t *testing.T) { {nil, nil, nil, "Feb", "South 1", 32080}, {nil, nil, nil, "Feb", "South 2", 45500}, } - prepareData := func() *File { - f := NewFile() - for r, row := range cellData { - for c, value := range row { - cell, _ := CoordinatesToCellName(c+1, r+1) - assert.NoError(t, f.SetCellValue("Sheet1", cell, value)) - } - } - return f - } - mathCalc := map[string]string{ "=2^3": "8", "=1=1": "TRUE", @@ -562,18 +562,28 @@ func TestCalcCellValue(t *testing.T) { "=CHOOSE(1,\"red\",\"blue\",\"green\",\"brown\")": "red", "=SUM(CHOOSE(A2,A1,B1:B2,A1:A3,A1:A4))": "9", // HLOOKUP - "=HLOOKUP(D2,D2:D8,1,FALSE)": "Jan", - "=HLOOKUP(F3,F3:F8,3,FALSE)": "34440", // should be Feb + "=HLOOKUP(D2,D2:D8,1,FALSE)": "Jan", + "=HLOOKUP(F3,F3:F8,3,FALSE)": "34440", + "=HLOOKUP(INT(F3),F3:F8,3,FALSE)": "34440", + "=HLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1", // VLOOKUP - "=VLOOKUP(D2,D:D,1,FALSE)": "Jan", - "=VLOOKUP(D2,D:D,1,TRUE)": "Month", // should be Feb - "=VLOOKUP(INT(36693),F2:F2,1,FALSE)": "36693", - "=VLOOKUP(INT(F2),F3:F9,1)": "32080", - "=VLOOKUP(MUNIT(3),MUNIT(2),1)": "0", // should be 1 - "=VLOOKUP(MUNIT(3),MUNIT(3),1)": "1", + "=VLOOKUP(D2,D:D,1,FALSE)": "Jan", + "=VLOOKUP(D2,D1:D10,1)": "Jan", + "=VLOOKUP(D2,D1:D11,1)": "Feb", + "=VLOOKUP(D2,D1:D10,1,FALSE)": "Jan", + "=VLOOKUP(INT(36693),F2:F2,1,FALSE)": "36693", + "=VLOOKUP(INT(F2),F3:F9,1)": "32080", + "=VLOOKUP(INT(F2),F3:F9,1,TRUE)": "32080", + "=VLOOKUP(MUNIT(3),MUNIT(3),1)": "0", + "=VLOOKUP(A1,A3:B5,1)": "0", + "=VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1", + // LOOKUP + "=LOOKUP(F8,F8:F9,F8:F9)": "32080", + "=LOOKUP(F8,F8:F9,D8:D9)": "Feb", + "=LOOKUP(1,MUNIT(1),MUNIT(1))": "1", } for formula, expected := range mathCalc { - f := prepareData() + f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err, formula) @@ -759,8 +769,9 @@ func TestCalcCellValue(t *testing.T) { // MULTINOMIAL `=MULTINOMIAL("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", // _xlfn.MUNIT - "=_xlfn.MUNIT()": "MUNIT requires 1 numeric argument", // not support currently - `=_xlfn.MUNIT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", // not support currently + "=_xlfn.MUNIT()": "MUNIT requires 1 numeric argument", + `=_xlfn.MUNIT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", + "=_xlfn.MUNIT(-1)": "", // ODD "=ODD()": "ODD requires 1 numeric argument", `=ODD("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax", @@ -947,10 +958,15 @@ func TestCalcCellValue(t *testing.T) { "=VLOOKUP(ISNUMBER(1),F3:F9,1)": "VLOOKUP no result found", "=VLOOKUP(INT(1),E2:E9,1)": "VLOOKUP no result found", "=VLOOKUP(MUNIT(2),MUNIT(3),1)": "VLOOKUP no result found", - "=VLOOKUP(A1:B2,B2:B3,1)": "VLOOKUP no result found", + "=VLOOKUP(1,G1:H2,1,FALSE)": "VLOOKUP no result found", + // LOOKUP + "=LOOKUP()": "LOOKUP requires at least 2 arguments", + "=LOOKUP(D2,D1,D2)": "LOOKUP requires second argument of table array", + "=LOOKUP(D2,D1,D2,FALSE)": "LOOKUP requires at most 3 arguments", + "=LOOKUP(D1,MUNIT(1),MUNIT(1))": "LOOKUP no result found", } for formula, expected := range mathCalcError { - f := prepareData() + f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.EqualError(t, err, expected, formula) @@ -974,7 +990,7 @@ func TestCalcCellValue(t *testing.T) { "=A1/A2/SUM(A1:A2:B1)*A3": "0.125", } for formula, expected := range referenceCalc { - f := prepareData() + f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) @@ -988,7 +1004,7 @@ func TestCalcCellValue(t *testing.T) { "=1+SUM(SUM(A1+A2/A4)*(2-3),2)": "#DIV/0!", } for formula, expected := range referenceCalcError { - f := prepareData() + f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) result, err := f.CalcCellValue("Sheet1", "C1") assert.EqualError(t, err, expected) @@ -1000,23 +1016,23 @@ func TestCalcCellValue(t *testing.T) { "=RANDBETWEEN(1,2)", } for _, formula := range volatileFuncs { - f := prepareData() + f := prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) _, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) } // Test get calculated cell value on not formula cell. - f := prepareData() + f := prepareCalcData(cellData) result, err := f.CalcCellValue("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, "", result) // Test get calculated cell value on not exists worksheet. - f = prepareData() + f = prepareCalcData(cellData) _, err = f.CalcCellValue("SheetN", "A1") assert.EqualError(t, err, "sheet SheetN is not exist") // Test get calculated cell value with not support formula. - f = prepareData() + f = prepareCalcData(cellData) assert.NoError(t, f.SetCellFormula("Sheet1", "A1", "=UNSUPPORT(A1)")) _, err = f.CalcCellValue("Sheet1", "A1") assert.EqualError(t, err, "not support UNSUPPORT function") @@ -1036,24 +1052,13 @@ func TestCalculate(t *testing.T) { assert.EqualError(t, calculate(opd, opt), err) } -func TestCalcCellValueWithDefinedName(t *testing.T) { +func TestCalcWithDefinedName(t *testing.T) { cellData := [][]interface{}{ {"A1 value", "B1 value", nil}, } - prepareData := func() *File { - f := NewFile() - for r, row := range cellData { - for c, value := range row { - cell, _ := CoordinatesToCellName(c+1, r+1) - assert.NoError(t, f.SetCellValue("Sheet1", cell, value)) - } - } - assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!A1", Scope: "Workbook"})) - assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!B1", Scope: "Sheet1"})) - - return f - } - f := prepareData() + f := prepareCalcData(cellData) + assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!A1", Scope: "Workbook"})) + assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "defined_name1", RefersTo: "Sheet1!B1", Scope: "Sheet1"})) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=defined_name1")) result, err := f.CalcCellValue("Sheet1", "C1") assert.NoError(t, err) @@ -1061,7 +1066,7 @@ func TestCalcCellValueWithDefinedName(t *testing.T) { assert.Equal(t, "B1 value", result, "=defined_name1") } -func TestCalcPow(t *testing.T) { +func TestCalcArithmeticOperations(t *testing.T) { err := `strconv.ParseFloat: parsing "text": invalid syntax` assert.EqualError(t, calcPow("1", "text", nil), err) assert.EqualError(t, calcPow("text", "1", nil), err) @@ -1085,7 +1090,7 @@ func TestCalcPow(t *testing.T) { assert.EqualError(t, calcDiv("text", "1", nil), err) } -func TestISBLANK(t *testing.T) { +func TestCalcISBLANK(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{ Type: ArgUnknown, @@ -1096,7 +1101,7 @@ func TestISBLANK(t *testing.T) { assert.Empty(t, result.Error) } -func TestAND(t *testing.T) { +func TestCalcAND(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{ Type: ArgUnknown, @@ -1107,7 +1112,7 @@ func TestAND(t *testing.T) { assert.Empty(t, result.Error) } -func TestOR(t *testing.T) { +func TestCalcOR(t *testing.T) { argsList := list.New() argsList.PushBack(formulaArg{ Type: ArgUnknown, @@ -1118,7 +1123,7 @@ func TestOR(t *testing.T) { assert.Empty(t, result.Error) } -func TestDet(t *testing.T) { +func TestCalcDet(t *testing.T) { assert.Equal(t, det([][]float64{ {1, 2, 3, 4}, {2, 3, 4, 5}, @@ -1127,7 +1132,12 @@ func TestDet(t *testing.T) { }), float64(0)) } -func TestCompareFormulaArg(t *testing.T) { +func TestCalcToBool(t *testing.T) { + b := newBoolFormulaArg(true).ToBool() + assert.Equal(t, b.Boolean, true) + assert.Equal(t, b.Number, 1.0) +} +func TestCalcCompareFormulaArg(t *testing.T) { assert.Equal(t, compareFormulaArg(newEmptyFormulaArg(), newEmptyFormulaArg(), false, false), criteriaEq) lhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg()}) rhs := newListFormulaArg([]formulaArg{newEmptyFormulaArg(), newEmptyFormulaArg()}) @@ -1141,9 +1151,82 @@ func TestCompareFormulaArg(t *testing.T) { assert.Equal(t, compareFormulaArg(formulaArg{Type: ArgUnknown}, formulaArg{Type: ArgUnknown}, false, false), criteriaErr) } -func TestMatchPattern(t *testing.T) { +func TestCalcMatchPattern(t *testing.T) { assert.True(t, matchPattern("", "")) assert.True(t, matchPattern("file/*", "file/abc/bcd/def")) assert.True(t, matchPattern("*", "")) assert.False(t, matchPattern("file/?", "file/abc/bcd/def")) } + +func TestCalcVLOOKUP(t *testing.T) { + cellData := [][]interface{}{ + {nil, nil, nil, nil, nil, nil}, + {nil, "Score", "Grade", nil, nil, nil}, + {nil, 0, "F", nil, "Score", 85}, + {nil, 60, "D", nil, "Grade"}, + {nil, 70, "C", nil, nil, nil}, + {nil, 80, "b", nil, nil, nil}, + {nil, 90, "A", nil, nil, nil}, + {nil, 85, "B", nil, nil, nil}, + {nil, nil, nil, nil, nil, nil}, + } + f := prepareCalcData(cellData) + calc := map[string]string{ + "=VLOOKUP(F3,B3:C8,2)": "b", + "=VLOOKUP(F3,B3:C8,2,TRUE)": "b", + "=VLOOKUP(F3,B3:C8,2,FALSE)": "B", + } + for formula, expected := range calc { + assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula)) + result, err := f.CalcCellValue("Sheet1", "F4") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } + calcError := map[string]string{ + "=VLOOKUP(INT(1),C3:C3,1,FALSE)": "VLOOKUP no result found", + } + for formula, expected := range calcError { + assert.NoError(t, f.SetCellFormula("Sheet1", "F4", formula)) + result, err := f.CalcCellValue("Sheet1", "F4") + assert.EqualError(t, err, expected, formula) + assert.Equal(t, "", result, formula) + } +} + +func TestCalcHLOOKUP(t *testing.T) { + cellData := [][]interface{}{ + {"Example Result Table"}, + {nil, "A", "B", "C", "E", "F"}, + {"Math", .58, .9, .67, .76, .8}, + {"French", .61, .71, .59, .59, .76}, + {"Physics", .75, .45, .39, .52, .69}, + {"Biology", .39, .55, .77, .61, .45}, + {}, + {"Individual Student Score"}, + {"Student:", "Biology Score:"}, + {"E"}, + } + f := prepareCalcData(cellData) + formulaList := map[string]string{ + "=HLOOKUP(A10,A2:F6,5,FALSE)": "0.61", + "=HLOOKUP(D3,D3:D3,1,TRUE)": "0.67", + "=HLOOKUP(F3,D3:F3,1,TRUE)": "0.8", + "=HLOOKUP(A5,A2:F2,1,TRUE)": "F", + "=HLOOKUP(\"D\",A2:F2,1,TRUE)": "C", + } + for formula, expected := range formulaList { + assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula)) + result, err := f.CalcCellValue("Sheet1", "B10") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } + calcError := map[string]string{ + "=HLOOKUP(INT(1),A3:A3,1,FALSE)": "HLOOKUP no result found", + } + for formula, expected := range calcError { + assert.NoError(t, f.SetCellFormula("Sheet1", "B10", formula)) + result, err := f.CalcCellValue("Sheet1", "B10") + assert.EqualError(t, err, expected, formula) + assert.Equal(t, "", result, formula) + } +} -- GitLab