diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index b9c41735..5f3d4c4c 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -384,6 +384,9 @@ func Accrintm(args []Result) Result { if errResult.Type == ResultTypeError { return errResult } + if issueDate >= settlementDate { + return MakeErrorResultType(ErrorTypeNum, "Issue date should be earlier than settlement date") + } if args[2].Type != ResultTypeNumber { return MakeErrorResult("ACCRINTM requires rate to be number argument") } diff --git a/spreadsheet/formula/fnmathtrig.go b/spreadsheet/formula/fnmathtrig.go index 9c147c45..d9c09d7d 100644 --- a/spreadsheet/formula/fnmathtrig.go +++ b/spreadsheet/formula/fnmathtrig.go @@ -1376,8 +1376,8 @@ const ( // Round is an implementation of the Excel ROUND function that rounds a number // to a specified number of digits. func round(args []Result, mode rmode) Result { - if len(args) == 0 { - return MakeErrorResult("ROUND() requires at least one numeric arguments") + if len(args) != 2 { + return MakeErrorResult("ROUND() requires two numeric arguments") } // number to round number := args[0].AsNumber() @@ -1385,14 +1385,11 @@ func round(args []Result, mode rmode) Result { return MakeErrorResult("first argument to ROUND() must be a number") } - digits := float64(0) - if len(args) > 1 { - digitArg := args[1].AsNumber() - if digitArg.Type != ResultTypeNumber { - return MakeErrorResult("second argument to ROUND() must be a number") - } - digits = digitArg.ValueNumber + digitArg := args[1].AsNumber() + if digitArg.Type != ResultTypeNumber { + return MakeErrorResult("second argument to ROUND() must be a number") } + digits := digitArg.ValueNumber v := number.ValueNumber diff --git a/spreadsheet/formula/fntext.go b/spreadsheet/formula/fntext.go index 899dd1c4..04621320 100644 --- a/spreadsheet/formula/fntext.go +++ b/spreadsheet/formula/fntext.go @@ -33,6 +33,7 @@ func init() { RegisterFunction("LEN", Len) RegisterFunction("LENB", Len) RegisterFunction("LOWER", Lower) + RegisterFunction("MID", Mid) RegisterFunction("PROPER", Proper) RegisterFunction("REPLACE", Replace) RegisterFunction("REPT", Rept) @@ -40,6 +41,7 @@ func init() { RegisterFunction("RIGHTB", Right) RegisterFunction("SEARCH", Search) RegisterFunctionComplex("SEARCHB", Searchb) + RegisterFunction("SUBSTITUTE", Substitute) RegisterFunction("T", T) RegisterFunction("TEXT", Text) RegisterFunction("TEXTJOIN", TextJoin) @@ -380,6 +382,43 @@ func lower(arg Result) Result { } } +// Mid is an implementation of the Excel MID function that returns a copy +// of the string with each word capitalized. +func Mid(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("MID requires three arguments") + } + if args[0].Type != ResultTypeString { + return MakeErrorResult("MID requires text to be a string") + } + text := args[0].ValueString + if args[1].Type != ResultTypeNumber { + return MakeErrorResult("MID requires start_num to be a number") + } + startNum := int(args[1].ValueNumber) + if startNum < 1 { + return MakeErrorResult("MID requires start_num to be more than 0") + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("MID requires num_chars to be a number") + } + numChars := int(args[2].ValueNumber) + if numChars < 0 { + return MakeErrorResult("MID requires num_chars to be non negative") + } + l := len(text) + if startNum > l { + return MakeStringResult("") + } + startNum-- + endNum := startNum + numChars + if endNum > l + 1 { + return MakeStringResult(text[startNum:]) + } else { + return MakeStringResult(text[startNum:endNum]) + } +} + // Proper is an implementation of the Excel PROPER function that returns a copy // of the string with each word capitalized. func Proper(args []Result) Result { @@ -534,6 +573,67 @@ func Searchb(ctx Context, ev Evaluator, args []Result) Result { return MakeErrorResultType(ErrorTypeValue, "Not found") } +// Substitute is an implementation of the Excel SUBSTITUTE function. +func Substitute(args []Result) Result { + argsNum := len(args) + if argsNum != 3 && argsNum != 4 { + return MakeErrorResult("SUBSTITUTE requires three or four arguments") + } + text, errResult := getString(args[0], "SUBSTITUTE", "text") + if errResult.Type == ResultTypeError { + return errResult + } + oldText, errResult := getString(args[1], "SUBSTITUTE", "old text") + if errResult.Type == ResultTypeError { + return errResult + } + newText, errResult := getString(args[2], "SUBSTITUTE", "new text") + if errResult.Type == ResultTypeError { + return errResult + } + instanceNum := 0 + if argsNum == 3 { + return MakeStringResult(strings.Replace(text, oldText, newText, -1)) + } else { + instanceNumF, errResult := getNumber(args[3], "SUBSTITUTE", "instance_num") + if errResult.Type == ResultTypeError { + return errResult + } + instanceNum = int(instanceNumF) + if instanceNum < 1 { + return MakeErrorResult("instance_num should be more than zero") + } + textCopy := text + countdown := instanceNum + pos := -1 + l := len(oldText) + thrownTotal := 0 + for { + countdown-- + index := strings.Index(textCopy, oldText) + if index == -1 { + pos = -1 + break + } else { + pos = index + thrownTotal + if countdown == 0 { + break + } + thrown := l + index + thrownTotal += thrown + textCopy = textCopy[thrown:] + } + } + if pos == -1 { + return MakeStringResult(text) + } else { + pre := text[:pos] + post := text[pos+l:] + return MakeStringResult(pre + newText + post) + } + } +} + // T is an implementation of the Excel T function that returns whether the // argument is text. func T(args []Result) Result { @@ -752,3 +852,29 @@ func Text(args []Result) Result { return MakeErrorResult("Incorrect argument for TEXT") } } + +func getString(arg Result, funcName, argName string) (string, Result) { + switch arg.Type { + case ResultTypeString, ResultTypeNumber, ResultTypeEmpty: + return arg.Value(), empty + default: + return "", MakeErrorResult(funcName + " requires " + argName + " to be a number or string") + } +} + +func getNumber(arg Result, funcName, argName string) (float64, Result) { + switch arg.Type { + case ResultTypeEmpty: + return 0, empty + case ResultTypeNumber: + return arg.ValueNumber, empty + case ResultTypeString: + f, err := strconv.ParseFloat(arg.ValueString, 64) + if err != nil { + return 0, MakeErrorResult(argName + " should be a number for " + funcName) + } + return f, empty + default: + return 0, MakeErrorResult(funcName + " requires " + argName + " to be a number or empty") + } +} diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 3429a581..020c659a 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -564,8 +564,29 @@ func TestMatch(t *testing.T) { runTests(t, ctx, td) } -func TestMaxA(t *testing.T) { +func TestMax(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + sheet.Cell("A1").SetNumber(0.1) + sheet.Cell("B1").SetNumber(0.2) + + sheet.Cell("A2").SetNumber(0.4) + sheet.Cell("B2").SetNumber(0.8) + + sheet.Cell("A3").SetBool(true) + sheet.Cell("B3").SetBool(false) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`MAX(A1:B3)`, `0.8 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestMaxA(t *testing.T) { ss := spreadsheet.New() sheet := ss.AddSheet() @@ -587,8 +608,29 @@ func TestMaxA(t *testing.T) { runTests(t, ctx, td) } -func TestMinA(t *testing.T) { +func TestMin(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + sheet.Cell("A1").SetNumber(0.1) + sheet.Cell("B1").SetNumber(0.2) + + sheet.Cell("A2").SetNumber(0.4) + sheet.Cell("B2").SetNumber(0.8) + + sheet.Cell("A3").SetBool(true) + sheet.Cell("B3").SetBool(false) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`MIN(A1:B3)`, `0.1 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestMinA(t *testing.T) { ss := spreadsheet.New() sheet := ss.AddSheet() @@ -611,7 +653,6 @@ func TestMinA(t *testing.T) { } func TestIfs(t *testing.T) { - ss := spreadsheet.New() sheet := ss.AddSheet() @@ -2742,3 +2783,111 @@ func TestYieldmat(t *testing.T) { runTests(t, ctx, td) } + +func TestMid(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=MID("Fluid Flow",1,5)`, `Fluid ResultTypeString`}, + {`=MID("Fluid Flow",7,20)`, `Flow ResultTypeString`}, + {`=MID("Fluid Flow",20,5)`, ` ResultTypeString`}, + {`=MID("Fluid Flow",1,0)`, ` ResultTypeString`}, + {`=MID("Fluid Flow",0,5)`, `#VALUE! ResultTypeError`}, + {`=MID("Fluid Flow",7,-20)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestSubstitute(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetString("Hello Earth Earth Earth") + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=SUBSTITUTE(A1,"Earth","Krypton",1)`, `Hello Krypton Earth Earth ResultTypeString`}, + {`=SUBSTITUTE(A1,"Earth","Krypton",2)`, `Hello Earth Krypton Earth ResultTypeString`}, + {`=SUBSTITUTE(A1,"Earth","Krypton",3)`, `Hello Earth Earth Krypton ResultTypeString`}, + {`=SUBSTITUTE(A1,"Earth","Krypton",4)`, `Hello Earth Earth Earth ResultTypeString`}, + {`=SUBSTITUTE(A1,"Earth","Krypton")`, `Hello Krypton Krypton Krypton ResultTypeString`}, + {`=SUBSTITUTE(A1,"World","Krypton")`, `Hello Earth Earth Earth ResultTypeString`}, + {`=SUBSTITUTE(A1,"Earth","Krypton",0)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestAnd(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=AND(FALSE,FALSE)`, `0 ResultTypeNumber`}, + {`=AND(TRUE,FALSE)`, `0 ResultTypeNumber`}, + {`=AND(FALSE,TRUE)`, `0 ResultTypeNumber`}, + {`=AND(TRUE,TRUE)`, `1 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} + +func TestIferror(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=IFERROR("No error","ERROR")`, `No error ResultTypeString`}, + {`=IFERROR(1/0,"ERROR")`, `ERROR ResultTypeString`}, + } + + runTests(t, ctx, td) +} + +func TestChar(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=CHAR(65)`, `A ResultTypeString`}, + {`=CHAR(255)`, `ΓΏ ResultTypeString`}, + {`=CHAR(1000)`, `#VALUE! ResultTypeError`}, + {`=CHAR("invalid")`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} + +func TestRound(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=ROUND(2.14,1)`, `2.1 ResultTypeNumber`}, + {`=ROUND(2.16,1)`, `2.2 ResultTypeNumber`}, + {`=ROUND(-2.14,1)`, `-2.1 ResultTypeNumber`}, + {`=ROUND(-2.16,1)`, `-2.2 ResultTypeNumber`}, + {`=ROUND(21.5,-1)`, `20 ResultTypeNumber`}, + {`=ROUND(21.5,-2)`, `0 ResultTypeNumber`}, + {`=ROUND(-55.5,-1)`, `-60 ResultTypeNumber`}, + {`=ROUND(-55.5,-2)`, `-100 ResultTypeNumber`}, + {`=ROUND(-55.5,0)`, `-56 ResultTypeNumber`}, + {`=ROUND(-55.4,)`, `-55 ResultTypeNumber`}, + {`=ROUND(-55.4)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +}