summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxuri <xuri.me@gmail.com>2021-12-17 00:08:56 +0800
committerxuri <xuri.me@gmail.com>2021-12-17 00:08:56 +0800
commit63fe422299ed9d31d079711d1173b288faa6838d (patch)
tree8550157a177ddc79b9b3ad77441d4117ebda22e4
parentc0ac3165bd8923efdaf95b377771d14f5879b1ec (diff)
new formula function and update docs for formula functions
ref #65, new formula function: INDEX (array formula not support yet)
-rw-r--r--calc.go93
-rw-r--r--calc_test.go19
2 files changed, 100 insertions, 12 deletions
diff --git a/calc.go b/calc.go
index 79fefb1..cf2f95a 100644
--- a/calc.go
+++ b/calc.go
@@ -445,6 +445,7 @@ type formulaFuncs struct {
// IMSUB
// IMSUM
// IMTAN
+// INDEX
// INT
// INTRATE
// IPMT
@@ -5791,7 +5792,7 @@ func calcListMatrixMax(maxa bool, max float64, arg formulaArg) float64 {
return max
}
-// max is an implementation of the formula function MAX and MAXA.
+// max is an implementation of the formula functions MAX and MAXA.
func (fn *formulaFuncs) max(maxa bool, argsList *list.List) formulaArg {
max := -math.MaxFloat64
for token := argsList.Front(); token != nil; token = token.Next() {
@@ -5926,7 +5927,7 @@ func calcListMatrixMin(mina bool, min float64, arg formulaArg) float64 {
return min
}
-// min is an implementation of the formula function MIN and MINA.
+// min is an implementation of the formula functions MIN and MINA.
func (fn *formulaFuncs) min(mina bool, argsList *list.List) formulaArg {
min := math.MaxFloat64
for token := argsList.Front(); token != nil; token = token.Next() {
@@ -8136,7 +8137,7 @@ func (fn *formulaFuncs) CODE(argsList *list.List) formulaArg {
return fn.code("CODE", argsList)
}
-// code is an implementation of the formula function CODE and UNICODE.
+// code is an implementation of the formula functions CODE and UNICODE.
func (fn *formulaFuncs) code(name string, argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 1 argument", name))
@@ -8169,7 +8170,8 @@ func (fn *formulaFuncs) CONCATENATE(argsList *list.List) formulaArg {
return fn.concat("CONCATENATE", argsList)
}
-// concat is an implementation of the formula function CONCAT and CONCATENATE.
+// concat is an implementation of the formula functions CONCAT and
+// CONCATENATE.
func (fn *formulaFuncs) concat(name string, argsList *list.List) formulaArg {
buf := bytes.Buffer{}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
@@ -8279,7 +8281,7 @@ func (fn *formulaFuncs) FINDB(argsList *list.List) formulaArg {
return fn.find("FINDB", argsList)
}
-// find is an implementation of the formula function FIND and FINDB.
+// find is an implementation of the formula functions FIND and FINDB.
func (fn *formulaFuncs) find(name string, argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 2 arguments", name))
@@ -8333,7 +8335,7 @@ func (fn *formulaFuncs) LEFTB(argsList *list.List) formulaArg {
return fn.leftRight("LEFTB", argsList)
}
-// leftRight is an implementation of the formula function LEFT, LEFTB, RIGHT,
+// leftRight is an implementation of the formula functions LEFT, LEFTB, RIGHT,
// RIGHTB. TODO: support DBCS include Japanese, Chinese (Simplified), Chinese
// (Traditional), and Korean.
func (fn *formulaFuncs) leftRight(name string, argsList *list.List) formulaArg {
@@ -8422,7 +8424,7 @@ func (fn *formulaFuncs) MIDB(argsList *list.List) formulaArg {
return fn.mid("MIDB", argsList)
}
-// mid is an implementation of the formula function MID and MIDB. TODO:
+// mid is an implementation of the formula functions MID and MIDB. TODO:
// support DBCS include Japanese, Chinese (Simplified), Chinese
// (Traditional), and Korean.
func (fn *formulaFuncs) mid(name string, argsList *list.List) formulaArg {
@@ -8495,7 +8497,7 @@ func (fn *formulaFuncs) REPLACEB(argsList *list.List) formulaArg {
return fn.replace("REPLACEB", argsList)
}
-// replace is an implementation of the formula function REPLACE and REPLACEB.
+// replace is an implementation of the formula functions REPLACE and REPLACEB.
// TODO: support DBCS include Japanese, Chinese (Simplified), Chinese
// (Traditional), and Korean.
func (fn *formulaFuncs) replace(name string, argsList *list.List) formulaArg {
@@ -9491,6 +9493,73 @@ func iterateLookupArgs(lookupValue, lookupVector formulaArg) ([]formulaArg, int,
return cols, matchIdx, ok
}
+// index is an implementation of the formula function INDEX.
+func (fn *formulaFuncs) index(array formulaArg, rowIdx, colIdx int) formulaArg {
+ var cells []formulaArg
+ if array.Type == ArgMatrix {
+ cellMatrix := array.Matrix
+ if rowIdx < -1 || rowIdx >= len(cellMatrix) {
+ return newErrorFormulaArg(formulaErrorREF, "INDEX row_num out of range")
+ }
+ if rowIdx == -1 {
+ if colIdx >= len(cellMatrix[0]) {
+ return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range")
+ }
+ column := [][]formulaArg{}
+ for _, cells = range cellMatrix {
+ column = append(column, []formulaArg{cells[colIdx]})
+ }
+ return newMatrixFormulaArg(column)
+ }
+ cells = cellMatrix[rowIdx]
+ }
+ if colIdx < -1 || colIdx >= len(cells) {
+ return newErrorFormulaArg(formulaErrorREF, "INDEX col_num out of range")
+ }
+ return newListFormulaArg(cells)
+}
+
+// INDEX function returns a reference to a cell that lies in a specified row
+// and column of a range of cells. The syntax of the function is:
+//
+// INDEX(array,row_num,[col_num])
+//
+func (fn *formulaFuncs) INDEX(argsList *list.List) formulaArg {
+ if argsList.Len() < 2 || argsList.Len() > 3 {
+ return newErrorFormulaArg(formulaErrorVALUE, "INDEX requires 2 or 3 arguments")
+ }
+ array := argsList.Front().Value.(formulaArg)
+ if array.Type != ArgMatrix && array.Type != ArgList {
+ array = newMatrixFormulaArg([][]formulaArg{{array}})
+ }
+ rowArg := argsList.Front().Next().Value.(formulaArg).ToNumber()
+ if rowArg.Type != ArgNumber {
+ return rowArg
+ }
+ rowIdx, colIdx := int(rowArg.Number)-1, -1
+ if argsList.Len() == 3 {
+ colArg := argsList.Back().Value.(formulaArg).ToNumber()
+ if colArg.Type != ArgNumber {
+ return colArg
+ }
+ colIdx = int(colArg.Number) - 1
+ }
+ if rowIdx == -1 && colIdx == -1 {
+ if len(array.ToList()) != 1 {
+ return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
+ }
+ return array.ToList()[0]
+ }
+ cells := fn.index(array, rowIdx, colIdx)
+ if cells.Type != ArgList {
+ return cells
+ }
+ if colIdx == -1 {
+ return newMatrixFormulaArg([][]formulaArg{cells.List})
+ }
+ return cells.List[colIdx]
+}
+
// LOOKUP function performs an approximate match lookup in a one-column or
// one-row range, and returns the corresponding value from another one-column
// or one-row range. The syntax of the function is:
@@ -10011,7 +10080,7 @@ func (fn *formulaFuncs) COUPDAYSNC(argsList *list.List) formulaArg {
return newNumberFormulaArg(coupdays(settlement, ncd, basis))
}
-// coupons is an implementation of the formula function COUPNCD and COUPPCD.
+// coupons is an implementation of the formula functions COUPNCD and COUPPCD.
func (fn *formulaFuncs) coupons(name string, arg formulaArg) formulaArg {
settlement := timeFromExcelTime(arg.List[0].Number, false)
maturity := timeFromExcelTime(arg.List[1].Number, false)
@@ -10106,7 +10175,7 @@ func (fn *formulaFuncs) CUMPRINC(argsList *list.List) formulaArg {
return fn.cumip("CUMPRINC", argsList)
}
-// cumip is an implementation of the formula function CUMIPMT and CUMPRINC.
+// cumip is an implementation of the formula functions CUMIPMT and CUMPRINC.
func (fn *formulaFuncs) cumip(name string, argsList *list.List) formulaArg {
if argsList.Len() != 6 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 arguments", name))
@@ -10370,7 +10439,7 @@ func (fn *formulaFuncs) DOLLARFR(argsList *list.List) formulaArg {
return fn.dollar("DOLLARFR", argsList)
}
-// dollar is an implementation of the formula function DOLLARDE and DOLLARFR.
+// dollar is an implementation of the formula functions DOLLARDE and DOLLARFR.
func (fn *formulaFuncs) dollar(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 arguments", name))
@@ -10658,7 +10727,7 @@ func calcIpmt(name string, typ, per, pmt, pv, rate formulaArg) formulaArg {
return newNumberFormulaArg(principal)
}
-// ipmt is an implementation of the formula function IPMT and PPMT.
+// ipmt is an implementation of the formula functions IPMT and PPMT.
func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires at least 4 arguments", name))
diff --git a/calc_test.go b/calc_test.go
index 0544806..89ec5e5 100644
--- a/calc_test.go
+++ b/calc_test.go
@@ -1403,6 +1403,15 @@ func TestCalcCellValue(t *testing.T) {
"=VLOOKUP(MUNIT(3),MUNIT(3),1)": "0",
"=VLOOKUP(A1,A3:B5,1)": "0",
"=VLOOKUP(MUNIT(1),MUNIT(1),1,FALSE)": "1",
+ // INDEX
+ "=INDEX(0,0,0)": "0",
+ "=INDEX(A1,0,0)": "1",
+ "=INDEX(A1:A1,0,0)": "1",
+ "=SUM(INDEX(A1:B1,1))": "5",
+ "=SUM(INDEX(A1:B1,1,0))": "5",
+ "=SUM(INDEX(A1:B2,2,0))": "7",
+ "=SUM(INDEX(A1:B4,0,2))": "9",
+ "=SUM(INDEX(E1:F5,5,2))": "34440",
// LOOKUP
"=LOOKUP(F8,F8:F9,F8:F9)": "32080",
"=LOOKUP(F8,F8:F9,D8:D9)": "Feb",
@@ -2792,6 +2801,16 @@ func TestCalcCellValue(t *testing.T) {
"=VLOOKUP(INT(1),E2:E9,1)": "VLOOKUP no result found",
"=VLOOKUP(MUNIT(2),MUNIT(3),1)": "VLOOKUP no result found",
"=VLOOKUP(1,G1:H2,1,FALSE)": "VLOOKUP no result found",
+ // INDEX
+ "=INDEX()": "INDEX requires 2 or 3 arguments",
+ "=INDEX(A1,2)": "INDEX row_num out of range",
+ "=INDEX(A1,0,2)": "INDEX col_num out of range",
+ "=INDEX(A1:A1,2)": "INDEX row_num out of range",
+ "=INDEX(A1:A1,0,2)": "INDEX col_num out of range",
+ "=INDEX(A1:B2,2,3)": "INDEX col_num out of range",
+ "=INDEX(A1:A2,0,0)": "#VALUE!",
+ "=INDEX(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=INDEX(0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
// LOOKUP
"=LOOKUP()": "LOOKUP requires at least 2 arguments",
"=LOOKUP(D2,D1,D2)": "LOOKUP requires second argument of table array",