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

View File

@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/xuri/efp"
) )
func prepareCalcData(cellData [][]interface{}) *File { func prepareCalcData(cellData [][]interface{}) *File {
@ -572,6 +573,7 @@ func TestCalcCellValue(t *testing.T) {
"=FLOOR(-26.75,-0.1)": "-26.7", "=FLOOR(-26.75,-0.1)": "-26.7",
"=FLOOR(-26.75,-1)": "-26", "=FLOOR(-26.75,-1)": "-26",
"=FLOOR(-26.75,-5)": "-25", "=FLOOR(-26.75,-5)": "-25",
"=FLOOR(-2.05,2)": "-4",
"=FLOOR(FLOOR(26.75,1),1)": "26", "=FLOOR(FLOOR(26.75,1),1)": "26",
// _xlfn.FLOOR.MATH // _xlfn.FLOOR.MATH
"=_xlfn.FLOOR.MATH(58.55)": "58", "=_xlfn.FLOOR.MATH(58.55)": "58",
@ -706,8 +708,8 @@ func TestCalcCellValue(t *testing.T) {
"=POWER(4,POWER(1,1))": "4", "=POWER(4,POWER(1,1))": "4",
// PRODUCT // PRODUCT
"=PRODUCT(3,6)": "18", "=PRODUCT(3,6)": "18",
`=PRODUCT("",3,6)`: "18", "=PRODUCT(\"3\",\"6\")": "18",
`=PRODUCT(PRODUCT(1),3,6)`: "18", "=PRODUCT(PRODUCT(1),3,6)": "18",
"=PRODUCT(C1:C2)": "1", "=PRODUCT(C1:C2)": "1",
// QUOTIENT // QUOTIENT
"=QUOTIENT(5,2)": "2", "=QUOTIENT(5,2)": "2",
@ -836,7 +838,8 @@ func TestCalcCellValue(t *testing.T) {
"=SUBTOTAL(111,A1:A6,A1:A6)": "1.25", "=SUBTOTAL(111,A1:A6,A1:A6)": "1.25",
// SUM // SUM
"=SUM(1,2)": "3", "=SUM(1,2)": "3",
`=SUM("",1,2)`: "3", "=SUM(\"1\",\"2\")": "3",
"=SUM(\"\",1,2)": "3",
"=SUM(1,2+3)": "6", "=SUM(1,2+3)": "6",
"=SUM(SUM(1,2),2)": "5", "=SUM(SUM(1,2),2)": "5",
"=(-2-SUM(-4+7))*5": "-25", "=(-2-SUM(-4+7))*5": "-25",
@ -874,11 +877,12 @@ func TestCalcCellValue(t *testing.T) {
"=SUMPRODUCT(A1:B3)": "15", "=SUMPRODUCT(A1:B3)": "15",
"=SUMPRODUCT(A1:A3,B1:B3,B2:B4)": "20", "=SUMPRODUCT(A1:A3,B1:B3,B2:B4)": "20",
// SUMSQ // SUMSQ
"=SUMSQ(A1:A4)": "14", "=SUMSQ(A1:A4)": "14",
"=SUMSQ(A1,B1,A2,B2,6)": "82", "=SUMSQ(A1,B1,A2,B2,6)": "82",
`=SUMSQ("",A1,B1,A2,B2,6)`: "82", "=SUMSQ(\"\",A1,B1,A2,B2,6)": "82",
`=SUMSQ(1,SUMSQ(1))`: "2", "=SUMSQ(1,SUMSQ(1))": "2",
"=SUMSQ(MUNIT(3))": "3", "=SUMSQ(\"1\",SUMSQ(1))": "2",
"=SUMSQ(MUNIT(3))": "3",
// SUMX2MY2 // SUMX2MY2
"=SUMX2MY2(A1:A4,B1:B4)": "-36", "=SUMX2MY2(A1:A4,B1:B4)": "-36",
// SUMX2PY2 // SUMX2PY2
@ -914,6 +918,7 @@ func TestCalcCellValue(t *testing.T) {
// AVERAGEA // AVERAGEA
"=AVERAGEA(INT(1))": "1", "=AVERAGEA(INT(1))": "1",
"=AVERAGEA(A1)": "1", "=AVERAGEA(A1)": "1",
"=AVERAGEA(\"1\")": "1",
"=AVERAGEA(A1:A2)": "1.5", "=AVERAGEA(A1:A2)": "1.5",
"=AVERAGEA(D2:F9)": "12671.375", "=AVERAGEA(D2:F9)": "12671.375",
// BETA.DIST // BETA.DIST
@ -1013,6 +1018,7 @@ func TestCalcCellValue(t *testing.T) {
"=COUNTA()": "0", "=COUNTA()": "0",
"=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8", "=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8",
"=COUNTA(COUNTA(1),MUNIT(1))": "2", "=COUNTA(COUNTA(1),MUNIT(1))": "2",
"=COUNTA(D1:D2)": "2",
// COUNTBLANK // COUNTBLANK
"=COUNTBLANK(MUNIT(1))": "0", "=COUNTBLANK(MUNIT(1))": "0",
"=COUNTBLANK(1)": "0", "=COUNTBLANK(1)": "0",
@ -1074,10 +1080,11 @@ func TestCalcCellValue(t *testing.T) {
"=GAMMALN.PRECISE(0.4)": "0.796677817701784", "=GAMMALN.PRECISE(0.4)": "0.796677817701784",
"=GAMMALN.PRECISE(4.5)": "2.45373657084244", "=GAMMALN.PRECISE(4.5)": "2.45373657084244",
// GAUSS // GAUSS
"=GAUSS(-5)": "-0.499999713348428", "=GAUSS(-5)": "-0.499999713348428",
"=GAUSS(0)": "0", "=GAUSS(0)": "0",
"=GAUSS(0.1)": "0.039827837277029", "=GAUSS(\"0\")": "0",
"=GAUSS(2.5)": "0.493790334674224", "=GAUSS(0.1)": "0.039827837277029",
"=GAUSS(2.5)": "0.493790334674224",
// GEOMEAN // GEOMEAN
"=GEOMEAN(2.5,3,0.5,1,3)": "1.6226711115996", "=GEOMEAN(2.5,3,0.5,1,3)": "1.6226711115996",
// HARMEAN // HARMEAN
@ -1373,6 +1380,7 @@ func TestCalcCellValue(t *testing.T) {
// ISEVEN // ISEVEN
"=ISEVEN(A1)": "FALSE", "=ISEVEN(A1)": "FALSE",
"=ISEVEN(A2)": "TRUE", "=ISEVEN(A2)": "TRUE",
"=ISEVEN(G1)": "TRUE",
// ISFORMULA // ISFORMULA
"=ISFORMULA(A1)": "FALSE", "=ISFORMULA(A1)": "FALSE",
"=ISFORMULA(\"A\")": "FALSE", "=ISFORMULA(\"A\")": "FALSE",
@ -1388,7 +1396,7 @@ func TestCalcCellValue(t *testing.T) {
"=ISNA(A1)": "FALSE", "=ISNA(A1)": "FALSE",
"=ISNA(NA())": "TRUE", "=ISNA(NA())": "TRUE",
// ISNONTEXT // ISNONTEXT
"=ISNONTEXT(A1)": "FALSE", "=ISNONTEXT(A1)": "TRUE",
"=ISNONTEXT(A5)": "TRUE", "=ISNONTEXT(A5)": "TRUE",
`=ISNONTEXT("Excelize")`: "FALSE", `=ISNONTEXT("Excelize")`: "FALSE",
"=ISNONTEXT(NA())": "TRUE", "=ISNONTEXT(NA())": "TRUE",
@ -1421,7 +1429,7 @@ func TestCalcCellValue(t *testing.T) {
// TYPE // TYPE
"=TYPE(2)": "1", "=TYPE(2)": "1",
"=TYPE(10/2)": "1", "=TYPE(10/2)": "1",
"=TYPE(C1)": "1", "=TYPE(C2)": "1",
"=TYPE(\"text\")": "2", "=TYPE(\"text\")": "2",
"=TYPE(TRUE)": "4", "=TYPE(TRUE)": "4",
"=TYPE(NA())": "16", "=TYPE(NA())": "16",
@ -1446,6 +1454,7 @@ func TestCalcCellValue(t *testing.T) {
"=IFERROR(1/2,0)": "0.5", "=IFERROR(1/2,0)": "0.5",
"=IFERROR(ISERROR(),0)": "0", "=IFERROR(ISERROR(),0)": "0",
"=IFERROR(1/0,0)": "0", "=IFERROR(1/0,0)": "0",
"=IFERROR(G1,2)": "0",
"=IFERROR(B2/MROUND(A2,1),0)": "2.5", "=IFERROR(B2/MROUND(A2,1),0)": "2.5",
// IFNA // IFNA
"=IFNA(1,\"not found\")": "1", "=IFNA(1,\"not found\")": "1",
@ -1787,16 +1796,17 @@ func TestCalcCellValue(t *testing.T) {
"=VALUE(\"01/02/2006 15:04:05\")": "38719.6278356481", "=VALUE(\"01/02/2006 15:04:05\")": "38719.6278356481",
// Conditional Functions // Conditional Functions
// IF // IF
"=IF(1=1)": "TRUE", "=IF(1=1)": "TRUE",
"=IF(1<>1)": "FALSE", "=IF(1<>1)": "FALSE",
"=IF(5<0, \"negative\", \"positive\")": "positive", "=IF(5<0, \"negative\", \"positive\")": "positive",
"=IF(-2<0, \"negative\", \"positive\")": "negative", "=IF(-2<0, \"negative\", \"positive\")": "negative",
`=IF(1=1, "equal", "notequal")`: "equal", "=IF(1=1, \"equal\", \"notequal\")": "equal",
`=IF(1<>1, "equal", "notequal")`: "notequal", "=IF(1<>1, \"equal\", \"notequal\")": "notequal",
`=IF("A"="A", "equal", "notequal")`: "equal", "=IF(\"A\"=\"A\", \"equal\", \"notequal\")": "equal",
`=IF("A"<>"A", "equal", "notequal")`: "notequal", "=IF(\"A\"<>\"A\", \"equal\", \"notequal\")": "notequal",
`=IF(FALSE,0,ROUND(4/2,0))`: "2", "=IF(FALSE,0,ROUND(4/2,0))": "2",
`=IF(TRUE,ROUND(4/2,0),0)`: "2", "=IF(TRUE,ROUND(4/2,0),0)": "2",
"=IF(A4>0.4,\"TRUE\",\"FALSE\")": "FALSE",
// Excel Lookup and Reference Functions // Excel Lookup and Reference Functions
// ADDRESS // ADDRESS
"=ADDRESS(1,1,1,TRUE)": "$A$1", "=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(INT(F2),F3:F9,1,TRUE)": "32080",
"=VLOOKUP(MUNIT(3),MUNIT(3),1)": "0", "=VLOOKUP(MUNIT(3),MUNIT(3),1)": "0",
"=VLOOKUP(A1,A3:B5,1)": "0", "=VLOOKUP(A1,A3:B5,1)": "0",
"=VLOOKUP(A1:A2,A1:A1,1)": "1",
"=VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1", "=VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
// INDEX // INDEX
"=INDEX(0,0,0)": "0", "=INDEX(0,0,0)": "0",
@ -2556,13 +2567,13 @@ func TestCalcCellValue(t *testing.T) {
"=MDETERM()": "MDETERM requires 1 argument", "=MDETERM()": "MDETERM requires 1 argument",
// MINVERSE // MINVERSE
"=MINVERSE()": "MINVERSE requires 1 argument", "=MINVERSE()": "MINVERSE requires 1 argument",
"=MINVERSE(B3:C4)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=MINVERSE(B3:C4)": "#VALUE!",
"=MINVERSE(A1:C2)": "#VALUE!", "=MINVERSE(A1:C2)": "#VALUE!",
"=MINVERSE(A4:A4)": "#NUM!", "=MINVERSE(A4:A4)": "#NUM!",
// MMULT // MMULT
"=MMULT()": "MMULT requires 2 argument", "=MMULT()": "MMULT requires 2 argument",
"=MMULT(A1:B2,B3:C4)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=MMULT(A1:B2,B3:C4)": "#VALUE!",
"=MMULT(B3:C4,A1:B2)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=MMULT(B3:C4,A1:B2)": "#VALUE!",
"=MMULT(A1:A2,B1:B2)": "#VALUE!", "=MMULT(A1:A2,B1:B2)": "#VALUE!",
// MOD // MOD
"=MOD()": "MOD requires 2 numeric arguments", "=MOD()": "MOD requires 2 numeric arguments",
@ -2593,7 +2604,8 @@ func TestCalcCellValue(t *testing.T) {
"=POWER(0,-1)": "#DIV/0!", "=POWER(0,-1)": "#DIV/0!",
"=POWER(1)": "POWER requires 2 numeric arguments", "=POWER(1)": "POWER requires 2 numeric arguments",
// PRODUCT // 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
`=QUOTIENT("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax", `=QUOTIENT("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
`=QUOTIENT(1,"X")`: "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,D1)": "#VALUE!",
"=SUMPRODUCT(A1:A3,D1:D3)": "#VALUE!", "=SUMPRODUCT(A1:A3,D1:D3)": "#VALUE!",
"=SUMPRODUCT(A1:A2,B1:B3)": "#VALUE!", "=SUMPRODUCT(A1:A2,B1:B3)": "#VALUE!",
"=SUMPRODUCT(\"\")": "#VALUE!",
"=SUMPRODUCT(A1,NA())": "#N/A", "=SUMPRODUCT(A1,NA())": "#N/A",
// SUMX2MY2 // SUMX2MY2
"=SUMX2MY2()": "SUMX2MY2 requires 2 arguments", "=SUMX2MY2()": "SUMX2MY2 requires 2 arguments",
@ -2922,6 +2935,7 @@ func TestCalcCellValue(t *testing.T) {
// FISHER // FISHER
"=FISHER()": "FISHER requires 1 numeric argument", "=FISHER()": "FISHER requires 1 numeric argument",
"=FISHER(2)": "#N/A", "=FISHER(2)": "#N/A",
"=FISHER(\"2\")": "#N/A",
"=FISHER(INT(-2)))": "#N/A", "=FISHER(INT(-2)))": "#N/A",
"=FISHER(F1)": "FISHER requires 1 numeric argument", "=FISHER(F1)": "FISHER requires 1 numeric argument",
// FISHERINV // FISHERINV
@ -2984,7 +2998,8 @@ func TestCalcCellValue(t *testing.T) {
// GEOMEAN // GEOMEAN
"=GEOMEAN()": "GEOMEAN requires at least 1 numeric argument", "=GEOMEAN()": "GEOMEAN requires at least 1 numeric argument",
"=GEOMEAN(0)": "#NUM!", "=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()": "HARMEAN requires at least 1 argument", "=HARMEAN()": "HARMEAN requires at least 1 argument",
"=HARMEAN(-1)": "#N/A", "=HARMEAN(-1)": "#N/A",
@ -3184,7 +3199,7 @@ func TestCalcCellValue(t *testing.T) {
// MEDIAN // MEDIAN
"=MEDIAN()": "MEDIAN requires at least 1 argument", "=MEDIAN()": "MEDIAN requires at least 1 argument",
"=MEDIAN(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", "=MEDIAN(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=MEDIAN(D1:D2)": "strconv.ParseFloat: parsing \"Month\": invalid syntax", "=MEDIAN(D1:D2)": "#NUM!",
// MIN // MIN
"=MIN()": "MIN requires at least 1 argument", "=MIN()": "MIN requires at least 1 argument",
"=MIN(NA())": "#N/A", "=MIN(NA())": "#N/A",
@ -3407,8 +3422,9 @@ func TestCalcCellValue(t *testing.T) {
// ISERROR // ISERROR
"=ISERROR()": "ISERROR requires 1 argument", "=ISERROR()": "ISERROR requires 1 argument",
// ISEVEN // ISEVEN
"=ISEVEN()": "ISEVEN requires 1 argument", "=ISEVEN()": "ISEVEN requires 1 argument",
`=ISEVEN("text")`: "strconv.Atoi: parsing \"text\": invalid syntax", "=ISEVEN(\"text\")": "#VALUE!",
"=ISEVEN(A1:A2)": "#VALUE!",
// ISFORMULA // ISFORMULA
"=ISFORMULA()": "ISFORMULA requires 1 argument", "=ISFORMULA()": "ISFORMULA requires 1 argument",
// ISLOGICAL // ISLOGICAL
@ -3420,8 +3436,8 @@ func TestCalcCellValue(t *testing.T) {
// ISNUMBER // ISNUMBER
"=ISNUMBER()": "ISNUMBER requires 1 argument", "=ISNUMBER()": "ISNUMBER requires 1 argument",
// ISODD // ISODD
"=ISODD()": "ISODD requires 1 argument", "=ISODD()": "ISODD requires 1 argument",
`=ISODD("text")`: "strconv.Atoi: parsing \"text\": invalid syntax", "=ISODD(\"text\")": "#VALUE!",
// ISREF // ISREF
"=ISREF()": "ISREF requires 1 argument", "=ISREF()": "ISREF requires 1 argument",
// ISTEXT // ISTEXT
@ -3717,7 +3733,7 @@ func TestCalcCellValue(t *testing.T) {
"=SUBSTITUTE(\"\",\"\",\"\",0)": "instance_num should be > 0", "=SUBSTITUTE(\"\",\"\",\"\",0)": "instance_num should be > 0",
// TEXTJOIN // TEXTJOIN
"=TEXTJOIN()": "TEXTJOIN requires at least 3 arguments", "=TEXTJOIN()": "TEXTJOIN requires at least 3 arguments",
"=TEXTJOIN(\"\",\"\",1)": "strconv.ParseBool: parsing \"\": invalid syntax", "=TEXTJOIN(\"\",\"\",1)": "#VALUE!",
"=TEXTJOIN(\"\",TRUE,NA())": "#N/A", "=TEXTJOIN(\"\",TRUE,NA())": "#N/A",
"=TEXTJOIN(\"\",TRUE," + strings.Repeat("0,", 250) + ",0)": "TEXTJOIN accepts at most 252 arguments", "=TEXTJOIN(\"\",TRUE," + strings.Repeat("0,", 250) + ",0)": "TEXTJOIN accepts at most 252 arguments",
"=TEXTJOIN(\",\",FALSE,REPT(\"*\",32768))": "TEXTJOIN function exceeds 32767 characters", "=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,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,FALSE,FALSE)": "VLOOKUP requires numeric col argument",
"=VLOOKUP(D2,D:D,1,FALSE,FALSE)": "VLOOKUP requires at most 4 arguments", "=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,D10:D10,1,FALSE)": "VLOOKUP no result found",
"=VLOOKUP(D2,D:D,2,FALSE)": "VLOOKUP has invalid column index", "=VLOOKUP(D2,D:D,2,FALSE)": "VLOOKUP has invalid column index",
"=VLOOKUP(D2,C:C,1,FALSE)": "VLOOKUP no result found", "=VLOOKUP(D2,C:C,1,FALSE)": "VLOOKUP no result found",
@ -4455,7 +4470,7 @@ func TestCalcISBLANK(t *testing.T) {
}) })
fn := formulaFuncs{} fn := formulaFuncs{}
result := fn.ISBLANK(argsList) result := fn.ISBLANK(argsList)
assert.Equal(t, result.String, "TRUE") assert.Equal(t, "TRUE", result.Value())
assert.Empty(t, result.Error) assert.Empty(t, result.Error)
} }
@ -4520,6 +4535,7 @@ func TestCalcMatchPattern(t *testing.T) {
assert.True(t, matchPattern("", "")) assert.True(t, matchPattern("", ""))
assert.True(t, matchPattern("file/*", "file/abc/bcd/def")) assert.True(t, matchPattern("file/*", "file/abc/bcd/def"))
assert.True(t, matchPattern("*", "")) assert.True(t, matchPattern("*", ""))
assert.False(t, matchPattern("?", ""))
assert.False(t, matchPattern("file/?", "file/abc/bcd/def")) assert.False(t, matchPattern("file/?", "file/abc/bcd/def"))
} }
@ -4574,15 +4590,14 @@ func TestCalcVLOOKUP(t *testing.T) {
} }
func TestCalcBoolean(t *testing.T) { func TestCalcBoolean(t *testing.T) {
cellData := [][]interface{}{ cellData := [][]interface{}{{0.5, "TRUE", -0.5, "FALSE", true}}
{0.5, "TRUE", -0.5, "FALSE"},
}
f := prepareCalcData(cellData) f := prepareCalcData(cellData)
formulaList := map[string]string{ formulaList := map[string]string{
"=AVERAGEA(A1:C1)": "0.333333333333333", "=AVERAGEA(A1:C1)": "0.333333333333333",
"=MAX(0.5,B1)": "0.5", "=MAX(0.5,B1)": "0.5",
"=MAX(A1: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", "=MAXA(0.5,B1)": "1",
"=MIN(-0.5,D1)": "-0.5", "=MIN(-0.5,D1)": "-0.5",
"=MIN(C1: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) { func TestCalcAVERAGEIF(t *testing.T) {
f := prepareCalcData([][]interface{}{ f := prepareCalcData([][]interface{}{
{"Monday", 500}, {"Monday", 500},
@ -4822,28 +4854,29 @@ func TestCalcGROWTHandTREND(t *testing.T) {
calcError := map[string]string{ calcError := map[string]string{
"=GROWTH()": "GROWTH requires at least 1 argument", "=GROWTH()": "GROWTH requires at least 1 argument",
"=GROWTH(B2:B5,A2:A5,A8:A10,TRUE,0)": "GROWTH allows at most 4 arguments", "=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(A1:B1,A2:A5,A8:A10,TRUE)": "#VALUE!",
"=GROWTH(B2:B5,A1:B1,A8:A10,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax", "=GROWTH(B2:B5,A1:B1,A8:A10,TRUE)": "#VALUE!",
"=GROWTH(B2:B5,A2:A5,A1:B1,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax", "=GROWTH(B2:B5,A2:A5,A1:B1,TRUE)": "#VALUE!",
"=GROWTH(B2:B5,A2:A5,A8:A10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax", "=GROWTH(B2:B5,A2:A5,A8:A10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=GROWTH(A2:B3,A4:B4)": "#REF!", "=GROWTH(A2:B3,A4:B4)": "#REF!",
"=GROWTH(A4:B4,A2:A2)": "#REF!", "=GROWTH(A4:B4,A2:A2)": "#REF!",
"=GROWTH(A2:A2,A4:A5)": "#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(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()": "TREND requires at least 1 argument",
"=TREND(B2:B5,A2:A5,A8:A10,TRUE,0)": "TREND allows at most 4 arguments", "=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(A1:B1,A2:A5,A8:A10,TRUE)": "#VALUE!",
"=TREND(B2:B5,A1:B1,A8:A10,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax", "=TREND(B2:B5,A1:B1,A8:A10,TRUE)": "#VALUE!",
"=TREND(B2:B5,A2:A5,A1:B1,TRUE)": "strconv.ParseFloat: parsing \"known_x's\": invalid syntax", "=TREND(B2:B5,A2:A5,A1:B1,TRUE)": "#VALUE!",
"=TREND(B2:B5,A2:A5,A8:A10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax", "=TREND(B2:B5,A2:A5,A8:A10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=TREND(A2:B3,A4:B4)": "#REF!", "=TREND(A2:B3,A4:B4)": "#REF!",
"=TREND(A4:B4,A2:A2)": "#REF!", "=TREND(A4:B4,A2:A2)": "#REF!",
"=TREND(A2:A2,A4:A5)": "#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(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 { for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
@ -5586,8 +5619,8 @@ func TestCalcTTEST(t *testing.T) {
"=TTEST()": "TTEST requires 4 arguments", "=TTEST()": "TTEST requires 4 arguments",
"=TTEST(\"\",B1:B12,1,1)": "#NUM!", "=TTEST(\"\",B1:B12,1,1)": "#NUM!",
"=TTEST(A1:A12,\"\",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)": "#VALUE!",
"=TTEST(A1:A12,B1:B12,1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", "=TTEST(A1:A12,B1:B12,1,\"\")": "#VALUE!",
"=TTEST(A1:A12,B1:B12,0,1)": "#NUM!", "=TTEST(A1:A12,B1:B12,0,1)": "#NUM!",
"=TTEST(A1:A12,B1:B12,1,0)": "#NUM!", "=TTEST(A1:A12,B1:B12,1,0)": "#NUM!",
"=TTEST(A1:A2,B1:B1,1,1)": "#N/A", "=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()": "T.TEST requires 4 arguments",
"=T.TEST(\"\",B1:B12,1,1)": "#NUM!", "=T.TEST(\"\",B1:B12,1,1)": "#NUM!",
"=T.TEST(A1:A12,\"\",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)": "#VALUE!",
"=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,0,1)": "#NUM!", "=T.TEST(A1:A12,B1:B12,0,1)": "#NUM!",
"=T.TEST(A1:A12,B1:B12,1,0)": "#NUM!", "=T.TEST(A1:A12,B1:B12,1,0)": "#NUM!",
"=T.TEST(A1:A2,B1:B1,1,1)": "#N/A", "=T.TEST(A1:A2,B1:B1,1,1)": "#N/A",
@ -5618,8 +5651,8 @@ func TestCalcTTEST(t *testing.T) {
func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) { func TestCalcNETWORKDAYSandWORKDAY(t *testing.T) {
cellData := [][]interface{}{ cellData := [][]interface{}{
{"05/01/2019", 43586}, {"05/01/2019", 43586, "text1"},
{"09/13/2019", 43721}, {"09/13/2019", 43721, "text2"},
{"10/01/2019", 43739}, {"10/01/2019", 43739},
{"12/25/2019", 43824}, {"12/25/2019", 43824},
{"01/01/2020", 43831}, {"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\",17)": "219",
"=NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,A1:A12)": "178", "=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,B1:B12)": "178",
"=NETWORKDAYS.INTL(\"01/01/2020\",\"09/12/2020\",1,C1:C2)": "183",
"=WORKDAY(\"12/01/2015\",25)": "42374", "=WORKDAY(\"12/01/2015\",25)": "42374",
"=WORKDAY(\"01/01/2020\",123,B1:B12)": "44006", "=WORKDAY(\"01/01/2020\",123,B1:B12)": "44006",
"=WORKDAY.INTL(\"12/01/2015\",0)": "42339", "=WORKDAY.INTL(\"12/01/2015\",0)": "42339",
@ -5813,3 +5847,27 @@ func TestNestedFunctionsWithOperators(t *testing.T) {
assert.Equal(t, expected, result, formula) 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,8 +519,12 @@ func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
// string. // string.
func (c *xlsxC) setCellDefault(value string) { func (c *xlsxC) setCellDefault(value string) {
if ok, _, _ := isNumeric(value); !ok { if ok, _, _ := isNumeric(value); !ok {
c.setInlineStr(value) if value != "" {
c.IS.T.Val = value c.setInlineStr(value)
c.IS.T.Val = value
return
}
c.T, c.V, c.IS = value, value, nil
return return
} }
c.V = value c.V = value

View File

@ -253,7 +253,8 @@ func TestOpenReader(t *testing.T) {
for _, defaultXMLPath := range []string{ for _, defaultXMLPath := range []string{
defaultXMLPathCalcChain, defaultXMLPathCalcChain,
defaultXMLPathStyles, defaultXMLPathStyles,
defaultXMLPathWorkbookRels} { defaultXMLPathWorkbookRels,
} {
_, err = OpenReader(preset(defaultXMLPath)) _, err = OpenReader(preset(defaultXMLPath))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") 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/stretchr/testify v1.8.0
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 github.com/xuri/efp v0.0.0-20220603152613-6918739fd470
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 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/image v0.0.0-20220902085622-e7cb96979f69
golang.org/x/net v0.4.0 golang.org/x/net v0.5.0
golang.org/x/text v0.5.0 golang.org/x/text v0.6.0
) )
require github.com/richardlehane/msoleps v1.0.3 // indirect 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= 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-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.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.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= 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 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= 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/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-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-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.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.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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/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= 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-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-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.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-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.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.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.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.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.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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-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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=