mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-27 13:48:54 +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" {
|
if got := sheet.Cell("F2").GetFormattedValue(); got != "8" {
|
||||||
t.Errorf("expected 8 in F2, got %s", got)
|
t.Errorf("expected 8 in F2, got %s", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -147,26 +147,32 @@ func Days(args []Result) Result {
|
|||||||
return MakeErrorResult("DAYS requires two arguments")
|
return MakeErrorResult("DAYS requires two arguments")
|
||||||
}
|
}
|
||||||
var sd, ed float64
|
var sd, ed float64
|
||||||
if args[0].Type == ResultTypeNumber {
|
switch args[0].Type {
|
||||||
|
case ResultTypeNumber:
|
||||||
ed = args[0].ValueNumber
|
ed = args[0].ValueNumber
|
||||||
} else {
|
case ResultTypeString:
|
||||||
edResult := DateValue([]Result{args[0]})
|
edResult := DateValue([]Result{args[0]})
|
||||||
if edResult.Type == ResultTypeError {
|
if edResult.Type == ResultTypeError {
|
||||||
return MakeErrorResult("Incorrect end date for DAYS")
|
return MakeErrorResult("Incorrect end date for DAYS")
|
||||||
}
|
}
|
||||||
ed = edResult.ValueNumber
|
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
|
sd = args[1].ValueNumber
|
||||||
if sd < 62 && ed >= 62 {
|
if sd < 62 && ed >= 62 {
|
||||||
sd--
|
sd--
|
||||||
}
|
}
|
||||||
} else {
|
case ResultTypeString:
|
||||||
sdResult := DateValue([]Result{args[1]})
|
sdResult := DateValue([]Result{args[1]})
|
||||||
if sdResult.Type == ResultTypeError {
|
if sdResult.Type == ResultTypeError {
|
||||||
return MakeErrorResult("Incorrect start date for DAYS")
|
return MakeErrorResult("Incorrect start date for DAYS")
|
||||||
}
|
}
|
||||||
sd = sdResult.ValueNumber
|
sd = sdResult.ValueNumber
|
||||||
|
default:
|
||||||
|
return MakeErrorResult("Incorrect argument for DAYS")
|
||||||
}
|
}
|
||||||
days := float64(int(ed - sd))
|
days := float64(int(ed - sd))
|
||||||
return MakeNumberResult(days)
|
return MakeNumberResult(days)
|
||||||
@ -769,11 +775,15 @@ func YearFrac(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return MakeErrorResult("incorrect start date")
|
return MakeErrorResult("incorrect start date")
|
||||||
}
|
}
|
||||||
startDateS := startDate.Unix()
|
|
||||||
endDate, err := getValueAsTime(args[1].Value(), epoch)
|
endDate, err := getValueAsTime(args[1].Value(), epoch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MakeErrorResult("incorrect end date")
|
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()
|
endDateS := endDate.Unix()
|
||||||
sy, sm, sd := startDate.Date()
|
sy, sm, sd := startDate.Date()
|
||||||
ey, em, ed := endDate.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/internal/wildcard"
|
||||||
"github.com/unidoc/unioffice/spreadsheet/reference"
|
"github.com/unidoc/unioffice/spreadsheet/reference"
|
||||||
|
"github.com/unidoc/unioffice/internal/mergesort"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -24,7 +25,11 @@ func init() {
|
|||||||
RegisterFunctionComplex("OFFSET", Offset)
|
RegisterFunctionComplex("OFFSET", Offset)
|
||||||
RegisterFunction("MATCH", Match)
|
RegisterFunction("MATCH", Match)
|
||||||
RegisterFunction("HLOOKUP", HLookup)
|
RegisterFunction("HLOOKUP", HLookup)
|
||||||
|
RegisterFunction("LARGE", Large)
|
||||||
RegisterFunction("LOOKUP", Lookup)
|
RegisterFunction("LOOKUP", Lookup)
|
||||||
|
RegisterFunction("ROW", Row)
|
||||||
|
RegisterFunction("ROWS", Rows)
|
||||||
|
RegisterFunction("SMALL", Small)
|
||||||
RegisterFunction("VLOOKUP", VLookup)
|
RegisterFunction("VLOOKUP", VLookup)
|
||||||
RegisterFunction("TRANSPOSE", Transpose)
|
RegisterFunction("TRANSPOSE", Transpose)
|
||||||
}
|
}
|
||||||
@ -79,8 +84,9 @@ func Columns(args []Result) Result {
|
|||||||
|
|
||||||
// Index implements the Excel INDEX function.
|
// Index implements the Excel INDEX function.
|
||||||
func Index(args []Result) Result {
|
func Index(args []Result) Result {
|
||||||
if len(args) < 3 {
|
argsNum := len(args)
|
||||||
return MakeErrorResult("INDEX requires three arguments")
|
if argsNum < 2 || argsNum > 3 {
|
||||||
|
return MakeErrorResult("INDEX requires from one to three arguments")
|
||||||
}
|
}
|
||||||
arr := args[0]
|
arr := args[0]
|
||||||
if arr.Type != ResultTypeArray && arr.Type != ResultTypeList {
|
if arr.Type != ResultTypeArray && arr.Type != ResultTypeList {
|
||||||
@ -90,29 +96,73 @@ func Index(args []Result) Result {
|
|||||||
if rowArg.Type != ResultTypeNumber {
|
if rowArg.Type != ResultTypeNumber {
|
||||||
return MakeErrorResult("INDEX requires numeric row argument")
|
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
|
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
|
var rowVal []Result
|
||||||
if arr.Type == ResultTypeArray {
|
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")
|
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 {
|
} else {
|
||||||
if row < 0 || row >= 1 {
|
valueList := arr.ValueList
|
||||||
|
if row < -1 || row >= 1 {
|
||||||
return MakeErrorResult("INDEX has row out of range")
|
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")
|
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]
|
rv := rowVal[col]
|
||||||
// empty cell returns a zero
|
// empty cell returns a zero
|
||||||
if rv.Type == ResultTypeEmpty {
|
if rv.Type == ResultTypeEmpty {
|
||||||
@ -601,3 +651,98 @@ func Transpose(args []Result) Result {
|
|||||||
}
|
}
|
||||||
return MakeArrayResult(res)
|
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("_xlfn.IFS", Ifs)
|
||||||
RegisterFunction("NOT", Not)
|
RegisterFunction("NOT", Not)
|
||||||
RegisterFunction("OR", Or)
|
RegisterFunction("OR", Or)
|
||||||
// TODO: RegisterFunction("SWITCH", Switch) // Only in Excel 2016+
|
RegisterFunction("TRUE", True)
|
||||||
RegisterFunction("TRUE", True) // yup, TRUE()/FALSE() are functions in Excel, news to me...
|
|
||||||
RegisterFunction("_xlfn.XOR", Xor)
|
RegisterFunction("_xlfn.XOR", Xor)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ func init() {
|
|||||||
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
RegisterFunction("ACOSH", makeMathWrapper("ASIN", math.Acosh))
|
||||||
RegisterFunction("_xlfn.ACOT", makeMathWrapper("ACOT", func(v float64) float64 { return math.Pi/2 - math.Atan(v) }))
|
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) }))
|
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("_xlfn.ARABIC", Arabic)
|
||||||
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
RegisterFunction("ASIN", makeMathWrapper("ASIN", math.Asin))
|
||||||
RegisterFunction("ASINH", makeMathWrapper("ASINH", math.Asinh))
|
RegisterFunction("ASINH", makeMathWrapper("ASINH", math.Asinh))
|
||||||
@ -56,14 +55,12 @@ func init() {
|
|||||||
RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise)
|
RegisterFunction("_xlfn.FLOOR.PRECISE", FloorPrecise)
|
||||||
RegisterFunction("GCD", GCD)
|
RegisterFunction("GCD", GCD)
|
||||||
RegisterFunction("INT", Int)
|
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("LCM", LCM)
|
||||||
RegisterFunction("LN", makeMathWrapper("LN", math.Log))
|
RegisterFunction("LN", makeMathWrapper("LN", math.Log))
|
||||||
RegisterFunction("LOG", Log)
|
RegisterFunction("LOG", Log)
|
||||||
RegisterFunction("LOG10", makeMathWrapper("LOG10", math.Log10))
|
RegisterFunction("LOG10", makeMathWrapper("LOG10", math.Log10))
|
||||||
RegisterFunction("MDETERM", MDeterm)
|
RegisterFunction("MDETERM", MDeterm)
|
||||||
// RegisterFunction("MINVERSE", MInverse) // TODO: skipping the other matrix functinos, not sure how common they are
|
|
||||||
// RegisterFunction("MMULT"
|
|
||||||
RegisterFunction("MOD", Mod)
|
RegisterFunction("MOD", Mod)
|
||||||
RegisterFunction("MROUND", Mround)
|
RegisterFunction("MROUND", Mround)
|
||||||
RegisterFunction("MULTINOMIAL", Multinomial)
|
RegisterFunction("MULTINOMIAL", Multinomial)
|
||||||
@ -88,15 +85,11 @@ func init() {
|
|||||||
RegisterFunction("SINH", makeMathWrapper("SINH", math.Sinh))
|
RegisterFunction("SINH", makeMathWrapper("SINH", math.Sinh))
|
||||||
RegisterFunction("SQRT", makeMathWrapper("SQRT", math.Sqrt))
|
RegisterFunction("SQRT", makeMathWrapper("SQRT", math.Sqrt))
|
||||||
RegisterFunction("SQRTPI", makeMathWrapper("SQRTPI", func(v float64) float64 { return math.Sqrt(v * math.Pi) }))
|
RegisterFunction("SQRTPI", makeMathWrapper("SQRTPI", func(v float64) float64 { return math.Sqrt(v * math.Pi) }))
|
||||||
// RegisterFunction("SUBTOTAL"
|
|
||||||
RegisterFunction("SUM", Sum)
|
RegisterFunction("SUM", Sum)
|
||||||
RegisterFunction("SUMIF", SumIf)
|
RegisterFunction("SUMIF", SumIf)
|
||||||
RegisterFunction("SUMIFS", SumIfs)
|
RegisterFunction("SUMIFS", SumIfs)
|
||||||
RegisterFunction("SUMPRODUCT", SumProduct)
|
RegisterFunction("SUMPRODUCT", SumProduct)
|
||||||
RegisterFunction("SUMSQ", SumSquares)
|
RegisterFunction("SUMSQ", SumSquares)
|
||||||
//RegisterFunction("SUMX2MY2"
|
|
||||||
//RegisterFunction("SUMX2PY2"
|
|
||||||
//RegisterFunction("SUMXMY2"
|
|
||||||
RegisterFunction("TAN", makeMathWrapper("TAN", math.Tan))
|
RegisterFunction("TAN", makeMathWrapper("TAN", math.Tan))
|
||||||
RegisterFunction("TANH", makeMathWrapper("TANH", math.Tanh))
|
RegisterFunction("TANH", makeMathWrapper("TANH", math.Tanh))
|
||||||
RegisterFunction("TRUNC", Trunc)
|
RegisterFunction("TRUNC", Trunc)
|
||||||
|
@ -18,42 +18,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// RegisterFunction("ASC") Need to figure out how to test
|
|
||||||
// RegisterFunction("BAHTTEXT")
|
|
||||||
RegisterFunction("CHAR", Char)
|
RegisterFunction("CHAR", Char)
|
||||||
RegisterFunction("CLEAN", Clean)
|
RegisterFunction("CLEAN", Clean)
|
||||||
RegisterFunction("CODE", Code)
|
RegisterFunction("CODE", Code)
|
||||||
RegisterFunction("CONCATENATE", Concat)
|
RegisterFunction("CONCATENATE", Concat)
|
||||||
RegisterFunction("CONCAT", Concat)
|
RegisterFunction("CONCAT", Concat)
|
||||||
RegisterFunction("_xlfn.CONCAT", Concat)
|
RegisterFunction("_xlfn.CONCAT", Concat)
|
||||||
// RegisterFunction("DBCS")
|
|
||||||
// RegisterFunction("DOLLAR") Need to test with Excel
|
|
||||||
RegisterFunction("EXACT", Exact)
|
RegisterFunction("EXACT", Exact)
|
||||||
RegisterFunction("FIND", Find)
|
RegisterFunction("FIND", Find)
|
||||||
RegisterFunctionComplex("FINDB", Findb)
|
RegisterFunctionComplex("FINDB", Findb)
|
||||||
RegisterFunction("LEFT", Left)
|
RegisterFunction("LEFT", Left)
|
||||||
RegisterFunction("LEFTB", Left) // for now
|
RegisterFunction("LEFTB", Left)
|
||||||
RegisterFunction("LEN", Len)
|
RegisterFunction("LEN", Len)
|
||||||
RegisterFunction("LENB", Len) // for now
|
RegisterFunction("LENB", Len)
|
||||||
RegisterFunction("LOWER", Lower)
|
RegisterFunction("LOWER", Lower)
|
||||||
// RegisterFunction("MID")
|
|
||||||
// RegisterFunction("MIDB")
|
|
||||||
// RegisterFunction("NUMBERVALUE")
|
|
||||||
// RegisterFunction("PHONETIC")
|
|
||||||
RegisterFunction("PROPER", Proper)
|
RegisterFunction("PROPER", Proper)
|
||||||
// RegisterFunction("REPLACE")
|
RegisterFunction("REPLACE", Replace)
|
||||||
// RegisterFunction("REPLACEB")
|
|
||||||
RegisterFunction("REPT", Rept)
|
RegisterFunction("REPT", Rept)
|
||||||
RegisterFunction("RIGHT", Right)
|
RegisterFunction("RIGHT", Right)
|
||||||
RegisterFunction("RIGHTB", Right) // for now
|
RegisterFunction("RIGHTB", Right)
|
||||||
RegisterFunction("SEARCH", Search)
|
RegisterFunction("SEARCH", Search)
|
||||||
RegisterFunctionComplex("SEARCHB", Searchb)
|
RegisterFunctionComplex("SEARCHB", Searchb)
|
||||||
//RegisterFunction("SUBSTITUTE", )
|
|
||||||
RegisterFunction("T", T)
|
RegisterFunction("T", T)
|
||||||
//RegisterFunction("TEXT")
|
RegisterFunction("TEXTJOIN", TextJoin)
|
||||||
//RegisterFunction("TEXTJOIN")
|
RegisterFunction("_xlfn.TEXTJOIN", TextJoin)
|
||||||
RegisterFunction("TRIM", Trim)
|
RegisterFunction("TRIM", Trim)
|
||||||
RegisterFunction("_xlfn.UNICHAR", Char) // for now
|
RegisterFunction("_xlfn.UNICHAR", Char)
|
||||||
RegisterFunction("_xlfn.UNICODE", Unicode)
|
RegisterFunction("_xlfn.UNICODE", Unicode)
|
||||||
RegisterFunction("UPPER", Upper)
|
RegisterFunction("UPPER", Upper)
|
||||||
RegisterFunction("VALUE", Value)
|
RegisterFunction("VALUE", Value)
|
||||||
@ -327,12 +317,64 @@ func Lower(args []Result) Result {
|
|||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return MakeErrorResult("LOWER requires a single string argument")
|
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 {
|
if s.Type != ResultTypeString {
|
||||||
return MakeErrorResult("LOWER requires a single string argument")
|
return MakeErrorResult("LOWER requires a single string argument")
|
||||||
}
|
}
|
||||||
|
if arg.IsBoolean {
|
||||||
return MakeStringResult(strings.ToLower(s.ValueString))
|
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
|
// 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")
|
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()
|
ctx := sheet.FormulaContext()
|
||||||
runTests(t, ctx, td)
|
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))
|
sr := s.Row(cref.RowIdx + uint32(ir))
|
||||||
for ic, val := range row {
|
for ic, val := range row {
|
||||||
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
||||||
cell.SetCachedFormulaResult(val.String())
|
if val.Type != formula.ResultTypeEmpty {
|
||||||
|
cell.SetCachedFormulaResult(val.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
x
Reference in New Issue
Block a user