new formula fn: IF, LEN; not equal operator support and faster numeric precision process
This commit is contained in:
parent
e568319bbc
commit
b84bd1abc0
70
calc.go
70
calc.go
|
@ -110,6 +110,7 @@ var tokenPriority = map[string]int{
|
|||
"+": 3,
|
||||
"-": 3,
|
||||
"=": 2,
|
||||
"<>": 2,
|
||||
"<": 2,
|
||||
"<=": 2,
|
||||
">": 2,
|
||||
|
@ -151,11 +152,9 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
|
|||
return
|
||||
}
|
||||
result = token.TValue
|
||||
if len(result) > 16 {
|
||||
num, e := roundPrecision(result)
|
||||
if e != nil {
|
||||
return result, err
|
||||
}
|
||||
isNum, precision := isNumeric(result)
|
||||
if isNum && precision > 15 {
|
||||
num, _ := roundPrecision(result)
|
||||
result = strings.ToUpper(num)
|
||||
}
|
||||
return
|
||||
|
@ -353,6 +352,12 @@ func calcEq(rOpd, lOpd string, opdStack *Stack) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// calcNEq evaluate not equal arithmetic operations.
|
||||
func calcNEq(rOpd, lOpd string, opdStack *Stack) error {
|
||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd != lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcL evaluate less than arithmetic operations.
|
||||
func calcL(rOpd, lOpd string, opdStack *Stack) error {
|
||||
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
|
||||
|
@ -498,6 +503,7 @@ func calculate(opdStack *Stack, opt efp.Token) error {
|
|||
"/": calcDiv,
|
||||
"+": calcAdd,
|
||||
"=": calcEq,
|
||||
"<>": calcNEq,
|
||||
"<": calcL,
|
||||
"<=": calcLe,
|
||||
">": calcG,
|
||||
|
@ -3400,6 +3406,20 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) (result string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// LEN returns the length of a supplied text string. The syntax of the
|
||||
// function is:
|
||||
//
|
||||
// LEN(text)
|
||||
//
|
||||
func (fn *formulaFuncs) LEN(argsList *list.List) (result string, err error) {
|
||||
if argsList.Len() != 1 {
|
||||
err = errors.New("LEN requires 1 string argument")
|
||||
return
|
||||
}
|
||||
result = strconv.Itoa(len(argsList.Front().Value.(formulaArg).String))
|
||||
return
|
||||
}
|
||||
|
||||
// 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:
|
||||
|
@ -3469,3 +3489,43 @@ func (fn *formulaFuncs) UPPER(argsList *list.List) (result string, err error) {
|
|||
result = strings.ToUpper(argsList.Front().Value.(formulaArg).String)
|
||||
return
|
||||
}
|
||||
|
||||
// Conditional Functions
|
||||
|
||||
// IF function tests a supplied condition and returns one result if the
|
||||
// condition evaluates to TRUE, and another result if the condition evaluates
|
||||
// to FALSE. The syntax of the function is:
|
||||
//
|
||||
// IF( logical_test, value_if_true, value_if_false )
|
||||
//
|
||||
func (fn *formulaFuncs) IF(argsList *list.List) (result string, err error) {
|
||||
if argsList.Len() == 0 {
|
||||
err = errors.New("IF requires at least 1 argument")
|
||||
return
|
||||
}
|
||||
if argsList.Len() > 3 {
|
||||
err = errors.New("IF accepts at most 3 arguments")
|
||||
return
|
||||
}
|
||||
token := argsList.Front().Value.(formulaArg)
|
||||
var cond bool
|
||||
switch token.Type {
|
||||
case ArgString:
|
||||
if cond, err = strconv.ParseBool(token.String); err != nil {
|
||||
err = errors.New(formulaErrorVALUE)
|
||||
return
|
||||
}
|
||||
if argsList.Len() == 1 {
|
||||
result = strings.ToUpper(strconv.FormatBool(cond))
|
||||
return
|
||||
}
|
||||
if cond {
|
||||
result = argsList.Front().Next().Value.(formulaArg).String
|
||||
return
|
||||
}
|
||||
if argsList.Len() == 3 {
|
||||
result = argsList.Back().Value.(formulaArg).String
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
24
calc_test.go
24
calc_test.go
|
@ -323,13 +323,13 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=ROUND(991,-1)": "990",
|
||||
// ROUNDDOWN
|
||||
"=ROUNDDOWN(99.999,1)": "99.9",
|
||||
"=ROUNDDOWN(99.999,2)": "99.99000000000001",
|
||||
"=ROUNDDOWN(99.999,2)": "99.99000000000002",
|
||||
"=ROUNDDOWN(99.999,0)": "99",
|
||||
"=ROUNDDOWN(99.999,-1)": "90",
|
||||
"=ROUNDDOWN(-99.999,2)": "-99.99000000000001",
|
||||
"=ROUNDDOWN(-99.999,2)": "-99.99000000000002",
|
||||
"=ROUNDDOWN(-99.999,-1)": "-90",
|
||||
// ROUNDUP
|
||||
"=ROUNDUP(11.111,1)": "11.200000000000003",
|
||||
// ROUNDUP`
|
||||
"=ROUNDUP(11.111,1)": "11.200000000000001",
|
||||
"=ROUNDUP(11.111,2)": "11.120000000000003",
|
||||
"=ROUNDUP(11.111,0)": "12",
|
||||
"=ROUNDUP(11.111,-1)": "20",
|
||||
|
@ -467,6 +467,9 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// CLEAN
|
||||
"=CLEAN(\"\u0009clean text\")": "clean text",
|
||||
"=CLEAN(0)": "0",
|
||||
// LEN
|
||||
"=LEN(\"\")": "0",
|
||||
"=LEN(D1)": "5",
|
||||
// TRIM
|
||||
"=TRIM(\" trim text \")": "trim text",
|
||||
"=TRIM(0)": "0",
|
||||
|
@ -485,6 +488,12 @@ func TestCalcCellValue(t *testing.T) {
|
|||
"=UPPER(\"TEST\")": "TEST",
|
||||
"=UPPER(\"Test\")": "TEST",
|
||||
"=UPPER(\"TEST 123\")": "TEST 123",
|
||||
// Conditional Functions
|
||||
// IF
|
||||
"=IF(1=1)": "TRUE",
|
||||
"=IF(1<>1)": "FALSE",
|
||||
"=IF(5<0, \"negative\", \"positive\")": "positive",
|
||||
"=IF(-2<0, \"negative\", \"positive\")": "negative",
|
||||
}
|
||||
for formula, expected := range mathCalc {
|
||||
f := prepareData()
|
||||
|
@ -805,6 +814,8 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// CLEAN
|
||||
"=CLEAN()": "CLEAN requires 1 argument",
|
||||
"=CLEAN(1,2)": "CLEAN requires 1 argument",
|
||||
// LEN
|
||||
"=LEN()": "LEN requires 1 string argument",
|
||||
// TRIM
|
||||
"=TRIM()": "TRIM requires 1 argument",
|
||||
"=TRIM(1,2)": "TRIM requires 1 argument",
|
||||
|
@ -817,6 +828,11 @@ func TestCalcCellValue(t *testing.T) {
|
|||
// PROPER
|
||||
"=PROPER()": "PROPER requires 1 argument",
|
||||
"=PROPER(1,2)": "PROPER requires 1 argument",
|
||||
// Conditional Functions
|
||||
// IF
|
||||
"=IF()": "IF requires at least 1 argument",
|
||||
"=IF(0,1,2,3)": "IF accepts at most 3 arguments",
|
||||
"=IF(D1,1,2)": "#VALUE!",
|
||||
}
|
||||
for formula, expected := range mathCalcError {
|
||||
f := prepareData()
|
||||
|
|
27
lib.go
27
lib.go
|
@ -403,8 +403,8 @@ func (f *File) addNameSpaces(path string, ns xml.Attr) {
|
|||
}
|
||||
}
|
||||
|
||||
// setIgnorableNameSpace provides a function to set XML namespace as ignorable by the given
|
||||
// attribute.
|
||||
// setIgnorableNameSpace provides a function to set XML namespace as ignorable
|
||||
// by the given attribute.
|
||||
func (f *File) setIgnorableNameSpace(path string, index int, ns xml.Attr) {
|
||||
ignorableNS := []string{"c14", "cdr14", "a14", "pic14", "x14", "xdr14", "x14ac", "dsp", "mso14", "dgm14", "x15", "x12ac", "x15ac", "xr", "xr2", "xr3", "xr4", "xr5", "xr6", "xr7", "xr8", "xr9", "xr10", "xr11", "xr12", "xr13", "xr14", "xr15", "x15", "x16", "x16r2", "mo", "mx", "mv", "o", "v"}
|
||||
if inStrSlice(strings.Fields(f.xmlAttr[path][index].Value), ns.Name.Local) == -1 && inStrSlice(ignorableNS, ns.Name.Local) != -1 {
|
||||
|
@ -418,6 +418,29 @@ func (f *File) addSheetNameSpace(sheet string, ns xml.Attr) {
|
|||
f.addNameSpaces(name, ns)
|
||||
}
|
||||
|
||||
// isNumeric determines whether an expression is a valid numeric type and get
|
||||
// the precision for the numeric.
|
||||
func isNumeric(s string) (bool, int) {
|
||||
dot := false
|
||||
p := 0
|
||||
for i, v := range s {
|
||||
if v == '.' {
|
||||
if dot {
|
||||
return false, 0
|
||||
}
|
||||
dot = true
|
||||
} else if v < '0' || v > '9' {
|
||||
if i == 0 && v == '-' {
|
||||
continue
|
||||
}
|
||||
return false, 0
|
||||
} else if dot {
|
||||
p++
|
||||
}
|
||||
}
|
||||
return true, p
|
||||
}
|
||||
|
||||
// Stack defined an abstract data type that serves as a collection of elements.
|
||||
type Stack struct {
|
||||
list *list.List
|
||||
|
|
10
rows.go
10
rows.go
|
@ -20,7 +20,6 @@ import (
|
|||
"log"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mohae/deepcopy"
|
||||
)
|
||||
|
@ -346,12 +345,9 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
|
|||
}
|
||||
return f.formattedValue(c.S, c.V), nil
|
||||
default:
|
||||
splited := strings.Split(c.V, ".")
|
||||
if len(splited) == 2 && len(splited[1]) > 15 {
|
||||
val, err := roundPrecision(c.V)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
isNum, precision := isNumeric(c.V)
|
||||
if isNum && precision > 15 {
|
||||
val, _ := roundPrecision(c.V)
|
||||
if val != c.V {
|
||||
return f.formattedValue(c.S, val), nil
|
||||
}
|
||||
|
|
11
rows_test.go
11
rows_test.go
|
@ -835,9 +835,14 @@ func TestGetValueFromNumber(t *testing.T) {
|
|||
assert.Equal(t, "2.22", val)
|
||||
|
||||
c = &xlsxC{T: "n", V: "2.220000ddsf0000000002-r"}
|
||||
_, err = c.getValueFrom(f, d)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "strconv.ParseFloat: parsing \"2.220000ddsf0000000002-r\": invalid syntax", err.Error())
|
||||
val, err = c.getValueFrom(f, d)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "2.220000ddsf0000000002-r", val)
|
||||
|
||||
c = &xlsxC{T: "n", V: "2.2."}
|
||||
val, err = c.getValueFrom(f, d)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "2.2.", val)
|
||||
}
|
||||
|
||||
func TestErrSheetNotExistError(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue