Support 5 new kinds of conditional formatting types

- New conditional formatting types: text, blanks, no blanks, errors, and no errors
- Support calculate formula with multiple dash arithmetic symbol
- Fix empty calculate result with numeric arguments in LEN, LOWER, PROPER, REPT, UPPER, and IF formula functions
- Uniform double quote in calculation unit tests
- Update unit tests
This commit is contained in:
xuri 2023-11-13 00:16:29 +08:00
parent c7acf4fafe
commit 2499bf6b5b
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
5 changed files with 426 additions and 238 deletions

45
calc.go
View File

@ -844,7 +844,7 @@ func (f *File) calcCellValue(ctx *calcContext, sheet, cell string) (result formu
ps := efp.ExcelParser()
tokens := ps.Parse(formula)
if tokens == nil {
return
return f.cellResolver(ctx, sheet, cell)
}
result, err = f.evalInfixExp(ctx, sheet, cell, tokens)
return
@ -1225,6 +1225,12 @@ func calcAdd(rOpd, lOpd formulaArg, opdStack *Stack) error {
// calcSubtract evaluate subtraction arithmetic operations.
func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Value() == "" {
rOpd = newNumberFormulaArg(0)
}
if lOpd.Value() == "" {
lOpd = newNumberFormulaArg(0)
}
lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value())
@ -1300,22 +1306,27 @@ func calculate(opdStack *Stack, opt efp.Token) error {
">=": calcGe,
"&": calcSplice,
}
fn, ok := tokenCalcFunc[opt.TValue]
if ok {
if fn, ok := tokenCalcFunc[opt.TValue]; ok {
if opdStack.Len() < 2 {
return ErrInvalidFormula
}
rOpd := opdStack.Pop().(formulaArg)
lOpd := opdStack.Pop().(formulaArg)
if opt.TValue != "&" {
if rOpd.Value() == "" {
rOpd = newNumberFormulaArg(0)
}
if lOpd.Value() == "" {
lOpd = newNumberFormulaArg(0)
}
}
if rOpd.Type == ArgError {
return errors.New(rOpd.Value())
}
if lOpd.Type == ArgError {
return errors.New(lOpd.Value())
}
if err := fn(rOpd, lOpd, opdStack); err != nil {
return err
}
return fn(rOpd, lOpd, opdStack)
}
return nil
}
@ -1329,6 +1340,10 @@ func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Tok
tokenPriority := getPriority(token)
topOpt := optStack.Peek().(efp.Token)
topOptPriority := getPriority(topOpt)
if topOpt.TValue == "-" && topOpt.TType == efp.TokenTypeOperatorPrefix && token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
optStack.Pop()
return
}
if tokenPriority > topOptPriority {
optStack.Push(token)
return
@ -13757,7 +13772,7 @@ func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LEN requires 1 string argument")
}
return newNumberFormulaArg(float64(utf8.RuneCountInString(argsList.Front().Value.(formulaArg).String)))
return newNumberFormulaArg(float64(utf8.RuneCountInString(argsList.Front().Value.(formulaArg).Value())))
}
// LENB returns the number of bytes used to represent the characters in a text
@ -13790,7 +13805,7 @@ func (fn *formulaFuncs) LOWER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LOWER requires 1 argument")
}
return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).String))
return newStringFormulaArg(strings.ToLower(argsList.Front().Value.(formulaArg).Value()))
}
// MID function returns a specified number of characters from the middle of a
@ -13881,7 +13896,7 @@ func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg {
}
buf := bytes.Buffer{}
isLetter := false
for _, char := range argsList.Front().Value.(formulaArg).String {
for _, char := range argsList.Front().Value.(formulaArg).Value() {
if !isLetter && unicode.IsLetter(char) {
buf.WriteRune(unicode.ToUpper(char))
} else {
@ -13962,7 +13977,7 @@ func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg {
}
buf := bytes.Buffer{}
for i := 0; i < int(times.Number); i++ {
buf.WriteString(text.String)
buf.WriteString(text.Value())
}
return newStringFormulaArg(buf.String())
}
@ -14327,7 +14342,7 @@ func (fn *formulaFuncs) UPPER(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "UPPER requires 1 argument")
}
return newStringFormulaArg(strings.ToUpper(argsList.Front().Value.(formulaArg).String))
return newStringFormulaArg(strings.ToUpper(argsList.Front().Value.(formulaArg).Value()))
}
// VALUE function converts a text string into a numeric value. The syntax of
@ -14405,7 +14420,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
)
switch token.Type {
case ArgString:
if cond, err = strconv.ParseBool(token.String); err != nil {
if cond, err = strconv.ParseBool(token.Value()); err != nil {
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
}
case ArgNumber:
@ -14421,7 +14436,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
case ArgNumber:
result = value.ToNumber()
default:
result = newStringFormulaArg(value.String)
result = newStringFormulaArg(value.Value())
}
return result
}
@ -14431,7 +14446,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
case ArgNumber:
result = value.ToNumber()
default:
result = newStringFormulaArg(value.String)
result = newStringFormulaArg(value.Value())
}
}
return result
@ -14582,7 +14597,7 @@ func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte
}
return criteriaG
case ArgString:
ls, rs := lhs.String, rhs.String
ls, rs := lhs.Value(), rhs.Value()
if !caseSensitive {
ls, rs = strings.ToLower(ls), strings.ToLower(rs)
}

View File

