This closes #1438, fix cell data type issue for formula calculation engine

- Update dependencies module
- Update unit tests
This commit is contained in:
xuri 2023-01-08 00:23:53 +08:00
parent 9c3a5eb983
commit 5429f131f8
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
6 changed files with 335 additions and 308 deletions

437
calc.go
View File

@ -768,28 +768,11 @@ type formulaFuncs struct {
// Z.TEST
// ZTEST
func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
return f.calcCellValue(&calcContext{
var token formulaArg
token, err = f.calcCellValue(&calcContext{
entry: fmt.Sprintf("%s!%s", sheet, cell),
iterations: make(map[string]uint),
}, sheet, cell)
}
func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result string, err error) {
var (
formula string
token formulaArg
)
if formula, err = f.GetCellFormula(sheet, cell); err != nil {
return
}
ps := efp.ExcelParser()
tokens := ps.Parse(formula)
if tokens == nil {
return
}
if token, err = f.evalInfixExp(ctx, sheet, cell, tokens); err != nil {
return
}
result = token.Value()
if isNum, precision, decimal := isNumeric(result); isNum {
if precision > 15 {
@ -803,6 +786,22 @@ func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result strin
return
}
// calcCellValue calculate cell value by given context, worksheet name and cell
// reference.
func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result formulaArg, err error) {
var formula string
if formula, err = f.GetCellFormula(sheet, cell); err != nil {
return
}
ps := efp.ExcelParser()
tokens := ps.Parse(formula)
if tokens == nil {
return
}
result, err = f.evalInfixExp(ctx, sheet, cell, tokens)
return
}
// getPriority calculate arithmetic operator priority.
func getPriority(token efp.Token) (pri int) {
pri = tokenPriority[token.TValue]
@ -919,8 +918,8 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
if err != nil {
return result, err
}
if result.Type != ArgString {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), errors.New(formulaErrorVALUE)
if result.Type == ArgError {
return result, errors.New(result.Error)
}
opfdStack.Push(result)
continue
@ -933,7 +932,7 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
}
result, err := f.parseReference(ctx, sheet, token.TValue)
if err != nil {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), err
return newEmptyFormulaArg(), err
}
if result.Type == ArgUnknown {
return newEmptyFormulaArg(), errors.New(formulaErrorVALUE)
@ -977,10 +976,6 @@ func (f *File) evalInfixExp(ctx *calcContext, sheet, cell string, tokens []efp.T
continue
}
// current token is logical
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical {
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
}
if inArrayRow && isOperand(token) {
continue
}
@ -1341,17 +1336,34 @@ func isOperatorPrefixToken(token efp.Token) bool {
// isOperand determine if the token is parse operand.
func isOperand(token efp.Token) bool {
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText)
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText || token.TSubType == efp.TokenSubTypeLogical)
}
// tokenToFormulaArg create a formula argument by given token.
func tokenToFormulaArg(token efp.Token) formulaArg {
if token.TSubType == efp.TokenSubTypeNumber {
switch token.TSubType {
case efp.TokenSubTypeLogical:
return newBoolFormulaArg(strings.EqualFold(token.TValue, "TRUE"))
case efp.TokenSubTypeNumber:
num, _ := strconv.ParseFloat(token.TValue, 64)
return newNumberFormulaArg(num)
}
default:
return newStringFormulaArg(token.TValue)
}
}
// formulaArgToToken create a token by given formula argument.
func formulaArgToToken(arg formulaArg) efp.Token {
switch arg.Type {
case ArgNumber:
if arg.Boolean {
return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeLogical}
}
return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber}
default:
return efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeText}
}
}
// parseToken parse basic arithmetic operator priority and evaluate based on
// operators and operands.
@ -1366,12 +1378,7 @@ func (f *File) parseToken(ctx *calcContext, sheet string, token efp.Token, opdSt
if err != nil {
return errors.New(formulaErrorNAME)
}
if result.Type != ArgString {
return errors.New(formulaErrorVALUE)
}
token.TValue = result.String
token.TType = efp.TokenTypeOperand
token.TSubType = efp.TokenSubTypeText
token = formulaArgToToken(result)
}
if isOperatorPrefixToken(token) {
if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil {
@ -1505,20 +1512,39 @@ func prepareValueRef(cr cellRef, valueRange []int) {
}
// cellResolver calc cell value by given worksheet name, cell reference and context.
func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (string, error) {
var value string
func (f *File) cellResolver(ctx *calcContext, sheet, cell string) (formulaArg, error) {
var (
arg formulaArg
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()
value, _ = f.calcCellValue(ctx, sheet, cell)
return value, nil
arg, _ = f.calcCellValue(ctx, sheet, cell)
return arg, nil
}
ctx.Unlock()
}
return f.GetCellValue(sheet, cell, Options{RawCellValue: true})
if value, err = f.GetCellValue(sheet, cell, Options{RawCellValue: true}); err != nil {
return arg, err
}
arg = newStringFormulaArg(value)
cellType, _ := f.GetCellType(sheet, cell)
switch cellType {
case CellTypeBool:
return arg.ToBool(), err
case CellTypeNumber, CellTypeUnset:
if arg.Value() == "" {
return newEmptyFormulaArg(), err
}
return arg.ToNumber(), err
default:
return arg, err
}
}
// rangeResolver extract value as string from given reference and range list.
@ -1556,17 +1582,15 @@ func (f *File) rangeResolver(ctx *calcContext, cellRefs, cellRanges *list.List)
for row := valueRange[0]; row <= valueRange[1]; row++ {
var matrixRow []formulaArg
for col := valueRange[2]; col <= valueRange[3]; col++ {
var cell, value string
var cell string
var value formulaArg
if cell, err = CoordinatesToCellName(col, row); err != nil {
return
}
if value, err = f.cellResolver(ctx, sheet, cell); err != nil {
return
}
matrixRow = append(matrixRow, formulaArg{
String: value,
Type: ArgString,
})
matrixRow = append(matrixRow, value)
}
arg.Matrix = append(arg.Matrix, matrixRow)
}
@ -1579,10 +1603,10 @@ func (f *File) rangeResolver(ctx *calcContext, cellRefs, cellRanges *list.List)
if cell, err = CoordinatesToCellName(cr.Col, cr.Row); err != nil {
return
}
if arg.String, err = f.cellResolver(ctx, cr.Sheet, cell); err != nil {
if arg, err = f.cellResolver(ctx, cr.Sheet, cell); err != nil {
return
}
arg.Type = ArgString
arg.cellRefs, arg.cellRanges = cellRefs, cellRanges
}
return
}
@ -4618,10 +4642,11 @@ func newNumberMatrix(arg formulaArg, phalanx bool) (numMtx [][]float64, ele form
}
numMtx = append(numMtx, make([]float64, len(row)))
for c, cell := range row {
if ele = cell.ToNumber(); ele.Type != ArgNumber {
if cell.Type != ArgNumber {
ele = newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
return
}
numMtx[r][c] = ele.Number
numMtx[r][c] = cell.Number
}
}
return
@ -4946,31 +4971,24 @@ func (fn *formulaFuncs) POWER(argsList *list.List) formulaArg {
//
// PRODUCT(number1,[number2],...)
func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg {
val, product := 0.0, 1.0
var err error
product := 1.0
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
token := arg.Value.(formulaArg)
switch token.Type {
case ArgString:
if token.String == "" {
continue
num := token.ToNumber()
if num.Type != ArgNumber {
return num
}
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
product = product * val
product = product * num.Number
case ArgNumber:
product = product * token.Number
case ArgMatrix:
for _, row := range token.Matrix {
for _, value := range row {
if value.Value() == "" {
continue
for _, cell := range row {
if cell.Type == ArgNumber {
product *= cell.Number
}
if val, err = strconv.ParseFloat(value.String, 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
product *= val
}
}
}
@ -5685,26 +5703,23 @@ func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
if argsList.Len() == 3 {
sumRange = argsList.Back().Value.(formulaArg).Matrix
}
var sum, val float64
var err error
var sum float64
var arg formulaArg
for rowIdx, row := range rangeMtx {
for colIdx, col := range row {
var ok bool
fromVal := col.String
if col.String == "" {
for colIdx, cell := range row {
arg = cell
if arg.Type == ArgEmpty {
continue
}
ok, _ = formulaCriteriaEval(fromVal, criteria)
if ok {
if ok, _ := formulaCriteriaEval(arg.Value(), criteria); ok {
if argsList.Len() == 3 {
if len(sumRange) > rowIdx && len(sumRange[rowIdx]) > colIdx {
fromVal = sumRange[rowIdx][colIdx].String
arg = sumRange[rowIdx][colIdx]
}
}
if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
continue
if arg.Type == ArgNumber {
sum += arg.Number
}
sum += val
}
}
}
@ -7662,14 +7677,16 @@ func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg {
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString, ArgNumber:
if arg.ToNumber().Type != ArgError {
case ArgString:
if num := arg.ToNumber(); num.Type == ArgNumber {
count++
}
case ArgNumber:
count++
case ArgMatrix:
for _, row := range arg.Matrix {
for _, value := range row {
if value.ToNumber().Type != ArgError {
for _, cell := range row {
if cell.Type == ArgNumber {
count++
}
}
@ -7818,17 +7835,16 @@ func (fn *formulaFuncs) DEVSQ(argsList *list.List) formulaArg {
}
avg, count, result := fn.AVERAGE(argsList), -1, 0.0
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
for _, number := range arg.Value.(formulaArg).ToList() {
num := number.ToNumber()
if num.Type != ArgNumber {
for _, cell := range arg.Value.(formulaArg).ToList() {
if cell.Type != ArgNumber {
continue
}
count++
if count == 0 {
result = math.Pow(num.Number-avg.Number, 2)
result = math.Pow(cell.Number-avg.Number, 2)
continue
}
result += math.Pow(num.Number-avg.Number, 2)
result += math.Pow(cell.Number-avg.Number, 2)
}
}
if count == -1 {
@ -9338,12 +9354,12 @@ func (fn *formulaFuncs) MODE(argsList *list.List) formulaArg {
var values []float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
cells := arg.Value.(formulaArg)
if cells.Type != ArgMatrix && cells.ToNumber().Type != ArgNumber {
if cells.Type != ArgMatrix && cells.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
for _, cell := range cells.ToList() {
if num := cell.ToNumber(); num.Type == ArgNumber {
values = append(values, num.Number)
if cell.Type == ArgNumber {
values = append(values, cell.Number)
}
}
}
@ -9381,12 +9397,12 @@ func (fn *formulaFuncs) MODEdotMULT(argsList *list.List) formulaArg {
var values []float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
cells := arg.Value.(formulaArg)
if cells.Type != ArgMatrix && cells.ToNumber().Type != ArgNumber {
if cells.Type != ArgMatrix && cells.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
for _, cell := range cells.ToList() {
if num := cell.ToNumber(); num.Type == ArgNumber {
values = append(values, num.Number)
if cell.Type == ArgNumber {
values = append(values, cell.Number)
}
}
}
@ -9700,8 +9716,8 @@ func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg {
}
var data []float64
for _, arg := range array {
if numArg := arg.ToNumber(); numArg.Type == ArgNumber {
data = append(data, numArg.Number)
if arg.Type == ArgNumber {
data = append(data, arg.Number)
}
}
if len(data) < k {
@ -9776,25 +9792,10 @@ func (fn *formulaFuncs) MAXIFS(argsList *list.List) formulaArg {
// calcListMatrixMax is part of the implementation max.
func calcListMatrixMax(maxa bool, max float64, arg formulaArg) float64 {
for _, row := range arg.ToList() {
switch row.Type {
case ArgString:
if !maxa && (row.Value() == "TRUE" || row.Value() == "FALSE") {
continue
} else {
num := row.ToBool()
if num.Type == ArgNumber && num.Number > max {
max = num.Number
continue
}
}
num := row.ToNumber()
if num.Type != ArgError && num.Number > max {
max = num.Number
}
case ArgNumber:
if row.Number > max {
max = row.Number
for _, cell := range arg.ToList() {
if cell.Type == ArgNumber && cell.Number > max {
if maxa && cell.Boolean || !cell.Boolean {
max = cell.Number
}
}
}
@ -9846,33 +9847,31 @@ func (fn *formulaFuncs) MEDIAN(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorVALUE, "MEDIAN requires at least 1 argument")
}
var values []float64
var median, digits float64
var err error
var median float64
for token := argsList.Front(); token != nil; token = token.Next() {
arg := token.Value.(formulaArg)
switch arg.Type {
case ArgString:
num := arg.ToNumber()
if num.Type == ArgError {
return newErrorFormulaArg(formulaErrorVALUE, num.Error)
value := arg.ToNumber()
if value.Type != ArgNumber {
return value
}
values = append(values, num.Number)
values = append(values, value.Number)
case ArgNumber:
values = append(values, arg.Number)
case ArgMatrix:
for _, row := range arg.Matrix {
for _, value := range row {
if value.String == "" {
continue
}
if digits, err = strconv.ParseFloat(value.String, 64); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
values = append(values, digits)
for _, cell := range row {
if cell.Type == ArgNumber {
values = append(values, cell.Number)
}
}
}
}
}
if len(values) == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
sort.Float64s(values)
if len(values)%2 == 0 {
median = (values[len(values)/2-1] + values[len(values)/2]) / 2
@ -9936,25 +9935,10 @@ func (fn *formulaFuncs) MINIFS(argsList *list.List) formulaArg {
// calcListMatrixMin is part of the implementation min.
func calcListMatrixMin(mina bool, min float64, arg formulaArg) float64 {
for _, row := range arg.ToList() {
switch row.Type {
case ArgString:
if !mina && (row.Value() == "TRUE" || row.Value() == "FALSE") {
continue
} else {
num := row.ToBool()
if num.Type == ArgNumber && num.Number < min {
min = num.Number
continue
}
}
num := row.ToNumber()
if num.Type != ArgError && num.Number < min {
min = num.Number
}
case ArgNumber:
if row.Number < min {
min = row.Number
for _, cell := range arg.ToList() {
if cell.Type == ArgNumber && cell.Number < min {
if mina && cell.Boolean || !cell.Boolean {
min = cell.Number
}
}
}
@ -10016,7 +10000,7 @@ func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formula
}
var sum, deltaX, deltaY, x, y, length float64
for i := 0; i < len(array1); i++ {
num1, num2 := array1[i].ToNumber(), array2[i].ToNumber()
num1, num2 := array1[i], array2[i]
if !(num1.Type == ArgNumber && num2.Type == ArgNumber) {
continue
}
@ -10027,7 +10011,7 @@ func (fn *formulaFuncs) pearsonProduct(name string, argsList *list.List) formula
x /= length
y /= length
for i := 0; i < len(array1); i++ {
num1, num2 := array1[i].ToNumber(), array2[i].ToNumber()
num1, num2 := array1[i], array2[i]
if !(num1.Type == ArgNumber && num2.Type == ArgNumber) {
continue
}
@ -10077,9 +10061,8 @@ func (fn *formulaFuncs) PERCENTILEdotEXC(argsList *list.List) formulaArg {
if arg.Type == ArgError {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
num := arg.ToNumber()
if num.Type == ArgNumber {
numbers = append(numbers, num.Number)
if arg.Type == ArgNumber {
numbers = append(numbers, arg.Number)
}
}
cnt := len(numbers)
@ -10125,9 +10108,8 @@ func (fn *formulaFuncs) PERCENTILE(argsList *list.List) formulaArg {
if arg.Type == ArgError {
return arg
}
num := arg.ToNumber()
if num.Type == ArgNumber {
numbers = append(numbers, num.Number)
if arg.Type == ArgNumber {
numbers = append(numbers, arg.Number)
}
}
cnt := len(numbers)
@ -10156,11 +10138,10 @@ func (fn *formulaFuncs) percentrank(name string, argsList *list.List) formulaArg
var numbers []float64
for _, arg := range array {
if arg.Type == ArgError {
return arg
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
num := arg.ToNumber()
if num.Type == ArgNumber {
numbers = append(numbers, num.Number)
if arg.Type == ArgNumber {
numbers = append(numbers, arg.Number)
}
}
cnt := len(numbers)
@ -10350,9 +10331,8 @@ func (fn *formulaFuncs) rank(name string, argsList *list.List) formulaArg {
}
var arr []float64
for _, arg := range argsList.Front().Next().Value.(formulaArg).ToList() {
n := arg.ToNumber()
if n.Type == ArgNumber {
arr = append(arr, n.Number)
if arg.Type == ArgNumber {
arr = append(arr, arg.Number)
}
}
sort.Float64s(arr)
@ -10422,12 +10402,11 @@ func (fn *formulaFuncs) skew(name string, argsList *list.List) formulaArg {
summer += math.Pow((num.Number-mean.Number)/stdDev.Number, 3)
count++
case ArgList, ArgMatrix:
for _, row := range token.ToList() {
numArg := row.ToNumber()
if numArg.Type != ArgNumber {
for _, cell := range token.ToList() {
if cell.Type != ArgNumber {
continue
}
summer += math.Pow((numArg.Number-mean.Number)/stdDev.Number, 3)
summer += math.Pow((cell.Number-mean.Number)/stdDev.Number, 3)
count++
}
}
@ -10558,7 +10537,7 @@ func (fn *formulaFuncs) STEYX(argsList *list.List) formulaArg {
}
var count, sumX, sumY, squareX, squareY, sigmaXY float64
for i := 0; i < len(array1); i++ {
num1, num2 := array1[i].ToNumber(), array2[i].ToNumber()
num1, num2 := array1[i], array2[i]
if !(num1.Type == ArgNumber && num2.Type == ArgNumber) {
continue
}
@ -10804,8 +10783,7 @@ func tTest(bTemplin bool, mtx1, mtx2 [][]formulaArg, c1, c2, r1, r2 int) (float6
var fVal formulaArg
for i := 0; i < c1; i++ {
for j := 0; j < r1; j++ {
fVal = mtx1[i][j].ToNumber()
if fVal.Type == ArgNumber {
if fVal = mtx1[i][j]; fVal.Type == ArgNumber {
sum1 += fVal.Number
sumSqr1 += fVal.Number * fVal.Number
cnt1++
@ -10814,8 +10792,7 @@ func tTest(bTemplin bool, mtx1, mtx2 [][]formulaArg, c1, c2, r1, r2 int) (float6
}
for i := 0; i < c2; i++ {
for j := 0; j < r2; j++ {
fVal = mtx2[i][j].ToNumber()
if fVal.Type == ArgNumber {
if fVal = mtx2[i][j]; fVal.Type == ArgNumber {
sum2 += fVal.Number
sumSqr2 += fVal.Number * fVal.Number
cnt2++
@ -10851,7 +10828,7 @@ func (fn *formulaFuncs) tTest(mtx1, mtx2 [][]formulaArg, fTails, fTyp float64) f
var fVal1, fVal2 formulaArg
for i := 0; i < c1; i++ {
for j := 0; j < r1; j++ {
fVal1, fVal2 = mtx1[i][j].ToNumber(), mtx2[i][j].ToNumber()
fVal1, fVal2 = mtx1[i][j], mtx2[i][j]
if fVal1.Type != ArgNumber || fVal2.Type != ArgNumber {
continue
}
@ -10895,11 +10872,11 @@ func (fn *formulaFuncs) TTEST(argsList *list.List) formulaArg {
var array1, array2, tails, typeArg formulaArg
array1 = argsList.Front().Value.(formulaArg)
array2 = argsList.Front().Next().Value.(formulaArg)
if tails = argsList.Front().Next().Next().Value.(formulaArg).ToNumber(); tails.Type != ArgNumber {
return tails
if tails = argsList.Front().Next().Next().Value.(formulaArg); tails.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if typeArg = argsList.Back().Value.(formulaArg).ToNumber(); typeArg.Type != ArgNumber {
return typeArg
if typeArg = argsList.Back().Value.(formulaArg); typeArg.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if len(array1.Matrix) == 0 || len(array2.Matrix) == 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
@ -10944,11 +10921,10 @@ func (fn *formulaFuncs) TRIMMEAN(argsList *list.List) formulaArg {
var arr []float64
arrArg := argsList.Front().Value.(formulaArg).ToList()
for _, cell := range arrArg {
num := cell.ToNumber()
if num.Type != ArgNumber {
if cell.Type != ArgNumber {
continue
}
arr = append(arr, num.Number)
arr = append(arr, cell.Number)
}
discard := math.Floor(float64(len(arr)) * percent.Number / 2)
sort.Float64s(arr)
@ -11184,17 +11160,13 @@ func (fn *formulaFuncs) ISBLANK(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorVALUE, "ISBLANK requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
result := "FALSE"
switch token.Type {
case ArgUnknown:
result = "TRUE"
case ArgString:
if token.String == "" {
result = "TRUE"
case ArgUnknown, ArgEmpty:
return newBoolFormulaArg(true)
default:
return newBoolFormulaArg(false)
}
}
return newStringFormulaArg(result)
}
// ISERR function tests if an initial supplied expression (or value) returns
// any Excel Error, except the #N/A error. If so, the function returns the
@ -11256,21 +11228,22 @@ func (fn *formulaFuncs) ISEVEN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISEVEN requires 1 argument")
}
var (
token = argsList.Front().Value.(formulaArg)
result = "FALSE"
numeric int
err error
)
if token.Type == ArgString {
if numeric, err = strconv.Atoi(token.String); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
token := argsList.Front().Value.(formulaArg)
switch token.Type {
case ArgEmpty:
return newBoolFormulaArg(true)
case ArgNumber, ArgString:
num := token.ToNumber()
if num.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if numeric == numeric/2*2 {
return newStringFormulaArg("TRUE")
if num.Number == 1 {
return newBoolFormulaArg(false)
}
return newBoolFormulaArg(num.Number == num.Number/2*2)
default:
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
return newStringFormulaArg(result)
}
// ISFORMULA function tests if a specified cell contains a formula, and if so,
@ -11335,12 +11308,10 @@ func (fn *formulaFuncs) ISNONTEXT(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISNONTEXT requires 1 argument")
}
token := argsList.Front().Value.(formulaArg)
result := "TRUE"
if token.Type == ArgString && token.String != "" {
result = "FALSE"
if argsList.Front().Value.(formulaArg).Type == ArgString {
return newBoolFormulaArg(false)
}
return newStringFormulaArg(result)
return newBoolFormulaArg(true)
}
// ISNUMBER function tests if a supplied value is a number. If so,
@ -11352,13 +11323,10 @@ func (fn *formulaFuncs) ISNUMBER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISNUMBER requires 1 argument")
}
token, result := argsList.Front().Value.(formulaArg), false
if token.Type == ArgString && token.String != "" {
if _, err := strconv.Atoi(token.String); err == nil {
result = true
if argsList.Front().Value.(formulaArg).Type == ArgNumber {
return newBoolFormulaArg(true)
}
}
return newBoolFormulaArg(result)
return newBoolFormulaArg(false)
}
// ISODD function tests if a supplied number (or numeric expression) evaluates
@ -11370,21 +11338,14 @@ func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ISODD requires 1 argument")
}
var (
token = argsList.Front().Value.(formulaArg)
result = "FALSE"
numeric int
err error
)
if token.Type == ArgString {
if numeric, err = strconv.Atoi(token.String); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
arg := argsList.Front().Value.(formulaArg).ToNumber()
if arg.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if numeric != numeric/2*2 {
return newStringFormulaArg("TRUE")
if int(arg.Number) != int(arg.Number)/2*2 {
return newBoolFormulaArg(true)
}
}
return newStringFormulaArg(result)
return newBoolFormulaArg(false)
}
// ISREF function tests if a supplied value is a reference. If so, the
@ -11524,13 +11485,12 @@ func (fn *formulaFuncs) TYPE(argsList *list.List) formulaArg {
return newNumberFormulaArg(16)
case ArgMatrix:
return newNumberFormulaArg(64)
default:
if arg := token.ToNumber(); arg.Type != ArgError || len(token.Value()) == 0 {
return newNumberFormulaArg(1)
}
if arg := token.ToBool(); arg.Type != ArgError {
case ArgNumber, ArgEmpty:
if token.Boolean {
return newNumberFormulaArg(4)
}
return newNumberFormulaArg(1)
default:
return newNumberFormulaArg(2)
}
}
@ -13734,9 +13694,9 @@ func (fn *formulaFuncs) TEXTJOIN(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorVALUE, "TEXTJOIN accepts at most 252 arguments")
}
delimiter := argsList.Front().Value.(formulaArg)
ignoreEmpty := argsList.Front().Next().Value.(formulaArg).ToBool()
if ignoreEmpty.Type != ArgNumber {
return ignoreEmpty
ignoreEmpty := argsList.Front().Next().Value.(formulaArg)
if ignoreEmpty.Type != ArgNumber || !ignoreEmpty.Boolean {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
args, ok := textJoin(argsList.Front().Next().Next(), []string{}, ignoreEmpty.Number != 0)
if ok.Type != ArgNumber {
@ -13755,7 +13715,7 @@ func textJoin(arg *list.Element, arr []string, ignoreEmpty bool) ([]string, form
switch arg.Value.(formulaArg).Type {
case ArgError:
return arr, arg.Value.(formulaArg)
case ArgString:
case ArgString, ArgEmpty:
val := arg.Value.(formulaArg).Value()
if val != "" || !ignoreEmpty {
arr = append(arr, val)
@ -14040,7 +14000,7 @@ func matchPattern(pattern, name string) (matched bool) {
// match, and make compare result as formula criteria condition type.
func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte {
if lhs.Type != rhs.Type {
return criteriaErr
return criteriaNe
}
switch lhs.Type {
case ArgNumber:
@ -14068,9 +14028,10 @@ func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte
return compareFormulaArgList(lhs, rhs, matchMode, caseSensitive)
case ArgMatrix:
return compareFormulaArgMatrix(lhs, rhs, matchMode, caseSensitive)
}
default:
return criteriaErr
}
}
// compareFormulaArgList compares the left-hand sides and the right-hand sides
// list type formula arguments.
@ -14247,8 +14208,8 @@ func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue,
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires second argument of table array", name))
return
}
arg := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if arg.Type != ArgNumber {
arg := argsList.Front().Next().Next().Value.(formulaArg)
if arg.Type != ArgNumber || arg.Boolean {
errArg = newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires numeric %s argument", name, unit))
return
}
@ -14256,7 +14217,7 @@ func checkHVLookupArgs(name string, argsList *list.List) (idx int, lookupValue,
if argsList.Len() == 4 {
rangeLookup := argsList.Back().Value.(formulaArg).ToBool()
if rangeLookup.Type == ArgError {
errArg = newErrorFormulaArg(formulaErrorVALUE, rangeLookup.Error)
errArg = rangeLookup
return
}
if rangeLookup.Number == 0 {
@ -14442,6 +14403,8 @@ start:
}
} else if lookupValue.Type == ArgMatrix {
lhs = lookupArray
} else if lookupArray.Type == ArgString {
lhs = newStringFormulaArg(cell.Value())
}
if compareFormulaArg(lhs, lookupValue, matchMode, false) == criteriaEq {
matchIdx = i
@ -14512,6 +14475,8 @@ func lookupBinarySearch(vertical bool, lookupValue, lookupArray, matchMode, sear
}
} else if lookupValue.Type == ArgMatrix && vertical {
lhs = lookupArray
} else if lookupValue.Type == ArgString {
lhs = newStringFormulaArg(cell.Value())
}
result := compareFormulaArg(lhs, lookupValue, matchMode, false)
if result == criteriaEq {
@ -14524,7 +14489,7 @@ func lookupBinarySearch(vertical bool, lookupValue, lookupArray, matchMode, sear
high = mid - 1
} else if result == criteriaL {
matchIdx = mid
if lhs.Value() != "" {
if cell.Type != ArgEmpty {
lastMatchIdx = matchIdx
}
low = mid + 1

View File

@ -8,6 +8,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/xuri/efp"
)
func prepareCalcData(cellData [][]interface{}) *File {
@ -572,6 +573,7 @@ func TestCalcCellValue(t *testing.T) {
"=FLOOR(-26.75,-0.1)": "-26.7",
"=FLOOR(-26.75,-1)": "-26",
"=FLOOR(-26.75,-5)": "-25",
"=FLOOR(-2.05,2)": "-4",
"=FLOOR(FLOOR(26.75,1),1)": "26",
// _xlfn.FLOOR.MATH
"=_xlfn.FLOOR.MATH(58.55)": "58",
@ -706,8 +708,8 @@ func TestCalcCellValue(t *testing.T) {
"=POWER(4,POWER(1,1))": "4",
// PRODUCT
"=PRODUCT(3,6)": "18",
`=PRODUCT("",3,6)`: "18",
`=PRODUCT(PRODUCT(1),3,6)`: "18",
"=PRODUCT(\"3\",\"6\")": "18",
"=PRODUCT(PRODUCT(1),3,6)": "18",
"=PRODUCT(C1:C2)": "1",
// QUOTIENT
"=QUOTIENT(5,2)": "2",
@ -836,7 +838,8 @@ func TestCalcCellValue(t *testing.T) {
"=SUBTOTAL(111,A1:A6,A1:A6)": "1.25",
// SUM
"=SUM(1,2)": "3",
`=SUM("",1,2)`: "3",
"=SUM(\"1\",\"2\")": "3",
"=SUM(\"\",1,2)": "3",
"=SUM(1,2+3)": "6",
"=SUM(SUM(1,2),2)": "5",
"=(-2-SUM(-4+7))*5": "-25",
@ -876,8 +879,9 @@ func TestCalcCellValue(t *testing.T) {
// SUMSQ
"=SUMSQ(A1:A4)": "14",
"=SUMSQ(A1,B1,A2,B2,6)": "82",
`=SUMSQ("",A1,B1,A2,B2,6)`: "82",
`=SUMSQ(1,SUMSQ(1))`: "2",
"=SUMSQ(\"\",A1,B1,A2,B2,6)": "82",
"=SUMSQ(1,SUMSQ(1))": "2",
"=SUMSQ(\"1\",SUMSQ(1))": "2",
"=SUMSQ(MUNIT(3))": "3",
// SUMX2MY2
"=SUMX2MY2(A1:A4,B1:B4)": "-36",
@ -914,6 +918,7 @@ func TestCalcCellValue(t *testing.T) {
// AVERAGEA
"=AVERAGEA(INT(1))": "1",
"=AVERAGEA(A1)": "1",
"=AVERAGEA(\"1\")": "1",
"=AVERAGEA(A1:A2)": "1.5",
"=AVERAGEA(D2:F9)": "12671.375",
// BETA.DIST
@ -1013,6 +1018,7 @@ func TestCalcCellValue(t *testing.T) {
"=COUNTA()": "0",
"=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8",
"=COUNTA(COUNTA(1),MUNIT(1))": "2",
"=COUNTA(D1:D2)": "2",
// COUNTBLANK
"=COUNTBLANK(MUNIT(1))": "0",
"=COUNTBLANK(1)": "0",
@ -1076,6 +1082,7 @@ func TestCalcCellValue(t *testing.T) {
// GAUSS
"=GAUSS(-5)": "-0.499999713348428",
"=GAUSS(0)": "0",
"=GAUSS(\"0\")": "0",
"=GAUSS(0.1)": "0.039827837277029",
"=GAUSS(2.5)": "0.493790334674224",
// GEOMEAN
@ -1373,6 +1380,7 @@ func TestCalcCellValue(t *testing.T) {
// ISEVEN
"=ISEVEN(A1)": "FALSE",
"=ISEVEN(A2)": "TRUE",
"=ISEVEN(G1)": "TRUE",
// ISFORMULA
"=ISFORMULA(A1)": "FALSE",
"=ISFORMULA(\"A\")": "FALSE",
@ -1388,7 +1396,7 @@ func TestCalcCellValue(t *testing.T) {
"=ISNA(A1)": "FALSE",
"=ISNA(NA())": "TRUE",
// ISNONTEXT
"=ISNONTEXT(A1)": "FALSE",
"=ISNONTEXT(A1)": "TRUE",
"=ISNONTEXT(A5)": "TRUE",
`=ISNONTEXT("Excelize")`: "FALSE",
"=ISNONTEXT(NA())": "TRUE",
@ -1421,7 +1429,7 @@ func TestCalcCellValue(t *testing.T) {
// TYPE
"=TYPE(2)": "1",
"=TYPE(10/2)": "1",
"=TYPE(C1)": "1",
"=TYPE(C2)": "1",
"=TYPE(\"text\")": "2",
"=TYPE(TRUE)": "4",
"=TYPE(NA())": "16",
@ -1446,6 +1454,7 @@ func TestCalcCellValue(t *testing.T) {
"=IFERROR(1/2,0)": "0.5",
"=IFERROR(ISERROR(),0)": "0",
"=IFERROR(1/0,0)": "0",
"=IFERROR(G1,2)": "0",
"=IFERROR(B2/MROUND(A2,1),0)": "2.5",
// IFNA
"=IFNA(1,\"not found\")": "1",
@ -1791,12 +1800,13 @@ func TestCalcCellValue(t *testing.T) {
"=IF(1<>1)": "FALSE",
"=IF(5<0, \"negative\", \"positive\")": "positive",
"=IF(-2<0, \"negative\", \"positive\")": "negative",
`=IF(1=1, "equal", "notequal")`: "equal",
`=IF(1<>1, "equal", "notequal")`: "notequal",
`=IF("A"="A", "equal", "notequal")`: "equal",
`=IF("A"<>"A", "equal", "notequal")`: "notequal",
`=IF(FALSE,0,ROUND(4/2,0))`: "2",
`=IF(TRUE,ROUND(4/2,0),0)`: "2",
"=IF(1=1, \"equal\", \"notequal\")": "equal",
"=IF(1<>1, \"equal\", \"notequal\")": "notequal",
"=IF(\"A\"=\"A\", \"equal\", \"notequal\")": "equal",
"=IF(\"A\"<>\"A\", \"equal\", \"notequal\")": "notequal",
"=IF(FALSE,0,ROUND(4/2,0))": "2",
"=IF(TRUE,ROUND(4/2,0),0)": "2",
"=IF(A4>0.4,\"TRUE\",\"FALSE\")": "FALSE",
// Excel Lookup and Reference Functions
// ADDRESS
"=ADDRESS(1,1,1,TRUE)": "$A$1",
@ -1855,6 +1865,7 @@ func TestCalcCellValue(t *testing.T) {
"=VLOOKUP(INT(F2),F3:F9,1,TRUE)": "32080",
"=VLOOKUP(MUNIT(3),MUNIT(3),1)": "0",
"=VLOOKUP(A1,A3:B5,1)": "0",
"=VLOOKUP(A1:A2,A1:A1,1)": "1",
"=VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
// INDEX
"=INDEX(0,0,0)": "0",
@ -2556,13 +2567,13 @@ func TestCalcCellValue(t *testing.T) {
"=MDETERM()": "MDETERM requires 1 argument",
// MINVERSE
"=MINVERSE()": "MINVERSE requires 1 argument",
"=MINVERSE(B3:C4)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=MINVERSE(B3:C4)": "#VALUE!",
"=MINVERSE(A1:C2)": "#VALUE!",
"=MINVERSE(A4:A4)": "#NUM!",
// MMULT
"=MMULT()": "MMULT requires 2 argument",
"=MMULT(A1:B2,B3:C4)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=MMULT(B3:C4,A1:B2)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=MMULT(A1:B2,B3:C4)": "#VALUE!",
"=MMULT(B3:C4,A1:B2)": "#VALUE!",
"=MMULT(A1:A2,B1:B2)": "#VALUE!",
// MOD
"=MOD()": "MOD requires 2 numeric arguments",
@ -2593,7 +2604,8 @@ func TestCalcCellValue(t *testing.T) {
"=POWER(0,-1)": "#DIV/0!",
"=POWER(1)": "POWER requires 2 numeric arguments",
// PRODUCT
`=PRODUCT("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
"=PRODUCT(\"X\")": "strconv.ParseFloat: parsing \"X\": invalid syntax",
"=PRODUCT(\"\",3,6)": "strconv.ParseFloat: parsing \"\": invalid syntax",
// QUOTIENT
`=QUOTIENT("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
`=QUOTIENT(1,"X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
@ -2697,6 +2709,7 @@ func TestCalcCellValue(t *testing.T) {
"=SUMPRODUCT(A1,D1)": "#VALUE!",
"=SUMPRODUCT(A1:A3,D1:D3)": "#VALUE!",
"=SUMPRODUCT(A1:A2,B1:B3)": "#VALUE!",
"=SUMPRODUCT(\"\")": "#VALUE!",
"=SUMPRODUCT(A1,NA())": "#N/A",
// SUMX2MY2
"=SUMX2MY2()": "SUMX2MY2 requires 2 arguments",
@ -2922,6 +2935,7 @@ func TestCalcCellValue(t *testing.T) {
// FISHER
"=FISHER()": "FISHER requires 1 numeric argument",
"=FISHER(2)": "#N/A",
"=FISHER(\"2\")": "#N/A",
"=FISHER(INT(-2)))": "#N/A",
"=FISHER(F1)": "FISHER requires 1 numeric argument",
// FISHERINV
@ -2984,7 +2998,8 @@ func TestCalcCellValue(t *testing.T) {
// GEOMEAN
"=GEOMEAN()": "GEOMEAN requires at least 1 numeric argument",
"=GEOMEAN(0)": "#NUM!",
"=GEOMEAN(D1:D2)": "strconv.ParseFloat: parsing \"Month\": invalid syntax",
"=GEOMEAN(D1:D2)": "#NUM!",
"=GEOMEAN(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
// HARMEAN
"=HARMEAN()": "HARMEAN requires at least 1 argument",
"=HARMEAN(-1)": "#N/A",
@ -3184,7 +3199,7 @@ func TestCalcCellValue(t *testing.T) {
// MEDIAN
"=MEDIAN()": "MEDIAN requires at least 1 argument",
"=MEDIAN(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=MEDIAN(D1:D2)": "strconv.ParseFloat: parsing \"Month\": invalid syntax",
"=MEDIAN(D1:D2)": "#NUM!",
// MIN
"=MIN()": "MIN requires at least 1 argument",
"=MIN(NA())": "#N/A",
@ -3408,7 +3423,8 @@ func TestCalcCellValue(t *testing.T) {
"=ISERROR()": "ISERROR requires 1 argument",
// ISEVEN
"=ISEVEN()": "ISEVEN requires 1 argument",
`=ISEVEN("text")`: "strconv.Atoi: parsing \"text\": invalid syntax",
"=ISEVEN(\"text\")": "#VALUE!",
"=ISEVEN(A1:A2)": "#VALUE!",
// ISFORMULA
"=ISFORMULA()": "ISFORMULA requires 1 argument",
// ISLOGICAL
@ -3421,7 +3437,7 @@ func TestCalcCellValue(t *testing.T) {
"=ISNUMBER()": "ISNUMBER requires 1 argument",
// ISODD
"=ISODD()": "ISODD requires 1 argument",
`=ISODD("text")`: "strconv.Atoi: parsing \"text\": invalid syntax",
"=ISODD(\"text\")": "#VALUE!",
// ISREF
"=ISREF()": "ISREF requires 1 argument",
// ISTEXT
@ -3717,7 +3733,7 @@ func TestCalcCellValue(t *testing.T) {
"=SUBSTITUTE(\"\",\"\",\"\",0)": "instance_num should be > 0",
// TEXTJOIN
"=TEXTJOIN()": "TEXTJOIN requires at least 3 arguments",
"=TEXTJOIN(\"\",\"\",1)": "strconv.ParseBool: parsing \"\": invalid syntax",
"=TEXTJOIN(\"\",\"\",1)": "#VALUE!",
"=TEXTJOIN(\"\",TRUE,NA())": "#N/A",
"=TEXTJOIN(\"\",TRUE," + strings.Repeat("0,", 250) + ",0)": "TEXTJOIN accepts at most 252 arguments",
"=TEXTJOIN(\",\",FALSE,REPT(\"*\",32768))": "TEXTJOIN function exceeds 32767 characters",
@ -3804,7 +3820,6 @@ func TestCalcCellValue(t *testing.T) {
"=VLOOKUP(D2,D1,1,FALSE)": "VLOOKUP requires second argument of table array",
"=VLOOKUP(D2,D:D,FALSE,FALSE)": "VLOOKUP requires numeric col argument",
"=VLOOKUP(D2,D:D,1,FALSE,FALSE)": "VLOOKUP requires at most 4 arguments",
"=VLOOKUP(A1:A2,A1:A1,1)": "VLOOKUP no result found",
"=VLOOKUP(D2,D10:D10,1,FALSE)": "VLOOKUP no result found",
"=VLOOKUP(D2,D:D,2,FALSE)": "VLOOKUP has invalid column index",
"=VLOOKUP(D2,C:C,1,FALSE)": "VLOOKUP no result found",
@ -4455,7 +4470,7 @@ func TestCalcISBLANK(t *testing.T) {
})
fn := formulaFuncs{}
result := fn.ISBLANK(argsList)
assert.Equal(t, result.String, "TRUE")
assert.Equal(t, "TRUE", result.Value())
assert.Empty(t, result.Error)
}
@ -4520,6 +4535,7 @@ 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("?", ""))
assert.False(t, matchPattern("file/?", "file/abc/bcd/def"))
}
@ -4574,15 +4590,14 @@ func TestCalcVLOOKUP(t *testing.T) {
}
func TestCalcBoolean(t *testing.T) {
cellData := [][]interface{}{
{0.5, "TRUE", -0.5, "FALSE"},
}
cellData := [][]interface{}{{0.5, "TRUE", -0.5, "FALSE", true}}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"=AVERAGEA(A1:C1)": "0.333333333333333",
"=MAX(0.5,B1)": "0.5",
"=MAX(A1:B1)": "0.5",
"=MAXA(A1:B1)": "1",
"=MAXA(A1:B1)": "0.5",
"=MAXA(A1:E1)": "1",
"=MAXA(0.5,B1)": "1",
"=MIN(-0.5,D1)": "-0.5",
"=MIN(C1:D1)": "-0.5",
@ -4600,6 +4615,23 @@ func TestCalcBoolean(t *testing.T) {
}
}
func TestCalcMAXMIN(t *testing.T) {
cellData := [][]interface{}{{"1"}, {"2"}, {true}}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"=MAX(A1:A3)": "0",
"=MAXA(A1:A3)": "1",
"=MIN(A1:A3)": "0",
"=MINA(A1:A3)": "1",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula))
result, err := f.CalcCellValue("Sheet1", "B1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
}
func TestCalcAVERAGEIF(t *testing.T) {
f := prepareCalcData([][]interface{}{
{"Monday", 500},
@ -4822,28 +4854,29 @@ func TestCalcGROWTHandTREND(t *testing.T) {
calcError := map[string]string{
"=GROWTH()": "GROWTH requires at least 1 argument",
"=GROWTH(B2:B5,A2:A5,A8:A10,TRUE,0)": "GROWTH allows at most 4 arguments",
"=GROWTH(A1:B1,A2:A5,A8:A10,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax",
"=GROWTH(B2:B5,A1:B1,A8:A10,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax",
"=GROWTH(B2:B5,A2:A5,A1:B1,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax",
"=GROWTH(A1:B1,A2:A5,A8:A10,TRUE)": "#VALUE!",
"=GROWTH(B2:B5,A1:B1,A8:A10,TRUE)": "#VALUE!",
"=GROWTH(B2:B5,A2:A5,A1:B1,TRUE)": "#VALUE!",
"=GROWTH(B2:B5,A2:A5,A8:A10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=GROWTH(A2:B3,A4:B4)": "#REF!",
"=GROWTH(A4:B4,A2:A2)": "#REF!",
"=GROWTH(A2:A2,A4:A5)": "#REF!",
"=GROWTH(C1:C1,A2:A3)": "#NUM!",
"=GROWTH(C1:C1,A2:A3)": "#VALUE!",
"=GROWTH(D1:D1,A2:A3)": "#NUM!",
"=GROWTH(A2:A3,C1:C1)": "#NUM!",
"=GROWTH(A2:A3,C1:C1)": "#VALUE!",
"=TREND()": "TREND requires at least 1 argument",
"=TREND(B2:B5,A2:A5,A8:A10,TRUE,0)": "TREND allows at most 4 arguments",
"=TREND(A1:B1,A2:A5,A8:A10,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax",
"=TREND(B2:B5,A1:B1,A8:A10,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax",
"=TREND(B2:B5,A2:A5,A1:B1,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax",
"=TREND(A1:B1,A2:A5,A8:A10,TRUE)": "#VALUE!",
"=TREND(B2:B5,A1:B1,A8:A10,TRUE)": "#VALUE!",
"=TREND(B2:B5,A2:A5,A1:B1,TRUE)": "#VALUE!",
"=TREND(B2:B5,A2:A5,A8:A10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=TREND(A2:B3,A4:B4)": "#REF!",
"=TREND(A4:B4,A2:A2)": "#REF!",
"=TREND(A2:A2,A4:A5)": "#REF!",
"=TREND(C1:C1,A2:A3)": "#NUM!",
"=TREND(C1:C1,A2:A3)": "#VALUE!",
"=TREND(D1:D1,A2:A3)": "#REF!",
"=TREND(A2:A3,C1:C1)": "#NUM!",
"=TREND(A2:A3,C1:C1)": "#VALUE!",
"=TREND(C1:C1,C1:C1)": "#VALUE!",
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
@ -5586,8 +5619,8 @@ func TestCalcTTEST(t *testing.T) {
"=TTEST()": "TTEST requires 4 arguments",
"=TTEST(\"\",B1:B12,1,1)": "#NUM!",
"=TTEST(A1:A12,\"\",1,1)": "#NUM!",
"=TTEST(A1:A12,B1:B12,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=TTEST(A1:A12,B1:B12,1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=TTEST(A1:A12,B1:B12,\"\",1)": "#VALUE!",
"=TTEST(A1:A12,B1:B12,1,\"\")": "#VALUE!",
"=TTEST(A1:A12,B1:B12,0,1)": "#NUM!",
"=TTEST(A1:A12,B1:B12,1,0)": "#NUM!",
"=TTEST(A1:A2,B1:B1,1,1)": "#N/A",
@ -5598,8 +5631,8 @@ func TestCalcTTEST(t *testing.T) {
"=T.TEST()": "T.TEST requires 4 arguments",
"=T.TEST(\"\",B1:B12,1,1)": "#NUM!",
"=T.TEST(A1:A12,\"\",1,1)": "#NUM!",
"=T.TEST(A1:A12,B1:B12,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=T.TEST(A1:A12,B1:B12,1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=T.TEST(A1:A12,B1:B12,\"\",1)": "#VALUE!",
"=T.TEST(A1:A12,B1:B12,1,\"\")": "#VALUE!",
"=T.TEST(A1:A12,B1:B12,0,1)": "#NUM!",
"=T.TEST(A1:A12,B1:B12,1,0)": "#NUM!",
"=T.TEST(A1:A2,B1:B1,1,1)": "#N/A",
@ -5618,8 +5651,8 @@ func TestCalcTTEST(t *testing.T) {
func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) {
cellData := [][]interface{}{
{"05/01/2019", 43586},
{"09/13/2019", 43721},
{"05/01/2019", 43586, "text1"},
{"09/13/2019", 43721, "text2"},
{"10/01/2019", 43739},
{"12/25/2019", 43824},
{"01/01/2020", 43831},
@ -5651,6 +5684,7 @@ func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) {
"=NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",17)": "219",
"=NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,A1:A12)": "178",
"=NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,B1:B12)": "178",
"=NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,C1:C2)": "183",
"=WORKDAY(\"12/01/2015\",25)": "42374",
"=WORKDAY(\"01/01/2020\",123,B1:B12)": "44006",
"=WORKDAY.INTL(\"12/01/2015\",0)": "42339",
@ -5813,3 +5847,27 @@ func TestNestedFunctionsWithOperators(t *testing.T) {
assert.Equal(t, expected, result, formula)
}
}
func TestFormulaArgToToken(t *testing.T) {
assert.Equal(t,
efp.Token{
TType: efp.TokenTypeOperand,
TSubType: efp.TokenSubTypeLogical,
TValue: "TRUE",
},
formulaArgToToken(newBoolFormulaArg(true)),
)
}
func TestPrepareTrendGrowth(t *testing.T) {
assert.Equal(t, [][]float64(nil), prepareTrendGrowthMtxX([][]float64{{0, 0}, {0, 0}}))
assert.Equal(t, [][]float64(nil), prepareTrendGrowthMtxY(false, [][]float64{{0, 0}, {0, 0}}))
info, err := prepareTrendGrowth(false, [][]float64{{0, 0}, {0, 0}}, [][]float64{{0, 0}, {0, 0}})
assert.Nil(t, info)
assert.Equal(t, newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM), err)
}
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))
}

View File

@ -519,10 +519,14 @@ func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
// string.
func (c *xlsxC) setCellDefault(value string) {
if ok, _, _ := isNumeric(value); !ok {
if value != "" {
c.setInlineStr(value)
c.IS.T.Val = value
return
}
c.T, c.V, c.IS = value, value, nil
return
}
c.V = value
}

View File

@ -253,7 +253,8 @@ func TestOpenReader(t *testing.T) {
for _, defaultXMLPath := range []string{
defaultXMLPathCalcChain,
defaultXMLPathStyles,
defaultXMLPathWorkbookRels} {
defaultXMLPathWorkbookRels,
} {
_, err = OpenReader(preset(defaultXMLPath))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}

6
go.mod
View File

@ -8,10 +8,10 @@ require (
github.com/stretchr/testify v1.8.0
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22
golang.org/x/crypto v0.4.0
golang.org/x/crypto v0.5.0
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
golang.org/x/net v0.4.0
golang.org/x/text v0.5.0
golang.org/x/net v0.5.0
golang.org/x/text v0.6.0
)
require github.com/richardlehane/msoleps v1.0.3 // indirect

17
go.sum
View File

@ -22,17 +22,16 @@ github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -40,15 +39,15 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=