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() ps := efp.ExcelParser()
tokens := ps.Parse(formula) tokens := ps.Parse(formula)
if tokens == nil { if tokens == nil {
return return f.cellResolver(ctx, sheet, cell)
} }
result, err = f.evalInfixExp(ctx, sheet, cell, tokens) result, err = f.evalInfixExp(ctx, sheet, cell, tokens)
return return
@ -1225,6 +1225,12 @@ func calcAdd(rOpd, lOpd formulaArg, opdStack *Stack) error {
// calcSubtract evaluate subtraction arithmetic operations. // calcSubtract evaluate subtraction arithmetic operations.
func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error { func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error {
if rOpd.Value() == "" {
rOpd = newNumberFormulaArg(0)
}
if lOpd.Value() == "" {
lOpd = newNumberFormulaArg(0)
}
lOpdVal := lOpd.ToNumber() lOpdVal := lOpd.ToNumber()
if lOpdVal.Type != ArgNumber { if lOpdVal.Type != ArgNumber {
return errors.New(lOpdVal.Value()) return errors.New(lOpdVal.Value())
@ -1300,22 +1306,27 @@ func calculate(opdStack *Stack, opt efp.Token) error {
">=": calcGe, ">=": calcGe,
"&": calcSplice, "&": calcSplice,
} }
fn, ok := tokenCalcFunc[opt.TValue] if fn, ok := tokenCalcFunc[opt.TValue]; ok {
if ok {
if opdStack.Len() < 2 { if opdStack.Len() < 2 {
return ErrInvalidFormula return ErrInvalidFormula
} }
rOpd := opdStack.Pop().(formulaArg) rOpd := opdStack.Pop().(formulaArg)
lOpd := 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 { if rOpd.Type == ArgError {
return errors.New(rOpd.Value()) return errors.New(rOpd.Value())
} }
if lOpd.Type == ArgError { if lOpd.Type == ArgError {
return errors.New(lOpd.Value()) return errors.New(lOpd.Value())
} }
if err := fn(rOpd, lOpd, opdStack); err != nil { return fn(rOpd, lOpd, opdStack)
return err
}
} }
return nil return nil
} }
@ -1329,6 +1340,10 @@ func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Tok
tokenPriority := getPriority(token) tokenPriority := getPriority(token)
topOpt := optStack.Peek().(efp.Token) topOpt := optStack.Peek().(efp.Token)
topOptPriority := getPriority(topOpt) topOptPriority := getPriority(topOpt)
if topOpt.TValue == "-" && topOpt.TType == efp.TokenTypeOperatorPrefix && token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
optStack.Pop()
return
}
if tokenPriority > topOptPriority { if tokenPriority > topOptPriority {
optStack.Push(token) optStack.Push(token)
return return
@ -13757,7 +13772,7 @@ func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg {
if argsList.Len() != 1 { if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LEN requires 1 string argument") 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 // 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 { if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "LOWER requires 1 argument") 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 // 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{} buf := bytes.Buffer{}
isLetter := false isLetter := false
for _, char := range argsList.Front().Value.(formulaArg).String { for _, char := range argsList.Front().Value.(formulaArg).Value() {
if !isLetter && unicode.IsLetter(char) { if !isLetter && unicode.IsLetter(char) {
buf.WriteRune(unicode.ToUpper(char)) buf.WriteRune(unicode.ToUpper(char))
} else { } else {
@ -13962,7 +13977,7 @@ func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg {
} }
buf := bytes.Buffer{} buf := bytes.Buffer{}
for i := 0; i < int(times.Number); i++ { for i := 0; i < int(times.Number); i++ {
buf.WriteString(text.String) buf.WriteString(text.Value())
} }
return newStringFormulaArg(buf.String()) return newStringFormulaArg(buf.String())
} }
@ -14327,7 +14342,7 @@ func (fn *formulaFuncs) UPPER(argsList *list.List) formulaArg {
if argsList.Len() != 1 { if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "UPPER requires 1 argument") 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 // 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 { switch token.Type {
case ArgString: 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()) return newErrorFormulaArg(formulaErrorVALUE, err.Error())
} }
case ArgNumber: case ArgNumber:
@ -14421,7 +14436,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
case ArgNumber: case ArgNumber:
result = value.ToNumber() result = value.ToNumber()
default: default:
result = newStringFormulaArg(value.String) result = newStringFormulaArg(value.Value())
} }
return result return result
} }
@ -14431,7 +14446,7 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
case ArgNumber: case ArgNumber:
result = value.ToNumber() result = value.ToNumber()
default: default:
result = newStringFormulaArg(value.String) result = newStringFormulaArg(value.Value())
} }
} }
return result return result
@ -14582,7 +14597,7 @@ func compareFormulaArg(lhs, rhs, matchMode formulaArg, caseSensitive bool) byte
} }
return criteriaG return criteriaG
case ArgString: case ArgString:
ls, rs := lhs.String, rhs.String ls, rs := lhs.Value(), rhs.Value()
if !caseSensitive { if !caseSensitive {
ls, rs = strings.ToLower(ls), strings.ToLower(rs) ls, rs = strings.ToLower(ls), strings.ToLower(rs)
} }