@ -58,12 +58,22 @@ func TestCalcCellValue(t *testing.T) {
"=1>=\"-1\"": "FALSE",
"=\"-1\">=-1": "TRUE",
"=\"-1\">=\"-2\"": "FALSE",
"=-----1+1": "0",
"=------1+1": "2",
"=---1---1": "-2",
"=---1----1": "0",
"=1&2": "12",
"=15%": "0.15",
"=1+20%": "1.2",
"={1}+2": "3",
"=1+{2}": "3",
"={1}+{2}": "3",
"=A1+(B1-C1)": "5",
"=A1+(C1-B1)": "-3",
"=A1&B1&C1": "14",
"=B1+C1": "4",
"=C1+B1": "4",
"=C1+C1": "0",
"=\"A\"=\"A\"": "TRUE",
"=\"A\"<>\"A\"": "FALSE",
"=TRUE()&FALSE()": "TRUEFALSE",
@ -1760,10 +1770,12 @@ func TestCalcCellValue(t *testing.T) {
"=LEFTB(\"Original Text\",13)": "Original Text",
"=LEFTB(\"Original Text\",20)": "Original Text",
// LEN
"=LEN(\"\")": "0",
"=LEN(D1)": "5",
"=LEN(\"テキスト\")": "4",
"=LEN(\"オリジナルテキスト\")": "9",
"=LEN(\"\")": "0",
"=LEN(D1)": "5",
"=LEN(\"テキスト\")": "4",
"=LEN(\"オリジナルテキスト\")": "9",
"=LEN(7+LEN(A1&B1&C1))": "1",
"=LEN(8+LEN(A1+(C1-B1)))": "2",
// LENB
"=LENB(\"\")": "0",
"=LENB(D1)": "5",
@ -2546,146 +2558,146 @@ func TestCalcCellValue(t *testing.T) {
"=_xlfn.ARABIC()": {"#VALUE!", "ARABIC requires 1 numeric argument"},
"=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": {"#VALUE!", "#VALUE!"},
// ASIN
"=ASIN()": {"#VALUE!", "ASIN requires 1 numeric argument"},
`=ASIN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ASIN()": {"#VALUE!", "ASIN requires 1 numeric argument"},
"=ASIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ASINH
"=ASINH()": {"#VALUE!", "ASINH requires 1 numeric argument"},
`=ASINH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ASINH()": {"#VALUE!", "ASINH requires 1 numeric argument"},
"=ASINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATAN
"=ATAN()": {"#VALUE!", "ATAN requires 1 numeric argument"},
`=ATAN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ATAN()": {"#VALUE!", "ATAN requires 1 numeric argument"},
"=ATAN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATANH
"=ATANH()": {"#VALUE!", "ATANH requires 1 numeric argument"},
`=ATANH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ATANH()": {"#VALUE!", "ATANH requires 1 numeric argument"},
"=ATANH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATAN2
"=ATAN2()": {"#VALUE!", "ATAN2 requires 2 numeric arguments"},
`=ATAN2("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ATAN2(0,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ATAN2()": {"#VALUE!", "ATAN2 requires 2 numeric arguments"},
"=ATAN2(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ATAN2(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// BASE
"=BASE()": {"#VALUE!", "BASE requires at least 2 arguments"},
"=BASE(1,2,3,4)": {"#VALUE!", "BASE allows at most 3 arguments"},
"=BASE(1,1)": {"#VALUE!", "radix must be an integer >= 2 and <= 36"},
`=BASE("X",2)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=BASE(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=BASE(1,2,"X")`: {"#VALUE!", "strconv.Atoi: parsing \"X\": invalid syntax"},
"=BASE()": {"#VALUE!", "BASE requires at least 2 arguments"},
"=BASE(1,2,3,4)": {"#VALUE!", "BASE allows at most 3 arguments"},
"=BASE(1,1)": {"#VALUE!", "radix must be an integer >= 2 and <= 36"},
"=BASE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=BASE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=BASE(1,2,\"X\")": {"#VALUE!", "strconv.Atoi: parsing \"X\": invalid syntax"},
// CEILING
"=CEILING()": {"#VALUE!", "CEILING requires at least 1 argument"},
"=CEILING(1,2,3)": {"#VALUE!", "CEILING allows at most 2 arguments"},
"=CEILING(1,-1)": {"#VALUE!", "negative sig to CEILING invalid"},
`=CEILING("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=CEILING(0,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=CEILING()": {"#VALUE!", "CEILING requires at least 1 argument"},
"=CEILING(1,2,3)": {"#VALUE!", "CEILING allows at most 2 arguments"},
"=CEILING(1,-1)": {"#VALUE!", "negative sig to CEILING invalid"},
"=CEILING(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=CEILING(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.CEILING.MATH
"=_xlfn.CEILING.MATH()": {"#VALUE!", "CEILING.MATH requires at least 1 argument"},
"=_xlfn.CEILING.MATH(1,2,3,4)": {"#VALUE!", "CEILING.MATH allows at most 3 arguments"},
`=_xlfn.CEILING.MATH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.CEILING.MATH(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.CEILING.MATH(1,2,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CEILING.MATH()": {"#VALUE!", "CEILING.MATH requires at least 1 argument"},
"=_xlfn.CEILING.MATH(1,2,3,4)": {"#VALUE!", "CEILING.MATH allows at most 3 arguments"},
"=_xlfn.CEILING.MATH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CEILING.MATH(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CEILING.MATH(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.CEILING.PRECISE
"=_xlfn.CEILING.PRECISE()": {"#VALUE!", "CEILING.PRECISE requires at least 1 argument"},
"=_xlfn.CEILING.PRECISE(1,2,3)": {"#VALUE!", "CEILING.PRECISE allows at most 2 arguments"},
`=_xlfn.CEILING.PRECISE("X",2)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.CEILING.PRECISE(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CEILING.PRECISE()": {"#VALUE!", "CEILING.PRECISE requires at least 1 argument"},
"=_xlfn.CEILING.PRECISE(1,2,3)": {"#VALUE!", "CEILING.PRECISE allows at most 2 arguments"},
"=_xlfn.CEILING.PRECISE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CEILING.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COMBIN
"=COMBIN()": {"#VALUE!", "COMBIN requires 2 argument"},
"=COMBIN(-1,1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
`=COMBIN("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=COMBIN(-1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COMBIN()": {"#VALUE!", "COMBIN requires 2 argument"},
"=COMBIN(-1,1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
"=COMBIN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COMBIN(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.COMBINA
"=_xlfn.COMBINA()": {"#VALUE!", "COMBINA requires 2 argument"},
"=_xlfn.COMBINA(-1,1)": {"#VALUE!", "COMBINA requires number > number_chosen"},
"=_xlfn.COMBINA(-1,-1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
`=_xlfn.COMBINA("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.COMBINA(-1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.COMBINA()": {"#VALUE!", "COMBINA requires 2 argument"},
"=_xlfn.COMBINA(-1,1)": {"#VALUE!", "COMBINA requires number > number_chosen"},
"=_xlfn.COMBINA(-1,-1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
"=_xlfn.COMBINA(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.COMBINA(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COS
"=COS()": {"#VALUE!", "COS requires 1 numeric argument"},
`=COS("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COS()": {"#VALUE!", "COS requires 1 numeric argument"},
"=COS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COSH
"=COSH()": {"#VALUE!", "COSH requires 1 numeric argument"},
`=COSH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COSH()": {"#VALUE!", "COSH requires 1 numeric argument"},
"=COSH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.COT
"=COT()": {"#VALUE!", "COT requires 1 numeric argument"},
`=COT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COT(0)": {"#DIV/0!", "#DIV/0!"},
"=COT()": {"#VALUE!", "COT requires 1 numeric argument"},
"=COT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COT(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.COTH
"=COTH()": {"#VALUE!", "COTH requires 1 numeric argument"},
`=COTH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COTH(0)": {"#DIV/0!", "#DIV/0!"},
"=COTH()": {"#VALUE!", "COTH requires 1 numeric argument"},
"=COTH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COTH(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.CSC
"=_xlfn.CSC()": {"#VALUE!", "CSC requires 1 numeric argument"},
`=_xlfn.CSC("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CSC(0)": {"#DIV/0!", "#DIV/0!"},
"=_xlfn.CSC()": {"#VALUE!", "CSC requires 1 numeric argument"},
"=_xlfn.CSC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CSC(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.CSCH
"=_xlfn.CSCH()": {"#VALUE!", "CSCH requires 1 numeric argument"},
`=_xlfn.CSCH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CSCH(0)": {"#DIV/0!", "#DIV/0!"},
"=_xlfn.CSCH()": {"#VALUE!", "CSCH requires 1 numeric argument"},
"=_xlfn.CSCH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CSCH(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.DECIMAL
"=_xlfn.DECIMAL()": {"#VALUE!", "DECIMAL requires 2 numeric arguments"},
`=_xlfn.DECIMAL("X",2)`: {"#VALUE!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
`=_xlfn.DECIMAL(2000,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.DECIMAL()": {"#VALUE!", "DECIMAL requires 2 numeric arguments"},
"=_xlfn.DECIMAL(\"X\",2)": {"#VALUE!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
"=_xlfn.DECIMAL(2000,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// DEGREES
"=DEGREES()": {"#VALUE!", "DEGREES requires 1 numeric argument"},
`=DEGREES("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=DEGREES(0)": {"#DIV/0!", "#DIV/0!"},
"=DEGREES()": {"#VALUE!", "DEGREES requires 1 numeric argument"},
"=DEGREES(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=DEGREES(0)": {"#DIV/0!", "#DIV/0!"},
// EVEN
"=EVEN()": {"#VALUE!", "EVEN requires 1 numeric argument"},
`=EVEN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=EVEN()": {"#VALUE!", "EVEN requires 1 numeric argument"},
"=EVEN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// EXP
"=EXP()": {"#VALUE!", "EXP requires 1 numeric argument"},
`=EXP("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=EXP()": {"#VALUE!", "EXP requires 1 numeric argument"},
"=EXP(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// FACT
"=FACT()": {"#VALUE!", "FACT requires 1 numeric argument"},
`=FACT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FACT(-1)": {"#NUM!", "#NUM!"},
"=FACT()": {"#VALUE!", "FACT requires 1 numeric argument"},
"=FACT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FACT(-1)": {"#NUM!", "#NUM!"},
// FACTDOUBLE
"=FACTDOUBLE()": {"#VALUE!", "FACTDOUBLE requires 1 numeric argument"},
`=FACTDOUBLE("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FACTDOUBLE(-1)": {"#NUM!", "#NUM!"},
"=FACTDOUBLE()": {"#VALUE!", "FACTDOUBLE requires 1 numeric argument"},
"=FACTDOUBLE(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FACTDOUBLE(-1)": {"#NUM!", "#NUM!"},
// FLOOR
"=FLOOR()": {"#VALUE!", "FLOOR requires 2 numeric arguments"},
`=FLOOR("X",-1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=FLOOR(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FLOOR(1,-1)": {"#NUM!", "invalid arguments to FLOOR"},
"=FLOOR()": {"#VALUE!", "FLOOR requires 2 numeric arguments"},
"=FLOOR(\"X\",-1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FLOOR(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FLOOR(1,-1)": {"#NUM!", "invalid arguments to FLOOR"},
// _xlfn.FLOOR.MATH
"=_xlfn.FLOOR.MATH()": {"#VALUE!", "FLOOR.MATH requires at least 1 argument"},
"=_xlfn.FLOOR.MATH(1,2,3,4)": {"#VALUE!", "FLOOR.MATH allows at most 3 arguments"},
`=_xlfn.FLOOR.MATH("X",2,3)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.FLOOR.MATH(1,"X",3)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.FLOOR.MATH(1,2,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.FLOOR.MATH()": {"#VALUE!", "FLOOR.MATH requires at least 1 argument"},
"=_xlfn.FLOOR.MATH(1,2,3,4)": {"#VALUE!", "FLOOR.MATH allows at most 3 arguments"},
"=_xlfn.FLOOR.MATH(\"X\",2,3)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.FLOOR.MATH(1,\"X\",3)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.FLOOR.MATH(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.FLOOR.PRECISE
"=_xlfn.FLOOR.PRECISE()": {"#VALUE!", "FLOOR.PRECISE requires at least 1 argument"},
"=_xlfn.FLOOR.PRECISE(1,2,3)": {"#VALUE!", "FLOOR.PRECISE allows at most 2 arguments"},
`=_xlfn.FLOOR.PRECISE("X",2)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.FLOOR.PRECISE(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.FLOOR.PRECISE()": {"#VALUE!", "FLOOR.PRECISE requires at least 1 argument"},
"=_xlfn.FLOOR.PRECISE(1,2,3)": {"#VALUE!", "FLOOR.PRECISE allows at most 2 arguments"},
"=_xlfn.FLOOR.PRECISE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.FLOOR.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// GCD
"=GCD()": {"#VALUE!", "GCD requires at least 1 argument"},
"=GCD(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"=GCD(-1)": {"#VALUE!", "GCD only accepts positive arguments"},
"=GCD(1,-1)": {"#VALUE!", "GCD only accepts positive arguments"},
`=GCD("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=GCD()": {"#VALUE!", "GCD requires at least 1 argument"},
"=GCD(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"=GCD(-1)": {"#VALUE!", "GCD only accepts positive arguments"},
"=GCD(1,-1)": {"#VALUE!", "GCD only accepts positive arguments"},
"=GCD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// INT
"=INT()": {"#VALUE!", "INT requires 1 numeric argument"},
`=INT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=INT()": {"#VALUE!", "INT requires 1 numeric argument"},
"=INT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ISO.CEILING
"=ISO.CEILING()": {"#VALUE!", "ISO.CEILING requires at least 1 argument"},
"=ISO.CEILING(1,2,3)": {"#VALUE!", "ISO.CEILING allows at most 2 arguments"},
`=ISO.CEILING("X",2)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ISO.CEILING(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ISO.CEILING()": {"#VALUE!", "ISO.CEILING requires at least 1 argument"},
"=ISO.CEILING(1,2,3)": {"#VALUE!", "ISO.CEILING allows at most 2 arguments"},
"=ISO.CEILING(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ISO.CEILING(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LCM
"=LCM()": {"#VALUE!", "LCM requires at least 1 argument"},
"=LCM(-1)": {"#VALUE!", "LCM only accepts positive arguments"},
"=LCM(1,-1)": {"#VALUE!", "LCM only accepts positive arguments"},
`=LCM("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=LCM()": {"#VALUE!", "LCM requires at least 1 argument"},
"=LCM(-1)": {"#VALUE!", "LCM only accepts positive arguments"},
"=LCM(1,-1)": {"#VALUE!", "LCM only accepts positive arguments"},
"=LCM(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LN
"=LN()": {"#VALUE!", "LN requires 1 numeric argument"},
"=LN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LOG
"=LOG()": {"#VALUE!", "LOG requires at least 1 argument"},
"=LOG(1,2,3)": {"#VALUE!", "LOG allows at most 2 arguments"},
`=LOG("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=LOG(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=LOG(0,0)": {"#NUM!", "#DIV/0!"},
"=LOG(1,0)": {"#NUM!", "#DIV/0!"},
"=LOG(1,1)": {"#DIV/0!", "#DIV/0!"},
"=LOG()": {"#VALUE!", "LOG requires at least 1 argument"},
"=LOG(1,2,3)": {"#VALUE!", "LOG allows at most 2 arguments"},
"=LOG(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=LOG(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=LOG(0,0)": {"#NUM!", "#DIV/0!"},
"=LOG(1,0)": {"#NUM!", "#DIV/0!"},
"=LOG(1,1)": {"#DIV/0!", "#DIV/0!"},
// LOG10
"=LOG10()": {"#VALUE!", "LOG10 requires 1 numeric argument"},
"=LOG10(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
@ -2702,51 +2714,51 @@ func TestCalcCellValue(t *testing.T) {
"=MMULT(B3:C4,A1:B2)": {"#VALUE!", "#VALUE!"},
"=MMULT(A1:A2,B1:B2)": {"#VALUE!", "#VALUE!"},
// MOD
"=MOD()": {"#VALUE!", "MOD requires 2 numeric arguments"},
"=MOD(6,0)": {"#DIV/0!", "MOD divide by zero"},
`=MOD("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=MOD(6,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=MOD()": {"#VALUE!", "MOD requires 2 numeric arguments"},
"=MOD(6,0)": {"#DIV/0!", "MOD divide by zero"},
"=MOD(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=MOD(6,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MROUND
"=MROUND()": {"#VALUE!", "MROUND requires 2 numeric arguments"},
"=MROUND(1,0)": {"#NUM!", "#NUM!"},
"=MROUND(1,-1)": {"#NUM!", "#NUM!"},
`=MROUND("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=MROUND(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=MROUND()": {"#VALUE!", "MROUND requires 2 numeric arguments"},
"=MROUND(1,0)": {"#NUM!", "#NUM!"},
"=MROUND(1,-1)": {"#NUM!", "#NUM!"},
"=MROUND(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=MROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MULTINOMIAL
`=MULTINOMIAL("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=MULTINOMIAL(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.MUNIT
"=_xlfn.MUNIT()": {"#VALUE!", "MUNIT requires 1 numeric argument"},
`=_xlfn.MUNIT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.MUNIT(-1)": {"#VALUE!", ""},
"=_xlfn.MUNIT()": {"#VALUE!", "MUNIT requires 1 numeric argument"},
"=_xlfn.MUNIT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.MUNIT(-1)": {"#VALUE!", ""},
// ODD
"=ODD()": {"#VALUE!", "ODD requires 1 numeric argument"},
`=ODD("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ODD()": {"#VALUE!", "ODD requires 1 numeric argument"},
"=ODD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// PI
"=PI(1)": {"#VALUE!", "PI accepts no arguments"},
// POWER
`=POWER("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=POWER(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=POWER(0,0)": {"#NUM!", "#NUM!"},
"=POWER(0,-1)": {"#DIV/0!", "#DIV/0!"},
"=POWER(1)": {"#VALUE!", "POWER requires 2 numeric arguments"},
"=POWER(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=POWER(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=POWER(0,0)": {"#NUM!", "#NUM!"},
"=POWER(0,-1)": {"#DIV/0!", "#DIV/0!"},
"=POWER(1)": {"#VALUE!", "POWER requires 2 numeric arguments"},
// PRODUCT
"=PRODUCT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=PRODUCT(\"\",3,6)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// QUOTIENT
`=QUOTIENT("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=QUOTIENT(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=QUOTIENT(1,0)": {"#DIV/0!", "#DIV/0!"},
"=QUOTIENT(1)": {"#VALUE!", "QUOTIENT requires 2 numeric arguments"},
"=QUOTIENT(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=QUOTIENT(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=QUOTIENT(1,0)": {"#DIV/0!", "#DIV/0!"},
"=QUOTIENT(1)": {"#VALUE!", "QUOTIENT requires 2 numeric arguments"},
// RADIANS
`=RADIANS("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=RADIANS()": {"#VALUE!", "RADIANS requires 1 numeric argument"},
"=RADIANS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=RADIANS()": {"#VALUE!", "RADIANS requires 1 numeric argument"},
// RAND
"=RAND(1)": {"#VALUE!", "RAND accepts no arguments"},
// RANDBETWEEN
`=RANDBETWEEN("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=RANDBETWEEN(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=RANDBETWEEN()": {"#VALUE!", "RANDBETWEEN requires 2 numeric arguments"},
"=RANDBETWEEN(2,1)": {"#NUM!", "#NUM!"},
"=RANDBETWEEN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=RANDBETWEEN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=RANDBETWEEN()": {"#VALUE!", "RANDBETWEEN requires 2 numeric arguments"},
"=RANDBETWEEN(2,1)": {"#NUM!", "#NUM!"},
// ROMAN
"=ROMAN()": {"#VALUE!", "ROMAN requires at least 1 argument"},
"=ROMAN(1,2,3)": {"#VALUE!", "ROMAN allows at most 2 arguments"},
@ -2754,17 +2766,17 @@ func TestCalcCellValue(t *testing.T) {
"=ROMAN(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"=ROMAN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ROUND
"=ROUND()": {"#VALUE!", "ROUND requires 2 numeric arguments"},
`=ROUND("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ROUND(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ROUND()": {"#VALUE!", "ROUND requires 2 numeric arguments"},
"=ROUND(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ROUNDDOWN
"=ROUNDDOWN()": {"#VALUE!", "ROUNDDOWN requires 2 numeric arguments"},
`=ROUNDDOWN("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ROUNDDOWN(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ROUNDDOWN()": {"#VALUE!", "ROUNDDOWN requires 2 numeric arguments"},
"=ROUNDDOWN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ROUNDDOWN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ROUNDUP
"=ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"},
`=ROUNDUP("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ROUNDUP(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"},
"=ROUNDUP(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=ROUNDUP(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SEARCH
"=SEARCH()": {"#VALUE!", "SEARCH requires at least 2 arguments"},
"=SEARCH(1,A1,1,1)": {"#VALUE!", "SEARCH allows at most 3 arguments"},
@ -2777,11 +2789,11 @@ func TestCalcCellValue(t *testing.T) {
"=SEARCHB(\"?w\",\"你好world\")": {"#VALUE!", "#VALUE!"},
"=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// SEC
"=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"},
`=_xlfn.SEC("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"},
"=_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.SECH
"=_xlfn.SECH()": {"#VALUE!", "SECH requires 1 numeric argument"},
`=_xlfn.SECH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.SECH()": {"#VALUE!", "SECH requires 1 numeric argument"},
"=_xlfn.SECH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SERIESSUM
"=SERIESSUM()": {"#VALUE!", "SERIESSUM requires 4 arguments"},
"=SERIESSUM(\"\",2,3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
@ -2789,22 +2801,22 @@ func TestCalcCellValue(t *testing.T) {
"=SERIESSUM(1,2,\"\",A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"=SERIESSUM(1,2,3,A1:D1)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"},
// SIGN
"=SIGN()": {"#VALUE!", "SIGN requires 1 numeric argument"},
`=SIGN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SIGN()": {"#VALUE!", "SIGN requires 1 numeric argument"},
"=SIGN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SIN
"=SIN()": {"#VALUE!", "SIN requires 1 numeric argument"},
`=SIN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SIN()": {"#VALUE!", "SIN requires 1 numeric argument"},
"=SIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SINH
"=SINH()": {"#VALUE!", "SINH requires 1 numeric argument"},
`=SINH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SINH()": {"#VALUE!", "SINH requires 1 numeric argument"},
"=SINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SQRT
"=SQRT()": {"#VALUE!", "SQRT requires 1 numeric argument"},
`=SQRT("")`: {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
`=SQRT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SQRT(-1)": {"#NUM!", "#NUM!"},
"=SQRT()": {"#VALUE!", "SQRT requires 1 numeric argument"},
"=SQRT(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"=SQRT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SQRT(-1)": {"#NUM!", "#NUM!"},
// SQRTPI
"=SQRTPI()": {"#VALUE!", "SQRTPI requires 1 numeric argument"},
`=SQRTPI("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SQRTPI()": {"#VALUE!", "SQRTPI requires 1 numeric argument"},
"=SQRTPI(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// STDEV
"=STDEV()": {"#VALUE!", "STDEV requires at least 1 argument"},
"=STDEV(E2:E9)": {"#DIV/0!", "#DIV/0!"},
@ -4666,7 +4678,7 @@ func TestCalcCellValue(t *testing.T) {
f := prepareCalcData(cellData)
result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "", result)
assert.Equal(t, "1", result)
// Test get calculated cell value on not exists worksheet
f = prepareCalcData(cellData)
_, err = f.CalcCellValue("SheetN", "A1")
@ -4718,7 +4730,7 @@ func TestCalcWithDefinedName(t *testing.T) {
assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "=IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")"))
result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err)
assert.Equal(t, "YES", result, `=IF("B1_as_string"=defined_name1,"YES","NO")`)
assert.Equal(t, "YES", result, "=IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")")
}
func TestCalcISBLANK(t *testing.T) {
@ -6140,14 +6152,14 @@ func TestCalcBetainvProbIterator(t *testing.T) {
func TestNestedFunctionsWithOperators(t *testing.T) {
f := NewFile()
formulaList := map[string]string{
`=LEN("KEEP")`: "4",
`=LEN("REMOVEKEEP") - LEN("REMOVE")`: "4",
`=RIGHT("REMOVEKEEP", 4)`: "KEEP",
`=RIGHT("REMOVEKEEP", 10 - 6))`: "KEEP",
`=RIGHT("REMOVEKEEP", LEN("REMOVEKEEP") - 6)`: "KEEP",
`=RIGHT("REMOVEKEEP", LEN("REMOVEKEEP") - LEN("REMOV") - 1)`: "KEEP",
`=RIGHT("REMOVEKEEP", 10 - LEN("REMOVE"))`: "KEEP",
`=RIGHT("REMOVEKEEP", LEN("REMOVEKEEP") - LEN("REMOVE"))`: "KEEP",
"=LEN(\"KEEP\")": "4",
"=LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\")": "4",
"=RIGHT(\"REMOVEKEEP\", 4)": "KEEP",
"=RIGHT(\"REMOVEKEEP\", 10 - 6))": "KEEP",
"=RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - 6)": "KEEP",
"=RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOV\") - 1)": "KEEP",
"=RIGHT(\"REMOVEKEEP\", 10 - LEN(\"REMOVE\"))": "KEEP",
"=RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\"))": "KEEP",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))

View File

@ -1202,16 +1202,18 @@ func TestConditionalFormat(t *testing.T) {
},
},
))
// Test set conditional format with invalid cell reference
assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.SetConditionalFormat("Sheet1", "A1:-", nil))
// Test set conditional format on not exists worksheet
assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist")
// Test set conditional format with invalid sheet name
assert.EqualError(t, f.SetConditionalFormat("Sheet:1", "L1:L10", nil), ErrSheetNameInvalid.Error())
assert.Equal(t, ErrSheetNameInvalid, f.SetConditionalFormat("Sheet:1", "L1:L10", nil))
err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
assert.NoError(t, err)
// Set conditional format with illegal valid type
assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10",
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat(sheet1, "K1:K10",
[]ConditionalFormatOptions{
{
Type: "",

234
styles.go
View File

@ -33,12 +33,12 @@ var validType = map[string]string{
"unique": "uniqueValues",
"top": "top10",
"bottom": "top10",
"text": "text", // Doesn't support currently
"time_period": "timePeriod", // Doesn't support currently
"blanks": "containsBlanks", // Doesn't support currently
"no_blanks": "notContainsBlanks", // Doesn't support currently
"errors": "containsErrors", // Doesn't support currently
"no_errors": "notContainsErrors", // Doesn't support currently
"text": "text",
"time_period": "timePeriod", // Doesn't support currently
"blanks": "containsBlanks",
"no_blanks": "notContainsBlanks",
"errors": "containsErrors",
"no_errors": "notContainsErrors",
"2_color_scale": "2_color_scale",
"3_color_scale": "3_color_scale",
"data_bar": "dataBar",
@ -1223,29 +1223,42 @@ var (
},
}
// drawContFmtFunc defines functions to create conditional formats.
drawContFmtFunc = map[string]func(p int, ct, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){
"cellIs": drawCondFmtCellIs,
"top10": drawCondFmtTop10,
"aboveAverage": drawCondFmtAboveAverage,
"duplicateValues": drawCondFmtDuplicateUniqueValues,
"uniqueValues": drawCondFmtDuplicateUniqueValues,
"2_color_scale": drawCondFmtColorScale,
"3_color_scale": drawCondFmtColorScale,
"dataBar": drawCondFmtDataBar,
"expression": drawCondFmtExp,
"iconSet": drawCondFmtIconSet,
drawContFmtFunc = map[string]func(p int, ct, ref, GUID string, fmtCond *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule){
"cellIs": drawCondFmtCellIs,
"text": drawCondFmtText,
"top10": drawCondFmtTop10,
"aboveAverage": drawCondFmtAboveAverage,
"duplicateValues": drawCondFmtDuplicateUniqueValues,
"uniqueValues": drawCondFmtDuplicateUniqueValues,
"containsBlanks": drawCondFmtBlanks,
"notContainsBlanks": drawCondFmtNoBlanks,
"containsErrors": drawCondFmtErrors,
"notContainsErrors": drawCondFmtNoErrors,
"2_color_scale": drawCondFmtColorScale,
"3_color_scale": drawCondFmtColorScale,
"dataBar": drawCondFmtDataBar,
"expression": drawCondFmtExp,
"iconSet": drawCondFmtIconSet,
}
// extractContFmtFunc defines functions to get conditional formats.
extractContFmtFunc = map[string]func(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions{
"cellIs": extractCondFmtCellIs,
"top10": extractCondFmtTop10,
"aboveAverage": extractCondFmtAboveAverage,
"duplicateValues": extractCondFmtDuplicateUniqueValues,
"uniqueValues": extractCondFmtDuplicateUniqueValues,
"colorScale": extractCondFmtColorScale,
"dataBar": extractCondFmtDataBar,
"expression": extractCondFmtExp,
"iconSet": extractCondFmtIconSet,
"cellIs": extractCondFmtCellIs,
"containsText": extractCondFmtText,
"notContainsText": extractCondFmtText,
"beginsWith": extractCondFmtText,
"endsWith": extractCondFmtText,
"top10": extractCondFmtTop10,
"aboveAverage": extractCondFmtAboveAverage,
"duplicateValues": extractCondFmtDuplicateUniqueValues,
"uniqueValues": extractCondFmtDuplicateUniqueValues,
"containsBlanks": extractCondFmtBlanks,
"notContainsBlanks": extractCondFmtNoBlanks,
"containsErrors": extractCondFmtErrors,
"notContainsErrors": extractCondFmtNoErrors,
"colorScale": extractCondFmtColorScale,
"dataBar": extractCondFmtDataBar,
"expression": extractCondFmtExp,
"iconSet": extractCondFmtIconSet,
}
)
@ -2635,13 +2648,31 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
if err != nil {
return err
}
if strings.Contains(rangeRef, ":") {
rect, err := rangeRefToCoordinates(rangeRef)
if err != nil {
return err
}
_ = sortCoordinates(rect)
rangeRef, _ = f.coordinatesToRangeRef(rect, strings.Contains(rangeRef, "$"))
}
// Create a pseudo GUID for each unique rule.
var rules int
for _, cf := range ws.ConditionalFormatting {
rules += len(cf.CfRule)
}
GUID := fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), rules)
var cfRule []*xlsxCfRule
var (
GUID = fmt.Sprintf("{00000000-0000-0000-%04X-%012X}", f.getSheetID(sheet), rules)
cfRule []*xlsxCfRule
noCriteriaTypes = []string{
"containsBlanks",
"notContainsBlanks",
"containsErrors",
"notContainsErrors",
"expression",
"iconSet",
}
)
for p, v := range opts {
var vt, ct string
var ok bool
@ -2650,10 +2681,10 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
if ok {
// Check for valid criteria types.
ct, ok = criteriaType[v.Criteria]
if ok || vt == "expression" || vt == "iconSet" {
if ok || inStrSlice(noCriteriaTypes, vt, true) != -1 {
drawFunc, ok := drawContFmtFunc[vt]
if ok {
rule, x14rule := drawFunc(p, ct, GUID, &v)
rule, x14rule := drawFunc(p, ct, strings.Split(rangeRef, ":")[0], GUID, &v)
if rule == nil {
return ErrParameterInvalid
}
@ -2669,6 +2700,7 @@ func (f *File) SetConditionalFormat(sheet, rangeRef string, opts []ConditionalFo
}
return ErrParameterInvalid
}
return ErrParameterInvalid
}
ws.ConditionalFormatting = append(ws.ConditionalFormatting, &xlsxConditionalFormatting{
@ -2740,6 +2772,12 @@ func extractCondFmtCellIs(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOp
return format
}
// extractCondFmtText provides a function to extract conditional format
// settings for text cell values by given conditional formatting rule.
func extractCondFmtText(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{StopIfTrue: c.StopIfTrue, Type: "text", Criteria: operatorType[c.Operator], Format: *c.DxfID, Value: c.Text}
}
// extractCondFmtTop10 provides a function to extract conditional format
// settings for top N (default is top 10) by given conditional formatting
// rule.
@ -2786,6 +2824,46 @@ func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule, extLst *xlsxExtLst) Cond
}
}
// extractCondFmtBlanks provides a function to extract conditional format
// settings for blank cells by given conditional formatting rule.
func extractCondFmtBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
StopIfTrue: c.StopIfTrue,
Type: "blanks",
Format: *c.DxfID,
}
}
// extractCondFmtNoBlanks provides a function to extract conditional format
// settings for no blank cells by given conditional formatting rule.
func extractCondFmtNoBlanks(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
StopIfTrue: c.StopIfTrue,
Type: "no_blanks",
Format: *c.DxfID,
}
}
// extractCondFmtErrors provides a function to extract conditional format
// settings for cells with errors by given conditional formatting rule.
func extractCondFmtErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
StopIfTrue: c.StopIfTrue,
Type: "errors",
Format: *c.DxfID,
}
}
// extractCondFmtNoErrors provides a function to extract conditional format
// settings for cells without errors by given conditional formatting rule.
func extractCondFmtNoErrors(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatOptions {
return ConditionalFormatOptions{
StopIfTrue: c.StopIfTrue,
Type: "no_errors",
Format: *c.DxfID,
}
}
// extractCondFmtColorScale provides a function to extract conditional format
// settings for color scale (include 2 color scale and 3 color scale) by given
// conditional formatting rule.
@ -2938,7 +3016,7 @@ func (f *File) UnsetConditionalFormat(sheet, rangeRef string) error {
// drawCondFmtCellIs provides a function to create conditional formatting rule
// for cell value (include between, not between, equal, not equal, greater
// than and less than) by given priority, criteria type and format settings.
func drawCondFmtCellIs(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtCellIs(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
c := &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
@ -2950,16 +3028,46 @@ func drawCondFmtCellIs(p int, ct, GUID string, format *ConditionalFormatOptions)
if ct == "between" || ct == "notBetween" {
c.Formula = append(c.Formula, []string{format.MinValue, format.MaxValue}...)
}
if idx := inStrSlice([]string{"equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual", "containsText", "notContains", "beginsWith", "endsWith"}, ct, true); idx != -1 {
if inStrSlice([]string{"equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual", "containsText", "notContains", "beginsWith", "endsWith"}, ct, true) != -1 {
c.Formula = append(c.Formula, format.Value)
}
return c, nil
}
// drawCondFmtText provides a function to create conditional formatting rule for
// text cell values by given priority, criteria type and format settings.
func drawCondFmtText(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: map[string]string{
"containsText": "containsText",
"notContains": "notContainsText",
"beginsWith": "beginsWith",
"endsWith": "endsWith",
}[ct],
Text: format.Value,
Operator: ct,
Formula: []string{
map[string]string{
"containsText": fmt.Sprintf("NOT(ISERROR(SEARCH(\"%s\",%s)))",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
"notContains": fmt.Sprintf("ISERROR(SEARCH(\"%s\",%s))",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
"beginsWith": fmt.Sprintf("LEFT(%[2]s,LEN(\"%[1]s\"))=\"%[1]s\"",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
"endsWith": fmt.Sprintf("RIGHT(%[2]s,LEN(\"%[1]s\"))=\"%[1]s\"",
strings.NewReplacer(`"`, `""`).Replace(format.Value), ref),
}[ct],
},
DxfID: intPtr(format.Format),
}, nil
}
// drawCondFmtTop10 provides a function to create conditional formatting rule
// for top N (default is top 10) by given priority, criteria type and format
// settings.
func drawCondFmtTop10(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtTop10(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
c := &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
@ -2978,7 +3086,7 @@ func drawCondFmtTop10(p int, ct, GUID string, format *ConditionalFormatOptions)
// drawCondFmtAboveAverage provides a function to create conditional
// formatting rule for above average and below average by given priority,
// criteria type and format settings.
func drawCondFmtAboveAverage(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtAboveAverage(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
@ -2991,7 +3099,7 @@ func drawCondFmtAboveAverage(p int, ct, GUID string, format *ConditionalFormatOp
// drawCondFmtDuplicateUniqueValues provides a function to create conditional
// formatting rule for duplicate and unique values by given priority, criteria
// type and format settings.
func drawCondFmtDuplicateUniqueValues(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtDuplicateUniqueValues(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
@ -3003,7 +3111,7 @@ func drawCondFmtDuplicateUniqueValues(p int, ct, GUID string, format *Conditiona
// drawCondFmtColorScale provides a function to create conditional formatting
// rule for color scale (include 2 color scale and 3 color scale) by given
// priority, criteria type and format settings.
func drawCondFmtColorScale(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtColorScale(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
minValue := format.MinValue
if minValue == "" {
minValue = "0"
@ -3041,7 +3149,7 @@ func drawCondFmtColorScale(p int, ct, GUID string, format *ConditionalFormatOpti
// drawCondFmtDataBar provides a function to create conditional formatting
// rule for data bar by given priority, criteria type and format settings.
func drawCondFmtDataBar(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtDataBar(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
var x14CfRule *xlsxX14CfRule
var extLst *xlsxExtLst
if format.BarSolid || format.BarDirection == "leftToRight" || format.BarDirection == "rightToLeft" || format.BarBorderColor != "" {
@ -3078,7 +3186,7 @@ func drawCondFmtDataBar(p int, ct, GUID string, format *ConditionalFormatOptions
// drawCondFmtExp provides a function to create conditional formatting rule
// for expression by given priority, criteria type and format settings.
func drawCondFmtExp(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtExp(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
@ -3088,9 +3196,57 @@ func drawCondFmtExp(p int, ct, GUID string, format *ConditionalFormatOptions) (*
}, nil
}
// drawCondFmtErrors provides a function to create conditional formatting rule
// for cells with errors by given priority, criteria type and format settings.
func drawCondFmtErrors(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("ISERROR(%s)", ref)},
DxfID: intPtr(format.Format),
}, nil
}
// drawCondFmtErrors provides a function to create conditional formatting rule
// for cells without errors by given priority, criteria type and format settings.
func drawCondFmtNoErrors(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("NOT(ISERROR(%s))", ref)},
DxfID: intPtr(format.Format),
}, nil
}
// drawCondFmtErrors provides a function to create conditional formatting rule
// for blank cells by given priority, criteria type and format settings.
func drawCondFmtBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("LEN(TRIM(%s))=0", ref)},
DxfID: intPtr(format.Format),
}, nil
}
// drawCondFmtErrors provides a function to create conditional formatting rule
// for no blanks cells by given priority, criteria type and format settings.
func drawCondFmtNoBlanks(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
return &xlsxCfRule{
Priority: p + 1,
StopIfTrue: format.StopIfTrue,
Type: validType[format.Type],
Formula: []string{fmt.Sprintf("LEN(TRIM(%s))>0", ref)},
DxfID: intPtr(format.Format),
}, nil
}
// drawCondFmtIconSet provides a function to create conditional formatting rule
// for icon set by given priority, criteria type and format settings.
func drawCondFmtIconSet(p int, ct, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
func drawCondFmtIconSet(p int, ct, ref, GUID string, format *ConditionalFormatOptions) (*xlsxCfRule, *xlsxX14CfRule) {
cfvo3 := &xlsxCfRule{IconSet: &xlsxIconSet{Cfvo: []*xlsxCfvo{
{Type: "percent", Val: "0"},
{Type: "percent", Val: "33"},

View File

@ -195,12 +195,7 @@ func TestSetConditionalFormat(t *testing.T) {
for _, val := range []string{
"date",
"time",
"text",
"time_period",
"blanks",
"no_blanks",
"errors",
"no_errors",
} {
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1", []ConditionalFormatOptions{{Type: val}}))
}
@ -210,6 +205,10 @@ func TestGetConditionalFormats(t *testing.T) {
for _, format := range [][]ConditionalFormatOptions{
{{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}},
{{Type: "cell", Format: 1, Criteria: "between", MinValue: "6", MaxValue: "8"}},
{{Type: "text", Format: 1, Criteria: "containing", Value: "~!@#$%^&*()_+{}|:<>?\"';"}},
{{Type: "text", Format: 1, Criteria: "not containing", Value: "text"}},
{{Type: "text", Format: 1, Criteria: "begins with", Value: "prefix"}},
{{Type: "text", Format: 1, Criteria: "ends with", Value: "suffix"}},
{{Type: "top", Format: 1, Criteria: "=", Value: "6"}},
{{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}},
{{Type: "average", AboveAverage: true, Format: 1, Criteria: "="}},
@ -220,10 +219,14 @@ func TestGetConditionalFormats(t *testing.T) {
{{Type: "data_bar", Criteria: "=", MinType: "num", MaxType: "num", MinValue: "-10", MaxValue: "10", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarOnly: true, BarSolid: true, StopIfTrue: true}},
{{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarBorderColor: "#0000FF", BarColor: "#638EC6", BarDirection: "rightToLeft", BarOnly: true, BarSolid: true, StopIfTrue: true}},
{{Type: "formula", Format: 1, Criteria: "="}},
{{Type: "blanks", Format: 1}},
{{Type: "no_blanks", Format: 1}},
{{Type: "errors", Format: 1}},
{{Type: "no_errors", Format: 1}},
{{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
} {
f := NewFile()
err := f.SetConditionalFormat("Sheet1", "A1:A2", format)
err := f.SetConditionalFormat("Sheet1", "A2:A1", format)
assert.NoError(t, err)
opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err)