mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
Excel functions part 4 (#353)
* DURATION, MDURATION * PDURATION * ROW, ROWS * tests for LOOKUP and VLOOKUP * LARGE, SMALL * LOWER * REPLACE * TEXTJOIN * INDEX
This commit is contained in:
parent
055ebf0f4c
commit
8fadaaeecf
45
internal/mergesort/mergesort.go
Normal file
45
internal/mergesort/mergesort.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Package mergesort provides an implementation for merge sort sorting algorithm
|
||||
package mergesort
|
||||
|
||||
// MergeSort receives a slice and returns a new sorted slice
|
||||
func MergeSort(array []float64) []float64 {
|
||||
if len(array) <= 1 {
|
||||
result := make([]float64, len(array))
|
||||
copy(result, array)
|
||||
return result
|
||||
}
|
||||
|
||||
mid := len(array) / 2
|
||||
leftArray := MergeSort(array[:mid])
|
||||
rightArray := MergeSort(array[mid:])
|
||||
|
||||
result := make([]float64, len(array))
|
||||
|
||||
arrayIndex := 0
|
||||
leftIndex := 0
|
||||
rightIndex := 0
|
||||
for leftIndex < len(leftArray) && rightIndex < len(rightArray) {
|
||||
if leftArray[leftIndex] <= rightArray[rightIndex] {
|
||||
result[arrayIndex] = leftArray[leftIndex]
|
||||
leftIndex++
|
||||
} else {
|
||||
result[arrayIndex] = rightArray[rightIndex]
|
||||
rightIndex++
|
||||
}
|
||||
arrayIndex++
|
||||
}
|
||||
|
||||
for leftIndex < len(leftArray) {
|
||||
result[arrayIndex] = leftArray[leftIndex]
|
||||
leftIndex++
|
||||
arrayIndex++
|
||||
}
|
||||
|
||||
for rightIndex < len(rightArray) {
|
||||
result[arrayIndex] = rightArray[rightIndex]
|
||||
rightIndex++
|
||||
arrayIndex++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@ -177,5 +177,4 @@ func TestArrayFormula(t *testing.T) {
|
||||
if got := sheet.Cell("F2").GetFormattedValue(); got != "8" {
|
||||
t.Errorf("expected 8 in F2, got %s", got)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -147,26 +147,32 @@ func Days(args []Result) Result {
|
||||
return MakeErrorResult("DAYS requires two arguments")
|
||||
}
|
||||
var sd, ed float64
|
||||
if args[0].Type == ResultTypeNumber {
|
||||
switch args[0].Type {
|
||||
case ResultTypeNumber:
|
||||
ed = args[0].ValueNumber
|
||||
} else {
|
||||
case ResultTypeString:
|
||||
edResult := DateValue([]Result{args[0]})
|
||||
if edResult.Type == ResultTypeError {
|
||||
return MakeErrorResult("Incorrect end date for DAYS")
|
||||
}
|
||||
ed = edResult.ValueNumber
|
||||
default:
|
||||
return MakeErrorResult("Incorrect argument for DAYS")
|
||||
}
|
||||
if args[1].Type == ResultTypeNumber {
|
||||
switch args[1].Type {
|
||||
case ResultTypeNumber:
|
||||
sd = args[1].ValueNumber
|
||||
if sd < 62 && ed >= 62 {
|
||||
sd--
|
||||
}
|
||||
} else {
|
||||
case ResultTypeString:
|
||||
sdResult := DateValue([]Result{args[1]})
|
||||
if sdResult.Type == ResultTypeError {
|
||||
return MakeErrorResult("Incorrect start date for DAYS")
|
||||
}
|
||||
sd = sdResult.ValueNumber
|
||||
default:
|
||||
return MakeErrorResult("Incorrect argument for DAYS")
|
||||
}
|
||||
days := float64(int(ed - sd))
|
||||
return MakeNumberResult(days)
|
||||
@ -769,11 +775,15 @@ func YearFrac(ctx Context, ev Evaluator, args []Result) Result {
|
||||
if err != nil {
|
||||
return MakeErrorResult("incorrect start date")
|
||||
}
|
||||
startDateS := startDate.Unix()
|
||||
endDate, err := getValueAsTime(args[1].Value(), epoch)
|
||||
if err != nil {
|
||||
return MakeErrorResult("incorrect end date")
|
||||
}
|
||||
return yearFrac(startDate, endDate, basis)
|
||||
}
|
||||
|
||||
func yearFrac(startDate, endDate time.Time, basis int) Result {
|
||||
startDateS := startDate.Unix()
|
||||
endDateS := endDate.Unix()
|
||||
sy, sm, sd := startDate.Date()
|
||||
ey, em, ed := endDate.Date()
|
||||
|
202
spreadsheet/formula/fnfinance.go
Normal file
202
spreadsheet/formula/fnfinance.go
Normal file
@ -0,0 +1,202 @@
|
||||
// Copyright 2017 FoxyUtils ehf. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by the terms of the Affero GNU General
|
||||
// Public License version 3.0 as published by the Free Software Foundation and
|
||||
// appearing in the file LICENSE included in the packaging of this file. A
|
||||
// commercial license can be purchased on https://unidoc.io.
|
||||
|
||||
package formula
|
||||
|
||||
import (
|
||||
"time"
|
||||
"math"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterFunction("DURATION", Duration)
|
||||
RegisterFunction("MDURATION", Mduration)
|
||||
RegisterFunction("PDURATION", Pduration)
|
||||
RegisterFunction("_xlfn.PDURATION", Pduration)
|
||||
}
|
||||
|
||||
// Duration implements the Excel DURATION function.
|
||||
func Duration(args []Result) Result {
|
||||
settlementDate, maturityDate, coupon, yield, freq, basis, err := parseDurationData(args, "DURATION")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
return getDuration(dateFromDays(settlementDate), dateFromDays(maturityDate), coupon, yield, freq, basis)
|
||||
}
|
||||
|
||||
// Mduration implements the Excel MDURATION function.
|
||||
func Mduration(args []Result) Result {
|
||||
settlementDate, maturityDate, coupon, yield, freq, basis, err := parseDurationData(args, "MDURATION")
|
||||
if err.Type == ResultTypeError {
|
||||
return err
|
||||
}
|
||||
duration := getDuration(dateFromDays(settlementDate), dateFromDays(maturityDate), coupon, yield, freq, basis)
|
||||
if duration.Type == ResultTypeError {
|
||||
return duration
|
||||
}
|
||||
mDuration := duration.ValueNumber / (1.0 + yield / freq)
|
||||
return MakeNumberResult(mDuration)
|
||||
}
|
||||
|
||||
// Pduration implements the Excel PDURATION function.
|
||||
func Pduration(args []Result) Result {
|
||||
if len(args) != 3 {
|
||||
return MakeErrorResult("PDURATION requires three number arguments")
|
||||
}
|
||||
if args[0].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("PDURATION requires rate to be number argument")
|
||||
}
|
||||
rate := args[0].ValueNumber
|
||||
if rate <= 0 {
|
||||
return MakeErrorResultType(ErrorTypeNum, "PDURATION requires rate to be positive")
|
||||
}
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("PDURATION requires current value to be number argument")
|
||||
}
|
||||
currentValue := args[1].ValueNumber
|
||||
if currentValue <= 0 {
|
||||
return MakeErrorResultType(ErrorTypeNum, "PDURATION requires current value to be positive")
|
||||
}
|
||||
if args[2].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("PDURATION requires specified value to be number argument")
|
||||
}
|
||||
specifiedValue := args[2].ValueNumber
|
||||
if specifiedValue <= 0 {
|
||||
return MakeErrorResultType(ErrorTypeNum, "PDURATION requires specified value to be positive")
|
||||
}
|
||||
return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate))
|
||||
}
|
||||
|
||||
// getCouppcd finds last coupon date before settlement (can be equal to settlement).
|
||||
func getCouppcd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
||||
rDate := maturityDate
|
||||
diffYears := settlementDate.Year() - maturityDate.Year()
|
||||
rDate = rDate.AddDate(diffYears, 0, 0)
|
||||
if settlementDate.After(rDate) {
|
||||
rDate = rDate.AddDate(1, 0, 0)
|
||||
}
|
||||
monthsToAdd := -12 / freq
|
||||
for rDate.After(settlementDate) {
|
||||
rDate = rDate.AddDate(0, monthsToAdd, 0)
|
||||
}
|
||||
return rDate
|
||||
}
|
||||
|
||||
// getCoupnum gets count of coupon dates.
|
||||
func getCoupnum(settlementDate, maturityDate time.Time, freq, basis int) float64 {
|
||||
if maturityDate.After(settlementDate) {
|
||||
aDate := getCouppcd(settlementDate, maturityDate, freq)
|
||||
months := (maturityDate.Year() - aDate.Year()) * 12 + int(maturityDate.Month()) - int(aDate.Month())
|
||||
return float64(months * freq) / 12.0
|
||||
}
|
||||
return 0.0 // replace for error
|
||||
}
|
||||
|
||||
// getDuration returns the Macauley duration for an assumed par value of $100. It is defined as the weighted average of the present value of cash flows, and is used as a measure of a bond price's response to changes in yield.
|
||||
func getDuration(settlementDate, maturityDate time.Time, coup, yield, freq float64, basis int) Result {
|
||||
fracResult := yearFrac(settlementDate, maturityDate, basis)
|
||||
if fracResult.Type == ResultTypeError {
|
||||
return fracResult
|
||||
}
|
||||
frac := fracResult.ValueNumber
|
||||
nCoups := getCoupnum(settlementDate, maturityDate, int(freq), basis)
|
||||
duration := 0.0
|
||||
p := 0.0
|
||||
coup *= 100 / freq
|
||||
yield /= freq
|
||||
yield++
|
||||
diff := frac * freq - nCoups
|
||||
for t := 1.0; t < nCoups; t++ {
|
||||
tDiff := t + diff
|
||||
add := coup / math.Pow(yield, tDiff)
|
||||
p += add
|
||||
duration += tDiff * add
|
||||
}
|
||||
|
||||
add := (coup + 100) / math.Pow(yield, nCoups + diff)
|
||||
|
||||
p += add
|
||||
duration += (nCoups + diff) * add
|
||||
|
||||
duration /= p
|
||||
duration /= freq
|
||||
|
||||
return MakeNumberResult(duration)
|
||||
}
|
||||
|
||||
// validateDurationData returns settlement date, maturity date, coupon rate, yield rate, frequency of payments, day count basis and error result by parsing incoming arguments
|
||||
func parseDurationData(args []Result, funcName string) (float64, float64, float64, float64, float64, int, Result) {
|
||||
if len(args) != 5 && len(args) != 6 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires five or six arguments")
|
||||
}
|
||||
var settlementDate, maturityDate float64
|
||||
settlementResult := args[0]
|
||||
switch settlementResult.Type {
|
||||
case ResultTypeNumber:
|
||||
settlementDate = float64(int(settlementResult.ValueNumber))
|
||||
case ResultTypeString:
|
||||
settlementFromString := DateValue([]Result{settlementResult})
|
||||
if settlementFromString.Type == ResultTypeError {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||
}
|
||||
settlementDate = settlementFromString.ValueNumber
|
||||
default:
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect argument for " + funcName)
|
||||
}
|
||||
maturityResult := args[1]
|
||||
switch maturityResult.Type {
|
||||
case ResultTypeNumber:
|
||||
maturityDate = float64(int(maturityResult.ValueNumber))
|
||||
case ResultTypeString:
|
||||
maturityFromString := DateValue([]Result{maturityResult})
|
||||
if maturityFromString.Type == ResultTypeError {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||
}
|
||||
maturityDate = maturityFromString.ValueNumber
|
||||
default:
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect argument for " + funcName)
|
||||
}
|
||||
if settlementDate >= maturityDate {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
||||
}
|
||||
couponResult := args[2]
|
||||
if couponResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires third argument of type number")
|
||||
}
|
||||
coupon := couponResult.ValueNumber
|
||||
if coupon < 0 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Coupon rate should not be negative")
|
||||
}
|
||||
yieldResult := args[3]
|
||||
if yieldResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires fourth argument of type number")
|
||||
}
|
||||
yield := yieldResult.ValueNumber
|
||||
if yield < 0 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Yield rate should not be negative")
|
||||
}
|
||||
freqResult := args[4]
|
||||
if freqResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires fifth argument of type number")
|
||||
}
|
||||
freq := float64(int(freqResult.ValueNumber))
|
||||
if freq != 1 && freq != 2 && freq != 4 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
||||
}
|
||||
basis := 0
|
||||
if len(args) == 6 {
|
||||
basisResult := args[5]
|
||||
if basisResult.Type != ResultTypeNumber {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires sixth argument of type number")
|
||||
}
|
||||
basis = int(basisResult.ValueNumber)
|
||||
if basis < 0 || basis > 4 {
|
||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value")
|
||||
}
|
||||
}
|
||||
return settlementDate, maturityDate, coupon, yield, freq, basis, MakeEmptyResult()
|
||||
}
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/unidoc/unioffice/internal/wildcard"
|
||||
"github.com/unidoc/unioffice/spreadsheet/reference"
|
||||
"github.com/unidoc/unioffice/internal/mergesort"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -24,7 +25,11 @@ func init() {
|
||||
RegisterFunctionComplex("OFFSET", Offset)
|
||||
RegisterFunction("MATCH", Match)
|
||||
RegisterFunction("HLOOKUP", HLookup)
|
||||
RegisterFunction("LARGE", Large)
|
||||
RegisterFunction("LOOKUP", Lookup)
|
||||
RegisterFunction("ROW", Row)
|
||||
RegisterFunction("ROWS", Rows)
|
||||
RegisterFunction("SMALL", Small)
|
||||
RegisterFunction("VLOOKUP", VLookup)
|
||||
RegisterFunction("TRANSPOSE", Transpose)
|
||||
}
|
||||
@ -79,8 +84,9 @@ func Columns(args []Result) Result {
|
||||
|
||||
// Index implements the Excel INDEX function.
|
||||
func Index(args []Result) Result {
|
||||
if len(args) < 3 {
|
||||
return MakeErrorResult("INDEX requires three arguments")
|
||||
argsNum := len(args)
|
||||
if argsNum < 2 || argsNum > 3 {
|
||||
return MakeErrorResult("INDEX requires from one to three arguments")
|
||||
}
|
||||
arr := args[0]
|
||||
if arr.Type != ResultTypeArray && arr.Type != ResultTypeList {
|
||||
@ -90,29 +96,73 @@ func Index(args []Result) Result {
|
||||
if rowArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("INDEX requires numeric row argument")
|
||||
}
|
||||
colArg := args[2].AsNumber()
|
||||
if colArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("INDEX requires numeric col argument")
|
||||
}
|
||||
row := int(rowArg.ValueNumber) - 1
|
||||
col := int(colArg.ValueNumber) - 1
|
||||
col := -1
|
||||
if argsNum == 3 {
|
||||
colArg := args[2].AsNumber()
|
||||
if colArg.Type != ResultTypeNumber {
|
||||
return MakeErrorResult("INDEX requires numeric col argument")
|
||||
}
|
||||
col = int(colArg.ValueNumber) - 1
|
||||
}
|
||||
if row == -1 && col == -1 {
|
||||
return MakeErrorResult("INDEX requires row or col argument")
|
||||
}
|
||||
var rowVal []Result
|
||||
if arr.Type == ResultTypeArray {
|
||||
if row < 0 || row >= len(arr.ValueArray) {
|
||||
valueArray := arr.ValueArray
|
||||
if row < -1 || row >= len(valueArray) {
|
||||
return MakeErrorResult("INDEX has row out of range")
|
||||
}
|
||||
rowVal = arr.ValueArray[row]
|
||||
if row == -1 {
|
||||
if col >= len(valueArray[0]) {
|
||||
return MakeErrorResult("INDEX has col out of range")
|
||||
}
|
||||
oneColumnArray := [][]Result{}
|
||||
for _, row := range valueArray {
|
||||
v := row[col]
|
||||
if v.Type == ResultTypeEmpty {
|
||||
v = MakeNumberResult(0)
|
||||
}
|
||||
oneColumnArray = append(oneColumnArray, []Result{v})
|
||||
}
|
||||
return MakeArrayResult(oneColumnArray)
|
||||
}
|
||||
rowVal = valueArray[row]
|
||||
} else {
|
||||
if row < 0 || row >= 1 {
|
||||
valueList := arr.ValueList
|
||||
if row < -1 || row >= 1 {
|
||||
return MakeErrorResult("INDEX has row out of range")
|
||||
}
|
||||
rowVal = arr.ValueList
|
||||
if row == -1 {
|
||||
if col >= len(valueList) {
|
||||
return MakeErrorResult("INDEX has col out of range")
|
||||
}
|
||||
v := valueList[col]
|
||||
if v.Type == ResultTypeEmpty {
|
||||
v = MakeNumberResult(0)
|
||||
}
|
||||
return v
|
||||
}
|
||||
rowVal = valueList
|
||||
}
|
||||
|
||||
if col < 0 || col > len(rowVal) {
|
||||
if col < -1 || col > len(rowVal) {
|
||||
return MakeErrorResult("INDEX has col out of range")
|
||||
}
|
||||
|
||||
if col == -1 {
|
||||
listResult := []Result{}
|
||||
for _, v := range rowVal {
|
||||
if v.Type == ResultTypeEmpty {
|
||||
listResult = append(listResult, MakeNumberResult(0))
|
||||
} else {
|
||||
listResult = append(listResult, v)
|
||||
}
|
||||
}
|
||||
return MakeArrayResult([][]Result{listResult})
|
||||
}
|
||||
|
||||
rv := rowVal[col]
|
||||
// empty cell returns a zero
|
||||
if rv.Type == ResultTypeEmpty {
|
||||
@ -601,3 +651,98 @@ func Transpose(args []Result) Result {
|
||||
}
|
||||
return MakeArrayResult(res)
|
||||
}
|
||||
|
||||
// Row implements the Excel ROW function.
|
||||
func Row(args []Result) Result {
|
||||
if len(args) < 1 {
|
||||
return MakeErrorResult("ROW requires one argument")
|
||||
}
|
||||
ref := args[0].Ref
|
||||
if ref.Type != ReferenceTypeCell {
|
||||
return MakeErrorResult("ROW requires an argument to be of type reference")
|
||||
}
|
||||
cr, err := reference.ParseCellReference(ref.Value)
|
||||
if err != nil {
|
||||
return MakeErrorResult("Incorrect reference: " + ref.Value)
|
||||
}
|
||||
return MakeNumberResult(float64(cr.RowIdx))
|
||||
}
|
||||
|
||||
// Rows implements the Excel ROWS function.
|
||||
func Rows(args []Result) Result {
|
||||
if len(args) < 1 {
|
||||
return MakeErrorResult("ROWS requires one argument")
|
||||
}
|
||||
arrResult := args[0]
|
||||
if arrResult.Type != ResultTypeArray && arrResult.Type != ResultTypeList {
|
||||
return MakeErrorResult("ROWS requires first argument of type array")
|
||||
}
|
||||
arr := arrResult.ValueArray
|
||||
if len(arr) == 0 {
|
||||
return MakeErrorResult("ROWS requires array to contain at least 1 row")
|
||||
}
|
||||
return MakeNumberResult(float64(len(arr)))
|
||||
}
|
||||
|
||||
// Large implements the Excel LARGE function.
|
||||
func Large(args []Result) Result {
|
||||
return kth(args, true)
|
||||
}
|
||||
|
||||
// Small implements the Excel SMALL function.
|
||||
func Small(args []Result) Result {
|
||||
return kth(args, false)
|
||||
}
|
||||
|
||||
func kth(args []Result, large bool) Result {
|
||||
var funcName string
|
||||
if large {
|
||||
funcName = "LARGE"
|
||||
} else {
|
||||
funcName = "SMALL"
|
||||
}
|
||||
if len(args) != 2 {
|
||||
return MakeErrorResult(funcName + " requires two arguments")
|
||||
}
|
||||
arrResult := args[0]
|
||||
var arr [][]Result
|
||||
switch arrResult.Type {
|
||||
case ResultTypeArray:
|
||||
arr = arrResult.ValueArray
|
||||
case ResultTypeList:
|
||||
arr = [][]Result{arrResult.ValueList}
|
||||
default:
|
||||
return MakeErrorResult(funcName + " requires first argument of type array")
|
||||
}
|
||||
if len(arr) == 0 {
|
||||
return MakeErrorResult(funcName + " requires array to contain at least 1 row")
|
||||
}
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return MakeErrorResult(funcName + " requires second argument of type number")
|
||||
}
|
||||
kfloat := args[1].ValueNumber
|
||||
if kfloat < 1 {
|
||||
return MakeErrorResultType(ErrorTypeNum, funcName + " requires second argument of type number more than 0")
|
||||
}
|
||||
k := int(kfloat)
|
||||
if float64(k) != kfloat {
|
||||
return MakeErrorResultType(ErrorTypeNum, funcName + " requires second argument of type number more than 0")
|
||||
}
|
||||
unsorted := []float64{}
|
||||
for _, row := range arr {
|
||||
for _, v := range row {
|
||||
if v.Type == ResultTypeNumber {
|
||||
unsorted = append(unsorted, v.ValueNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
if k > len(unsorted) {
|
||||
return MakeErrorResultType(ErrorTypeNum, funcName + " requires second argument of type number less or equal than the number of numbers in the array")
|
||||
}
|
||||
sorted := mergesort.MergeSort(unsorted)
|
||||
if large {
|
||||
return MakeNumberResult(sorted[len(sorted) - k])
|
||||
} else {
|
||||
return MakeNumberResult(sorted[k - 1])
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,7 @@ func init() {
|
||||
RegisterFunction("_xlfn.IFS", Ifs)
|
||||
RegisterFunction("NOT", Not)
|
||||
RegisterFunction("OR", Or)
|
||||
// TODO: RegisterFunction("SWITCH", Switch) // Only in Excel 2016+
|
||||
RegisterFunction("TRUE", True) // yup, TRUE()/FALSE() are functions in Excel, news to me...
|
||||
RegisterFunction("TRUE", True)
|
||||
RegisterFunction("_xlfn.XOR", Xor)
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ func init() {
|
||||
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
||||
RegisterFunction("_xlfn.ACOT", makeMathWrapper("ACOT", func(v float64) float64 { return math.Pi/2 - math.Atan(v) }))
|
||||
RegisterFunction("_xlfn.ACOTH", makeMathWrapper("ACOTH", func(v float64) float64 { return math.Atanh(1 / v) }))
|
||||
// TODO: RegisterFunction("_xlfn.AGGREGATE", Aggregate) // lots of dependencies
|
||||
RegisterFunction("_xlfn.ARABIC", Arabic)
|
||||
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
||||
RegisterFunction("ASINH", makeMathWrapper("ASINH", math.Asinh))
|
||||
@ -56,14 +55,12 @@ func init() {
|
||||
RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise)
|
||||
RegisterFunction("GCD", GCD)
|
||||
RegisterFunction("INT", Int)
|
||||
RegisterFunction("ISO.CEILING", CeilingPrecise) // appears to be the same from what I can tell
|
||||
RegisterFunction("ISO.CEILING", CeilingPrecise)
|
||||
RegisterFunction("LCM", LCM)
|
||||
RegisterFunction("LN", makeMathWrapper("LN", math.Log))
|
||||
RegisterFunction("LOG", Log)
|
||||
RegisterFunction("LOG10", makeMathWrapper("LOG10", math.Log10))
|
||||
RegisterFunction("MDETERM", MDeterm)
|
||||
// RegisterFunction("MINVERSE", MInverse) // TODO: skipping the other matrix functinos, not sure how common they are
|
||||
// RegisterFunction("MMULT"
|
||||
RegisterFunction("MOD", Mod)
|
||||
RegisterFunction("MROUND", Mround)
|
||||
RegisterFunction("MULTINOMIAL", Multinomial)
|
||||
@ -88,15 +85,11 @@ func init() {
|
||||
RegisterFunction("SINH", makeMathWrapper("SINH", math.Sinh))
|
||||
RegisterFunction("SQRT", makeMathWrapper("SQRT", math.Sqrt))
|
||||
RegisterFunction("SQRTPI", makeMathWrapper("SQRTPI", func(v float64) float64 { return math.Sqrt(v * math.Pi) }))
|
||||
// RegisterFunction("SUBTOTAL"
|
||||
RegisterFunction("SUM", Sum)
|
||||
RegisterFunction("SUMIF", SumIf)
|
||||
RegisterFunction("SUMIFS", SumIfs)
|
||||
RegisterFunction("SUMPRODUCT", SumProduct)
|
||||
RegisterFunction("SUMSQ", SumSquares)
|
||||
//RegisterFunction("SUMX2MY2"
|
||||
//RegisterFunction("SUMX2PY2"
|
||||
//RegisterFunction("SUMXMY2"
|
||||
RegisterFunction("TAN", makeMathWrapper("TAN", math.Tan))
|
||||
RegisterFunction("TANH", makeMathWrapper("TANH", math.Tanh))
|
||||
RegisterFunction("TRUNC", Trunc)
|
||||
|
@ -18,42 +18,32 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// RegisterFunction("ASC") Need to figure out how to test
|
||||
// RegisterFunction("BAHTTEXT")
|
||||
RegisterFunction("CHAR", Char)
|
||||
RegisterFunction("CLEAN", Clean)
|
||||
RegisterFunction("CODE", Code)
|
||||
RegisterFunction("CONCATENATE", Concat)
|
||||
RegisterFunction("CONCAT", Concat)
|
||||
RegisterFunction("_xlfn.CONCAT", Concat)
|
||||
// RegisterFunction("DBCS")
|
||||
// RegisterFunction("DOLLAR") Need to test with Excel
|
||||
RegisterFunction("EXACT", Exact)
|
||||
RegisterFunction("FIND", Find)
|
||||
RegisterFunctionComplex("FINDB", Findb)
|
||||
RegisterFunction("LEFT", Left)
|
||||
RegisterFunction("LEFTB", Left) // for now
|
||||
RegisterFunction("LEFTB", Left)
|
||||
RegisterFunction("LEN", Len)
|
||||
RegisterFunction("LENB", Len) // for now
|
||||
RegisterFunction("LENB", Len)
|
||||
RegisterFunction("LOWER", Lower)
|
||||
// RegisterFunction("MID")
|
||||
// RegisterFunction("MIDB")
|
||||
// RegisterFunction("NUMBERVALUE")
|
||||
// RegisterFunction("PHONETIC")
|
||||
RegisterFunction("PROPER", Proper)
|
||||
// RegisterFunction("REPLACE")
|
||||
// RegisterFunction("REPLACEB")
|
||||
RegisterFunction("REPLACE", Replace)
|
||||
RegisterFunction("REPT", Rept)
|
||||
RegisterFunction("RIGHT", Right)
|
||||
RegisterFunction("RIGHTB", Right) // for now
|
||||
RegisterFunction("RIGHTB", Right)
|
||||
RegisterFunction("SEARCH", Search)
|
||||
RegisterFunctionComplex("SEARCHB", Searchb)
|
||||
//RegisterFunction("SUBSTITUTE", )
|
||||
RegisterFunction("T", T)
|
||||
//RegisterFunction("TEXT")
|
||||
//RegisterFunction("TEXTJOIN")
|
||||
RegisterFunction("TEXTJOIN", TextJoin)
|
||||
RegisterFunction("_xlfn.TEXTJOIN", TextJoin)
|
||||
RegisterFunction("TRIM", Trim)
|
||||
RegisterFunction("_xlfn.UNICHAR", Char) // for now
|
||||
RegisterFunction("_xlfn.UNICHAR", Char)
|
||||
RegisterFunction("_xlfn.UNICODE", Unicode)
|
||||
RegisterFunction("UPPER", Upper)
|
||||
RegisterFunction("VALUE", Value)
|
||||
@ -327,12 +317,64 @@ func Lower(args []Result) Result {
|
||||
if len(args) != 1 {
|
||||
return MakeErrorResult("LOWER requires a single string argument")
|
||||
}
|
||||
s := args[0].AsString()
|
||||
|
||||
arg := args[0]
|
||||
switch arg.Type {
|
||||
case ResultTypeError:
|
||||
return arg
|
||||
case ResultTypeNumber, ResultTypeString:
|
||||
return lower(args[0])
|
||||
case ResultTypeList:
|
||||
list := arg.ValueList
|
||||
resultList := []Result{}
|
||||
for _, v := range list {
|
||||
vLower := lower(v)
|
||||
if vLower.Type == ResultTypeError {
|
||||
return vLower
|
||||
}
|
||||
resultList = append(resultList, vLower)
|
||||
}
|
||||
return MakeListResult(resultList)
|
||||
case ResultTypeArray:
|
||||
array := arg.ValueArray
|
||||
resultArray := [][]Result{}
|
||||
for _, r := range array {
|
||||
row := []Result{}
|
||||
for _, v := range r {
|
||||
vLower := lower(v)
|
||||
if vLower.Type == ResultTypeError {
|
||||
return vLower
|
||||
}
|
||||
row = append(row, vLower)
|
||||
}
|
||||
resultArray = append(resultArray, row)
|
||||
}
|
||||
return MakeArrayResult(resultArray)
|
||||
|
||||
default:
|
||||
return MakeErrorResult("Incorrect argument for LOWER")
|
||||
}
|
||||
}
|
||||
|
||||
func lower(arg Result) Result {
|
||||
if arg.Type == ResultTypeEmpty {
|
||||
return arg
|
||||
}
|
||||
s := arg.AsString()
|
||||
if s.Type != ResultTypeString {
|
||||
return MakeErrorResult("LOWER requires a single string argument")
|
||||
}
|
||||
|
||||
return MakeStringResult(strings.ToLower(s.ValueString))
|
||||
if arg.IsBoolean {
|
||||
if s.ValueString == "1" {
|
||||
return MakeStringResult("true")
|
||||
} else if s.ValueString == "0" {
|
||||
return MakeStringResult("false")
|
||||
} else {
|
||||
return MakeErrorResult("Incorrect argument for LOWER")
|
||||
}
|
||||
} else {
|
||||
return MakeStringResult(strings.ToLower(s.ValueString))
|
||||
}
|
||||
}
|
||||
|
||||
// Proper is an implementation of the Excel PROPER function that returns a copy
|
||||
@ -571,3 +613,111 @@ func Value(args []Result) Result {
|
||||
|
||||
return MakeErrorResult("Incorrect argument for VALUE")
|
||||
}
|
||||
|
||||
type parsedReplaceObject struct {
|
||||
text string
|
||||
startPos int
|
||||
length int
|
||||
textToReplace string
|
||||
}
|
||||
|
||||
func parseReplaceResults(fname string, args []Result) (*parsedReplaceObject, Result) {
|
||||
if len(args) != 4 {
|
||||
return nil, MakeErrorResult(fname + " requires four arguments")
|
||||
}
|
||||
if args[0].Type != ResultTypeString {
|
||||
return nil, MakeErrorResult(fname + " requires first argument to be a string")
|
||||
}
|
||||
text := args[0].ValueString
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(fname + " requires second argument to be a number")
|
||||
}
|
||||
startPos := int(args[1].ValueNumber) - 1
|
||||
if args[2].Type != ResultTypeNumber {
|
||||
return nil, MakeErrorResult(fname + " requires third argument to be a number")
|
||||
}
|
||||
length := int(args[2].ValueNumber)
|
||||
if args[3].Type != ResultTypeString {
|
||||
return nil, MakeErrorResult(fname + " requires fourth argument to be a string")
|
||||
}
|
||||
textToReplace := args[3].ValueString
|
||||
return &parsedReplaceObject{
|
||||
text,
|
||||
startPos,
|
||||
length,
|
||||
textToReplace,
|
||||
}, MakeEmptyResult()
|
||||
}
|
||||
|
||||
// Replace is an implementation of the Excel REPLACE().
|
||||
func Replace(args []Result) Result {
|
||||
parsed, errResult := parseReplaceResults("REPLACE", args)
|
||||
if errResult.Type != ResultTypeEmpty {
|
||||
return errResult
|
||||
}
|
||||
text := parsed.text
|
||||
startPos := parsed.startPos
|
||||
length := parsed.length
|
||||
textToReplace := parsed.textToReplace
|
||||
textLen := len(text)
|
||||
if startPos > textLen {
|
||||
startPos = textLen
|
||||
}
|
||||
endPos := startPos + length
|
||||
if endPos > textLen {
|
||||
endPos = textLen
|
||||
}
|
||||
newText := text[0:startPos] + textToReplace + text[endPos:]
|
||||
return MakeStringResult(newText)
|
||||
}
|
||||
|
||||
// TextJoin is an implementation of the Excel TEXTJOIN function.
|
||||
func TextJoin(args []Result) Result {
|
||||
if len(args) < 3 {
|
||||
return MakeErrorResult("TEXTJOIN requires three or more arguments")
|
||||
}
|
||||
|
||||
if args[0].Type != ResultTypeString {
|
||||
return MakeErrorResult("TEXTJOIN requires delimiter to be a string")
|
||||
}
|
||||
delimiter := args[0].ValueString
|
||||
|
||||
if args[1].Type != ResultTypeNumber {
|
||||
return MakeErrorResult("TEXTJOIN requires second argument to be a number or boolean")
|
||||
}
|
||||
ignoreEmpty := args[1].ValueNumber != 0
|
||||
|
||||
arr := collectStrings(args[2:], []string{}, ignoreEmpty)
|
||||
return MakeStringResult(strings.Join(arr, delimiter))
|
||||
}
|
||||
|
||||
func collectStrings(args []Result, arr []string, ignoreEmpty bool) []string {
|
||||
for _, result := range args {
|
||||
switch result.Type {
|
||||
case ResultTypeEmpty:
|
||||
if !ignoreEmpty {
|
||||
arr = append(arr, "")
|
||||
}
|
||||
case ResultTypeString:
|
||||
if result.ValueString != "" || !ignoreEmpty {
|
||||
arr = append(arr, result.ValueString)
|
||||
}
|
||||
case ResultTypeNumber:
|
||||
arr = append(arr, result.Value())
|
||||
case ResultTypeList:
|
||||
arr = appendSlices(arr, collectStrings(result.ValueList, []string{}, ignoreEmpty))
|
||||
case ResultTypeArray:
|
||||
for _, row := range result.ValueArray {
|
||||
arr = appendSlices(arr, collectStrings(row, []string{}, ignoreEmpty))
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
func appendSlices(s0, s1 []string) []string {
|
||||
for _, item := range s1 {
|
||||
s0 = append(s0, item)
|
||||
}
|
||||
return s0
|
||||
}
|
||||
|
@ -1292,3 +1292,304 @@ func TestEomonth(t *testing.T) {
|
||||
ctx := sheet.FormulaContext()
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetTime(time.Date(2018, time.July, 1, 0, 0, 0, 0, time.UTC)) // settlement date
|
||||
sheet.Cell("A2").SetTime(time.Date(2048, time.January, 1, 0, 0, 0, 0, time.UTC)) // maturity date
|
||||
sheet.Cell("A3").SetNumber(0.08) // coupon rate
|
||||
sheet.Cell("A4").SetNumber(0.09) // yield rate
|
||||
sheet.Cell("A5").SetNumber(2) // frequency of payments
|
||||
sheet.Cell("A6").SetNumber(0) // basis
|
||||
sheet.Cell("A7").SetString("07/01/2018") // settlement date in string format
|
||||
sheet.Cell("A8").SetString("01/01/2048") // maturity date in string format
|
||||
|
||||
td := []testStruct{
|
||||
{`=DURATION(A1,A2,A3,A4,A5,A6)`, `10.9191452815 ResultTypeNumber`},
|
||||
{`=DURATION(A7,A8,A3,A4,A5,A6)`, `10.9191452815 ResultTypeNumber`},
|
||||
{`=DURATION(A1,A2,A3,A4,A5,5)`, `#NUM! ResultTypeError`},
|
||||
{`=DURATION(A8,A7,A3,A4,A5,A6)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestMduration(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetTime(time.Date(2008, time.January, 1, 0, 0, 0, 0, time.UTC)) // settlement date
|
||||
sheet.Cell("A2").SetTime(time.Date(2016, time.January, 1, 0, 0, 0, 0, time.UTC)) // maturity date
|
||||
sheet.Cell("A3").SetNumber(0.08) // coupon rate
|
||||
sheet.Cell("A4").SetNumber(0.09) // yield rate
|
||||
sheet.Cell("A5").SetNumber(2) // frequency of payments
|
||||
sheet.Cell("A6").SetNumber(0) // basis
|
||||
sheet.Cell("A7").SetString("01/01/2008") // settlement date in string format
|
||||
sheet.Cell("A8").SetString("01/01/2016") // maturity date in string format
|
||||
|
||||
td := []testStruct{
|
||||
{`=MDURATION(A1,A2,A3,A4,A5,A6)`, `5.73566981391 ResultTypeNumber`},
|
||||
{`=MDURATION(A7,A8,A3,A4,A5,A6)`, `5.73566981391 ResultTypeNumber`},
|
||||
{`=MDURATION(A1,A2,A3,A4,A5,5)`, `#NUM! ResultTypeError`},
|
||||
{`=MDURATION(A8,A7,A3,A4,A5,A6)`, `#NUM! ResultTypeError`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestPduration(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
td := []testStruct{
|
||||
{`=PDURATION(0.025,2000,2200)`, `3.85986616262 ResultTypeNumber`},
|
||||
{`=PDURATION(0,2000,2200)`, `#NUM! ResultTypeError`},
|
||||
{`=PDURATION(0.025,"2000",2200)`, `#VALUE! ResultTypeError`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestRow(t *testing.T) {
|
||||
td := []testStruct{
|
||||
{`=ROW(A1)`, `1 ResultTypeNumber`},
|
||||
{`=ROW(A11)`, `11 ResultTypeNumber`},
|
||||
{`=ROW(B11)`, `11 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestRows(t *testing.T) {
|
||||
td := []testStruct{
|
||||
{`=ROWS(A1:E8)`, `8 ResultTypeNumber`},
|
||||
{`=ROWS(E8:A1)`, `#VALUE! ResultTypeError`},
|
||||
}
|
||||
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestLookup(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(1)
|
||||
sheet.Cell("A2").SetNumber(2)
|
||||
sheet.Cell("A3").SetNumber(3)
|
||||
|
||||
sheet.Cell("B1").SetString("value1")
|
||||
sheet.Cell("B2").SetString("value2")
|
||||
sheet.Cell("B3").SetString("value3")
|
||||
|
||||
td := []testStruct{
|
||||
{`=LOOKUP(2,A1:A3,B1:B3)`, `value2 ResultTypeString`},
|
||||
{`=LOOKUP(2,A1:B1,A2:B2)`, `#N/A ResultTypeError`},
|
||||
{`=LOOKUP(1,A1:B1,A2:B2)`, `2 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestVlookup(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(100)
|
||||
sheet.Cell("A2").SetNumber(200)
|
||||
sheet.Cell("A3").SetNumber(300)
|
||||
sheet.Cell("A4").SetNumber(400)
|
||||
|
||||
sheet.Cell("B1").SetString("value1")
|
||||
sheet.Cell("B2").SetString("value2")
|
||||
sheet.Cell("B3").SetString("value3")
|
||||
sheet.Cell("B4").SetString("value4")
|
||||
|
||||
td := []testStruct{
|
||||
{`=VLOOKUP(150,A1:B4,2)`, `value1 ResultTypeString`},
|
||||
{`=VLOOKUP(250,A1:B4,2)`, `value2 ResultTypeString`},
|
||||
{`=VLOOKUP(250,A1:B4,2,FALSE)`, `#N/A ResultTypeError`},
|
||||
{`=VLOOKUP(300,A1:B4,2,FALSE)`, `value3 ResultTypeString`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestLarge(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(400)
|
||||
sheet.Cell("A2").SetNumber(300)
|
||||
sheet.Cell("A3").SetNumber(500)
|
||||
sheet.Cell("A4").SetNumber(100)
|
||||
sheet.Cell("A5").SetNumber(200)
|
||||
sheet.Cell("B1").SetString("abcde")
|
||||
sheet.Cell("B2").SetNumber(150)
|
||||
sheet.Cell("B3").SetNumber(350)
|
||||
sheet.Cell("B4").SetNumber(450)
|
||||
sheet.Cell("B5").SetNumber(250)
|
||||
|
||||
td := []testStruct{
|
||||
{`=LARGE(A1:B5,4)`, `350 ResultTypeNumber`},
|
||||
{`=LARGE(A1:B5,0)`, `#NUM! ResultTypeError`},
|
||||
{`=LARGE(A1:B5,10)`, `#NUM! ResultTypeError`},
|
||||
{`=LARGE(A2:B2,2)`, `150 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestSmall(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(400)
|
||||
sheet.Cell("A2").SetNumber(300)
|
||||
sheet.Cell("A3").SetNumber(500)
|
||||
sheet.Cell("A4").SetNumber(100)
|
||||
sheet.Cell("A5").SetNumber(200)
|
||||
sheet.Cell("B1").SetString("abcde")
|
||||
sheet.Cell("B2").SetNumber(150)
|
||||
sheet.Cell("B3").SetNumber(350)
|
||||
sheet.Cell("B4").SetNumber(450)
|
||||
sheet.Cell("B5").SetNumber(250)
|
||||
|
||||
td := []testStruct{
|
||||
{`=SMALL(A1:B5,4)`, `250 ResultTypeNumber`},
|
||||
{`=SMALL(A1:B5,0)`, `#NUM! ResultTypeError`},
|
||||
{`=SMALL(A1:B5,10)`, `#NUM! ResultTypeError`},
|
||||
{`=SMALL(A2:B2,2)`, `300 ResultTypeNumber`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestLower(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(400)
|
||||
sheet.Cell("A2").SetString("Hello")
|
||||
sheet.Cell("B1").SetString("World!")
|
||||
|
||||
sheet.Cell("D1").SetFormulaArray("=LOWER(A1:B2)")
|
||||
sheet.RecalculateFormulas()
|
||||
|
||||
td := []testStruct{
|
||||
{`=D1`, `400 ResultTypeArray`},
|
||||
{`=D2`, `hello ResultTypeString`},
|
||||
{`=E1`, `world! ResultTypeString`},
|
||||
{`=E2`, ` ResultTypeEmpty`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(400)
|
||||
sheet.Cell("A2").SetString("Hello")
|
||||
sheet.Cell("B1").SetString("World!")
|
||||
|
||||
sheet.Cell("D1").SetFormulaArray("=LOWER(A1:B2)")
|
||||
sheet.RecalculateFormulas()
|
||||
|
||||
td := []testStruct{
|
||||
{`=REPLACE("Hello World!",7,5,"Earth")`, `Hello Earth! ResultTypeString`},
|
||||
{`=REPLACE("Hello World!",7,10,"Earth")`, `Hello Earth ResultTypeString`},
|
||||
{`=REPLACE("Hello World",30,10,"!")`, `Hello World! ResultTypeString`},
|
||||
{`=REPLACE("Hello World!",7,0,"new ")`, `Hello new World! ResultTypeString`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestTextJoin(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(1)
|
||||
sheet.Cell("B1").SetString("2")
|
||||
sheet.Cell("A2").SetNumber(3)
|
||||
sheet.Cell("B2").SetString("4")
|
||||
sheet.Cell("A3").SetNumber(5)
|
||||
sheet.Cell("B3").SetString("6")
|
||||
sheet.Cell("A4").SetString("7")
|
||||
sheet.Cell("A6").SetString("8")
|
||||
sheet.Cell("A7").SetString("9")
|
||||
|
||||
td := []testStruct{
|
||||
{`=TEXTJOIN(".",FALSE,A1)`, `1 ResultTypeString`},
|
||||
{`=TEXTJOIN("|",TRUE,A4:A7)`, `7|8|9 ResultTypeString`},
|
||||
{`=TEXTJOIN("|",FALSE,A4:A7)`, `7||8|9 ResultTypeString`},
|
||||
{`=TEXTJOIN(".",TRUE,A1:B2,A3:B3,A4,A5,A6,A7)`, `1.2.3.4.5.6.7.8.9 ResultTypeString`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
ss := spreadsheet.New()
|
||||
sheet := ss.AddSheet()
|
||||
|
||||
sheet.Cell("A1").SetNumber(1)
|
||||
sheet.Cell("B1").SetString("2")
|
||||
sheet.Cell("C1").SetNumber(3)
|
||||
sheet.Cell("B2").SetNumber(5)
|
||||
sheet.Cell("C2").SetString("6")
|
||||
sheet.Cell("A3").SetString("7")
|
||||
sheet.Cell("B3").SetString("8")
|
||||
sheet.Cell("C3").SetString("9")
|
||||
|
||||
sheet.Cell("A4").SetFormulaRaw(`=INDEX(A1:C3,1,1)`)
|
||||
sheet.Cell("A5").SetFormulaArray(`=INDEX(A1:C3,2)`)
|
||||
sheet.Cell("A6").SetFormulaArray(`=INDEX(A1:C3,,2)`)
|
||||
sheet.Cell("A10").SetFormulaArray(`=INDEX(A1:C3)`)
|
||||
|
||||
sheet.RecalculateFormulas()
|
||||
|
||||
td := []testStruct{
|
||||
{`=A4`, `1 ResultTypeNumber`},
|
||||
{`=A5`, `0 ResultTypeArray`},
|
||||
{`=B5`, `5 ResultTypeNumber`},
|
||||
{`=C5`, `6 ResultTypeNumber`},
|
||||
{`=A7`, `5 ResultTypeNumber`},
|
||||
{`=A8`, `8 ResultTypeNumber`},
|
||||
{`=A10`, `#VALUE! ResultTypeError`},
|
||||
}
|
||||
|
||||
ctx := sheet.FormulaContext()
|
||||
|
||||
runTests(t, ctx, td)
|
||||
}
|
||||
|
@ -719,7 +719,9 @@ func (s *Sheet) setArray(origin string, arr formula.Result) error {
|
||||
sr := s.Row(cref.RowIdx + uint32(ir))
|
||||
for ic, val := range row {
|
||||
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
||||
cell.SetCachedFormulaResult(val.String())
|
||||
if val.Type != formula.ResultTypeEmpty {
|
||||
cell.SetCachedFormulaResult(val.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user