|
|
@@ -18,6 +18,7 @@ import (
|
|
|
"fmt"
|
|
|
"math"
|
|
|
"math/rand"
|
|
|
+ "net/url"
|
|
|
"reflect"
|
|
|
"regexp"
|
|
|
"sort"
|
|
|
@@ -99,13 +100,15 @@ const (
|
|
|
|
|
|
// formulaArg is the argument of a formula or function.
|
|
|
type formulaArg struct {
|
|
|
- Number float64
|
|
|
- String string
|
|
|
- List []formulaArg
|
|
|
- Matrix [][]formulaArg
|
|
|
- Boolean bool
|
|
|
- Error string
|
|
|
- Type ArgType
|
|
|
+ f *File
|
|
|
+ SheetName string
|
|
|
+ Number float64
|
|
|
+ String string
|
|
|
+ List []formulaArg
|
|
|
+ Matrix [][]formulaArg
|
|
|
+ Boolean bool
|
|
|
+ Error string
|
|
|
+ Type ArgType
|
|
|
}
|
|
|
|
|
|
// Value returns a string data type of the formula argument.
|
|
|
@@ -162,7 +165,10 @@ func (fa formulaArg) ToBool() formulaArg {
|
|
|
}
|
|
|
|
|
|
// formulaFuncs is the type of the formula functions.
|
|
|
-type formulaFuncs struct{}
|
|
|
+type formulaFuncs struct {
|
|
|
+ f *File
|
|
|
+ sheet string
|
|
|
+}
|
|
|
|
|
|
// tokenPriority defined basic arithmetic operator priority.
|
|
|
var tokenPriority = map[string]int{
|
|
|
@@ -184,7 +190,7 @@ var tokenPriority = map[string]int{
|
|
|
// feature is currently in working processing. Array formula, table formula
|
|
|
// and some other formulas are not supported currently.
|
|
|
//
|
|
|
-// Supported formulas:
|
|
|
+// Supported formula functions:
|
|
|
//
|
|
|
// ABS
|
|
|
// ACOS
|
|
|
@@ -215,16 +221,20 @@ var tokenPriority = map[string]int{
|
|
|
// DATE
|
|
|
// DECIMAL
|
|
|
// DEGREES
|
|
|
+// ENCODEURL
|
|
|
// EVEN
|
|
|
+// EXACT
|
|
|
// EXP
|
|
|
// FACT
|
|
|
// FACTDOUBLE
|
|
|
+// FALSE
|
|
|
// FLOOR
|
|
|
// FLOOR.MATH
|
|
|
// FLOOR.PRECISE
|
|
|
// GCD
|
|
|
// HLOOKUP
|
|
|
// IF
|
|
|
+// IFERROR
|
|
|
// INT
|
|
|
// ISBLANK
|
|
|
// ISERR
|
|
|
@@ -234,9 +244,11 @@ var tokenPriority = map[string]int{
|
|
|
// ISNONTEXT
|
|
|
// ISNUMBER
|
|
|
// ISODD
|
|
|
+// ISTEXT
|
|
|
// ISO.CEILING
|
|
|
// LCM
|
|
|
// LEN
|
|
|
+// LENB
|
|
|
// LN
|
|
|
// LOG
|
|
|
// LOG10
|
|
|
@@ -249,6 +261,7 @@ var tokenPriority = map[string]int{
|
|
|
// MULTINOMIAL
|
|
|
// MUNIT
|
|
|
// NA
|
|
|
+// NOT
|
|
|
// ODD
|
|
|
// OR
|
|
|
// PI
|
|
|
@@ -259,11 +272,13 @@ var tokenPriority = map[string]int{
|
|
|
// RADIANS
|
|
|
// RAND
|
|
|
// RANDBETWEEN
|
|
|
+// REPT
|
|
|
// ROUND
|
|
|
// ROUNDDOWN
|
|
|
// ROUNDUP
|
|
|
// SEC
|
|
|
// SECH
|
|
|
+// SHEET
|
|
|
// SIGN
|
|
|
// SIN
|
|
|
// SINH
|
|
|
@@ -275,6 +290,7 @@ var tokenPriority = map[string]int{
|
|
|
// TAN
|
|
|
// TANH
|
|
|
// TRIM
|
|
|
+// TRUE
|
|
|
// TRUNC
|
|
|
// UPPER
|
|
|
// VLOOKUP
|
|
|
@@ -445,15 +461,12 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
|
|
|
// calculate trigger
|
|
|
topOpt := opftStack.Peek().(efp.Token)
|
|
|
if err := calculate(opfdStack, topOpt); err != nil {
|
|
|
- return efp.Token{}, err
|
|
|
+ argsStack.Peek().(*list.List).PushFront(newErrorFormulaArg(formulaErrorVALUE, err.Error()))
|
|
|
}
|
|
|
opftStack.Pop()
|
|
|
}
|
|
|
if !opfdStack.Empty() {
|
|
|
- argsStack.Peek().(*list.List).PushBack(formulaArg{
|
|
|
- String: opfdStack.Pop().(efp.Token).TValue,
|
|
|
- Type: ArgString,
|
|
|
- })
|
|
|
+ argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
|
|
|
}
|
|
|
continue
|
|
|
}
|
|
|
@@ -462,20 +475,14 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
|
|
|
if token.TType == efp.OperatorsInfix && token.TSubType == efp.TokenSubTypeLogical {
|
|
|
}
|
|
|
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeLogical {
|
|
|
- argsStack.Peek().(*list.List).PushBack(formulaArg{
|
|
|
- String: token.TValue,
|
|
|
- Type: ArgString,
|
|
|
- })
|
|
|
+ argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
|
|
|
}
|
|
|
|
|
|
// current token is text
|
|
|
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
|
|
|
- argsStack.Peek().(*list.List).PushBack(formulaArg{
|
|
|
- String: token.TValue,
|
|
|
- Type: ArgString,
|
|
|
- })
|
|
|
+ argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(token.TValue))
|
|
|
}
|
|
|
- if err = evalInfixExpFunc(token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
|
|
|
+ if err = f.evalInfixExpFunc(sheet, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
|
|
|
return efp.Token{}, err
|
|
|
}
|
|
|
}
|
|
|
@@ -494,7 +501,7 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
|
|
|
}
|
|
|
|
|
|
// evalInfixExpFunc evaluate formula function in the infix expression.
|
|
|
-func evalInfixExpFunc(token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
|
|
|
+func (f *File) evalInfixExpFunc(sheet string, token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
|
|
|
if !isFunctionStopToken(token) {
|
|
|
return nil
|
|
|
}
|
|
|
@@ -510,16 +517,13 @@ func evalInfixExpFunc(token, nextToken efp.Token, opfStack, opdStack, opftStack,
|
|
|
|
|
|
// push opfd to args
|
|
|
if opfdStack.Len() > 0 {
|
|
|
- argsStack.Peek().(*list.List).PushBack(formulaArg{
|
|
|
- String: opfdStack.Pop().(efp.Token).TValue,
|
|
|
- Type: ArgString,
|
|
|
- })
|
|
|
+ argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
|
|
|
}
|
|
|
// call formula function to evaluate
|
|
|
- arg := callFuncByName(&formulaFuncs{}, strings.NewReplacer(
|
|
|
+ arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet}, strings.NewReplacer(
|
|
|
"_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue),
|
|
|
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
|
|
|
- if arg.Type == ArgError {
|
|
|
+ if arg.Type == ArgError && opfStack.Len() == 1 {
|
|
|
return errors.New(arg.Value())
|
|
|
}
|
|
|
argsStack.Pop()
|
|
|
@@ -793,7 +797,7 @@ func isEndParenthesesToken(token efp.Token) bool {
|
|
|
// token.
|
|
|
func isOperatorPrefixToken(token efp.Token) bool {
|
|
|
_, ok := tokenPriority[token.TValue]
|
|
|
- if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || ok {
|
|
|
+ if (token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix) || (ok && token.TType == efp.TokenTypeOperatorInfix) {
|
|
|
return true
|
|
|
}
|
|
|
return false
|
|
|
@@ -3274,6 +3278,22 @@ func (fn *formulaFuncs) ISODD(argsList *list.List) formulaArg {
|
|
|
return newStringFormulaArg(result)
|
|
|
}
|
|
|
|
|
|
+// ISTEXT function tests if a supplied value is text, and if so, returns TRUE;
|
|
|
+// Otherwise, the function returns FALSE. The syntax of the function is:
|
|
|
+//
|
|
|
+// ISTEXT(value)
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) ISTEXT(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 1 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "ISTEXT requires 1 argument")
|
|
|
+ }
|
|
|
+ token := argsList.Front().Value.(formulaArg)
|
|
|
+ if token.ToNumber().Type != ArgError {
|
|
|
+ return newBoolFormulaArg(false)
|
|
|
+ }
|
|
|
+ return newBoolFormulaArg(token.Type == ArgString)
|
|
|
+}
|
|
|
+
|
|
|
// NA function returns the Excel #N/A error. This error message has the
|
|
|
// meaning 'value not available' and is produced when an Excel Formula is
|
|
|
// unable to find a value that it needs. The syntax of the function is:
|
|
|
@@ -3287,6 +3307,18 @@ func (fn *formulaFuncs) NA(argsList *list.List) formulaArg {
|
|
|
return newStringFormulaArg(formulaErrorNA)
|
|
|
}
|
|
|
|
|
|
+// SHEET function returns the Sheet number for a specified reference. The
|
|
|
+// syntax of the function is:
|
|
|
+//
|
|
|
+// SHEET()
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) SHEET(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 0 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "SHEET accepts no arguments")
|
|
|
+ }
|
|
|
+ return newNumberFormulaArg(float64(fn.f.GetSheetIndex(fn.sheet) + 1))
|
|
|
+}
|
|
|
+
|
|
|
// Logical Functions
|
|
|
|
|
|
// AND function tests a number of supplied conditions and returns TRUE or
|
|
|
@@ -3330,6 +3362,64 @@ func (fn *formulaFuncs) AND(argsList *list.List) formulaArg {
|
|
|
return newBoolFormulaArg(and)
|
|
|
}
|
|
|
|
|
|
+// FALSE function function returns the logical value FALSE. The syntax of the
|
|
|
+// function is:
|
|
|
+//
|
|
|
+// FALSE()
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) FALSE(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 0 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "FALSE takes no arguments")
|
|
|
+ }
|
|
|
+ return newBoolFormulaArg(false)
|
|
|
+}
|
|
|
+
|
|
|
+// IFERROR function receives two values (or expressions) and tests if the
|
|
|
+// first of these evaluates to an error. The syntax of the function is:
|
|
|
+//
|
|
|
+// IFERROR(value,value_if_error)
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 2 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "IFERROR requires 2 arguments")
|
|
|
+ }
|
|
|
+ value := argsList.Front().Value.(formulaArg)
|
|
|
+ if value.Type != ArgError {
|
|
|
+ if value.Type == ArgEmpty {
|
|
|
+ return newNumberFormulaArg(0)
|
|
|
+ }
|
|
|
+ return value
|
|
|
+ }
|
|
|
+ return argsList.Back().Value.(formulaArg)
|
|
|
+}
|
|
|
+
|
|
|
+// NOT function returns the opposite to a supplied logical value. The syntax
|
|
|
+// of the function is:
|
|
|
+//
|
|
|
+// NOT(logical)
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) NOT(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 1 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "NOT requires 1 argument")
|
|
|
+ }
|
|
|
+ token := argsList.Front().Value.(formulaArg)
|
|
|
+ switch token.Type {
|
|
|
+ case ArgString, ArgList:
|
|
|
+ if strings.ToUpper(token.String) == "TRUE" {
|
|
|
+ return newBoolFormulaArg(false)
|
|
|
+ }
|
|
|
+ if strings.ToUpper(token.String) == "FALSE" {
|
|
|
+ return newBoolFormulaArg(true)
|
|
|
+ }
|
|
|
+ case ArgNumber:
|
|
|
+ return newBoolFormulaArg(!(token.Number != 0))
|
|
|
+ case ArgError:
|
|
|
+
|
|
|
+ return token
|
|
|
+ }
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "NOT expects 1 boolean or numeric argument")
|
|
|
+}
|
|
|
+
|
|
|
// OR function tests a number of supplied conditions and returns either TRUE
|
|
|
// or FALSE. The syntax of the function is:
|
|
|
//
|
|
|
@@ -3372,6 +3462,18 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
|
|
|
return newStringFormulaArg(strings.ToUpper(strconv.FormatBool(or)))
|
|
|
}
|
|
|
|
|
|
+// TRUE function returns the logical value TRUE. The syntax of the function
|
|
|
+// is:
|
|
|
+//
|
|
|
+// TRUE()
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) TRUE(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 0 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "TRUE takes no arguments")
|
|
|
+ }
|
|
|
+ return newBoolFormulaArg(true)
|
|
|
+}
|
|
|
+
|
|
|
// Date and Time Functions
|
|
|
|
|
|
// DATE returns a date, from a user-supplied year, month and day. The syntax
|
|
|
@@ -3434,6 +3536,21 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg {
|
|
|
return newStringFormulaArg(b.String())
|
|
|
}
|
|
|
|
|
|
+// EXACT function tests if two supplied text strings or values are exactly
|
|
|
+// equal and if so, returns TRUE; Otherwise, the function returns FALSE. The
|
|
|
+// function is case-sensitive. The syntax of the function is:
|
|
|
+//
|
|
|
+// EXACT(text1,text2)
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) EXACT(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 2 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "EXACT requires 2 arguments")
|
|
|
+ }
|
|
|
+ text1 := argsList.Front().Value.(formulaArg).Value()
|
|
|
+ text2 := argsList.Back().Value.(formulaArg).Value()
|
|
|
+ return newBoolFormulaArg(text1 == text2)
|
|
|
+}
|
|
|
+
|
|
|
// LEN returns the length of a supplied text string. The syntax of the
|
|
|
// function is:
|
|
|
//
|
|
|
@@ -3446,6 +3563,22 @@ func (fn *formulaFuncs) LEN(argsList *list.List) formulaArg {
|
|
|
return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String)))
|
|
|
}
|
|
|
|
|
|
+// LENB returns the number of bytes used to represent the characters in a text
|
|
|
+// string. LENB counts 2 bytes per character only when a DBCS language is set
|
|
|
+// as the default language. Otherwise LENB behaves the same as LEN, counting
|
|
|
+// 1 byte per character. The syntax of the function is:
|
|
|
+//
|
|
|
+// LENB(text)
|
|
|
+//
|
|
|
+// TODO: the languages that support DBCS include Japanese, Chinese
|
|
|
+// (Simplified), Chinese (Traditional), and Korean.
|
|
|
+func (fn *formulaFuncs) LENB(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 1 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "LENB requires 1 string argument")
|
|
|
+ }
|
|
|
+ return newStringFormulaArg(strconv.Itoa(len(argsList.Front().Value.(formulaArg).String)))
|
|
|
+}
|
|
|
+
|
|
|
// TRIM removes extra spaces (i.e. all spaces except for single spaces between
|
|
|
// words or characters) from a supplied text string. The syntax of the
|
|
|
// function is:
|
|
|
@@ -3495,6 +3628,36 @@ func (fn *formulaFuncs) PROPER(argsList *list.List) formulaArg {
|
|
|
return newStringFormulaArg(buf.String())
|
|
|
}
|
|
|
|
|
|
+// REPT function returns a supplied text string, repeated a specified number
|
|
|
+// of times. The syntax of the function is:
|
|
|
+//
|
|
|
+// REPT(text,number_times)
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) REPT(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 2 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "REPT requires 2 arguments")
|
|
|
+ }
|
|
|
+ text := argsList.Front().Value.(formulaArg)
|
|
|
+ if text.Type != ArgString {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "REPT requires first argument to be a string")
|
|
|
+ }
|
|
|
+ times := argsList.Back().Value.(formulaArg).ToNumber()
|
|
|
+ if times.Type != ArgNumber {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be a number")
|
|
|
+ }
|
|
|
+ if times.Number < 0 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "REPT requires second argument to be >= 0")
|
|
|
+ }
|
|
|
+ if times.Number == 0 {
|
|
|
+ return newStringFormulaArg("")
|
|
|
+ }
|
|
|
+ buf := bytes.Buffer{}
|
|
|
+ for i := 0; i < int(times.Number); i++ {
|
|
|
+ buf.WriteString(text.String)
|
|
|
+ }
|
|
|
+ return newStringFormulaArg(buf.String())
|
|
|
+}
|
|
|
+
|
|
|
// UPPER converts all characters in a supplied text string to upper case. The
|
|
|
// syntax of the function is:
|
|
|
//
|
|
|
@@ -3976,3 +4139,16 @@ func lookupCol(arr formulaArg) []formulaArg {
|
|
|
}
|
|
|
return col
|
|
|
}
|
|
|
+
|
|
|
+// Web Functions
|
|
|
+
|
|
|
+// ENCODEURL function returns a URL-encoded string, replacing certain non-alphanumeric characters with the percentage symbol (%) and a hexadecimal number. The syntax of the function is:
|
|
|
+//
|
|
|
+// ENCODEURL(url)
|
|
|
+//
|
|
|
+func (fn *formulaFuncs) ENCODEURL(argsList *list.List) formulaArg {
|
|
|
+ if argsList.Len() != 1 {
|
|
|
+ return newErrorFormulaArg(formulaErrorVALUE, "ENCODEURL requires 1 argument")
|
|
|
+ }
|
|
|
+ return newStringFormulaArg(strings.Replace(url.QueryEscape(argsList.Front().Value.(formulaArg).Value()), "+", "%20", -1))
|
|
|
+}
|