forked from p30928647/excelize
#65 fn: LARGE, SMALL, SUBSTITUTE, refactor ARABIC
This commit is contained in:
parent
afe2ebc261
commit
7ef1892f32
168
calc.go
168
calc.go
|
@ -167,15 +167,17 @@ func (fa formulaArg) ToBool() formulaArg {
|
||||||
|
|
||||||
// ToList returns a formula argument with array data type.
|
// ToList returns a formula argument with array data type.
|
||||||
func (fa formulaArg) ToList() []formulaArg {
|
func (fa formulaArg) ToList() []formulaArg {
|
||||||
if fa.Type == ArgMatrix {
|
switch fa.Type {
|
||||||
|
case ArgMatrix:
|
||||||
list := []formulaArg{}
|
list := []formulaArg{}
|
||||||
for _, row := range fa.Matrix {
|
for _, row := range fa.Matrix {
|
||||||
list = append(list, row...)
|
list = append(list, row...)
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
case ArgList:
|
||||||
if fa.Type == ArgList {
|
|
||||||
return fa.List
|
return fa.List
|
||||||
|
case ArgNumber, ArgString, ArgError, ArgUnknown:
|
||||||
|
return []formulaArg{fa}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -294,6 +296,7 @@ var tokenPriority = map[string]int{
|
||||||
// ISTEXT
|
// ISTEXT
|
||||||
// ISO.CEILING
|
// ISO.CEILING
|
||||||
// KURT
|
// KURT
|
||||||
|
// LARGE
|
||||||
// LCM
|
// LCM
|
||||||
// LEFT
|
// LEFT
|
||||||
// LEFTB
|
// LEFTB
|
||||||
|
@ -348,10 +351,12 @@ var tokenPriority = map[string]int{
|
||||||
// SIGN
|
// SIGN
|
||||||
// SIN
|
// SIN
|
||||||
// SINH
|
// SINH
|
||||||
|
// SMALL
|
||||||
// SQRT
|
// SQRT
|
||||||
// SQRTPI
|
// SQRTPI
|
||||||
// STDEV
|
// STDEV
|
||||||
// STDEVA
|
// STDEVA
|
||||||
|
// SUBSTITUTE
|
||||||
// SUM
|
// SUM
|
||||||
// SUMIF
|
// SUMIF
|
||||||
// SUMSQ
|
// SUMSQ
|
||||||
|
@ -454,10 +459,7 @@ func newEmptyFormulaArg() formulaArg {
|
||||||
// opf - Operation formula
|
// opf - Operation formula
|
||||||
// opfd - Operand of the operation formula
|
// opfd - Operand of the operation formula
|
||||||
// opft - Operator of the operation formula
|
// opft - Operator of the operation formula
|
||||||
//
|
// args - Arguments list of the operation formula
|
||||||
// Evaluate arguments of the operation formula by list:
|
|
||||||
//
|
|
||||||
// args - Arguments of the operation formula
|
|
||||||
//
|
//
|
||||||
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
|
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
|
||||||
//
|
//
|
||||||
|
@ -1705,28 +1707,48 @@ func (fn *formulaFuncs) ARABIC(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 1 {
|
if argsList.Len() != 1 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "ARABIC requires 1 numeric argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "ARABIC requires 1 numeric argument")
|
||||||
}
|
}
|
||||||
charMap := map[rune]float64{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
|
text := argsList.Front().Value.(formulaArg).Value()
|
||||||
val, last, prefix := 0.0, 0.0, 1.0
|
if len(text) > 255 {
|
||||||
for _, char := range argsList.Front().Value.(formulaArg).String {
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
digit := 0.0
|
}
|
||||||
if char == '-' {
|
text = strings.ToUpper(text)
|
||||||
prefix = -1
|
number, actualStart, index, isNegative := 0, 0, len(text)-1, false
|
||||||
|
startIndex, subtractNumber, currentPartValue, currentCharValue, prevCharValue := 0, 0, 0, 0, -1
|
||||||
|
for index >= 0 && text[index] == ' ' {
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
for actualStart <= index && text[actualStart] == ' ' {
|
||||||
|
actualStart++
|
||||||
|
}
|
||||||
|
if actualStart <= index && text[actualStart] == '-' {
|
||||||
|
isNegative = true
|
||||||
|
actualStart++
|
||||||
|
}
|
||||||
|
charMap := map[rune]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
|
||||||
|
for index >= actualStart {
|
||||||
|
startIndex = index
|
||||||
|
startChar := text[startIndex]
|
||||||
|
index--
|
||||||
|
for index >= actualStart && (text[index]|' ') == startChar {
|
||||||
|
index--
|
||||||
|
}
|
||||||
|
currentCharValue = charMap[rune(startChar)]
|
||||||
|
currentPartValue = (startIndex - index) * currentCharValue
|
||||||
|
if currentCharValue >= prevCharValue {
|
||||||
|
number += currentPartValue - subtractNumber
|
||||||
|
prevCharValue = currentCharValue
|
||||||
|
subtractNumber = 0
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
digit = charMap[char]
|
subtractNumber += currentPartValue
|
||||||
val += digit
|
|
||||||
switch {
|
|
||||||
case last == digit && (last == 5 || last == 50 || last == 500):
|
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
||||||
case 2*last == digit:
|
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
||||||
}
|
|
||||||
if last < digit {
|
|
||||||
val -= 2 * last
|
|
||||||
}
|
|
||||||
last = digit
|
|
||||||
}
|
}
|
||||||
return newNumberFormulaArg(prefix * val)
|
if subtractNumber != 0 {
|
||||||
|
number -= subtractNumber
|
||||||
|
}
|
||||||
|
if isNegative {
|
||||||
|
number = -number
|
||||||
|
}
|
||||||
|
return newNumberFormulaArg(float64(number))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASIN function calculates the arcsine (i.e. the inverse sine) of a given
|
// ASIN function calculates the arcsine (i.e. the inverse sine) of a given
|
||||||
|
@ -3933,6 +3955,45 @@ func (fn *formulaFuncs) KURT(argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
|
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// kth is an implementation of the formula function LARGE and SMALL.
|
||||||
|
func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg {
|
||||||
|
if argsList.Len() != 2 {
|
||||||
|
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
|
||||||
|
}
|
||||||
|
array := argsList.Front().Value.(formulaArg).ToList()
|
||||||
|
kArg := argsList.Back().Value.(formulaArg).ToNumber()
|
||||||
|
if kArg.Type != ArgNumber {
|
||||||
|
return kArg
|
||||||
|
}
|
||||||
|
k := int(kArg.Number)
|
||||||
|
if k < 1 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, "k should be > 0")
|
||||||
|
}
|
||||||
|
data := []float64{}
|
||||||
|
for _, arg := range array {
|
||||||
|
if numArg := arg.ToNumber(); numArg.Type == ArgNumber {
|
||||||
|
data = append(data, numArg.Number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(data) < k {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, "k should be <= length of array")
|
||||||
|
}
|
||||||
|
sort.Float64s(data)
|
||||||
|
if name == "LARGE" {
|
||||||
|
return newNumberFormulaArg(data[len(data)-k])
|
||||||
|
}
|
||||||
|
return newNumberFormulaArg(data[k-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// LARGE function returns the k'th largest value from an array of numeric
|
||||||
|
// values. The syntax of the function is:
|
||||||
|
//
|
||||||
|
// LARGE(array,k)
|
||||||
|
//
|
||||||
|
func (fn *formulaFuncs) LARGE(argsList *list.List) formulaArg {
|
||||||
|
return fn.kth("LARGE", argsList)
|
||||||
|
}
|
||||||
|
|
||||||
// MAX function returns the largest value from a supplied set of numeric
|
// MAX function returns the largest value from a supplied set of numeric
|
||||||
// values. The syntax of the function is:
|
// values. The syntax of the function is:
|
||||||
//
|
//
|
||||||
|
@ -4168,6 +4229,15 @@ func (fn *formulaFuncs) PERMUT(argsList *list.List) formulaArg {
|
||||||
return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number)))
|
return newNumberFormulaArg(math.Round(fact(number.Number) / fact(number.Number-chosen.Number)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SMALL function returns the k'th smallest value from an array of numeric
|
||||||
|
// values. The syntax of the function is:
|
||||||
|
//
|
||||||
|
// SMALL(array,k)
|
||||||
|
//
|
||||||
|
func (fn *formulaFuncs) SMALL(argsList *list.List) formulaArg {
|
||||||
|
return fn.kth("SMALL", argsList)
|
||||||
|
}
|
||||||
|
|
||||||
// Information Functions
|
// Information Functions
|
||||||
|
|
||||||
// ISBLANK function tests if a specified cell is blank (empty) and if so,
|
// ISBLANK function tests if a specified cell is blank (empty) and if so,
|
||||||
|
@ -5031,6 +5101,52 @@ func (fn *formulaFuncs) RIGHTB(argsList *list.List) formulaArg {
|
||||||
return fn.leftRight("RIGHTB", argsList)
|
return fn.leftRight("RIGHTB", argsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SUBSTITUTE function replaces one or more instances of a given text string,
|
||||||
|
// within an original text string. The syntax of the function is:
|
||||||
|
//
|
||||||
|
// SUBSTITUTE(text,old_text,new_text,[instance_num])
|
||||||
|
//
|
||||||
|
func (fn *formulaFuncs) SUBSTITUTE(argsList *list.List) formulaArg {
|
||||||
|
if argsList.Len() != 3 && argsList.Len() != 4 {
|
||||||
|
return newErrorFormulaArg(formulaErrorVALUE, "SUBSTITUTE requires 3 or 4 arguments")
|
||||||
|
}
|
||||||
|
text, oldText := argsList.Front().Value.(formulaArg), argsList.Front().Next().Value.(formulaArg)
|
||||||
|
newText, instanceNum := argsList.Front().Next().Next().Value.(formulaArg), 0
|
||||||
|
if argsList.Len() == 3 {
|
||||||
|
return newStringFormulaArg(strings.Replace(text.Value(), oldText.Value(), newText.Value(), -1))
|
||||||
|
}
|
||||||
|
instanceNumArg := argsList.Back().Value.(formulaArg).ToNumber()
|
||||||
|
if instanceNumArg.Type != ArgNumber {
|
||||||
|
return instanceNumArg
|
||||||
|
}
|
||||||
|
instanceNum = int(instanceNumArg.Number)
|
||||||
|
if instanceNum < 1 {
|
||||||
|
return newErrorFormulaArg(formulaErrorVALUE, "instance_num should be > 0")
|
||||||
|
}
|
||||||
|
str, oldTextLen, count, chars, pos := text.Value(), len(oldText.Value()), instanceNum, 0, -1
|
||||||
|
for {
|
||||||
|
count--
|
||||||
|
index := strings.Index(str, oldText.Value())
|
||||||
|
if index == -1 {
|
||||||
|
pos = -1
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
pos = index + chars
|
||||||
|
if count == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
idx := oldTextLen + index
|
||||||
|
chars += idx
|
||||||
|
str = str[idx:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pos == -1 {
|
||||||
|
return newStringFormulaArg(text.Value())
|
||||||
|
}
|
||||||
|
pre, post := text.Value()[:pos], text.Value()[pos+oldTextLen:]
|
||||||
|
return newStringFormulaArg(pre + newText.Value() + post)
|
||||||
|
}
|
||||||
|
|
||||||
// TRIM removes extra spaces (i.e. all spaces except for single spaces between
|
// 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
|
// words or characters) from a supplied text string. The syntax of the
|
||||||
// function is:
|
// function is:
|
||||||
|
|
40
calc_test.go
40
calc_test.go
|
@ -161,10 +161,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=_xlfn.ACOTH(2)": "0.549306144334055",
|
"=_xlfn.ACOTH(2)": "0.549306144334055",
|
||||||
"=_xlfn.ACOTH(ABS(-2))": "0.549306144334055",
|
"=_xlfn.ACOTH(ABS(-2))": "0.549306144334055",
|
||||||
// ARABIC
|
// ARABIC
|
||||||
`=_xlfn.ARABIC("IV")`: "4",
|
"=_xlfn.ARABIC(\"IV\")": "4",
|
||||||
`=_xlfn.ARABIC("-IV")`: "-4",
|
"=_xlfn.ARABIC(\"-IV\")": "-4",
|
||||||
`=_xlfn.ARABIC("MCXX")`: "1120",
|
"=_xlfn.ARABIC(\"MCXX\")": "1120",
|
||||||
`=_xlfn.ARABIC("")`: "0",
|
"=_xlfn.ARABIC(\"\")": "0",
|
||||||
|
"=_xlfn.ARABIC(\" ll lc \")": "-50",
|
||||||
// ASIN
|
// ASIN
|
||||||
"=ASIN(-1)": "-1.570796326794897",
|
"=ASIN(-1)": "-1.570796326794897",
|
||||||
"=ASIN(0)": "0",
|
"=ASIN(0)": "0",
|
||||||
|
@ -608,6 +609,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=KURT(F1:F9)": "-1.033503502551368",
|
"=KURT(F1:F9)": "-1.033503502551368",
|
||||||
"=KURT(F1,F2:F9)": "-1.033503502551368",
|
"=KURT(F1,F2:F9)": "-1.033503502551368",
|
||||||
"=KURT(INT(1),MUNIT(2))": "-3.333333333333336",
|
"=KURT(INT(1),MUNIT(2))": "-3.333333333333336",
|
||||||
|
// LARGE
|
||||||
|
"=LARGE(A1:A5,1)": "3",
|
||||||
|
"=LARGE(A1:B5,2)": "4",
|
||||||
|
"=LARGE(A1,1)": "1",
|
||||||
|
"=LARGE(A1:F2,1)": "36693",
|
||||||
// MAX
|
// MAX
|
||||||
"=MAX(1)": "1",
|
"=MAX(1)": "1",
|
||||||
"=MAX(TRUE())": "1",
|
"=MAX(TRUE())": "1",
|
||||||
|
@ -646,6 +652,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=PERMUT(6,6)": "720",
|
"=PERMUT(6,6)": "720",
|
||||||
"=PERMUT(7,6)": "5040",
|
"=PERMUT(7,6)": "5040",
|
||||||
"=PERMUT(10,6)": "151200",
|
"=PERMUT(10,6)": "151200",
|
||||||
|
// SMALL
|
||||||
|
"=SMALL(A1:A5,1)": "0",
|
||||||
|
"=SMALL(A1:B5,2)": "1",
|
||||||
|
"=SMALL(A1,1)": "1",
|
||||||
|
"=SMALL(A1:F2,1)": "1",
|
||||||
// Information Functions
|
// Information Functions
|
||||||
// ISBLANK
|
// ISBLANK
|
||||||
"=ISBLANK(A1)": "FALSE",
|
"=ISBLANK(A1)": "FALSE",
|
||||||
|
@ -814,6 +825,12 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=RIGHTB(\"Original Text\",0)": "",
|
"=RIGHTB(\"Original Text\",0)": "",
|
||||||
"=RIGHTB(\"Original Text\",13)": "Original Text",
|
"=RIGHTB(\"Original Text\",13)": "Original Text",
|
||||||
"=RIGHTB(\"Original Text\",20)": "Original Text",
|
"=RIGHTB(\"Original Text\",20)": "Original Text",
|
||||||
|
// SUBSTITUTE
|
||||||
|
"=SUBSTITUTE(\"abab\",\"a\",\"X\")": "XbXb",
|
||||||
|
"=SUBSTITUTE(\"abab\",\"a\",\"X\",2)": "abXb",
|
||||||
|
"=SUBSTITUTE(\"abab\",\"x\",\"X\",2)": "abab",
|
||||||
|
"=SUBSTITUTE(\"John is 5 years old\",\"John\",\"Jack\")": "Jack is 5 years old",
|
||||||
|
"=SUBSTITUTE(\"John is 5 years old\",\"5\",\"6\")": "John is 6 years old",
|
||||||
// TRIM
|
// TRIM
|
||||||
"=TRIM(\" trim text \")": "trim text",
|
"=TRIM(\" trim text \")": "trim text",
|
||||||
"=TRIM(0)": "0",
|
"=TRIM(0)": "0",
|
||||||
|
@ -1046,6 +1063,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=_xlfn.ACOTH(_xlfn.ACOTH(2))": "#NUM!",
|
"=_xlfn.ACOTH(_xlfn.ACOTH(2))": "#NUM!",
|
||||||
// _xlfn.ARABIC
|
// _xlfn.ARABIC
|
||||||
"=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument",
|
"=_xlfn.ARABIC()": "ARABIC requires 1 numeric argument",
|
||||||
|
"=_xlfn.ARABIC(\"" + strings.Repeat("I", 256) + "\")": "#VALUE!",
|
||||||
// ASIN
|
// ASIN
|
||||||
"=ASIN()": "ASIN requires 1 numeric argument",
|
"=ASIN()": "ASIN requires 1 numeric argument",
|
||||||
`=ASIN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
`=ASIN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
||||||
|
@ -1334,6 +1352,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
// KURT
|
// KURT
|
||||||
"=KURT()": "KURT requires at least 1 argument",
|
"=KURT()": "KURT requires at least 1 argument",
|
||||||
"=KURT(F1,INT(1))": "#DIV/0!",
|
"=KURT(F1,INT(1))": "#DIV/0!",
|
||||||
|
// LARGE
|
||||||
|
"=LARGE()": "LARGE requires 2 arguments",
|
||||||
|
"=LARGE(A1:A5,0)": "k should be > 0",
|
||||||
|
"=LARGE(A1:A5,6)": "k should be <= length of array",
|
||||||
|
"=LARGE(A1:A5,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
// MAX
|
// MAX
|
||||||
"=MAX()": "MAX requires at least 1 argument",
|
"=MAX()": "MAX requires at least 1 argument",
|
||||||
"=MAX(NA())": "#N/A",
|
"=MAX(NA())": "#N/A",
|
||||||
|
@ -1355,6 +1378,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=PERMUT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
"=PERMUT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
"=PERMUT(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
"=PERMUT(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
"=PERMUT(6,8)": "#N/A",
|
"=PERMUT(6,8)": "#N/A",
|
||||||
|
// SMALL
|
||||||
|
"=SMALL()": "SMALL requires 2 arguments",
|
||||||
|
"=SMALL(A1:A5,0)": "k should be > 0",
|
||||||
|
"=SMALL(A1:A5,6)": "k should be <= length of array",
|
||||||
|
"=SMALL(A1:A5,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
// Information Functions
|
// Information Functions
|
||||||
// ISBLANK
|
// ISBLANK
|
||||||
"=ISBLANK(A1,A2)": "ISBLANK requires 1 argument",
|
"=ISBLANK(A1,A2)": "ISBLANK requires 1 argument",
|
||||||
|
@ -1494,6 +1522,10 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=RIGHTB(\"\",2,3)": "RIGHTB allows at most 2 arguments",
|
"=RIGHTB(\"\",2,3)": "RIGHTB allows at most 2 arguments",
|
||||||
"=RIGHTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
"=RIGHTB(\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
"=RIGHTB(\"\",-1)": "#VALUE!",
|
"=RIGHTB(\"\",-1)": "#VALUE!",
|
||||||
|
// SUBSTITUTE
|
||||||
|
"=SUBSTITUTE()": "SUBSTITUTE requires 3 or 4 arguments",
|
||||||
|
"=SUBSTITUTE(\"\",\"\",\"\",\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
|
"=SUBSTITUTE(\"\",\"\",\"\",0)": "instance_num should be > 0",
|
||||||
// TRIM
|
// TRIM
|
||||||
"=TRIM()": "TRIM requires 1 argument",
|
"=TRIM()": "TRIM requires 1 argument",
|
||||||
"=TRIM(1,2)": "TRIM requires 1 argument",
|
"=TRIM(1,2)": "TRIM requires 1 argument",
|
||||||
|
|
Loading…
Reference in New Issue