View File

@ -58,12 +58,22 @@ func TestCalcCellValue(t *testing.T) {
"=1>=\"-1\"": "FALSE", "=1>=\"-1\"": "FALSE",
"=\"-1\">=-1": "TRUE", "=\"-1\">=-1": "TRUE",
"=\"-1\">=\"-2\"": "FALSE", "=\"-1\">=\"-2\"": "FALSE",
"=-----1+1": "0",
"=------1+1": "2",
"=---1---1": "-2",
"=---1----1": "0",
"=1&2": "12", "=1&2": "12",
"=15%": "0.15", "=15%": "0.15",
"=1+20%": "1.2", "=1+20%": "1.2",
"={1}+2": "3", "={1}+2": "3",
"=1+{2}": "3", "=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\"": "TRUE",
"=\"A\"<>\"A\"": "FALSE", "=\"A\"<>\"A\"": "FALSE",
"=TRUE()&FALSE()": "TRUEFALSE", "=TRUE()&FALSE()": "TRUEFALSE",
@ -1764,6 +1774,8 @@ func TestCalcCellValue(t *testing.T) {
"=LEN(D1)": "5", "=LEN(D1)": "5",
"=LEN(\"テキスト\")": "4", "=LEN(\"テキスト\")": "4",
"=LEN(\"オリジナルテキスト\")": "9", "=LEN(\"オリジナルテキスト\")": "9",
"=LEN(7+LEN(A1&B1&C1))": "1",
"=LEN(8+LEN(A1+(C1-B1)))": "2",
// LENB // LENB
"=LENB(\"\")": "0", "=LENB(\"\")": "0",
"=LENB(D1)": "5", "=LENB(D1)": "5",
@ -2547,142 +2559,142 @@ func TestCalcCellValue(t *testing.T) {
"=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": {"#VALUE!", "#VALUE!"}, "=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": {"#VALUE!", "#VALUE!"},
// ASIN // ASIN
"=ASIN()": {"#VALUE!", "ASIN requires 1 numeric argument"}, "=ASIN()": {"#VALUE!", "ASIN requires 1 numeric argument"},
`=ASIN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ASIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ASINH // ASINH
"=ASINH()": {"#VALUE!", "ASINH requires 1 numeric argument"}, "=ASINH()": {"#VALUE!", "ASINH requires 1 numeric argument"},
`=ASINH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ASINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATAN // ATAN
"=ATAN()": {"#VALUE!", "ATAN requires 1 numeric argument"}, "=ATAN()": {"#VALUE!", "ATAN requires 1 numeric argument"},
`=ATAN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ATAN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATANH // ATANH
"=ATANH()": {"#VALUE!", "ATANH requires 1 numeric argument"}, "=ATANH()": {"#VALUE!", "ATANH requires 1 numeric argument"},
`=ATANH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ATANH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ATAN2 // ATAN2
"=ATAN2()": {"#VALUE!", "ATAN2 requires 2 numeric arguments"}, "=ATAN2()": {"#VALUE!", "ATAN2 requires 2 numeric arguments"},
`=ATAN2("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ATAN2(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ATAN2(0,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ATAN2(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// BASE // BASE
"=BASE()": {"#VALUE!", "BASE requires at least 2 arguments"}, "=BASE()": {"#VALUE!", "BASE requires at least 2 arguments"},
"=BASE(1,2,3,4)": {"#VALUE!", "BASE allows at most 3 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(1,1)": {"#VALUE!", "radix must be an integer >= 2 and <= 36"},
`=BASE("X",2)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=BASE(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=BASE(1,"X")`: {"#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(1,2,\"X\")": {"#VALUE!", "strconv.Atoi: parsing \"X\": invalid syntax"},
// CEILING // CEILING
"=CEILING()": {"#VALUE!", "CEILING requires at least 1 argument"}, "=CEILING()": {"#VALUE!", "CEILING requires at least 1 argument"},
"=CEILING(1,2,3)": {"#VALUE!", "CEILING allows at most 2 arguments"}, "=CEILING(1,2,3)": {"#VALUE!", "CEILING allows at most 2 arguments"},
"=CEILING(1,-1)": {"#VALUE!", "negative sig to CEILING invalid"}, "=CEILING(1,-1)": {"#VALUE!", "negative sig to CEILING invalid"},
`=CEILING("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=CEILING(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=CEILING(0,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=CEILING(0,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.CEILING.MATH // _xlfn.CEILING.MATH
"=_xlfn.CEILING.MATH()": {"#VALUE!", "CEILING.MATH requires at least 1 argument"}, "=_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(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(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.CEILING.MATH(1,"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(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.CEILING.PRECISE // _xlfn.CEILING.PRECISE
"=_xlfn.CEILING.PRECISE()": {"#VALUE!", "CEILING.PRECISE requires at least 1 argument"}, "=_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(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(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.CEILING.PRECISE(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.CEILING.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COMBIN // COMBIN
"=COMBIN()": {"#VALUE!", "COMBIN requires 2 argument"}, "=COMBIN()": {"#VALUE!", "COMBIN requires 2 argument"},
"=COMBIN(-1,1)": {"#VALUE!", "COMBIN requires number >= number_chosen"}, "=COMBIN(-1,1)": {"#VALUE!", "COMBIN requires number >= number_chosen"},
`=COMBIN("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=COMBIN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=COMBIN(-1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=COMBIN(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.COMBINA // _xlfn.COMBINA
"=_xlfn.COMBINA()": {"#VALUE!", "COMBINA requires 2 argument"}, "=_xlfn.COMBINA()": {"#VALUE!", "COMBINA requires 2 argument"},
"=_xlfn.COMBINA(-1,1)": {"#VALUE!", "COMBINA requires number > number_chosen"}, "=_xlfn.COMBINA(-1,1)": {"#VALUE!", "COMBINA requires number > number_chosen"},
"=_xlfn.COMBINA(-1,-1)": {"#VALUE!", "COMBIN 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(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.COMBINA(-1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.COMBINA(-1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COS // COS
"=COS()": {"#VALUE!", "COS requires 1 numeric argument"}, "=COS()": {"#VALUE!", "COS requires 1 numeric argument"},
`=COS("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=COS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// COSH // COSH
"=COSH()": {"#VALUE!", "COSH requires 1 numeric argument"}, "=COSH()": {"#VALUE!", "COSH requires 1 numeric argument"},
`=COSH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=COSH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.COT // _xlfn.COT
"=COT()": {"#VALUE!", "COT requires 1 numeric argument"}, "=COT()": {"#VALUE!", "COT requires 1 numeric argument"},
`=COT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=COT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COT(0)": {"#DIV/0!", "#DIV/0!"}, "=COT(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.COTH // _xlfn.COTH
"=COTH()": {"#VALUE!", "COTH requires 1 numeric argument"}, "=COTH()": {"#VALUE!", "COTH requires 1 numeric argument"},
`=COTH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=COTH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=COTH(0)": {"#DIV/0!", "#DIV/0!"}, "=COTH(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.CSC // _xlfn.CSC
"=_xlfn.CSC()": {"#VALUE!", "CSC requires 1 numeric argument"}, "=_xlfn.CSC()": {"#VALUE!", "CSC requires 1 numeric argument"},
`=_xlfn.CSC("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.CSC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CSC(0)": {"#DIV/0!", "#DIV/0!"}, "=_xlfn.CSC(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.CSCH // _xlfn.CSCH
"=_xlfn.CSCH()": {"#VALUE!", "CSCH requires 1 numeric argument"}, "=_xlfn.CSCH()": {"#VALUE!", "CSCH requires 1 numeric argument"},
`=_xlfn.CSCH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.CSCH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.CSCH(0)": {"#DIV/0!", "#DIV/0!"}, "=_xlfn.CSCH(0)": {"#DIV/0!", "#DIV/0!"},
// _xlfn.DECIMAL // _xlfn.DECIMAL
"=_xlfn.DECIMAL()": {"#VALUE!", "DECIMAL requires 2 numeric arguments"}, "=_xlfn.DECIMAL()": {"#VALUE!", "DECIMAL requires 2 numeric arguments"},
`=_xlfn.DECIMAL("X",2)`: {"#VALUE!", "strconv.ParseInt: parsing \"X\": invalid syntax"}, "=_xlfn.DECIMAL(\"X\",2)": {"#VALUE!", "strconv.ParseInt: parsing \"X\": invalid syntax"},
`=_xlfn.DECIMAL(2000,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.DECIMAL(2000,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// DEGREES // DEGREES
"=DEGREES()": {"#VALUE!", "DEGREES requires 1 numeric argument"}, "=DEGREES()": {"#VALUE!", "DEGREES requires 1 numeric argument"},
`=DEGREES("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=DEGREES(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=DEGREES(0)": {"#DIV/0!", "#DIV/0!"}, "=DEGREES(0)": {"#DIV/0!", "#DIV/0!"},
// EVEN // EVEN
"=EVEN()": {"#VALUE!", "EVEN requires 1 numeric argument"}, "=EVEN()": {"#VALUE!", "EVEN requires 1 numeric argument"},
`=EVEN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=EVEN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// EXP // EXP
"=EXP()": {"#VALUE!", "EXP requires 1 numeric argument"}, "=EXP()": {"#VALUE!", "EXP requires 1 numeric argument"},
`=EXP("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=EXP(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// FACT // FACT
"=FACT()": {"#VALUE!", "FACT requires 1 numeric argument"}, "=FACT()": {"#VALUE!", "FACT requires 1 numeric argument"},
`=FACT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=FACT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FACT(-1)": {"#NUM!", "#NUM!"}, "=FACT(-1)": {"#NUM!", "#NUM!"},
// FACTDOUBLE // FACTDOUBLE
"=FACTDOUBLE()": {"#VALUE!", "FACTDOUBLE requires 1 numeric argument"}, "=FACTDOUBLE()": {"#VALUE!", "FACTDOUBLE requires 1 numeric argument"},
`=FACTDOUBLE("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=FACTDOUBLE(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=FACTDOUBLE(-1)": {"#NUM!", "#NUM!"}, "=FACTDOUBLE(-1)": {"#NUM!", "#NUM!"},
// FLOOR // FLOOR
"=FLOOR()": {"#VALUE!", "FLOOR requires 2 numeric arguments"}, "=FLOOR()": {"#VALUE!", "FLOOR requires 2 numeric arguments"},
`=FLOOR("X",-1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=FLOOR(\"X\",-1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=FLOOR(1,"X")`: {"#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(1,-1)": {"#NUM!", "invalid arguments to FLOOR"},
// _xlfn.FLOOR.MATH // _xlfn.FLOOR.MATH
"=_xlfn.FLOOR.MATH()": {"#VALUE!", "FLOOR.MATH requires at least 1 argument"}, "=_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(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(\"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,\"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(1,2,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.FLOOR.PRECISE // _xlfn.FLOOR.PRECISE
"=_xlfn.FLOOR.PRECISE()": {"#VALUE!", "FLOOR.PRECISE requires at least 1 argument"}, "=_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(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(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=_xlfn.FLOOR.PRECISE(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.FLOOR.PRECISE(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// GCD // GCD
"=GCD()": {"#VALUE!", "GCD requires at least 1 argument"}, "=GCD()": {"#VALUE!", "GCD requires at least 1 argument"},
"=GCD(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "=GCD(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
"=GCD(-1)": {"#VALUE!", "GCD only accepts positive arguments"}, "=GCD(-1)": {"#VALUE!", "GCD only accepts positive arguments"},
"=GCD(1,-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(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// INT // INT
"=INT()": {"#VALUE!", "INT requires 1 numeric argument"}, "=INT()": {"#VALUE!", "INT requires 1 numeric argument"},
`=INT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=INT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ISO.CEILING // ISO.CEILING
"=ISO.CEILING()": {"#VALUE!", "ISO.CEILING requires at least 1 argument"}, "=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(1,2,3)": {"#VALUE!", "ISO.CEILING allows at most 2 arguments"},
`=ISO.CEILING("X",2)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ISO.CEILING(\"X\",2)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ISO.CEILING(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ISO.CEILING(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LCM // LCM
"=LCM()": {"#VALUE!", "LCM requires at least 1 argument"}, "=LCM()": {"#VALUE!", "LCM requires at least 1 argument"},
"=LCM(-1)": {"#VALUE!", "LCM only accepts positive arguments"}, "=LCM(-1)": {"#VALUE!", "LCM only accepts positive arguments"},
"=LCM(1,-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(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LN // LN
"=LN()": {"#VALUE!", "LN requires 1 numeric argument"}, "=LN()": {"#VALUE!", "LN requires 1 numeric argument"},
"=LN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=LN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// LOG // LOG
"=LOG()": {"#VALUE!", "LOG requires at least 1 argument"}, "=LOG()": {"#VALUE!", "LOG requires at least 1 argument"},
"=LOG(1,2,3)": {"#VALUE!", "LOG allows at most 2 arguments"}, "=LOG(1,2,3)": {"#VALUE!", "LOG allows at most 2 arguments"},
`=LOG("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=LOG(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=LOG(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=LOG(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=LOG(0,0)": {"#NUM!", "#DIV/0!"}, "=LOG(0,0)": {"#NUM!", "#DIV/0!"},
"=LOG(1,0)": {"#NUM!", "#DIV/0!"}, "=LOG(1,0)": {"#NUM!", "#DIV/0!"},
"=LOG(1,1)": {"#DIV/0!", "#DIV/0!"}, "=LOG(1,1)": {"#DIV/0!", "#DIV/0!"},
@ -2704,28 +2716,28 @@ func TestCalcCellValue(t *testing.T) {
// MOD // MOD
"=MOD()": {"#VALUE!", "MOD requires 2 numeric arguments"}, "=MOD()": {"#VALUE!", "MOD requires 2 numeric arguments"},
"=MOD(6,0)": {"#DIV/0!", "MOD divide by zero"}, "=MOD(6,0)": {"#DIV/0!", "MOD divide by zero"},
`=MOD("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=MOD(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=MOD(6,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=MOD(6,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MROUND // MROUND
"=MROUND()": {"#VALUE!", "MROUND requires 2 numeric arguments"}, "=MROUND()": {"#VALUE!", "MROUND requires 2 numeric arguments"},
"=MROUND(1,0)": {"#NUM!", "#NUM!"}, "=MROUND(1,0)": {"#NUM!", "#NUM!"},
"=MROUND(1,-1)": {"#NUM!", "#NUM!"}, "=MROUND(1,-1)": {"#NUM!", "#NUM!"},
`=MROUND("X",0)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=MROUND(\"X\",0)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=MROUND(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=MROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// MULTINOMIAL // MULTINOMIAL
`=MULTINOMIAL("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=MULTINOMIAL(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.MUNIT // _xlfn.MUNIT
"=_xlfn.MUNIT()": {"#VALUE!", "MUNIT requires 1 numeric argument"}, "=_xlfn.MUNIT()": {"#VALUE!", "MUNIT requires 1 numeric argument"},
`=_xlfn.MUNIT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.MUNIT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=_xlfn.MUNIT(-1)": {"#VALUE!", ""}, "=_xlfn.MUNIT(-1)": {"#VALUE!", ""},
// ODD // ODD
"=ODD()": {"#VALUE!", "ODD requires 1 numeric argument"}, "=ODD()": {"#VALUE!", "ODD requires 1 numeric argument"},
`=ODD("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ODD(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// PI // PI
"=PI(1)": {"#VALUE!", "PI accepts no arguments"}, "=PI(1)": {"#VALUE!", "PI accepts no arguments"},
// POWER // POWER
`=POWER("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=POWER(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=POWER(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=POWER(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=POWER(0,0)": {"#NUM!", "#NUM!"}, "=POWER(0,0)": {"#NUM!", "#NUM!"},
"=POWER(0,-1)": {"#DIV/0!", "#DIV/0!"}, "=POWER(0,-1)": {"#DIV/0!", "#DIV/0!"},
"=POWER(1)": {"#VALUE!", "POWER requires 2 numeric arguments"}, "=POWER(1)": {"#VALUE!", "POWER requires 2 numeric arguments"},
@ -2733,18 +2745,18 @@ func TestCalcCellValue(t *testing.T) {
"=PRODUCT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=PRODUCT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=PRODUCT(\"\",3,6)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "=PRODUCT(\"\",3,6)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// QUOTIENT // QUOTIENT
`=QUOTIENT("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=QUOTIENT(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=QUOTIENT(1,"X")`: {"#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,0)": {"#DIV/0!", "#DIV/0!"},
"=QUOTIENT(1)": {"#VALUE!", "QUOTIENT requires 2 numeric arguments"}, "=QUOTIENT(1)": {"#VALUE!", "QUOTIENT requires 2 numeric arguments"},
// RADIANS // RADIANS
`=RADIANS("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=RADIANS(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=RADIANS()": {"#VALUE!", "RADIANS requires 1 numeric argument"}, "=RADIANS()": {"#VALUE!", "RADIANS requires 1 numeric argument"},
// RAND // RAND
"=RAND(1)": {"#VALUE!", "RAND accepts no arguments"}, "=RAND(1)": {"#VALUE!", "RAND accepts no arguments"},
// RANDBETWEEN // RANDBETWEEN
`=RANDBETWEEN("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=RANDBETWEEN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=RANDBETWEEN(1,"X")`: {"#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()": {"#VALUE!", "RANDBETWEEN requires 2 numeric arguments"},
"=RANDBETWEEN(2,1)": {"#NUM!", "#NUM!"}, "=RANDBETWEEN(2,1)": {"#NUM!", "#NUM!"},
// ROMAN // ROMAN
@ -2755,16 +2767,16 @@ func TestCalcCellValue(t *testing.T) {
"=ROMAN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "=ROMAN(\"\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// ROUND // ROUND
"=ROUND()": {"#VALUE!", "ROUND requires 2 numeric arguments"}, "=ROUND()": {"#VALUE!", "ROUND requires 2 numeric arguments"},
`=ROUND("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ROUND(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ROUND(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ROUND(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ROUNDDOWN // ROUNDDOWN
"=ROUNDDOWN()": {"#VALUE!", "ROUNDDOWN requires 2 numeric arguments"}, "=ROUNDDOWN()": {"#VALUE!", "ROUNDDOWN requires 2 numeric arguments"},
`=ROUNDDOWN("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ROUNDDOWN(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ROUNDDOWN(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ROUNDDOWN(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// ROUNDUP // ROUNDUP
"=ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"}, "=ROUNDUP()": {"#VALUE!", "ROUNDUP requires 2 numeric arguments"},
`=ROUNDUP("X",1)`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ROUNDUP(\"X\",1)": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
`=ROUNDUP(1,"X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=ROUNDUP(1,\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SEARCH // SEARCH
"=SEARCH()": {"#VALUE!", "SEARCH requires at least 2 arguments"}, "=SEARCH()": {"#VALUE!", "SEARCH requires at least 2 arguments"},
"=SEARCH(1,A1,1,1)": {"#VALUE!", "SEARCH allows at most 3 arguments"}, "=SEARCH(1,A1,1,1)": {"#VALUE!", "SEARCH allows at most 3 arguments"},
@ -2778,10 +2790,10 @@ func TestCalcCellValue(t *testing.T) {
"=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "=SEARCHB(1,A1,\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
// SEC // SEC
"=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"}, "=_xlfn.SEC()": {"#VALUE!", "SEC requires 1 numeric argument"},
`=_xlfn.SEC("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.SEC(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// _xlfn.SECH // _xlfn.SECH
"=_xlfn.SECH()": {"#VALUE!", "SECH requires 1 numeric argument"}, "=_xlfn.SECH()": {"#VALUE!", "SECH requires 1 numeric argument"},
`=_xlfn.SECH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=_xlfn.SECH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SERIESSUM // SERIESSUM
"=SERIESSUM()": {"#VALUE!", "SERIESSUM requires 4 arguments"}, "=SERIESSUM()": {"#VALUE!", "SERIESSUM requires 4 arguments"},
"=SERIESSUM(\"\",2,3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "=SERIESSUM(\"\",2,3,A1:A4)": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
@ -2790,21 +2802,21 @@ func TestCalcCellValue(t *testing.T) {
"=SERIESSUM(1,2,3,A1:D1)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"}, "=SERIESSUM(1,2,3,A1:D1)": {"#VALUE!", "strconv.ParseFloat: parsing \"Month\": invalid syntax"},
// SIGN // SIGN
"=SIGN()": {"#VALUE!", "SIGN requires 1 numeric argument"}, "=SIGN()": {"#VALUE!", "SIGN requires 1 numeric argument"},
`=SIGN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=SIGN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SIN // SIN
"=SIN()": {"#VALUE!", "SIN requires 1 numeric argument"}, "=SIN()": {"#VALUE!", "SIN requires 1 numeric argument"},
`=SIN("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=SIN(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SINH // SINH
"=SINH()": {"#VALUE!", "SINH requires 1 numeric argument"}, "=SINH()": {"#VALUE!", "SINH requires 1 numeric argument"},
`=SINH("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=SINH(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// SQRT // SQRT
"=SQRT()": {"#VALUE!", "SQRT requires 1 numeric argument"}, "=SQRT()": {"#VALUE!", "SQRT requires 1 numeric argument"},
`=SQRT("")`: {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"}, "=SQRT(\"\")": {"#VALUE!", "strconv.ParseFloat: parsing \"\": invalid syntax"},
`=SQRT("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=SQRT(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
"=SQRT(-1)": {"#NUM!", "#NUM!"}, "=SQRT(-1)": {"#NUM!", "#NUM!"},
// SQRTPI // SQRTPI
"=SQRTPI()": {"#VALUE!", "SQRTPI requires 1 numeric argument"}, "=SQRTPI()": {"#VALUE!", "SQRTPI requires 1 numeric argument"},
`=SQRTPI("X")`: {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"}, "=SQRTPI(\"X\")": {"#VALUE!", "strconv.ParseFloat: parsing \"X\": invalid syntax"},
// STDEV // STDEV
"=STDEV()": {"#VALUE!", "STDEV requires at least 1 argument"}, "=STDEV()": {"#VALUE!", "STDEV requires at least 1 argument"},
"=STDEV(E2:E9)": {"#DIV/0!", "#DIV/0!"}, "=STDEV(E2:E9)": {"#DIV/0!", "#DIV/0!"},
@ -4666,7 +4678,7 @@ func TestCalcCellValue(t *testing.T) {
f := prepareCalcData(cellData) f := prepareCalcData(cellData)
result, err := f.CalcCellValue("Sheet1", "A1") result, err := f.CalcCellValue("Sheet1", "A1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "", result) assert.Equal(t, "1", result)
// Test get calculated cell value on not exists worksheet // Test get calculated cell value on not exists worksheet
f = prepareCalcData(cellData) f = prepareCalcData(cellData)
_, err = f.CalcCellValue("SheetN", "A1") _, 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\")")) assert.NoError(t, f.SetCellFormula("Sheet1", "D1", "=IF(\"B1_as_string\"=defined_name1,\"YES\",\"NO\")"))
result, err = f.CalcCellValue("Sheet1", "D1") result, err = f.CalcCellValue("Sheet1", "D1")
assert.NoError(t, err) 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) { func TestCalcISBLANK(t *testing.T) {
@ -6140,14 +6152,14 @@ func TestCalcBetainvProbIterator(t *testing.T) {
func TestNestedFunctionsWithOperators(t *testing.T) { func TestNestedFunctionsWithOperators(t *testing.T) {
f := NewFile() f := NewFile()
formulaList := map[string]string{ formulaList := map[string]string{
`=LEN("KEEP")`: "4", "=LEN(\"KEEP\")": "4",
`=LEN("REMOVEKEEP") - LEN("REMOVE")`: "4", "=LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\")": "4",
`=RIGHT("REMOVEKEEP", 4)`: "KEEP", "=RIGHT(\"REMOVEKEEP\", 4)": "KEEP",
`=RIGHT("REMOVEKEEP", 10 - 6))`: "KEEP", "=RIGHT(\"REMOVEKEEP\", 10 - 6))": "KEEP",
`=RIGHT("REMOVEKEEP", LEN("REMOVEKEEP") - 6)`: "KEEP", "=RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - 6)": "KEEP",
`=RIGHT("REMOVEKEEP", LEN("REMOVEKEEP") - LEN("REMOV") - 1)`: "KEEP", "=RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOV\") - 1)": "KEEP",
`=RIGHT("REMOVEKEEP", 10 - LEN("REMOVE"))`: "KEEP", "=RIGHT(\"REMOVEKEEP\", 10 - LEN(\"REMOVE\"))": "KEEP",
`=RIGHT("REMOVEKEEP", LEN("REMOVEKEEP") - LEN("REMOVE"))`: "KEEP", "=RIGHT(\"REMOVEKEEP\", LEN(\"REMOVEKEEP\") - LEN(\"REMOVE\"))": "KEEP",
} }
for formula, expected := range formulaList { for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula)) 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 // Test set conditional format on not exists worksheet
assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist") assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist")
// Test set conditional format with invalid sheet name // 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")) err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
assert.NoError(t, err) assert.NoError(t, err)
// Set conditional format with illegal valid type // 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{ []ConditionalFormatOptions{
{ {
Type: "", Type: "",

194
styles.go
View File

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

View File

@ -195,12 +195,7 @@ func TestSetConditionalFormat(t *testing.T) {
for _, val := range []string{ for _, val := range []string{
"date", "date",
"time", "time",
"text",
"time_period", "time_period",
"blanks",
"no_blanks",
"errors",
"no_errors",
} { } {
assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1", []ConditionalFormatOptions{{Type: val}})) assert.Equal(t, ErrParameterInvalid, f.SetConditionalFormat("Sheet1", "A1", []ConditionalFormatOptions{{Type: val}}))
} }
@ -210,6 +205,10 @@ func TestGetConditionalFormats(t *testing.T) {
for _, format := range [][]ConditionalFormatOptions{ for _, format := range [][]ConditionalFormatOptions{
{{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}}, {{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}},
{{Type: "cell", Format: 1, Criteria: "between", MinValue: "6", MaxValue: "8"}}, {{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: "top", Format: 1, Criteria: "=", Value: "6"}},
{{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}}, {{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}},
{{Type: "average", AboveAverage: true, Format: 1, Criteria: "="}}, {{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: "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: "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: "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}}, {{Type: "icon_set", IconStyle: "3Arrows", ReverseIcons: true, IconsOnly: true}},
} { } {
f := NewFile() f := NewFile()
err := f.SetConditionalFormat("Sheet1", "A1:A2", format) err := f.SetConditionalFormat("Sheet1", "A2:A1", format)
assert.NoError(t, err) assert.NoError(t, err)
opts, err := f.GetConditionalFormats("Sheet1") opts, err := f.GetConditionalFormats("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)