136 lines
3.7 KiB
Go
Raw Normal View History

// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting sales@baliance.com.
package formula_test
import (
"fmt"
"math"
"strconv"
"strings"
"testing"
"baliance.com/gooxml/spreadsheet"
"baliance.com/gooxml/spreadsheet/formula"
)
func TestEval(t *testing.T) {
td := []struct {
Inp string
Exp string
}{
{"TRUE", "1 ResultTypeNumber"},
{"=FALSE", "0 ResultTypeNumber"},
{"=1+2", "3 ResultTypeNumber"},
{"=1+2+3", "6 ResultTypeNumber"},
{"=1+2-3", "0 ResultTypeNumber"},
{"=2*5", "10 ResultTypeNumber"},
{"=2*5-3", "7 ResultTypeNumber"},
{"=2*5+3*6-4", "24 ResultTypeNumber"},
{"=2^10", "1024 ResultTypeNumber"},
{"=1=1", "1 ResultTypeNumber"},
{"=1<>1", "0 ResultTypeNumber"},
{"=1<=5", "1 ResultTypeNumber"},
{"=1>=5", "0 ResultTypeNumber"},
{"=5*1>=5", "1 ResultTypeNumber"},
{"=5*1>=5+1", "0 ResultTypeNumber"},
{"=A1", "1.23 ResultTypeNumber"},
{"A1", "1.23 ResultTypeNumber"},
{"=A1+A1", "2.46 ResultTypeNumber"},
{"=A1+B2", "3.23 ResultTypeNumber"},
{"=SUM(1,2,3,4,5)", "15 ResultTypeNumber"},
{"SUM(-2,-3,2,3,4)", "4 ResultTypeNumber"},
{"SUM(B1:B3)", "6 ResultTypeNumber"},
{"TRUE()", "1 ResultTypeNumber"},
{`"test"`, "test ResultTypeString"},
{`"te""st"`, `te"st ResultTypeString`},
}
ss := spreadsheet.New()
sheet := ss.AddSheet()
sheet.Cell("A1").SetNumber(1.23)
sheet.Cell("B1").SetNumber(1)
sheet.Cell("B2").SetNumber(2)
sheet.Cell("B3").SetNumber(3)
ctx := sheet.FormulaContext()
ev := formula.NewEvaluator()
for _, tc := range td {
t.Run(tc.Inp, func(t *testing.T) {
p := formula.Parse(strings.NewReader(tc.Inp))
if p == nil {
t.Errorf("error parsing %s", tc.Inp)
return
}
result := p.Eval(ctx, ev)
got := fmt.Sprintf("%s %s", result.Value(), result.Type)
if got != tc.Exp {
t.Errorf("expected %s = %s, got %s", tc.Inp, tc.Exp, got)
}
})
}
}
func TestReferenceSheet(t *testing.T) {
testSheet("formulareference.xlsx", t)
}
func TestMacExcelSheet(t *testing.T) {
testSheet("MacExcel365.xlsx", t)
}
func testSheet(fn string, t *testing.T) {
// TODO: uncomment once we quit building on 1.8
//t.Helper()
wb, err := spreadsheet.Open("testdata/" + fn)
if err != nil {
t.Fatalf("error opening reference sheet: %s", err)
}
formulaCount := 0
for _, sheet := range wb.Sheets() {
for _, row := range sheet.Rows() {
for _, cell := range row.Cells() {
// the value should have a computed value for the formula that
// Excel has cached
cachedValue := cell.GetCachedFormulaResult()
if cell.HasFormula() {
cellFormula := formula.ParseString(cell.GetFormula())
if cellFormula == nil {
t.Errorf("error parsing formula %s", cell.GetFormula())
continue
}
// so evaluating the formula in the context of the sheet,
// should return the same results that Excel computed
result := cellFormula.Eval(sheet.FormulaContext(), formula.NewEvaluator())
if got := result.Value(); !cmpValue(got, cachedValue) {
t.Errorf("expected '%s', got '%s' for %s cell %s (%s) %s", cachedValue, got, sheet.Name(), cell.Reference(), cell.GetFormula(), result.ErrorMessage)
} else {
formulaCount++
}
}
}
}
}
t.Logf("evaluated %d formulas from %s sheet", formulaCount, fn)
}
func cmpValue(l, r string) bool {
if l == r {
return true
}
lf, el := strconv.ParseFloat(l, 64)
rf, er := strconv.ParseFloat(r, 64)
if el == nil && er == nil {
if math.Abs(lf-rf) < 1e-7 {
return true
}
}
return false
}