forked from p30928647/excelize
This closes #1438, fix cell data type issue for formula calculation engine
- Update dependencies module - Update unit tests
This commit is contained in:
parent
9c3a5eb983
commit
5429f131f8
437
calc.go
437
calc.go
|
@ -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
|
||||||
|
|
172
calc_test.go
172
calc_test.go
|
@ -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))
|
||||||
|
}
|
||||||
|
|
8
cell.go
8
cell.go
|
@ -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
|
||||||
|
|
|
@ -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
6
go.mod
|
@ -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
17
go.sum
|
@ -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=
|
||||||
|
|
Loading…
Reference in New Issue