mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
Get all cells in a row with empty ones (#377)
* Get all cells in a row with empty ones * sheet.MaxColumnIdx() changed * goimports for all
This commit is contained in:
parent
5f9c94bf84
commit
72ad869a28
36
_examples/spreadsheet/cells-with-empty/main.go
Normal file
36
_examples/spreadsheet/cells-with-empty/main.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2017 FoxyUtils ehf. All rights reserved.
|
||||||
|
package main
|
||||||
|
|
||||||
|
// This example demonstrates outputing all cells in a row of an excel spreadsheet, including empty cells.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/unidoc/unioffice/spreadsheet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ss, err := spreadsheet.Open("test.xlsx")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error opening document: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := ss.Sheets()[0]
|
||||||
|
|
||||||
|
maxColumnIdx := s.MaxColumnIdx()
|
||||||
|
for _, row := range s.Rows() {
|
||||||
|
for _, cell := range row.CellsWithEmpty(maxColumnIdx) {
|
||||||
|
fmt.Println(cell.Reference(), ":", cell.GetFormattedValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Print("\n\n\n")
|
||||||
|
|
||||||
|
s.Cell("F4").SetString("Hello world")
|
||||||
|
maxColumnIdx = s.MaxColumnIdx()
|
||||||
|
for _, row := range s.Rows() {
|
||||||
|
for _, cell := range row.CellsWithEmpty(maxColumnIdx) {
|
||||||
|
fmt.Println(cell.Reference(), ":", cell.GetFormattedValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
_examples/spreadsheet/cells-with-empty/test.xlsx
Normal file
BIN
_examples/spreadsheet/cells-with-empty/test.xlsx
Normal file
Binary file not shown.
@ -10,9 +10,9 @@ import (
|
|||||||
|
|
||||||
"github.com/unidoc/unioffice/common"
|
"github.com/unidoc/unioffice/common"
|
||||||
"github.com/unidoc/unioffice/document"
|
"github.com/unidoc/unioffice/document"
|
||||||
|
"github.com/unidoc/unioffice/schema/soo/ofc/docPropsVTypes"
|
||||||
"github.com/unidoc/unioffice/testhelper"
|
"github.com/unidoc/unioffice/testhelper"
|
||||||
"github.com/unidoc/unioffice/zippkg"
|
"github.com/unidoc/unioffice/zippkg"
|
||||||
"github.com/unidoc/unioffice/schema/soo/ofc/docPropsVTypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalCustomProperties(t *testing.T) {
|
func TestMarshalCustomProperties(t *testing.T) {
|
||||||
@ -181,9 +181,9 @@ func ExampleCustomProperties() {
|
|||||||
|
|
||||||
// And change them as well
|
// And change them as well
|
||||||
cp.SetPropertyAsLpwstr("Another text property", "My text value") // text
|
cp.SetPropertyAsLpwstr("Another text property", "My text value") // text
|
||||||
cp.SetPropertyAsI4("Another integer number property", 42) // int23
|
cp.SetPropertyAsI4("Another integer number property", 42) // int23
|
||||||
cp.SetPropertyAsR8("Another float number property", 3.14) // float64
|
cp.SetPropertyAsR8("Another float number property", 3.14) // float64
|
||||||
cp.SetPropertyAsDate("Another date property", time.Now()) // date
|
cp.SetPropertyAsDate("Another date property", time.Now()) // date
|
||||||
|
|
||||||
doc.SaveToFile("document.docx")
|
doc.SaveToFile("document.docx")
|
||||||
}
|
}
|
||||||
|
@ -395,7 +395,6 @@ func (p *Presentation) Save(w io.Writer) error {
|
|||||||
r.Properties().SetSolidFill(color.Red)
|
r.Properties().SetSolidFill(color.Red)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dt := unioffice.DocTypePresentation
|
dt := unioffice.DocTypePresentation
|
||||||
|
|
||||||
z := zip.NewWriter(w)
|
z := zip.NewWriter(w)
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
// commercial license can be purchased via https://unidoc.io website.
|
// commercial license can be purchased via https://unidoc.io website.
|
||||||
|
|
||||||
package custom_properties
|
package custom_properties
|
||||||
|
|
||||||
import "github.com/unidoc/unioffice"
|
import "github.com/unidoc/unioffice"
|
||||||
|
|
||||||
// init registers constructor functions for dynamically creating elements based off the XML namespace and name
|
// init registers constructor functions for dynamically creating elements based off the XML namespace and name
|
||||||
|
@ -26,10 +26,10 @@ const iso8601Format = "2006-01-02T15:04:05Z07:00"
|
|||||||
|
|
||||||
// Cell is a single cell within a sheet.
|
// Cell is a single cell within a sheet.
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
w *Workbook
|
w *Workbook
|
||||||
s *sml.Worksheet
|
sheet *Sheet
|
||||||
r *sml.CT_Row
|
r *sml.CT_Row
|
||||||
x *sml.CT_Cell
|
x *sml.CT_Cell
|
||||||
}
|
}
|
||||||
|
|
||||||
// X returns the inner wrapped XML type.
|
// X returns the inner wrapped XML type.
|
||||||
@ -110,8 +110,8 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sid := uint32(0)
|
sid := uint32(0)
|
||||||
for _, r := range c.s.SheetData.Row {
|
for _, r := range c.sheet.Rows() {
|
||||||
for _, c := range r.C {
|
for _, c := range r.x.C {
|
||||||
if c.F != nil && c.F.SiAttr != nil && *c.F.SiAttr >= sid {
|
if c.F != nil && c.F.SiAttr != nil && *c.F.SiAttr >= sid {
|
||||||
sid = *c.F.SiAttr
|
sid = *c.F.SiAttr
|
||||||
}
|
}
|
||||||
@ -122,7 +122,7 @@ func (c Cell) SetFormulaShared(formula string, rows, cols uint32) error {
|
|||||||
ref := fmt.Sprintf("%s%d:%s%d", cref.Column, cref.RowIdx, reference.IndexToColumn(cref.ColumnIdx+cols), cref.RowIdx+rows)
|
ref := fmt.Sprintf("%s%d:%s%d", cref.Column, cref.RowIdx, reference.IndexToColumn(cref.ColumnIdx+cols), cref.RowIdx+rows)
|
||||||
c.x.F.RefAttr = unioffice.String(ref)
|
c.x.F.RefAttr = unioffice.String(ref)
|
||||||
c.x.F.SiAttr = unioffice.Uint32(sid)
|
c.x.F.SiAttr = unioffice.Uint32(sid)
|
||||||
sheet := Sheet{c.w, nil, c.s}
|
sheet := Sheet{c.w, c.sheet.cts, c.sheet.x}
|
||||||
for row := cref.RowIdx; row <= cref.RowIdx+rows; row++ {
|
for row := cref.RowIdx; row <= cref.RowIdx+rows; row++ {
|
||||||
for col := cref.ColumnIdx; col <= cref.ColumnIdx+cols; col++ {
|
for col := cref.ColumnIdx; col <= cref.ColumnIdx+cols; col++ {
|
||||||
if row == cref.RowIdx && col == cref.ColumnIdx {
|
if row == cref.RowIdx && col == cref.ColumnIdx {
|
||||||
@ -200,11 +200,16 @@ func (c Cell) getLabelPrefix() string {
|
|||||||
sid := *c.x.SAttr
|
sid := *c.x.SAttr
|
||||||
cs := c.w.StyleSheet.GetCellStyle(sid)
|
cs := c.w.StyleSheet.GetCellStyle(sid)
|
||||||
switch cs.xf.Alignment.HorizontalAttr {
|
switch cs.xf.Alignment.HorizontalAttr {
|
||||||
case sml.ST_HorizontalAlignmentLeft: return "'"
|
case sml.ST_HorizontalAlignmentLeft:
|
||||||
case sml.ST_HorizontalAlignmentRight: return "\""
|
return "'"
|
||||||
case sml.ST_HorizontalAlignmentCenter: return "^"
|
case sml.ST_HorizontalAlignmentRight:
|
||||||
case sml.ST_HorizontalAlignmentFill: return "\\"
|
return "\""
|
||||||
default: return ""
|
case sml.ST_HorizontalAlignmentCenter:
|
||||||
|
return "^"
|
||||||
|
case sml.ST_HorizontalAlignmentFill:
|
||||||
|
return "\\"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,22 +509,23 @@ func (c Cell) GetRawValue() (string, error) {
|
|||||||
|
|
||||||
// SetHyperlink sets a hyperlink on a cell.
|
// SetHyperlink sets a hyperlink on a cell.
|
||||||
func (c Cell) SetHyperlink(hl common.Hyperlink) {
|
func (c Cell) SetHyperlink(hl common.Hyperlink) {
|
||||||
if c.s.Hyperlinks == nil {
|
ws := c.sheet.x
|
||||||
c.s.Hyperlinks = sml.NewCT_Hyperlinks()
|
if ws.Hyperlinks == nil {
|
||||||
|
ws.Hyperlinks = sml.NewCT_Hyperlinks()
|
||||||
}
|
}
|
||||||
rel := common.Relationship(hl)
|
rel := common.Relationship(hl)
|
||||||
|
|
||||||
hle := sml.NewCT_Hyperlink()
|
hle := sml.NewCT_Hyperlink()
|
||||||
hle.RefAttr = c.Reference()
|
hle.RefAttr = c.Reference()
|
||||||
hle.IdAttr = unioffice.String(rel.ID())
|
hle.IdAttr = unioffice.String(rel.ID())
|
||||||
c.s.Hyperlinks.Hyperlink = append(c.s.Hyperlinks.Hyperlink, hle)
|
ws.Hyperlinks.Hyperlink = append(ws.Hyperlinks.Hyperlink, hle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddHyperlink creates and sets a hyperlink on a cell.
|
// AddHyperlink creates and sets a hyperlink on a cell.
|
||||||
func (c Cell) AddHyperlink(url string) {
|
func (c Cell) AddHyperlink(url string) {
|
||||||
// store the relationships so we don't need to do a lookup here?
|
// store the relationships so we don't need to do a lookup here?
|
||||||
for i, ws := range c.w.xws {
|
for i, ws := range c.w.xws {
|
||||||
if ws == c.s {
|
if ws == c.sheet.x {
|
||||||
// add a hyperlink relationship in the worksheet relationships file
|
// add a hyperlink relationship in the worksheet relationships file
|
||||||
c.SetHyperlink(c.w.xwsRels[i].AddHyperlink(url))
|
c.SetHyperlink(c.w.xwsRels[i].AddHyperlink(url))
|
||||||
return
|
return
|
||||||
|
@ -9,10 +9,10 @@ package spreadsheet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/unidoc/unioffice/spreadsheet/formula"
|
"github.com/unidoc/unioffice/spreadsheet/formula"
|
||||||
"github.com/unidoc/unioffice/spreadsheet/reference"
|
"github.com/unidoc/unioffice/spreadsheet/reference"
|
||||||
@ -210,7 +210,7 @@ func (e *evalContext) LastColumn(rowFrom, rowTo int) string {
|
|||||||
max = l
|
max = l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reference.IndexToColumn(uint32(max-1))
|
return reference.IndexToColumn(uint32(max - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastRow returns the name of last row which contains data in range of context sheet's given columns.
|
// LastRow returns the name of last row which contains data in range of context sheet's given columns.
|
||||||
@ -220,7 +220,7 @@ func (e *evalContext) LastRow(col string) int {
|
|||||||
max := 1
|
max := 1
|
||||||
for _, r := range sheet.x.SheetData.Row {
|
for _, r := range sheet.x.SheetData.Row {
|
||||||
if r.RAttr != nil {
|
if r.RAttr != nil {
|
||||||
row := Row{sheet.w, sheet.x, r}
|
row := Row{sheet.w, sheet, r}
|
||||||
l := len(row.Cells())
|
l := len(row.Cells())
|
||||||
if l > colIdx {
|
if l > colIdx {
|
||||||
max = int(row.RowNumber())
|
max = int(row.RowNumber())
|
||||||
|
@ -410,30 +410,30 @@ func listValueOp(op BinOpType, lhs []Result, rhs Result) Result {
|
|||||||
func (b BinaryExpr) String() string {
|
func (b BinaryExpr) String() string {
|
||||||
opStr := ""
|
opStr := ""
|
||||||
switch b.op {
|
switch b.op {
|
||||||
case BinOpTypePlus:
|
case BinOpTypePlus:
|
||||||
opStr = "+"
|
opStr = "+"
|
||||||
case BinOpTypeMinus:
|
case BinOpTypeMinus:
|
||||||
opStr = "-"
|
opStr = "-"
|
||||||
case BinOpTypeMult:
|
case BinOpTypeMult:
|
||||||
opStr = "*"
|
opStr = "*"
|
||||||
case BinOpTypeDiv:
|
case BinOpTypeDiv:
|
||||||
opStr = "/"
|
opStr = "/"
|
||||||
case BinOpTypeExp:
|
case BinOpTypeExp:
|
||||||
opStr = "^"
|
opStr = "^"
|
||||||
case BinOpTypeLT:
|
case BinOpTypeLT:
|
||||||
opStr = "<"
|
opStr = "<"
|
||||||
case BinOpTypeGT:
|
case BinOpTypeGT:
|
||||||
opStr = ">"
|
opStr = ">"
|
||||||
case BinOpTypeEQ:
|
case BinOpTypeEQ:
|
||||||
opStr = "="
|
opStr = "="
|
||||||
case BinOpTypeLEQ:
|
case BinOpTypeLEQ:
|
||||||
opStr = "<="
|
opStr = "<="
|
||||||
case BinOpTypeGEQ:
|
case BinOpTypeGEQ:
|
||||||
opStr = ">="
|
opStr = ">="
|
||||||
case BinOpTypeNE:
|
case BinOpTypeNE:
|
||||||
opStr = "<>"
|
opStr = "<>"
|
||||||
case BinOpTypeConcat:
|
case BinOpTypeConcat:
|
||||||
opStr = "&"
|
opStr = "&"
|
||||||
}
|
}
|
||||||
return b.lhs.String() + opStr + b.rhs.String()
|
return b.lhs.String() + opStr + b.rhs.String()
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ import "sync"
|
|||||||
|
|
||||||
// evCache is a struct with collection of caching methods intended for add cache support to evaluators.
|
// evCache is a struct with collection of caching methods intended for add cache support to evaluators.
|
||||||
type evCache struct {
|
type evCache struct {
|
||||||
cache map[string]Result
|
cache map[string]Result
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEvCache() evCache {
|
func newEvCache() evCache {
|
||||||
|
@ -40,29 +40,29 @@ var daysTo1970 float64 = 25569.0
|
|||||||
var daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
var daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||||
|
|
||||||
var month2num = map[string]int{
|
var month2num = map[string]int{
|
||||||
"january": 1,
|
"january": 1,
|
||||||
"february": 2,
|
"february": 2,
|
||||||
"march": 3,
|
"march": 3,
|
||||||
"april": 4,
|
"april": 4,
|
||||||
"may": 5,
|
"may": 5,
|
||||||
"june": 6,
|
"june": 6,
|
||||||
"july": 7,
|
"july": 7,
|
||||||
"august": 8,
|
"august": 8,
|
||||||
"septemper": 9,
|
"septemper": 9,
|
||||||
"october": 10,
|
"october": 10,
|
||||||
"november": 11,
|
"november": 11,
|
||||||
"december": 12,
|
"december": 12,
|
||||||
"jan": 1,
|
"jan": 1,
|
||||||
"feb": 2,
|
"feb": 2,
|
||||||
"mar": 3,
|
"mar": 3,
|
||||||
"apr": 4,
|
"apr": 4,
|
||||||
"jun": 6,
|
"jun": 6,
|
||||||
"jul": 7,
|
"jul": 7,
|
||||||
"aug": 8,
|
"aug": 8,
|
||||||
"sep": 9,
|
"sep": 9,
|
||||||
"oct": 10,
|
"oct": 10,
|
||||||
"nov": 11,
|
"nov": 11,
|
||||||
"dec": 12,
|
"dec": 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
var dateFormats = map[string]*regexp.Regexp{}
|
var dateFormats = map[string]*regexp.Regexp{}
|
||||||
@ -208,8 +208,8 @@ func dateFromDays(days float64) time.Time {
|
|||||||
return time.Unix(0, unix)
|
return time.Unix(0, unix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func daysFromDate(y,m,d int) float64 {
|
func daysFromDate(y, m, d int) float64 {
|
||||||
return float64(makeDateS(y, time.Month(m), d) / 86400) + daysTo1970
|
return float64(makeDateS(y, time.Month(m), d)/86400) + daysTo1970
|
||||||
}
|
}
|
||||||
|
|
||||||
// DateDif is an implementation of the Excel DATEDIF() function.
|
// DateDif is an implementation of the Excel DATEDIF() function.
|
||||||
@ -588,7 +588,7 @@ func Now(args []Result) Result {
|
|||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
_, offset := now.Zone()
|
_, offset := now.Zone()
|
||||||
nowS := daysTo1970 + float64(now.Unix() + int64(offset))/86400
|
nowS := daysTo1970 + float64(now.Unix()+int64(offset))/86400
|
||||||
return MakeNumberResult(nowS)
|
return MakeNumberResult(nowS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,12 +599,12 @@ func Today(args []Result) Result {
|
|||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
_, offset := now.Zone()
|
_, offset := now.Zone()
|
||||||
nowS := daysBetween(date1900, now.Unix() + int64(offset)) + 1
|
nowS := daysBetween(date1900, now.Unix()+int64(offset)) + 1
|
||||||
return MakeNumberResult(nowS)
|
return MakeNumberResult(nowS)
|
||||||
}
|
}
|
||||||
|
|
||||||
func daysFromTime(hours, minutes, seconds float64) float64 {
|
func daysFromTime(hours, minutes, seconds float64) float64 {
|
||||||
return (hours * 3600 + minutes * 60 + seconds) / 86400
|
return (hours*3600 + minutes*60 + seconds) / 86400
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time is an implementation of the Excel TIME() function.
|
// Time is an implementation of the Excel TIME() function.
|
||||||
@ -819,17 +819,17 @@ func yearFrac(startDateF, endDateF float64, basis int) (float64, Result) {
|
|||||||
ed = 30
|
ed = 30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dayDiff = float64((ey - sy) * 360 + (em - sm) * 30 + (ed - sd))
|
dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd))
|
||||||
daysInYear = 360
|
daysInYear = 360
|
||||||
case 1:
|
case 1:
|
||||||
dayDiff = endDateF - startDateF
|
dayDiff = endDateF - startDateF
|
||||||
isYearDifferent := sy != ey
|
isYearDifferent := sy != ey
|
||||||
if isYearDifferent && (ey != sy + 1 || sm < em || (sm == em && sd < ed)) {
|
if isYearDifferent && (ey != sy+1 || sm < em || (sm == em && sd < ed)) {
|
||||||
dayCount := 0
|
dayCount := 0
|
||||||
for y := sy; y <= ey; y++ {
|
for y := sy; y <= ey; y++ {
|
||||||
dayCount += getDaysInYear(y, 1)
|
dayCount += getDaysInYear(y, 1)
|
||||||
}
|
}
|
||||||
daysInYear = float64(dayCount) / float64(ey - sy + 1)
|
daysInYear = float64(dayCount) / float64(ey-sy+1)
|
||||||
} else {
|
} else {
|
||||||
if !isYearDifferent && isLeapYear(sy) {
|
if !isYearDifferent && isLeapYear(sy) {
|
||||||
daysInYear = 366
|
daysInYear = 366
|
||||||
@ -847,14 +847,14 @@ func yearFrac(startDateF, endDateF float64, basis int) (float64, Result) {
|
|||||||
case 3:
|
case 3:
|
||||||
dayDiff = endDateF - startDateF
|
dayDiff = endDateF - startDateF
|
||||||
daysInYear = 365
|
daysInYear = 365
|
||||||
case 4:
|
case 4:
|
||||||
if sd == 31 {
|
if sd == 31 {
|
||||||
sd--
|
sd--
|
||||||
}
|
}
|
||||||
if ed == 31 {
|
if ed == 31 {
|
||||||
ed--
|
ed--
|
||||||
}
|
}
|
||||||
dayDiff = float64((ey - sy) * 360 + (em - sm) * 30 + (ed - sd))
|
dayDiff = float64((ey-sy)*360 + (em-sm)*30 + (ed - sd))
|
||||||
daysInYear = 360
|
daysInYear = 360
|
||||||
default:
|
default:
|
||||||
return 0, MakeErrorResultType(ErrorTypeNum, "Incorrect basis for YearFrac")
|
return 0, MakeErrorResultType(ErrorTypeNum, "Incorrect basis for YearFrac")
|
||||||
@ -896,7 +896,7 @@ func isLeapYear(year int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func daysBetween(startDate, endDate int64) float64 {
|
func daysBetween(startDate, endDate int64) float64 {
|
||||||
return float64(int(0.5 + float64((endDate - startDate) / 86400)))
|
return float64(int(0.5 + float64((endDate-startDate)/86400)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func feb29Between(date1, date2 time.Time) bool {
|
func feb29Between(date1, date2 time.Time) bool {
|
||||||
@ -904,7 +904,7 @@ func feb29Between(date1, date2 time.Time) bool {
|
|||||||
date2S := date2.Unix()
|
date2S := date2.Unix()
|
||||||
year1 := date1.Year()
|
year1 := date1.Year()
|
||||||
mar1year1 := makeDateS(year1, time.March, 1)
|
mar1year1 := makeDateS(year1, time.March, 1)
|
||||||
if (isLeapYear(year1) && date1S < mar1year1 && date2S >= mar1year1) {
|
if isLeapYear(year1) && date1S < mar1year1 && date2S >= mar1year1 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
var year2 = date2.Year()
|
var year2 = date2.Year()
|
||||||
@ -929,7 +929,7 @@ func getDiff(from, to time.Time, basis int) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if basis == 0 {
|
if basis == 0 {
|
||||||
if (mFrom == 2 || dFrom < 30 ) && dToOrig == 31 {
|
if (mFrom == 2 || dFrom < 30) && dToOrig == 31 {
|
||||||
dTo = 31
|
dTo = 31
|
||||||
} else if mTo == 2 && dTo == getDaysInMonth(yTo, mTo) {
|
} else if mTo == 2 && dTo == getDaysInMonth(yTo, mTo) {
|
||||||
dTo = getDaysInMonth(yTo, 2)
|
dTo = getDaysInMonth(yTo, 2)
|
||||||
@ -947,14 +947,14 @@ func getDiff(from, to time.Time, basis int) float64 {
|
|||||||
diff = 30 - dFrom + 1
|
diff = 30 - dFrom + 1
|
||||||
dFromOrig = 1
|
dFromOrig = 1
|
||||||
dFrom = 1
|
dFrom = 1
|
||||||
fromNew := time.Date(yFrom, time.Month(mFrom), dFromOrig, 0, 0, 0, 0, time.UTC).AddDate(0,1,0)
|
fromNew := time.Date(yFrom, time.Month(mFrom), dFromOrig, 0, 0, 0, 0, time.UTC).AddDate(0, 1, 0)
|
||||||
if fromNew.Year() < yTo {
|
if fromNew.Year() < yTo {
|
||||||
diff += getDaysInMonthRange(fromNew.Year(), int(fromNew.Month()), 12, basis)
|
diff += getDaysInMonthRange(fromNew.Year(), int(fromNew.Month()), 12, basis)
|
||||||
fromNew = fromNew.AddDate(0, 13 - int(fromNew.Month()), 0)
|
fromNew = fromNew.AddDate(0, 13-int(fromNew.Month()), 0)
|
||||||
diff += getDaysInYearRange(fromNew.Year(), yTo - 1, basis)
|
diff += getDaysInYearRange(fromNew.Year(), yTo-1, basis)
|
||||||
}
|
}
|
||||||
diff += getDaysInMonthRange(yTo, int(fromNew.Month()), mTo - 1, basis)
|
diff += getDaysInMonthRange(yTo, int(fromNew.Month()), mTo-1, basis)
|
||||||
fromNew = fromNew.AddDate(0, mTo - int(fromNew.Month()), 0)
|
fromNew = fromNew.AddDate(0, mTo-int(fromNew.Month()), 0)
|
||||||
mFrom = fromNew.Day()
|
mFrom = fromNew.Day()
|
||||||
}
|
}
|
||||||
diff += dTo - dFrom
|
diff += dTo - dFrom
|
||||||
@ -1028,7 +1028,7 @@ func parseDate(arg Result, dateName, funcName string) (float64, Result) {
|
|||||||
return 0, MakeErrorResult("Incorrect argument for " + funcName)
|
return 0, MakeErrorResult("Incorrect argument for " + funcName)
|
||||||
}
|
}
|
||||||
if date < 0 {
|
if date < 0 {
|
||||||
return 0, MakeErrorResultType(ErrorTypeNum, dateName + " should be non negative")
|
return 0, MakeErrorResultType(ErrorTypeNum, dateName+" should be non negative")
|
||||||
}
|
}
|
||||||
return date, empty
|
return date, empty
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,10 @@
|
|||||||
package formula
|
package formula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -81,16 +81,16 @@ func getSettlementMaturity(settlementResult, maturityResult Result, funcName str
|
|||||||
return 0, 0, errResult
|
return 0, 0, errResult
|
||||||
}
|
}
|
||||||
if settlementDate >= maturityDate {
|
if settlementDate >= maturityDate {
|
||||||
return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName + " requires maturity date to be later than settlement date")
|
return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName+" requires maturity date to be later than settlement date")
|
||||||
}
|
}
|
||||||
return settlementDate, maturityDate, empty
|
return settlementDate, maturityDate, empty
|
||||||
}
|
}
|
||||||
|
|
||||||
type couponArgs struct {
|
type couponArgs struct {
|
||||||
settlementDate float64
|
settlementDate float64
|
||||||
maturityDate float64
|
maturityDate float64
|
||||||
freq int
|
freq int
|
||||||
basis int
|
basis int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coupdaybs implements the Excel COUPDAYBS function.
|
// Coupdaybs implements the Excel COUPDAYBS function.
|
||||||
@ -125,7 +125,7 @@ func coupdays(settlementDateF, maturityDateF float64, freq, basis int) float64 {
|
|||||||
maturityDate := dateFromDays(maturityDateF)
|
maturityDate := dateFromDays(maturityDateF)
|
||||||
if basis == 1 {
|
if basis == 1 {
|
||||||
pcd := couppcd(settlementDate, maturityDate, freq, 1)
|
pcd := couppcd(settlementDate, maturityDate, freq, 1)
|
||||||
next := pcd.AddDate(0, 12 / freq, 0)
|
next := pcd.AddDate(0, 12/freq, 0)
|
||||||
return getDiff(pcd, next, basis)
|
return getDiff(pcd, next, basis)
|
||||||
}
|
}
|
||||||
return float64(getDaysInYear(0, basis)) / float64(freq)
|
return float64(getDaysInYear(0, basis)) / float64(freq)
|
||||||
@ -199,7 +199,7 @@ func coupncd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
|||||||
ncd = ncd.AddDate(-1, 0, 0)
|
ncd = ncd.AddDate(-1, 0, 0)
|
||||||
}
|
}
|
||||||
for !ncd.After(settlementDate) {
|
for !ncd.After(settlementDate) {
|
||||||
ncd = ncd.AddDate(0, 12 / freq, 0)
|
ncd = ncd.AddDate(0, 12/freq, 0)
|
||||||
}
|
}
|
||||||
return ncd
|
return ncd
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ func parseCouponArgs(args []Result, funcName string) (*couponArgs, Result) {
|
|||||||
}
|
}
|
||||||
basis = int(args[3].ValueNumber)
|
basis = int(args[3].ValueNumber)
|
||||||
if !checkBasis(basis) {
|
if !checkBasis(basis) {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for " + funcName)
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for "+funcName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &couponArgs{
|
return &couponArgs{
|
||||||
@ -258,8 +258,8 @@ func coupnum(settlementDateF, maturityDateF float64, freq, basis int) (float64,
|
|||||||
settlementDate, maturityDate := dateFromDays(settlementDateF), dateFromDays(maturityDateF)
|
settlementDate, maturityDate := dateFromDays(settlementDateF), dateFromDays(maturityDateF)
|
||||||
if maturityDate.After(settlementDate) {
|
if maturityDate.After(settlementDate) {
|
||||||
aDate := couppcd(settlementDate, maturityDate, freq, basis)
|
aDate := couppcd(settlementDate, maturityDate, freq, basis)
|
||||||
months := (maturityDate.Year() - aDate.Year()) * 12 + int(maturityDate.Month()) - int(aDate.Month())
|
months := (maturityDate.Year()-aDate.Year())*12 + int(maturityDate.Month()) - int(aDate.Month())
|
||||||
return float64(months * freq) / 12.0, empty
|
return float64(months*freq) / 12.0, empty
|
||||||
}
|
}
|
||||||
return 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
return 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ func getDuration(settlementDate, maturityDate, coup, yield, freq float64, basis
|
|||||||
coup *= 100 / freq
|
coup *= 100 / freq
|
||||||
yield /= freq
|
yield /= freq
|
||||||
yield++
|
yield++
|
||||||
diff := frac * freq - coups
|
diff := frac*freq - coups
|
||||||
for t := 1.0; t < coups; t++ {
|
for t := 1.0; t < coups; t++ {
|
||||||
tDiff := t + diff
|
tDiff := t + diff
|
||||||
add := coup / math.Pow(yield, tDiff)
|
add := coup / math.Pow(yield, tDiff)
|
||||||
@ -287,7 +287,7 @@ func getDuration(settlementDate, maturityDate, coup, yield, freq float64, basis
|
|||||||
duration += tDiff * add
|
duration += tDiff * add
|
||||||
}
|
}
|
||||||
|
|
||||||
add := (coup + 100) / math.Pow(yield, coups + diff)
|
add := (coup + 100) / math.Pow(yield, coups+diff)
|
||||||
|
|
||||||
p += add
|
p += add
|
||||||
duration += (coups + diff) * add
|
duration += (coups + diff) * add
|
||||||
@ -300,11 +300,11 @@ func getDuration(settlementDate, maturityDate, coup, yield, freq float64, basis
|
|||||||
|
|
||||||
type durationArgs struct {
|
type durationArgs struct {
|
||||||
settlementDate float64
|
settlementDate float64
|
||||||
maturityDate float64
|
maturityDate float64
|
||||||
coupon float64
|
coupon float64
|
||||||
yield float64
|
yield float64
|
||||||
freq float64
|
freq float64
|
||||||
basis int
|
basis int
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateDurationData returns settlement date, maturity date, coupon rate, yield rate, frequency of payments, day count basis and error result by parsing incoming arguments
|
// validateDurationData returns settlement date, maturity date, coupon rate, yield rate, frequency of payments, day count basis and error result by parsing incoming arguments
|
||||||
@ -349,7 +349,7 @@ func parseDurationData(args []Result, funcName string) (*durationArgs, Result) {
|
|||||||
}
|
}
|
||||||
basis = int(basisResult.ValueNumber)
|
basis = int(basisResult.ValueNumber)
|
||||||
if !checkBasis(basis) {
|
if !checkBasis(basis) {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value for " + funcName)
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value for "+funcName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &durationArgs{
|
return &durationArgs{
|
||||||
@ -501,21 +501,21 @@ func Amorlinc(args []Result) Result {
|
|||||||
|
|
||||||
if period <= numOfFullPeriods {
|
if period <= numOfFullPeriods {
|
||||||
return MakeNumberResult(oneRate)
|
return MakeNumberResult(oneRate)
|
||||||
} else if period == numOfFullPeriods + 1 {
|
} else if period == numOfFullPeriods+1 {
|
||||||
return MakeNumberResult(costDelta - oneRate * float64(numOfFullPeriods) - r0)
|
return MakeNumberResult(costDelta - oneRate*float64(numOfFullPeriods) - r0)
|
||||||
} else {
|
} else {
|
||||||
return MakeNumberResult(0)
|
return MakeNumberResult(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type amorArgs struct {
|
type amorArgs struct {
|
||||||
cost float64
|
cost float64
|
||||||
datePurchased float64
|
datePurchased float64
|
||||||
firstPeriod float64
|
firstPeriod float64
|
||||||
salvage float64
|
salvage float64
|
||||||
period int
|
period int
|
||||||
rate float64
|
rate float64
|
||||||
basis int
|
basis int
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
||||||
@ -528,7 +528,7 @@ func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
|||||||
}
|
}
|
||||||
cost := args[0].ValueNumber
|
cost := args[0].ValueNumber
|
||||||
if cost < 0 {
|
if cost < 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires cost to be non negative")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires cost to be non negative")
|
||||||
}
|
}
|
||||||
datePurchased, errResult := parseDate(args[1], "date purchased", funcName)
|
datePurchased, errResult := parseDate(args[1], "date purchased", funcName)
|
||||||
if errResult.Type == ResultTypeError {
|
if errResult.Type == ResultTypeError {
|
||||||
@ -539,28 +539,28 @@ func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
|||||||
return nil, errResult
|
return nil, errResult
|
||||||
}
|
}
|
||||||
if firstPeriod < datePurchased {
|
if firstPeriod < datePurchased {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires first period to be later than date purchased")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires first period to be later than date purchased")
|
||||||
}
|
}
|
||||||
if args[3].Type != ResultTypeNumber {
|
if args[3].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires salvage to be number argument")
|
return nil, MakeErrorResult(funcName + " requires salvage to be number argument")
|
||||||
}
|
}
|
||||||
salvage := args[3].ValueNumber
|
salvage := args[3].ValueNumber
|
||||||
if salvage < 0 || salvage > cost {
|
if salvage < 0 || salvage > cost {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires salvage to be between 0 and the initial cost")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires salvage to be between 0 and the initial cost")
|
||||||
}
|
}
|
||||||
if args[4].Type != ResultTypeNumber {
|
if args[4].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires period to be number argument")
|
return nil, MakeErrorResult(funcName + " requires period to be number argument")
|
||||||
}
|
}
|
||||||
period := int(args[4].ValueNumber)
|
period := int(args[4].ValueNumber)
|
||||||
if period < 0 {
|
if period < 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires period to be non-negative")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires period to be non-negative")
|
||||||
}
|
}
|
||||||
if args[5].Type != ResultTypeNumber {
|
if args[5].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires depreciation rate to be number argument")
|
return nil, MakeErrorResult(funcName + " requires depreciation rate to be number argument")
|
||||||
}
|
}
|
||||||
rate := args[5].ValueNumber
|
rate := args[5].ValueNumber
|
||||||
if rate < 0 {
|
if rate < 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires depreciation rate to be more than 0 and less than 0.5")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires depreciation rate to be more than 0 and less than 0.5")
|
||||||
}
|
}
|
||||||
basis := 0
|
basis := 0
|
||||||
if argsNum == 7 && args[6].Type != ResultTypeEmpty {
|
if argsNum == 7 && args[6].Type != ResultTypeEmpty {
|
||||||
@ -569,7 +569,7 @@ func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
|||||||
}
|
}
|
||||||
basis = int(args[6].ValueNumber)
|
basis = int(args[6].ValueNumber)
|
||||||
if !checkBasis(basis) || basis == 2 {
|
if !checkBasis(basis) || basis == 2 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for " + funcName)
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for "+funcName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &amorArgs{
|
return &amorArgs{
|
||||||
@ -588,12 +588,12 @@ func mathRound(x float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type cumulArgs struct {
|
type cumulArgs struct {
|
||||||
rate float64
|
rate float64
|
||||||
nPer float64
|
nPer float64
|
||||||
presentValue float64
|
presentValue float64
|
||||||
startPeriod float64
|
startPeriod float64
|
||||||
endPeriod float64
|
endPeriod float64
|
||||||
t int
|
t int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cumipmt implements the Excel CUMIPMT function.
|
// Cumipmt implements the Excel CUMIPMT function.
|
||||||
@ -619,9 +619,9 @@ func Cumipmt(args []Result) Result {
|
|||||||
}
|
}
|
||||||
for i := startPeriod; i <= endPeriod; i++ {
|
for i := startPeriod; i <= endPeriod; i++ {
|
||||||
if t == 1 {
|
if t == 1 {
|
||||||
interest += fv(rate, i - 2, payment, presentValue, 1) - payment
|
interest += fv(rate, i-2, payment, presentValue, 1) - payment
|
||||||
} else {
|
} else {
|
||||||
interest += fv(rate, i - 1, payment, presentValue, 0)
|
interest += fv(rate, i-1, payment, presentValue, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interest *= rate
|
interest *= rate
|
||||||
@ -645,7 +645,7 @@ func Cumprinc(args []Result) Result {
|
|||||||
principal := 0.0
|
principal := 0.0
|
||||||
if startPeriod == 1 {
|
if startPeriod == 1 {
|
||||||
if t == 0 {
|
if t == 0 {
|
||||||
principal = payment + presentValue * rate
|
principal = payment + presentValue*rate
|
||||||
} else {
|
} else {
|
||||||
principal = payment
|
principal = payment
|
||||||
}
|
}
|
||||||
@ -653,9 +653,9 @@ func Cumprinc(args []Result) Result {
|
|||||||
}
|
}
|
||||||
for i := startPeriod; i <= endPeriod; i++ {
|
for i := startPeriod; i <= endPeriod; i++ {
|
||||||
if t == 1 {
|
if t == 1 {
|
||||||
principal += payment - (fv(rate, i - 2, payment, presentValue, 1) - payment) * rate
|
principal += payment - (fv(rate, i-2, payment, presentValue, 1)-payment)*rate
|
||||||
} else {
|
} else {
|
||||||
principal += payment - fv(rate, i - 1, payment, presentValue, 0) * rate
|
principal += payment - fv(rate, i-1, payment, presentValue, 0)*rate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MakeNumberResult(principal)
|
return MakeNumberResult(principal)
|
||||||
@ -670,45 +670,45 @@ func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) {
|
|||||||
}
|
}
|
||||||
rate := args[0].ValueNumber
|
rate := args[0].ValueNumber
|
||||||
if rate <= 0 {
|
if rate <= 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires rate to be positive number argument")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires rate to be positive number argument")
|
||||||
}
|
}
|
||||||
if args[1].Type != ResultTypeNumber {
|
if args[1].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires number of periods to be number argument")
|
return nil, MakeErrorResult(funcName + " requires number of periods to be number argument")
|
||||||
}
|
}
|
||||||
nPer := args[1].ValueNumber
|
nPer := args[1].ValueNumber
|
||||||
if nPer <= 0 {
|
if nPer <= 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires number of periods to be positive number argument")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires number of periods to be positive number argument")
|
||||||
}
|
}
|
||||||
if args[2].Type != ResultTypeNumber {
|
if args[2].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires present value to be number argument")
|
return nil, MakeErrorResult(funcName + " requires present value to be number argument")
|
||||||
}
|
}
|
||||||
presentValue := args[2].ValueNumber
|
presentValue := args[2].ValueNumber
|
||||||
if presentValue <= 0 {
|
if presentValue <= 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires present value to be positive number argument")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires present value to be positive number argument")
|
||||||
}
|
}
|
||||||
if args[3].Type != ResultTypeNumber {
|
if args[3].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires start period to be number argument")
|
return nil, MakeErrorResult(funcName + " requires start period to be number argument")
|
||||||
}
|
}
|
||||||
startPeriod := args[3].ValueNumber
|
startPeriod := args[3].ValueNumber
|
||||||
if startPeriod <= 0 {
|
if startPeriod <= 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires start period to be positive number argument")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires start period to be positive number argument")
|
||||||
}
|
}
|
||||||
if args[4].Type != ResultTypeNumber {
|
if args[4].Type != ResultTypeNumber {
|
||||||
return nil, MakeErrorResult(funcName + " requires end period to be number argument")
|
return nil, MakeErrorResult(funcName + " requires end period to be number argument")
|
||||||
}
|
}
|
||||||
endPeriod := args[4].ValueNumber
|
endPeriod := args[4].ValueNumber
|
||||||
if endPeriod <= 0 {
|
if endPeriod <= 0 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires end period to be positive number argument")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires end period to be positive number argument")
|
||||||
}
|
}
|
||||||
if endPeriod < startPeriod {
|
if endPeriod < startPeriod {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires end period to be later or equal to start period")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires end period to be later or equal to start period")
|
||||||
}
|
}
|
||||||
if endPeriod > nPer {
|
if endPeriod > nPer {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires periods to be in periods range")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires periods to be in periods range")
|
||||||
}
|
}
|
||||||
t := int(args[5].ValueNumber)
|
t := int(args[5].ValueNumber)
|
||||||
if t != 0 && t != 1 {
|
if t != 0 && t != 1 {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires type to be 0 or 1")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires type to be 0 or 1")
|
||||||
}
|
}
|
||||||
return &cumulArgs{
|
return &cumulArgs{
|
||||||
rate,
|
rate,
|
||||||
@ -720,16 +720,16 @@ func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) {
|
|||||||
}, empty
|
}, empty
|
||||||
}
|
}
|
||||||
|
|
||||||
func pmt(rate, periods, present, future float64, t int ) float64 {
|
func pmt(rate, periods, present, future float64, t int) float64 {
|
||||||
var result float64
|
var result float64
|
||||||
if rate == 0 {
|
if rate == 0 {
|
||||||
result = (present + future) / periods
|
result = (present + future) / periods
|
||||||
} else {
|
} else {
|
||||||
term := math.Pow(1 + rate, periods)
|
term := math.Pow(1+rate, periods)
|
||||||
if t == 1 {
|
if t == 1 {
|
||||||
result = (future * rate / (term - 1) + present * rate / (1 - 1 / term)) / (1 + rate)
|
result = (future*rate/(term-1) + present*rate/(1-1/term)) / (1 + rate)
|
||||||
} else {
|
} else {
|
||||||
result = future * rate / (term - 1) + present * rate / (1 - 1 / term)
|
result = future*rate/(term-1) + present*rate/(1-1/term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -result
|
return -result
|
||||||
@ -738,13 +738,13 @@ func pmt(rate, periods, present, future float64, t int ) float64 {
|
|||||||
func fv(rate, periods, payment, value float64, t int) float64 {
|
func fv(rate, periods, payment, value float64, t int) float64 {
|
||||||
var result float64
|
var result float64
|
||||||
if rate == 0 {
|
if rate == 0 {
|
||||||
result = value + payment * periods
|
result = value + payment*periods
|
||||||
} else {
|
} else {
|
||||||
term := math.Pow(1 + rate, periods)
|
term := math.Pow(1+rate, periods)
|
||||||
if t == 1 {
|
if t == 1 {
|
||||||
result = value * term + payment * (1 + rate) * (term - 1) / rate
|
result = value*term + payment*(1+rate)*(term-1)/rate
|
||||||
} else {
|
} else {
|
||||||
result = value * term + payment * (term - 1) / rate
|
result = value*term + payment*(term-1)/rate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -result
|
return -result
|
||||||
@ -784,7 +784,7 @@ func Db(args []Result) Result {
|
|||||||
if period <= 0 {
|
if period <= 0 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "DB requires period to be positive")
|
return MakeErrorResultType(ErrorTypeNum, "DB requires period to be positive")
|
||||||
}
|
}
|
||||||
if period - life > 1 {
|
if period-life > 1 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "Incorrect period for DB")
|
return MakeErrorResultType(ErrorTypeNum, "Incorrect period for DB")
|
||||||
}
|
}
|
||||||
month := 12.0
|
month := 12.0
|
||||||
@ -803,8 +803,8 @@ func Db(args []Result) Result {
|
|||||||
if salvage >= cost {
|
if salvage >= cost {
|
||||||
return MakeNumberResult(0)
|
return MakeNumberResult(0)
|
||||||
}
|
}
|
||||||
rate := 1 - math.Pow(salvage / cost, 1 / life)
|
rate := 1 - math.Pow(salvage/cost, 1/life)
|
||||||
rate = float64(int(rate * 1000 + 0.5)) / 1000 // round to 3 decimal places
|
rate = float64(int(rate*1000+0.5)) / 1000 // round to 3 decimal places
|
||||||
initial := cost * rate * month / 12
|
initial := cost * rate * month / 12
|
||||||
if period == 1 {
|
if period == 1 {
|
||||||
return MakeNumberResult(initial)
|
return MakeNumberResult(initial)
|
||||||
@ -945,7 +945,7 @@ func Dollarde(args []Result) Result {
|
|||||||
return MakeErrorResult("Incorrect fraction argument for DOLLARDE")
|
return MakeErrorResult("Incorrect fraction argument for DOLLARDE")
|
||||||
}
|
}
|
||||||
dollarFrac *= math.Pow(10, power)
|
dollarFrac *= math.Pow(10, power)
|
||||||
dollarde := dollarInt + dollarFrac / fraction
|
dollarde := dollarInt + dollarFrac/fraction
|
||||||
if neg {
|
if neg {
|
||||||
dollarde = -dollarde
|
dollarde = -dollarde
|
||||||
}
|
}
|
||||||
@ -976,7 +976,7 @@ func Dollarfr(args []Result) Result {
|
|||||||
dollarFracOrder := float64(len(dollarFracStr))
|
dollarFracOrder := float64(len(dollarFracStr))
|
||||||
dollarFrac /= math.Pow(10, dollarFracOrder)
|
dollarFrac /= math.Pow(10, dollarFracOrder)
|
||||||
|
|
||||||
dollarfr := dollarFrac * fraction / math.Pow(10, float64(int(math.Log10(fraction))) + 1) + dollarInt
|
dollarfr := dollarFrac*fraction/math.Pow(10, float64(int(math.Log10(fraction)))+1) + dollarInt
|
||||||
if neg {
|
if neg {
|
||||||
dollarfr = -dollarfr
|
dollarfr = -dollarfr
|
||||||
}
|
}
|
||||||
@ -996,7 +996,7 @@ func parseDollarArgs(args []Result, funcName string) (float64, float64, Result)
|
|||||||
}
|
}
|
||||||
fraction := float64(int(args[1].ValueNumber))
|
fraction := float64(int(args[1].ValueNumber))
|
||||||
if fraction < 0 {
|
if fraction < 0 {
|
||||||
return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName + " requires fraction to be non negative number")
|
return 0, 0, MakeErrorResultType(ErrorTypeNum, funcName+" requires fraction to be non negative number")
|
||||||
}
|
}
|
||||||
return dollar, fraction, empty
|
return dollar, fraction, empty
|
||||||
}
|
}
|
||||||
@ -1036,7 +1036,7 @@ func Effect(args []Result) Result {
|
|||||||
if npery < 1 {
|
if npery < 1 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "EFFECT requires number of compounding periods to be 1 or more")
|
return MakeErrorResultType(ErrorTypeNum, "EFFECT requires number of compounding periods to be 1 or more")
|
||||||
}
|
}
|
||||||
return MakeNumberResult(math.Pow((1 + nominal / npery), npery) - 1)
|
return MakeNumberResult(math.Pow((1+nominal/npery), npery) - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fv implements the Excel FV function.
|
// Fv implements the Excel FV function.
|
||||||
@ -1091,7 +1091,7 @@ func Fvschedule(args []Result) Result {
|
|||||||
principal := args[0].ValueNumber
|
principal := args[0].ValueNumber
|
||||||
switch args[1].Type {
|
switch args[1].Type {
|
||||||
case ResultTypeNumber:
|
case ResultTypeNumber:
|
||||||
return MakeNumberResult(principal * (args[1].ValueNumber+1))
|
return MakeNumberResult(principal * (args[1].ValueNumber + 1))
|
||||||
case ResultTypeList, ResultTypeArray:
|
case ResultTypeList, ResultTypeArray:
|
||||||
schedule := arrayFromRange(args[1])
|
schedule := arrayFromRange(args[1])
|
||||||
for _, row := range schedule {
|
for _, row := range schedule {
|
||||||
@ -1209,9 +1209,9 @@ func ipmt(rate, period, nPer, presentValue, futureValue float64, t int) float64
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if t == 1 {
|
if t == 1 {
|
||||||
interest = fv(rate, period - 2, payment, presentValue, 1) - payment
|
interest = fv(rate, period-2, payment, presentValue, 1) - payment
|
||||||
} else {
|
} else {
|
||||||
interest = fv(rate, period - 1, payment, presentValue, 0)
|
interest = fv(rate, period-1, payment, presentValue, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return interest * rate
|
return interest * rate
|
||||||
@ -1256,7 +1256,7 @@ func Irr(args []Result) Result {
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
dates = append(dates, 0)
|
dates = append(dates, 0)
|
||||||
} else {
|
} else {
|
||||||
dates = append(dates, dates[i - 1] + 365)
|
dates = append(dates, dates[i-1]+365)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return irr(values, dates, guess)
|
return irr(values, dates, guess)
|
||||||
@ -1289,7 +1289,7 @@ func irr(values, dates []float64, guess float64) Result {
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
resultValue := irrResult(values, dates, resultRate)
|
resultValue := irrResult(values, dates, resultRate)
|
||||||
newRate := resultRate - resultValue / irrResultDeriv(values, dates, resultRate)
|
newRate := resultRate - resultValue/irrResultDeriv(values, dates, resultRate)
|
||||||
epsRate := math.Abs(newRate - resultRate)
|
epsRate := math.Abs(newRate - resultRate)
|
||||||
resultRate = newRate
|
resultRate = newRate
|
||||||
iter++
|
iter++
|
||||||
@ -1313,7 +1313,7 @@ func irrResult(values, dates []float64, rate float64) float64 {
|
|||||||
vlen := len(values)
|
vlen := len(values)
|
||||||
firstDate := dates[0]
|
firstDate := dates[0]
|
||||||
for i := 1; i < vlen; i++ {
|
for i := 1; i < vlen; i++ {
|
||||||
result += values[i] / math.Pow(r, (dates[i] - firstDate) / 365)
|
result += values[i] / math.Pow(r, (dates[i]-firstDate)/365)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -1325,7 +1325,7 @@ func irrResultDeriv(values, dates []float64, rate float64) float64 {
|
|||||||
firstDate := dates[0]
|
firstDate := dates[0]
|
||||||
for i := 1; i < vlen; i++ {
|
for i := 1; i < vlen; i++ {
|
||||||
frac := (dates[i] - firstDate) / 365
|
frac := (dates[i] - firstDate) / 365
|
||||||
result -= frac * values[i] / math.Pow(r, frac + 1)
|
result -= frac * values[i] / math.Pow(r, frac+1)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -1355,7 +1355,7 @@ func Ispmt(args []Result) Result {
|
|||||||
}
|
}
|
||||||
presentValue := args[3].ValueNumber
|
presentValue := args[3].ValueNumber
|
||||||
|
|
||||||
return MakeNumberResult(presentValue * rate * (period / nPer - 1))
|
return MakeNumberResult(presentValue * rate * (period/nPer - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mduration implements the Excel MDURATION function.
|
// Mduration implements the Excel MDURATION function.
|
||||||
@ -1375,7 +1375,7 @@ func Mduration(args []Result) Result {
|
|||||||
if duration.Type == ResultTypeError {
|
if duration.Type == ResultTypeError {
|
||||||
return duration
|
return duration
|
||||||
}
|
}
|
||||||
mDuration := duration.ValueNumber / (1.0 + yield / freq)
|
mDuration := duration.ValueNumber / (1.0 + yield/freq)
|
||||||
return MakeNumberResult(mDuration)
|
return MakeNumberResult(mDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1430,8 +1430,8 @@ func Mirr(args []Result) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := -npvReinvest / npvInvest
|
result := -npvReinvest / npvInvest
|
||||||
result *= math.Pow(reinvRate, n - 1)
|
result *= math.Pow(reinvRate, n-1)
|
||||||
result = math.Pow(result, 1 / (n - 1))
|
result = math.Pow(result, 1/(n-1))
|
||||||
return MakeNumberResult(result - 1)
|
return MakeNumberResult(result - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1454,7 +1454,7 @@ func Nominal(args []Result) Result {
|
|||||||
if npery < 1 {
|
if npery < 1 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "NOMINAL requires number of compounding periods to be 1 or more")
|
return MakeErrorResultType(ErrorTypeNum, "NOMINAL requires number of compounding periods to be 1 or more")
|
||||||
}
|
}
|
||||||
return MakeNumberResult((math.Pow(effect + 1, 1 / npery) - 1) * npery)
|
return MakeNumberResult((math.Pow(effect+1, 1/npery) - 1) * npery)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nper implements the Excel NPER function.
|
// Nper implements the Excel NPER function.
|
||||||
@ -1492,9 +1492,9 @@ func Nper(args []Result) Result {
|
|||||||
t = 1
|
t = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
num := pmt * (1 + rate * t) - futureValue * rate
|
num := pmt*(1+rate*t) - futureValue*rate
|
||||||
den := (presentValue * rate + pmt * (1 + rate * t))
|
den := (presentValue*rate + pmt*(1+rate*t))
|
||||||
return MakeNumberResult(math.Log(num / den) / math.Log(1 + rate))
|
return MakeNumberResult(math.Log(num/den) / math.Log(1+rate))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Npv implements the Excel NPV function.
|
// Npv implements the Excel NPV function.
|
||||||
@ -1528,7 +1528,7 @@ func Npv(args []Result) Result {
|
|||||||
}
|
}
|
||||||
npv := 0.0
|
npv := 0.0
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
npv += value / math.Pow(1 + rate, float64(i) + 1)
|
npv += value / math.Pow(1+rate, float64(i)+1)
|
||||||
}
|
}
|
||||||
return MakeNumberResult(npv)
|
return MakeNumberResult(npv)
|
||||||
}
|
}
|
||||||
@ -1582,7 +1582,7 @@ func Oddlprice(args []Result) Result {
|
|||||||
return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
||||||
}
|
}
|
||||||
basis := 0
|
basis := 0
|
||||||
if len(args) == 8 && args[7].Type != ResultTypeEmpty {
|
if len(args) == 8 && args[7].Type != ResultTypeEmpty {
|
||||||
basisResult := args[7]
|
basisResult := args[7]
|
||||||
if basisResult.Type != ResultTypeNumber {
|
if basisResult.Type != ResultTypeNumber {
|
||||||
return MakeErrorResult("ODDLPRICE requires basis of type number")
|
return MakeErrorResult("ODDLPRICE requires basis of type number")
|
||||||
@ -1609,8 +1609,8 @@ func Oddlprice(args []Result) Result {
|
|||||||
}
|
}
|
||||||
a *= freq
|
a *= freq
|
||||||
|
|
||||||
p := redemption + dc * 100 * rate / freq
|
p := redemption + dc*100*rate/freq
|
||||||
p /= dsc * yield / freq + 1
|
p /= dsc*yield/freq + 1
|
||||||
p -= a * 100 * rate / freq
|
p -= a * 100 * rate / freq
|
||||||
|
|
||||||
return MakeNumberResult(p)
|
return MakeNumberResult(p)
|
||||||
@ -1687,8 +1687,8 @@ func Oddlyield(args []Result) Result {
|
|||||||
}
|
}
|
||||||
a *= freq
|
a *= freq
|
||||||
|
|
||||||
yield := redemption + dc * 100 * rate / freq
|
yield := redemption + dc*100*rate/freq
|
||||||
yield /= pr + a * 100 * rate / freq
|
yield /= pr + a*100*rate/freq
|
||||||
yield--
|
yield--
|
||||||
yield *= freq / dsc
|
yield *= freq / dsc
|
||||||
|
|
||||||
@ -1721,7 +1721,7 @@ func Pduration(args []Result) Result {
|
|||||||
if specifiedValue <= 0 {
|
if specifiedValue <= 0 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "PDURATION requires specified value to be positive")
|
return MakeErrorResultType(ErrorTypeNum, "PDURATION requires specified value to be positive")
|
||||||
}
|
}
|
||||||
return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate))
|
return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1+rate))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pmt implements the Excel PMT function.
|
// Pmt implements the Excel PMT function.
|
||||||
@ -1766,11 +1766,11 @@ func Pmt(args []Result) Result {
|
|||||||
if rate == 0 {
|
if rate == 0 {
|
||||||
result = (presentValue + futureValue) / nPer
|
result = (presentValue + futureValue) / nPer
|
||||||
} else {
|
} else {
|
||||||
term := math.Pow(1 + rate, nPer)
|
term := math.Pow(1+rate, nPer)
|
||||||
if t == 1 {
|
if t == 1 {
|
||||||
result = (futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term)) / (1 + rate)
|
result = (futureValue*rate/(term-1) + presentValue*rate/(1-1/term)) / (1 + rate)
|
||||||
} else {
|
} else {
|
||||||
result = futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term)
|
result = futureValue*rate/(term-1) + presentValue*rate/(1-1/term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MakeNumberResult(-result)
|
return MakeNumberResult(-result)
|
||||||
@ -1889,12 +1889,12 @@ func getPrice(settlementDate, maturityDate, rate, yield, redemption, freqF float
|
|||||||
return 0, errResult
|
return 0, errResult
|
||||||
}
|
}
|
||||||
a := coupdaybs(settlementDate, maturityDate, freq, basis)
|
a := coupdaybs(settlementDate, maturityDate, freq, basis)
|
||||||
ret := redemption / math.Pow(1 + yield / freqF, n - 1 + dsc)
|
ret := redemption / math.Pow(1+yield/freqF, n-1+dsc)
|
||||||
ret -= 100 * rate / freqF * a / e
|
ret -= 100 * rate / freqF * a / e
|
||||||
t1 := 100 * rate / freqF
|
t1 := 100 * rate / freqF
|
||||||
t2 := 1 + yield / freqF
|
t2 := 1 + yield/freqF
|
||||||
for k := 0.0; k < n; k++ {
|
for k := 0.0; k < n; k++ {
|
||||||
ret += t1 / math.Pow(t2, k + dsc)
|
ret += t1 / math.Pow(t2, k+dsc)
|
||||||
}
|
}
|
||||||
return ret, MakeEmptyResult()
|
return ret, MakeEmptyResult()
|
||||||
}
|
}
|
||||||
@ -1937,7 +1937,7 @@ func Pricedisc(args []Result) Result {
|
|||||||
if errResult.Type == ResultTypeError {
|
if errResult.Type == ResultTypeError {
|
||||||
return errResult
|
return errResult
|
||||||
}
|
}
|
||||||
return MakeNumberResult(redemption * (1 - discount * yf))
|
return MakeNumberResult(redemption * (1 - discount*yf))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pricemat implements the Excel PRICEMAT function.
|
// Pricemat implements the Excel PRICEMAT function.
|
||||||
@ -1994,10 +1994,10 @@ func Pricemat(args []Result) Result {
|
|||||||
return errResult
|
return errResult
|
||||||
}
|
}
|
||||||
|
|
||||||
num := 1 + dim * rate
|
num := 1 + dim*rate
|
||||||
den := 1 + dsm * yield
|
den := 1 + dsm*yield
|
||||||
|
|
||||||
return MakeNumberResult((num / den - dis * rate) * 100)
|
return MakeNumberResult((num/den - dis*rate) * 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pv implements the Excel PV function.
|
// Pv implements the Excel PV function.
|
||||||
@ -2039,9 +2039,9 @@ func Pv(args []Result) Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rate == 0 {
|
if rate == 0 {
|
||||||
return MakeNumberResult(-pmt * nPer - futureValue)
|
return MakeNumberResult(-pmt*nPer - futureValue)
|
||||||
} else {
|
} else {
|
||||||
return MakeNumberResult((((1 - math.Pow(1 + rate, nPer)) / rate) * pmt * (1 + rate * t) - futureValue) / math.Pow(1 + rate, nPer))
|
return MakeNumberResult((((1-math.Pow(1+rate, nPer))/rate)*pmt*(1+rate*t) - futureValue) / math.Pow(1+rate, nPer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2098,13 +2098,13 @@ func Rate(args []Result) Result {
|
|||||||
|
|
||||||
rate := guess
|
rate := guess
|
||||||
for iter < maxIter && !close {
|
for iter < maxIter && !close {
|
||||||
t1 := math.Pow(rate + 1, nPer)
|
t1 := math.Pow(rate+1, nPer)
|
||||||
t2 := math.Pow(rate + 1, nPer - 1)
|
t2 := math.Pow(rate+1, nPer-1)
|
||||||
rt := rate * t + 1
|
rt := rate*t + 1
|
||||||
p0 := pmt * (t1 - 1)
|
p0 := pmt * (t1 - 1)
|
||||||
f1 := futureValue + t1 * presentValue + p0 * rt / rate
|
f1 := futureValue + t1*presentValue + p0*rt/rate
|
||||||
f2 := nPer * t2 * presentValue - p0 * rt / math.Pow(rate, 2)
|
f2 := nPer*t2*presentValue - p0*rt/math.Pow(rate, 2)
|
||||||
f3 := (nPer * pmt * t2 * rt + p0 * t) / rate
|
f3 := (nPer*pmt*t2*rt + p0*t) / rate
|
||||||
|
|
||||||
delta := f1 / (f2 + f3)
|
delta := f1 / (f2 + f3)
|
||||||
if math.Abs(delta) < epsMax {
|
if math.Abs(delta) < epsMax {
|
||||||
@ -2155,7 +2155,7 @@ func Received(args []Result) Result {
|
|||||||
if errResult.Type == ResultTypeError {
|
if errResult.Type == ResultTypeError {
|
||||||
return errResult
|
return errResult
|
||||||
}
|
}
|
||||||
return MakeNumberResult(investment / (1 - discount * frac))
|
return MakeNumberResult(investment / (1 - discount*frac))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rri implements the Excel RRI function.
|
// Rri implements the Excel RRI function.
|
||||||
@ -2185,7 +2185,7 @@ func Rri(args []Result) Result {
|
|||||||
return MakeErrorResultType(ErrorTypeNum, "RRI requires future value to be non negative")
|
return MakeErrorResultType(ErrorTypeNum, "RRI requires future value to be non negative")
|
||||||
}
|
}
|
||||||
|
|
||||||
return MakeNumberResult(math.Pow(futureValue / presentValue, 1 / nPer) - 1)
|
return MakeNumberResult(math.Pow(futureValue/presentValue, 1/nPer) - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sln implements the Excel SLN function.
|
// Sln implements the Excel SLN function.
|
||||||
@ -2209,7 +2209,7 @@ func Sln(args []Result) Result {
|
|||||||
return MakeErrorResultType(ErrorTypeDivideByZero, "SLN requires life to be non zero")
|
return MakeErrorResultType(ErrorTypeDivideByZero, "SLN requires life to be non zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
return MakeNumberResult((cost - salvage ) / life)
|
return MakeNumberResult((cost - salvage) / life)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syd implements the Excel SYD function.
|
// Syd implements the Excel SYD function.
|
||||||
@ -2269,7 +2269,7 @@ func Tbilleq(args []Result) Result {
|
|||||||
if discount <= 0 {
|
if discount <= 0 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "TBILLEQ requires discount to be positive number argument")
|
return MakeErrorResultType(ErrorTypeNum, "TBILLEQ requires discount to be positive number argument")
|
||||||
}
|
}
|
||||||
return MakeNumberResult((365 * discount) / (360 - discount * dsm))
|
return MakeNumberResult((365 * discount) / (360 - discount*dsm))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tbillprice implements the Excel TBILLPRICE function.
|
// Tbillprice implements the Excel TBILLPRICE function.
|
||||||
@ -2292,7 +2292,7 @@ func Tbillprice(args []Result) Result {
|
|||||||
if discount <= 0 {
|
if discount <= 0 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, "TBILLPRICE requires discount to be positive number argument")
|
return MakeErrorResultType(ErrorTypeNum, "TBILLPRICE requires discount to be positive number argument")
|
||||||
}
|
}
|
||||||
return MakeNumberResult(100 * (1 - discount * dsm / 360))
|
return MakeNumberResult(100 * (1 - discount*dsm/360))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tbillyield implements the Excel TBILLYIELD function.
|
// Tbillyield implements the Excel TBILLYIELD function.
|
||||||
@ -2316,7 +2316,7 @@ func Tbillyield(args []Result) Result {
|
|||||||
return MakeErrorResultType(ErrorTypeNum, "TBILLYIELD requires pr to be positive number argument")
|
return MakeErrorResultType(ErrorTypeNum, "TBILLYIELD requires pr to be positive number argument")
|
||||||
}
|
}
|
||||||
m1 := (100 - pr) / pr
|
m1 := (100 - pr) / pr
|
||||||
m2 := 360 /dsm
|
m2 := 360 / dsm
|
||||||
return MakeNumberResult(m1 * m2)
|
return MakeNumberResult(m1 * m2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2396,8 +2396,8 @@ func Vdb(args []Result) Result {
|
|||||||
if noSwitch {
|
if noSwitch {
|
||||||
for i := startInt + 1; i <= endInt; i++ {
|
for i := startInt + 1; i <= endInt; i++ {
|
||||||
term := getDDB(cost, salvage, life, i, factor)
|
term := getDDB(cost, salvage, life, i, factor)
|
||||||
if i == startInt + 1 {
|
if i == startInt+1 {
|
||||||
term *= math.Min(endPeriod, startInt + 1) - startPeriod
|
term *= math.Min(endPeriod, startInt+1) - startPeriod
|
||||||
} else if i == endInt {
|
} else if i == endInt {
|
||||||
term *= endPeriod + 1 - endInt
|
term *= endPeriod + 1 - endInt
|
||||||
}
|
}
|
||||||
@ -2420,7 +2420,7 @@ func Vdb(args []Result) Result {
|
|||||||
if factor != 0 {
|
if factor != 0 {
|
||||||
cost -= interVDB(cost, salvage, life, life1, startPeriod, factor)
|
cost -= interVDB(cost, salvage, life, life1, startPeriod, factor)
|
||||||
}
|
}
|
||||||
vdb = interVDB(cost, salvage, life, life - startPeriod, endPeriod - startPeriod, factor)
|
vdb = interVDB(cost, salvage, life, life-startPeriod, endPeriod-startPeriod, factor)
|
||||||
}
|
}
|
||||||
return MakeNumberResult(vdb)
|
return MakeNumberResult(vdb)
|
||||||
}
|
}
|
||||||
@ -2466,9 +2466,9 @@ func getDDB(cost, salvage, life, period, factor float64) float64 {
|
|||||||
oldValue = 0
|
oldValue = 0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
oldValue = cost * math.Pow(1 - rate, period - 1)
|
oldValue = cost * math.Pow(1-rate, period-1)
|
||||||
}
|
}
|
||||||
newValue := cost * math.Pow(1 - rate, period)
|
newValue := cost * math.Pow(1-rate, period)
|
||||||
|
|
||||||
var ddb float64
|
var ddb float64
|
||||||
|
|
||||||
@ -2522,11 +2522,11 @@ func Yielddisc(args []Result) Result {
|
|||||||
return errResult
|
return errResult
|
||||||
}
|
}
|
||||||
|
|
||||||
return MakeNumberResult((redemption / pr - 1) / frac)
|
return MakeNumberResult((redemption/pr - 1) / frac)
|
||||||
}
|
}
|
||||||
|
|
||||||
func approxEqual(a, b float64) bool {
|
func approxEqual(a, b float64) bool {
|
||||||
return math.Abs(a - b) < 1.0e-6
|
return math.Abs(a-b) < 1.0e-6
|
||||||
}
|
}
|
||||||
|
|
||||||
// Xirr implements the Excel XIRR function.
|
// Xirr implements the Excel XIRR function.
|
||||||
@ -2575,14 +2575,14 @@ func Xnpv(args []Result) Result {
|
|||||||
xnpv := 0.0
|
xnpv := 0.0
|
||||||
firstDate := dates[0]
|
firstDate := dates[0]
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
xnpv += value / math.Pow(1 + rate, (dates[i] - firstDate) / 365)
|
xnpv += value / math.Pow(1+rate, (dates[i]-firstDate)/365)
|
||||||
}
|
}
|
||||||
return MakeNumberResult(xnpv)
|
return MakeNumberResult(xnpv)
|
||||||
}
|
}
|
||||||
|
|
||||||
type xargs struct {
|
type xargs struct {
|
||||||
values []float64
|
values []float64
|
||||||
dates []float64
|
dates []float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) {
|
func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) {
|
||||||
@ -2615,7 +2615,7 @@ func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) {
|
|||||||
if vR.Type == ResultTypeNumber && !vR.IsBoolean {
|
if vR.Type == ResultTypeNumber && !vR.IsBoolean {
|
||||||
newDate := float64(int(vR.ValueNumber))
|
newDate := float64(int(vR.ValueNumber))
|
||||||
if newDate < lastDate {
|
if newDate < lastDate {
|
||||||
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires dates to be in ascending order")
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName+" requires dates to be in ascending order")
|
||||||
}
|
}
|
||||||
dates = append(dates, newDate)
|
dates = append(dates, newDate)
|
||||||
lastDate = newDate
|
lastDate = newDate
|
||||||
@ -2724,7 +2724,7 @@ func Yield(args []Result) Result {
|
|||||||
yield2 = yieldN
|
yield2 = yieldN
|
||||||
price2 = priceN
|
price2 = priceN
|
||||||
}
|
}
|
||||||
yieldN = yield2 - (yield2 - yield1) * ((pr - price2) / (price1 - price2))
|
yieldN = yield2 - (yield2-yield1)*((pr-price2)/(price1-price2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2785,8 +2785,8 @@ func Yieldmat(args []Result) Result {
|
|||||||
return errResult
|
return errResult
|
||||||
}
|
}
|
||||||
|
|
||||||
y := 1 + dim * rate
|
y := 1 + dim*rate
|
||||||
y /= pr / 100 + dis * rate
|
y /= pr/100 + dis*rate
|
||||||
y--
|
y--
|
||||||
y /= dsm
|
y /= dsm
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/unidoc/unioffice/internal/mergesort"
|
||||||
"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() {
|
||||||
@ -63,7 +63,7 @@ func Column(args []Result) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return MakeErrorResult("Incorrect reference: " + ref.Value)
|
return MakeErrorResult("Incorrect reference: " + ref.Value)
|
||||||
}
|
}
|
||||||
return MakeNumberResult(float64(cr.ColumnIdx+1))
|
return MakeNumberResult(float64(cr.ColumnIdx + 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns implements the Excel COLUMNS function.
|
// Columns implements the Excel COLUMNS function.
|
||||||
@ -226,13 +226,13 @@ func Match(args []Result) Result {
|
|||||||
case 0:
|
case 0:
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
if compareForMatch(value, criteria) {
|
if compareForMatch(value, criteria) {
|
||||||
return MakeNumberResult(float64(i+1))
|
return MakeNumberResult(float64(i + 1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case -1:
|
case -1:
|
||||||
for i := 0; i < len(values); i++ {
|
for i := 0; i < len(values); i++ {
|
||||||
if compareForMatch(values[i], criteria) {
|
if compareForMatch(values[i], criteria) {
|
||||||
return MakeNumberResult(float64(i+1))
|
return MakeNumberResult(float64(i + 1))
|
||||||
}
|
}
|
||||||
if criteria.isNumber && (values[i].ValueNumber < criteria.cNum) {
|
if criteria.isNumber && (values[i].ValueNumber < criteria.cNum) {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@ -244,7 +244,7 @@ func Match(args []Result) Result {
|
|||||||
case 1:
|
case 1:
|
||||||
for i := 0; i < len(values); i++ {
|
for i := 0; i < len(values); i++ {
|
||||||
if compareForMatch(values[i], criteria) {
|
if compareForMatch(values[i], criteria) {
|
||||||
return MakeNumberResult(float64(i+1))
|
return MakeNumberResult(float64(i + 1))
|
||||||
}
|
}
|
||||||
if criteria.isNumber && (values[i].ValueNumber > criteria.cNum) {
|
if criteria.isNumber && (values[i].ValueNumber > criteria.cNum) {
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
@ -738,11 +738,11 @@ func kth(args []Result, large bool) Result {
|
|||||||
}
|
}
|
||||||
kfloat := args[1].ValueNumber
|
kfloat := args[1].ValueNumber
|
||||||
if kfloat < 1 {
|
if kfloat < 1 {
|
||||||
return MakeErrorResultType(ErrorTypeNum, funcName + " requires second argument of type number more than 0")
|
return MakeErrorResultType(ErrorTypeNum, funcName+" requires second argument of type number more than 0")
|
||||||
}
|
}
|
||||||
k := int(kfloat)
|
k := int(kfloat)
|
||||||
if float64(k) != kfloat {
|
if float64(k) != kfloat {
|
||||||
return MakeErrorResultType(ErrorTypeNum, funcName + " requires second argument of type number more than 0")
|
return MakeErrorResultType(ErrorTypeNum, funcName+" requires second argument of type number more than 0")
|
||||||
}
|
}
|
||||||
unsorted := []float64{}
|
unsorted := []float64{}
|
||||||
for _, row := range arr {
|
for _, row := range arr {
|
||||||
@ -753,12 +753,12 @@ func kth(args []Result, large bool) Result {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if k > len(unsorted) {
|
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")
|
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)
|
sorted := mergesort.MergeSort(unsorted)
|
||||||
if large {
|
if large {
|
||||||
return MakeNumberResult(sorted[len(sorted) - k])
|
return MakeNumberResult(sorted[len(sorted)-k])
|
||||||
} else {
|
} else {
|
||||||
return MakeNumberResult(sorted[k - 1])
|
return MakeNumberResult(sorted[k-1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,21 +40,21 @@ var bs string = string([]byte{92})
|
|||||||
var integer, finance, intSep, intPar, decimal, decJust, decPar, par, percent, intCur, cur, curLabel, mdy, dmy, sci *regexp.Regexp
|
var integer, finance, intSep, intPar, decimal, decJust, decPar, par, percent, intCur, cur, curLabel, mdy, dmy, sci *regexp.Regexp
|
||||||
|
|
||||||
func initRegexpInformation() {
|
func initRegexpInformation() {
|
||||||
integer = regexp.MustCompile(`^0+$`) // 12345
|
integer = regexp.MustCompile(`^0+$`) // 12345
|
||||||
intSep = regexp.MustCompile("^((#|0)+,)+(#|0)+(;|$)") // 123,456,789
|
intSep = regexp.MustCompile("^((#|0)+,)+(#|0)+(;|$)") // 123,456,789
|
||||||
intPar = regexp.MustCompile("^(#|0|,)*_\\);") // (123,456,789)
|
intPar = regexp.MustCompile("^(#|0|,)*_\\);") // (123,456,789)
|
||||||
finance = regexp.MustCompile("^0+\\.(0+)$") // 1.23
|
finance = regexp.MustCompile("^0+\\.(0+)$") // 1.23
|
||||||
decimal = regexp.MustCompile("^((#|0)+,)+(#|0)+\\.(0+).*(;|$)") // 1.234
|
decimal = regexp.MustCompile("^((#|0)+,)+(#|0)+\\.(0+).*(;|$)") // 1.234
|
||||||
decJust = regexp.MustCompile("^(_|-| )+\\* #+,#+0\\.(0+).*;") // 1.234 with justified horizontal alignment
|
decJust = regexp.MustCompile("^(_|-| )+\\* #+,#+0\\.(0+).*;") // 1.234 with justified horizontal alignment
|
||||||
decPar = regexp.MustCompile("^((#|0)+,)+(#|0)+\\.((#|0)+)_\\).*;") // (1.234)
|
decPar = regexp.MustCompile("^((#|0)+,)+(#|0)+\\.((#|0)+)_\\).*;") // (1.234)
|
||||||
percent = regexp.MustCompile("^(#|0)+\\.((#|0)+)%$") // 12.34%
|
percent = regexp.MustCompile("^(#|0)+\\.((#|0)+)%$") // 12.34%
|
||||||
intCur = regexp.MustCompile("\\[\\$\\$-.+\\](\\* )?(#|0)+,(#|0)+;") // $1,234
|
intCur = regexp.MustCompile("\\[\\$\\$-.+\\](\\* )?(#|0)+,(#|0)+;") // $1,234
|
||||||
cur = regexp.MustCompile("\\[\\$\\$-.+\\](\\* )?(#|0)+,(#|0)+\\.((#|0|-)+).*;") // $1,234.56
|
cur = regexp.MustCompile("\\[\\$\\$-.+\\](\\* )?(#|0)+,(#|0)+\\.((#|0|-)+).*;") // $1,234.56
|
||||||
curLabel = regexp.MustCompile("^((#|0)+,)+(#|0)+(\\.((#|0|-)+))?.+\\[\\$.+\\].*;") // 1,234.56 USD
|
curLabel = regexp.MustCompile("^((#|0)+,)+(#|0)+(\\.((#|0|-)+))?.+\\[\\$.+\\].*;") // 1,234.56 USD
|
||||||
mdy = regexp.MustCompile("^M+(/| |,|\"|" + bs + bs + ")+D+(/| |,|\"|" + bs + bs + ")+Y+$") // 01/21/2019
|
mdy = regexp.MustCompile("^M+(/| |,|\"|" + bs + bs + ")+D+(/| |,|\"|" + bs + bs + ")+Y+$") // 01/21/2019
|
||||||
dmy = regexp.MustCompile("^D+(/| |\\.|\"|" + bs + bs + ")+M+(/| |\\.|\"|" + bs + bs + ")+Y+$") // 21. Jan. 2019
|
dmy = regexp.MustCompile("^D+(/| |\\.|\"|" + bs + bs + ")+M+(/| |\\.|\"|" + bs + bs + ")+Y+$") // 21. Jan. 2019
|
||||||
sci = regexp.MustCompile("^(#|0)+\\.((#|0)*)E\\+(#|0)+(;|$)") // 1.02E+002
|
sci = regexp.MustCompile("^(#|0)+\\.((#|0)*)E\\+(#|0)+(;|$)") // 1.02E+002
|
||||||
par = regexp.MustCompile("^.*_\\).*;") // (anything in parentheses)
|
par = regexp.MustCompile("^.*_\\).*;") // (anything in parentheses)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NA is an implementation of the Excel NA() function that just returns the #N/A! error.
|
// NA is an implementation of the Excel NA() function that just returns the #N/A! error.
|
||||||
@ -87,7 +87,7 @@ func Cell(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return MakeErrorResult("Incorrect reference: " + refStr)
|
return MakeErrorResult("Incorrect reference: " + refStr)
|
||||||
}
|
}
|
||||||
address := "$"+cr.Column+"$"+strconv.Itoa(int(cr.RowIdx))
|
address := "$" + cr.Column + "$" + strconv.Itoa(int(cr.RowIdx))
|
||||||
if cr.SheetName != "" {
|
if cr.SheetName != "" {
|
||||||
address = cr.SheetName + "!" + address
|
address = cr.SheetName + "!" + address
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ func Cell(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return MakeErrorResult("Incorrect reference: " + refStr)
|
return MakeErrorResult("Incorrect reference: " + refStr)
|
||||||
}
|
}
|
||||||
return MakeNumberResult(float64(cr.ColumnIdx+1))
|
return MakeNumberResult(float64(cr.ColumnIdx + 1))
|
||||||
case "color":
|
case "color":
|
||||||
red := strings.Contains(ctx.GetFormat(refStr), "[RED]")
|
red := strings.Contains(ctx.GetFormat(refStr), "[RED]")
|
||||||
return MakeBoolResult(red)
|
return MakeBoolResult(red)
|
||||||
@ -198,7 +198,7 @@ func Cell(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
return MakeNumberResult(ctx.Sheet(cr.SheetName).GetWidth(int(cr.ColumnIdx)))
|
return MakeNumberResult(ctx.Sheet(cr.SheetName).GetWidth(int(cr.ColumnIdx)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MakeErrorResult("Incorrect first argument of CELL: "+typ.ValueString)
|
return MakeErrorResult("Incorrect first argument of CELL: " + typ.ValueString)
|
||||||
}
|
}
|
||||||
|
|
||||||
func itemFromEndLength(submatch []string, additionalShift int) string {
|
func itemFromEndLength(submatch []string, additionalShift int) string {
|
||||||
@ -240,7 +240,7 @@ func IsEven(args []Result) Result {
|
|||||||
if args[0].Type != ResultTypeNumber {
|
if args[0].Type != ResultTypeNumber {
|
||||||
return MakeErrorResult("ISEVEN accepts a numeric argument")
|
return MakeErrorResult("ISEVEN accepts a numeric argument")
|
||||||
}
|
}
|
||||||
value:= int(args[0].ValueNumber)
|
value := int(args[0].ValueNumber)
|
||||||
|
|
||||||
return MakeBoolResult(value == value/2*2)
|
return MakeBoolResult(value == value/2*2)
|
||||||
}
|
}
|
||||||
@ -339,7 +339,7 @@ func IsOdd(args []Result) Result {
|
|||||||
if args[0].Type != ResultTypeNumber {
|
if args[0].Type != ResultTypeNumber {
|
||||||
return MakeErrorResult("ISODD accepts a numeric argument")
|
return MakeErrorResult("ISODD accepts a numeric argument")
|
||||||
}
|
}
|
||||||
value:= int(args[0].ValueNumber)
|
value := int(args[0].ValueNumber)
|
||||||
|
|
||||||
return MakeBoolResult(value != value/2*2)
|
return MakeBoolResult(value != value/2*2)
|
||||||
}
|
}
|
||||||
|
@ -358,7 +358,7 @@ func Ifs(args []Result) Result {
|
|||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return MakeErrorResult("IFS requires at least two arguments")
|
return MakeErrorResult("IFS requires at least two arguments")
|
||||||
}
|
}
|
||||||
for i := 0; i < len(args)-1; i+=2 {
|
for i := 0; i < len(args)-1; i += 2 {
|
||||||
if args[i].ValueNumber == 1 {
|
if args[i].ValueNumber == 1 {
|
||||||
return args[i+1]
|
return args[i+1]
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,9 @@ var number, eq, g, l, ge, le *regexp.Regexp
|
|||||||
|
|
||||||
func initRegexpStatistical() {
|
func initRegexpStatistical() {
|
||||||
number = regexp.MustCompile(`^([0-9]+)$`)
|
number = regexp.MustCompile(`^([0-9]+)$`)
|
||||||
eq = regexp.MustCompile(`^=(.*)$`) // =-12345.67, =A6
|
eq = regexp.MustCompile(`^=(.*)$`) // =-12345.67, =A6
|
||||||
l = regexp.MustCompile(`^<(.*)$`) // <-12345.67, <A6
|
l = regexp.MustCompile(`^<(.*)$`) // <-12345.67, <A6
|
||||||
g = regexp.MustCompile(`^>(.*)$`) // >-12345.67, >A6
|
g = regexp.MustCompile(`^>(.*)$`) // >-12345.67, >A6
|
||||||
le = regexp.MustCompile(`^<=(.*)$`) // <=-12345.67, <=A6
|
le = regexp.MustCompile(`^<=(.*)$`) // <=-12345.67, <=A6
|
||||||
ge = regexp.MustCompile(`^>=(.*)$`) // >=-12345.67, >=A6
|
ge = regexp.MustCompile(`^>=(.*)$`) // >=-12345.67, >=A6
|
||||||
}
|
}
|
||||||
@ -149,9 +149,9 @@ func CountBlank(args []Result) Result {
|
|||||||
|
|
||||||
type criteriaParsed struct {
|
type criteriaParsed struct {
|
||||||
isNumber bool
|
isNumber bool
|
||||||
cNum float64
|
cNum float64
|
||||||
cStr string
|
cStr string
|
||||||
cRegex *criteriaRegex
|
cRegex *criteriaRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -164,8 +164,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type criteriaRegex struct {
|
type criteriaRegex struct {
|
||||||
regexType byte // type of condition
|
regexType byte // type of condition
|
||||||
compareWith string // value to apply condition to
|
compareWith string // value to apply condition to
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCriteria(criteria Result) *criteriaParsed {
|
func parseCriteria(criteria Result) *criteriaParsed {
|
||||||
@ -268,7 +268,7 @@ func checkIfsRanges(args []Result, sumRange bool, fnName string) Result {
|
|||||||
|
|
||||||
rangeWidth := -1
|
rangeWidth := -1
|
||||||
rangeHeight := -1
|
rangeHeight := -1
|
||||||
for i := 0; i < argsNum; i+=2 {
|
for i := 0; i < argsNum; i += 2 {
|
||||||
arrResult := args[i]
|
arrResult := args[i]
|
||||||
if arrResult.Type != ResultTypeArray && arrResult.Type != ResultTypeList {
|
if arrResult.Type != ResultTypeArray && arrResult.Type != ResultTypeList {
|
||||||
return MakeErrorResult(fnName + " requires ranges of type list or array")
|
return MakeErrorResult(fnName + " requires ranges of type list or array")
|
||||||
@ -291,7 +291,7 @@ func checkIfsRanges(args []Result, sumRange bool, fnName string) Result {
|
|||||||
func getIfsMatch(args []Result) []rangeIndex {
|
func getIfsMatch(args []Result) []rangeIndex {
|
||||||
toLook := []rangeIndex{}
|
toLook := []rangeIndex{}
|
||||||
argsNum := len(args)
|
argsNum := len(args)
|
||||||
for i := 0; i < argsNum-1; i+=2 {
|
for i := 0; i < argsNum-1; i += 2 {
|
||||||
found := []rangeIndex{}
|
found := []rangeIndex{}
|
||||||
arr := arrayFromRange(args[i])
|
arr := arrayFromRange(args[i])
|
||||||
criteria := parseCriteria(args[i+1])
|
criteria := parseCriteria(args[i+1])
|
||||||
@ -403,7 +403,7 @@ func max(args []Result, isMaxA bool) Result {
|
|||||||
v = crit
|
v = crit
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
unioffice.Log("unhandled " + fName + "() argument type %s", a.Type)
|
unioffice.Log("unhandled "+fName+"() argument type %s", a.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v == -math.MaxFloat64 {
|
if v == -math.MaxFloat64 {
|
||||||
@ -454,7 +454,7 @@ func min(args []Result, isMinA bool) Result {
|
|||||||
v = crit
|
v = crit
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
unioffice.Log("unhandled " + fName + "() argument type %s", a.Type)
|
unioffice.Log("unhandled "+fName+"() argument type %s", a.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if v == math.MaxFloat64 {
|
if v == math.MaxFloat64 {
|
||||||
|
@ -164,7 +164,7 @@ func Exact(args []Result) Result {
|
|||||||
|
|
||||||
type parsedSearchObject struct {
|
type parsedSearchObject struct {
|
||||||
findText string
|
findText string
|
||||||
text string
|
text string
|
||||||
position int
|
position int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +257,7 @@ func Findb(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
for i := range text {
|
for i := range text {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
add := 1
|
add := 1
|
||||||
if i - lastIndex > 1 {
|
if i-lastIndex > 1 {
|
||||||
add = 2
|
add = 2
|
||||||
}
|
}
|
||||||
stepsCounter += add
|
stepsCounter += add
|
||||||
@ -422,7 +422,7 @@ func Mid(args []Result) Result {
|
|||||||
}
|
}
|
||||||
startNum--
|
startNum--
|
||||||
endNum := startNum + numChars
|
endNum := startNum + numChars
|
||||||
if endNum > l + 1 {
|
if endNum > l+1 {
|
||||||
return MakeStringResult(text[startNum:])
|
return MakeStringResult(text[startNum:])
|
||||||
} else {
|
} else {
|
||||||
return MakeStringResult(text[startNum:endNum])
|
return MakeStringResult(text[startNum:endNum])
|
||||||
@ -567,7 +567,7 @@ func Searchb(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
for i := range text {
|
for i := range text {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
add := 1
|
add := 1
|
||||||
if i - lastIndex > 1 {
|
if i-lastIndex > 1 {
|
||||||
add = 2
|
add = 2
|
||||||
}
|
}
|
||||||
stepsCounter += add
|
stepsCounter += add
|
||||||
@ -728,9 +728,9 @@ func Value(args []Result) Result {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type parsedReplaceObject struct {
|
type parsedReplaceObject struct {
|
||||||
text string
|
text string
|
||||||
startPos int
|
startPos int
|
||||||
length int
|
length int
|
||||||
textToReplace string
|
textToReplace string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,11 +879,11 @@ func getNumber(arg Result, funcName, argName string) (float64, Result) {
|
|||||||
case ResultTypeNumber:
|
case ResultTypeNumber:
|
||||||
return arg.ValueNumber, empty
|
return arg.ValueNumber, empty
|
||||||
case ResultTypeString:
|
case ResultTypeString:
|
||||||
f, err := strconv.ParseFloat(arg.ValueString, 64)
|
f, err := strconv.ParseFloat(arg.ValueString, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, MakeErrorResult(argName + " should be a number for " + funcName)
|
return 0, MakeErrorResult(argName + " should be a number for " + funcName)
|
||||||
}
|
}
|
||||||
return f, empty
|
return f, empty
|
||||||
default:
|
default:
|
||||||
return 0, MakeErrorResult(funcName + " requires " + argName + " to be a number or empty")
|
return 0, MakeErrorResult(funcName + " requires " + argName + " to be a number or empty")
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ package formula
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/unidoc/unioffice/spreadsheet/update"
|
"github.com/unidoc/unioffice/spreadsheet/update"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
// Input is an input formula string.
|
// Input is an input formula string.
|
||||||
// Expected is the expected output of the formula as a string of format: "value type". It depends on Input and workbook that is being worked with.
|
// Expected is the expected output of the formula as a string of format: "value type". It depends on Input and workbook that is being worked with.
|
||||||
type testStruct struct {
|
type testStruct struct {
|
||||||
Input string
|
Input string
|
||||||
Expected string
|
Expected string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ func TestCell(t *testing.T) {
|
|||||||
|
|
||||||
// cells with number, needed for testing different formats
|
// cells with number, needed for testing different formats
|
||||||
for i := 1; i <= 25; i++ {
|
for i := 1; i <= 25; i++ {
|
||||||
sheet.Cell("A"+strconv.Itoa(i)).SetNumber(-12345.6789)
|
sheet.Cell("A" + strconv.Itoa(i)).SetNumber(-12345.6789)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cells with string values, needed for testing different alignments
|
// cells with string values, needed for testing different alignments
|
||||||
@ -366,7 +366,6 @@ func TestCountIfs(t *testing.T) {
|
|||||||
sheet.Cell("B9").SetNumber(2)
|
sheet.Cell("B9").SetNumber(2)
|
||||||
sheet.Cell("B10").SetNumber(1)
|
sheet.Cell("B10").SetNumber(1)
|
||||||
|
|
||||||
|
|
||||||
ctx := sheet.FormulaContext()
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
runTests(t, ctx, td)
|
runTests(t, ctx, td)
|
||||||
@ -1354,14 +1353,14 @@ func TestDuration(t *testing.T) {
|
|||||||
ss := spreadsheet.New()
|
ss := spreadsheet.New()
|
||||||
sheet := ss.AddSheet()
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
sheet.Cell("A1").SetTime(time.Date(2018, time.July, 1, 0, 0, 0, 0, time.UTC)) // settlement date
|
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("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("A3").SetNumber(0.08) // coupon rate
|
||||||
sheet.Cell("A4").SetNumber(0.09) // yield rate
|
sheet.Cell("A4").SetNumber(0.09) // yield rate
|
||||||
sheet.Cell("A5").SetNumber(2) // frequency of payments
|
sheet.Cell("A5").SetNumber(2) // frequency of payments
|
||||||
sheet.Cell("A6").SetNumber(0) // basis
|
sheet.Cell("A6").SetNumber(0) // basis
|
||||||
sheet.Cell("A7").SetString("07/01/2018") // settlement date in string format
|
sheet.Cell("A7").SetString("07/01/2018") // settlement date in string format
|
||||||
sheet.Cell("A8").SetString("01/01/2048") // maturity date in string format
|
sheet.Cell("A8").SetString("01/01/2048") // maturity date in string format
|
||||||
|
|
||||||
td := []testStruct{
|
td := []testStruct{
|
||||||
{`=DURATION(A1,A2,A3,A4,A5)`, `10.9191452815 ResultTypeNumber`},
|
{`=DURATION(A1,A2,A3,A4,A5)`, `10.9191452815 ResultTypeNumber`},
|
||||||
@ -1382,12 +1381,12 @@ func TestMduration(t *testing.T) {
|
|||||||
|
|
||||||
sheet.Cell("A1").SetTime(time.Date(2008, time.January, 1, 0, 0, 0, 0, time.UTC)) // settlement date
|
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("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("A3").SetNumber(0.08) // coupon rate
|
||||||
sheet.Cell("A4").SetNumber(0.09) // yield rate
|
sheet.Cell("A4").SetNumber(0.09) // yield rate
|
||||||
sheet.Cell("A5").SetNumber(2) // frequency of payments
|
sheet.Cell("A5").SetNumber(2) // frequency of payments
|
||||||
sheet.Cell("A6").SetNumber(0) // basis
|
sheet.Cell("A6").SetNumber(0) // basis
|
||||||
sheet.Cell("A7").SetString("01/01/2008") // settlement date in string format
|
sheet.Cell("A7").SetString("01/01/2008") // settlement date in string format
|
||||||
sheet.Cell("A8").SetString("01/01/2016") // maturity date in string format
|
sheet.Cell("A8").SetString("01/01/2016") // maturity date in string format
|
||||||
|
|
||||||
td := []testStruct{
|
td := []testStruct{
|
||||||
{`=MDURATION(A1,A2,A3,A4,A5)`, `5.73566981391 ResultTypeNumber`},
|
{`=MDURATION(A1,A2,A3,A4,A5)`, `5.73566981391 ResultTypeNumber`},
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
package formula
|
package formula
|
||||||
|
|
||||||
// noCache is a struct with collection of caching methods stubs intended for evaluators without cache.
|
// noCache is a struct with collection of caching methods stubs intended for evaluators without cache.
|
||||||
type noCache struct {}
|
type noCache struct{}
|
||||||
|
|
||||||
func (nc *noCache) SetCache(key string, value Result) {}
|
func (nc *noCache) SetCache(key string, value Result) {}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
// PrefixHorizontalRange is a range expression that when evaluated returns a list of Results from references like Sheet1!1:4 (all cells from rows 1 to 4 of sheet 'Sheet1').
|
// PrefixHorizontalRange is a range expression that when evaluated returns a list of Results from references like Sheet1!1:4 (all cells from rows 1 to 4 of sheet 'Sheet1').
|
||||||
type PrefixHorizontalRange struct {
|
type PrefixHorizontalRange struct {
|
||||||
pfx Expression
|
pfx Expression
|
||||||
rowFrom, rowTo int
|
rowFrom, rowTo int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// PrefixVerticalRange is a range expression that when evaluated returns a list of Results from references like Sheet1!AA:IJ (all cells from columns AA to IJ of sheet 'Sheet1').
|
// PrefixVerticalRange is a range expression that when evaluated returns a list of Results from references like Sheet1!AA:IJ (all cells from columns AA to IJ of sheet 'Sheet1').
|
||||||
type PrefixVerticalRange struct {
|
type PrefixVerticalRange struct {
|
||||||
pfx Expression
|
pfx Expression
|
||||||
colFrom, colTo string
|
colFrom, colTo string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type MergedCell struct {
|
type MergedCell struct {
|
||||||
wb *Workbook
|
wb *Workbook
|
||||||
ws *sml.Worksheet
|
sheet *Sheet
|
||||||
x *sml.CT_MergeCell
|
x *sml.CT_MergeCell
|
||||||
}
|
}
|
||||||
|
|
||||||
// X returns the inner wrapped XML type.
|
// X returns the inner wrapped XML type.
|
||||||
@ -39,7 +39,7 @@ func (s MergedCell) Cell() Cell {
|
|||||||
ref := s.Reference()
|
ref := s.Reference()
|
||||||
if idx := strings.Index(s.Reference(), ":"); idx != -1 {
|
if idx := strings.Index(s.Reference(), ":"); idx != -1 {
|
||||||
ref = ref[0:idx]
|
ref = ref[0:idx]
|
||||||
return Sheet{w: s.wb, x: s.ws}.Cell(ref)
|
return s.sheet.Cell(ref)
|
||||||
}
|
}
|
||||||
// couldn't find it, log an error?
|
// couldn't find it, log an error?
|
||||||
return Cell{}
|
return Cell{}
|
||||||
|
@ -12,8 +12,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/unidoc/unioffice"
|
"github.com/unidoc/unioffice"
|
||||||
"github.com/unidoc/unioffice/zippkg"
|
"github.com/unidoc/unioffice/zippkg"
|
||||||
|
@ -18,9 +18,9 @@ import (
|
|||||||
|
|
||||||
// Row is a row within a spreadsheet.
|
// Row is a row within a spreadsheet.
|
||||||
type Row struct {
|
type Row struct {
|
||||||
w *Workbook
|
w *Workbook
|
||||||
s *sml.Worksheet
|
sheet *Sheet
|
||||||
x *sml.CT_Row
|
x *sml.CT_Row
|
||||||
}
|
}
|
||||||
|
|
||||||
// X returns the inner wrapped XML type.
|
// X returns the inner wrapped XML type.
|
||||||
@ -91,7 +91,7 @@ func (r Row) AddCell() Cell {
|
|||||||
nextCellID = unioffice.Stringf("%s%d", reference.IndexToColumn(nextIdx), r.RowNumber())
|
nextCellID = unioffice.Stringf("%s%d", reference.IndexToColumn(nextIdx), r.RowNumber())
|
||||||
}
|
}
|
||||||
c.RAttr = nextCellID
|
c.RAttr = nextCellID
|
||||||
return Cell{r.w, r.s, r.x, c}
|
return Cell{r.w, r.sheet, r.x, c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cells returns a slice of cells. The cells can be manipulated, but appending
|
// Cells returns a slice of cells. The cells can be manipulated, but appending
|
||||||
@ -110,13 +110,24 @@ func (r Row) Cells() []Cell {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
currentIndex := int(ref.ColumnIdx)
|
currentIndex := int(ref.ColumnIdx)
|
||||||
if currentIndex - lastIndex > 1 {
|
if currentIndex-lastIndex > 1 {
|
||||||
for col := lastIndex + 1; col < currentIndex; col++ {
|
for col := lastIndex + 1; col < currentIndex; col++ {
|
||||||
ret = append(ret, r.Cell(reference.IndexToColumn(uint32(col))))
|
ret = append(ret, r.Cell(reference.IndexToColumn(uint32(col))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastIndex = currentIndex
|
lastIndex = currentIndex
|
||||||
ret = append(ret, Cell{r.w, r.s, r.x, c})
|
ret = append(ret, Cell{r.w, r.sheet, r.x, c})
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// CellsWithEmpty returns a slice of cells including empty ones from the first column to the last one used in the sheet.
|
||||||
|
// The cells can be manipulated, but appending to the slice will have no effect.
|
||||||
|
func (r Row) CellsWithEmpty(lastColIdx uint32) []Cell {
|
||||||
|
ret := []Cell{}
|
||||||
|
for columnIdx := uint32(0); columnIdx <= lastColIdx; columnIdx++ {
|
||||||
|
c := r.Cell(reference.IndexToColumn(columnIdx))
|
||||||
|
ret = append(ret, c)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@ -147,7 +158,7 @@ func (r Row) AddNamedCell(col string) Cell {
|
|||||||
r.x.C = append(r.x.C[:indexToInsert], append([]*sml.CT_Cell{c}, r.x.C[indexToInsert:]...)...)
|
r.x.C = append(r.x.C[:indexToInsert], append([]*sml.CT_Cell{c}, r.x.C[indexToInsert:]...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Cell{r.w, r.s, r.x, c}
|
return Cell{r.w, r.sheet, r.x, c}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cell retrieves or adds a new cell to a row. Col is the column (e.g. 'A', 'B')
|
// Cell retrieves or adds a new cell to a row. Col is the column (e.g. 'A', 'B')
|
||||||
@ -155,7 +166,7 @@ func (r Row) Cell(col string) Cell {
|
|||||||
name := fmt.Sprintf("%s%d", col, r.RowNumber())
|
name := fmt.Sprintf("%s%d", col, r.RowNumber())
|
||||||
for _, c := range r.x.C {
|
for _, c := range r.x.C {
|
||||||
if c.RAttr != nil && *c.RAttr == name {
|
if c.RAttr != nil && *c.RAttr == name {
|
||||||
return Cell{r.w, r.s, r.x, c}
|
return Cell{r.w, r.sheet, r.x, c}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return r.AddNamedCell(col)
|
return r.AddNamedCell(col)
|
||||||
|
@ -31,6 +31,22 @@ type Sheet struct {
|
|||||||
x *sml.Worksheet
|
x *sml.Worksheet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxColumnIdx returns the max used column of the sheet.
|
||||||
|
func (s Sheet) MaxColumnIdx() uint32 {
|
||||||
|
maxColumnIdx := uint32(0)
|
||||||
|
for _, r := range s.Rows() {
|
||||||
|
cells := r.x.C
|
||||||
|
if len(cells) > 0 {
|
||||||
|
lastCell := cells[len(cells)-1]
|
||||||
|
ref, _ := reference.ParseCellReference(*lastCell.RAttr)
|
||||||
|
if maxColumnIdx < ref.ColumnIdx {
|
||||||
|
maxColumnIdx = ref.ColumnIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxColumnIdx
|
||||||
|
}
|
||||||
|
|
||||||
func (s Sheet) IsValid() bool {
|
func (s Sheet) IsValid() bool {
|
||||||
return s.x != nil
|
return s.x != nil
|
||||||
}
|
}
|
||||||
@ -42,11 +58,11 @@ func (s Sheet) X() *sml.Worksheet {
|
|||||||
|
|
||||||
// Row will return a row with a given row number, creating a new row if
|
// Row will return a row with a given row number, creating a new row if
|
||||||
// necessary.
|
// necessary.
|
||||||
func (s Sheet) Row(rowNum uint32) Row {
|
func (s *Sheet) Row(rowNum uint32) Row {
|
||||||
// see if the row exists
|
// see if the row exists
|
||||||
for _, r := range s.x.SheetData.Row {
|
for _, r := range s.x.SheetData.Row {
|
||||||
if r.RAttr != nil && *r.RAttr == rowNum {
|
if r.RAttr != nil && *r.RAttr == rowNum {
|
||||||
return Row{s.w, s.x, r}
|
return Row{s.w, s, r}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// create a new row
|
// create a new row
|
||||||
@ -54,7 +70,7 @@ func (s Sheet) Row(rowNum uint32) Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cell creates or returns a cell given a cell reference of the form 'A10'
|
// Cell creates or returns a cell given a cell reference of the form 'A10'
|
||||||
func (s Sheet) Cell(cellRef string) Cell {
|
func (s *Sheet) Cell(cellRef string) Cell {
|
||||||
cref, err := reference.ParseCellReference(cellRef)
|
cref, err := reference.ParseCellReference(cellRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
unioffice.Log("error parsing cell reference: %s", err)
|
unioffice.Log("error parsing cell reference: %s", err)
|
||||||
@ -66,7 +82,7 @@ func (s Sheet) Cell(cellRef string) Cell {
|
|||||||
// AddNumberedRow adds a row with a given row number. If you reuse a row number
|
// AddNumberedRow adds a row with a given row number. If you reuse a row number
|
||||||
// the resulting file will fail validation and fail to open in Office programs. Use
|
// the resulting file will fail validation and fail to open in Office programs. Use
|
||||||
// Row instead which creates a new row or returns an existing row.
|
// Row instead which creates a new row or returns an existing row.
|
||||||
func (s Sheet) AddNumberedRow(rowNum uint32) Row {
|
func (s *Sheet) AddNumberedRow(rowNum uint32) Row {
|
||||||
r := sml.NewCT_Row()
|
r := sml.NewCT_Row()
|
||||||
r.RAttr = unioffice.Uint32(rowNum)
|
r.RAttr = unioffice.Uint32(rowNum)
|
||||||
s.x.SheetData.Row = append(s.x.SheetData.Row, r)
|
s.x.SheetData.Row = append(s.x.SheetData.Row, r)
|
||||||
@ -84,22 +100,22 @@ func (s Sheet) AddNumberedRow(rowNum uint32) Row {
|
|||||||
return *l < *r
|
return *l < *r
|
||||||
})
|
})
|
||||||
|
|
||||||
return Row{s.w, s.x, r}
|
return Row{s.w, s, r}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNumberedRowFast is a fast path that can be used when adding consecutive
|
// addNumberedRowFast is a fast path that can be used when adding consecutive
|
||||||
// rows and not skipping any.
|
// rows and not skipping any.
|
||||||
func (s Sheet) addNumberedRowFast(rowNum uint32) Row {
|
func (s *Sheet) addNumberedRowFast(rowNum uint32) Row {
|
||||||
r := sml.NewCT_Row()
|
r := sml.NewCT_Row()
|
||||||
r.RAttr = unioffice.Uint32(rowNum)
|
r.RAttr = unioffice.Uint32(rowNum)
|
||||||
s.x.SheetData.Row = append(s.x.SheetData.Row, r)
|
s.x.SheetData.Row = append(s.x.SheetData.Row, r)
|
||||||
return Row{s.w, s.x, r}
|
return Row{s.w, s, r}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRow adds a new row to a sheet. You can mix this with numbered rows,
|
// AddRow adds a new row to a sheet. You can mix this with numbered rows,
|
||||||
// however it will get confusing. You should prefer to use either automatically
|
// however it will get confusing. You should prefer to use either automatically
|
||||||
// numbered rows with AddRow or manually numbered rows with Row/AddNumberedRow
|
// numbered rows with AddRow or manually numbered rows with Row/AddNumberedRow
|
||||||
func (s Sheet) AddRow() Row {
|
func (s *Sheet) AddRow() Row {
|
||||||
maxRowID := uint32(0)
|
maxRowID := uint32(0)
|
||||||
|
|
||||||
numRows := uint32(len(s.x.SheetData.Row))
|
numRows := uint32(len(s.x.SheetData.Row))
|
||||||
@ -120,7 +136,7 @@ func (s Sheet) AddRow() Row {
|
|||||||
|
|
||||||
// InsertRow inserts a new row into a spreadsheet at a particular row number. This
|
// InsertRow inserts a new row into a spreadsheet at a particular row number. This
|
||||||
// row will now be the row number specified, and any rows after it will be renumbed.
|
// row will now be the row number specified, and any rows after it will be renumbed.
|
||||||
func (s Sheet) InsertRow(rowNum int) Row {
|
func (s *Sheet) InsertRow(rowNum int) Row {
|
||||||
rIdx := uint32(rowNum)
|
rIdx := uint32(rowNum)
|
||||||
|
|
||||||
// Renumber every row after the row we're inserting
|
// Renumber every row after the row we're inserting
|
||||||
@ -166,7 +182,7 @@ func (s Sheet) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetName sets the sheet name.
|
// SetName sets the sheet name.
|
||||||
func (s Sheet) SetName(name string) {
|
func (s *Sheet) SetName(name string) {
|
||||||
s.cts.NameAttr = name
|
s.cts.NameAttr = name
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +198,7 @@ func (s Sheet) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := s.cts.Validate(); err != nil {
|
if err := s.x.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.x.Validate()
|
return s.x.Validate()
|
||||||
@ -249,21 +265,21 @@ func (s Sheet) validateMergedCells() error {
|
|||||||
// ValidateWithPath validates the sheet passing path informaton for a better
|
// ValidateWithPath validates the sheet passing path informaton for a better
|
||||||
// error message
|
// error message
|
||||||
func (s Sheet) ValidateWithPath(path string) error {
|
func (s Sheet) ValidateWithPath(path string) error {
|
||||||
return s.cts.ValidateWithPath(path)
|
return s.x.ValidateWithPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rows returns all of the rows in a sheet.
|
// Rows returns all of the rows in a sheet.
|
||||||
func (s Sheet) Rows() []Row {
|
func (s *Sheet) Rows() []Row {
|
||||||
ret := []Row{}
|
ret := []Row{}
|
||||||
for _, r := range s.x.SheetData.Row {
|
for _, r := range s.x.SheetData.Row {
|
||||||
ret = append(ret, Row{s.w, s.x, r})
|
ret = append(ret, Row{s.w, s, r})
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDrawing sets the worksheet drawing. A worksheet can have a reference to a
|
// SetDrawing sets the worksheet drawing. A worksheet can have a reference to a
|
||||||
// single drawing, but the drawing can have many charts.
|
// single drawing, but the drawing can have many charts.
|
||||||
func (s Sheet) SetDrawing(d Drawing) {
|
func (s *Sheet) SetDrawing(d Drawing) {
|
||||||
var rel common.Relationships
|
var rel common.Relationships
|
||||||
for i, wks := range s.w.xws {
|
for i, wks := range s.w.xws {
|
||||||
if wks == s.x {
|
if wks == s.x {
|
||||||
@ -288,7 +304,7 @@ func (s Sheet) SetDrawing(d Drawing) {
|
|||||||
// AddHyperlink adds a hyperlink to a sheet. Adding the hyperlink to the sheet
|
// AddHyperlink adds a hyperlink to a sheet. Adding the hyperlink to the sheet
|
||||||
// and setting it on a cell is more efficient than setting hyperlinks directly
|
// and setting it on a cell is more efficient than setting hyperlinks directly
|
||||||
// on a cell.
|
// on a cell.
|
||||||
func (s Sheet) AddHyperlink(url string) common.Hyperlink {
|
func (s *Sheet) AddHyperlink(url string) common.Hyperlink {
|
||||||
// store the relationships so we don't need to do a lookup here?
|
// store the relationships so we don't need to do a lookup here?
|
||||||
for i, ws := range s.w.xws {
|
for i, ws := range s.w.xws {
|
||||||
if ws == s.x {
|
if ws == s.x {
|
||||||
@ -318,7 +334,7 @@ func (s Sheet) RangeReference(n string) string {
|
|||||||
const autoFilterName = "_xlnm._FilterDatabase"
|
const autoFilterName = "_xlnm._FilterDatabase"
|
||||||
|
|
||||||
// ClearAutoFilter removes the autofilters from the sheet.
|
// ClearAutoFilter removes the autofilters from the sheet.
|
||||||
func (s Sheet) ClearAutoFilter() {
|
func (s *Sheet) ClearAutoFilter() {
|
||||||
s.x.AutoFilter = nil
|
s.x.AutoFilter = nil
|
||||||
sn := "'" + s.Name() + "'!"
|
sn := "'" + s.Name() + "'!"
|
||||||
// see if we have a defined auto filter name for the sheet
|
// see if we have a defined auto filter name for the sheet
|
||||||
@ -336,7 +352,7 @@ func (s Sheet) ClearAutoFilter() {
|
|||||||
// filters that are common for a header row. The RangeRef should be of the form
|
// filters that are common for a header row. The RangeRef should be of the form
|
||||||
// "A1:C5" and cover the entire range of cells to be filtered, not just the
|
// "A1:C5" and cover the entire range of cells to be filtered, not just the
|
||||||
// header. SetAutoFilter replaces any existing auto filter on the sheet.
|
// header. SetAutoFilter replaces any existing auto filter on the sheet.
|
||||||
func (s Sheet) SetAutoFilter(rangeRef string) {
|
func (s *Sheet) SetAutoFilter(rangeRef string) {
|
||||||
// this should have no $ in it
|
// this should have no $ in it
|
||||||
rangeRef = strings.Replace(rangeRef, "$", "", -1)
|
rangeRef = strings.Replace(rangeRef, "$", "", -1)
|
||||||
|
|
||||||
@ -369,7 +385,7 @@ func (s Sheet) SetAutoFilter(rangeRef string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddMergedCells merges cells within a sheet.
|
// AddMergedCells merges cells within a sheet.
|
||||||
func (s Sheet) AddMergedCells(fromRef, toRef string) MergedCell {
|
func (s *Sheet) AddMergedCells(fromRef, toRef string) MergedCell {
|
||||||
// TODO: we might need to actually create the merged cells if they don't
|
// TODO: we might need to actually create the merged cells if they don't
|
||||||
// exist, but it appears to work fine on both Excel and LibreOffice just
|
// exist, but it appears to work fine on both Excel and LibreOffice just
|
||||||
// creating the merged region
|
// creating the merged region
|
||||||
@ -383,24 +399,24 @@ func (s Sheet) AddMergedCells(fromRef, toRef string) MergedCell {
|
|||||||
|
|
||||||
s.x.MergeCells.MergeCell = append(s.x.MergeCells.MergeCell, merge)
|
s.x.MergeCells.MergeCell = append(s.x.MergeCells.MergeCell, merge)
|
||||||
s.x.MergeCells.CountAttr = unioffice.Uint32(uint32(len(s.x.MergeCells.MergeCell)))
|
s.x.MergeCells.CountAttr = unioffice.Uint32(uint32(len(s.x.MergeCells.MergeCell)))
|
||||||
return MergedCell{s.w, s.x, merge}
|
return MergedCell{s.w, s, merge}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergedCells returns the merged cell regions within the sheet.
|
// MergedCells returns the merged cell regions within the sheet.
|
||||||
func (s Sheet) MergedCells() []MergedCell {
|
func (s *Sheet) MergedCells() []MergedCell {
|
||||||
if s.x.MergeCells == nil {
|
if s.x.MergeCells == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ret := []MergedCell{}
|
ret := []MergedCell{}
|
||||||
for _, c := range s.x.MergeCells.MergeCell {
|
for _, c := range s.x.MergeCells.MergeCell {
|
||||||
ret = append(ret, MergedCell{s.w, s.x, c})
|
ret = append(ret, MergedCell{s.w, s, c})
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveMergedCell removes merging from a cell range within a sheet. The cells
|
// RemoveMergedCell removes merging from a cell range within a sheet. The cells
|
||||||
// that made up the merged cell remain, but are no lon merged.
|
// that made up the merged cell remain, but are no lon merged.
|
||||||
func (s Sheet) RemoveMergedCell(mc MergedCell) {
|
func (s *Sheet) RemoveMergedCell(mc MergedCell) {
|
||||||
for i, c := range s.x.MergeCells.MergeCell {
|
for i, c := range s.x.MergeCells.MergeCell {
|
||||||
if c == mc.X() {
|
if c == mc.X() {
|
||||||
copy(s.x.MergeCells.MergeCell[i:], s.x.MergeCells.MergeCell[i+1:])
|
copy(s.x.MergeCells.MergeCell[i:], s.x.MergeCells.MergeCell[i+1:])
|
||||||
@ -443,7 +459,7 @@ func (s Sheet) Extents() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddConditionalFormatting adds conditional formatting to the sheet.
|
// AddConditionalFormatting adds conditional formatting to the sheet.
|
||||||
func (s Sheet) AddConditionalFormatting(cellRanges []string) ConditionalFormatting {
|
func (s *Sheet) AddConditionalFormatting(cellRanges []string) ConditionalFormatting {
|
||||||
cfmt := sml.NewCT_ConditionalFormatting()
|
cfmt := sml.NewCT_ConditionalFormatting()
|
||||||
s.x.ConditionalFormatting = append(s.x.ConditionalFormatting, cfmt)
|
s.x.ConditionalFormatting = append(s.x.ConditionalFormatting, cfmt)
|
||||||
|
|
||||||
@ -460,7 +476,7 @@ func (s Sheet) AddConditionalFormatting(cellRanges []string) ConditionalFormatti
|
|||||||
// can span multiple column indices, this method will return the column that
|
// can span multiple column indices, this method will return the column that
|
||||||
// applies to a column index if it exists or create a new column that only
|
// applies to a column index if it exists or create a new column that only
|
||||||
// applies to the index passed in otherwise.
|
// applies to the index passed in otherwise.
|
||||||
func (s Sheet) Column(idx uint32) Column {
|
func (s *Sheet) Column(idx uint32) Column {
|
||||||
// scan for any existing column that covers this index
|
// scan for any existing column that covers this index
|
||||||
for _, colSet := range s.x.Cols {
|
for _, colSet := range s.x.Cols {
|
||||||
for _, col := range colSet.Col {
|
for _, col := range colSet.Col {
|
||||||
@ -487,7 +503,7 @@ func (s Sheet) Column(idx uint32) Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Comments returns the comments for a sheet.
|
// Comments returns the comments for a sheet.
|
||||||
func (s Sheet) Comments() Comments {
|
func (s *Sheet) Comments() Comments {
|
||||||
for i, wks := range s.w.xws {
|
for i, wks := range s.w.xws {
|
||||||
if wks == s.x {
|
if wks == s.x {
|
||||||
if s.w.comments[i] == nil {
|
if s.w.comments[i] == nil {
|
||||||
@ -518,7 +534,7 @@ func (s Sheet) Comments() Comments {
|
|||||||
// the cells on the top,left,right,bottom and four corners. This function
|
// the cells on the top,left,right,bottom and four corners. This function
|
||||||
// breaks apart a single border into its components and applies it to cells as
|
// breaks apart a single border into its components and applies it to cells as
|
||||||
// needed to give the effect of a border applying to multiple cells.
|
// needed to give the effect of a border applying to multiple cells.
|
||||||
func (s Sheet) SetBorder(cellRange string, border Border) error {
|
func (s *Sheet) SetBorder(cellRange string, border Border) error {
|
||||||
from, to, err := reference.ParseRangeReference(cellRange)
|
from, to, err := reference.ParseRangeReference(cellRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -605,7 +621,7 @@ func (s Sheet) SetBorder(cellRange string, border Border) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddDataValidation adds a data validation rule to a sheet.
|
// AddDataValidation adds a data validation rule to a sheet.
|
||||||
func (s Sheet) AddDataValidation() DataValidation {
|
func (s *Sheet) AddDataValidation() DataValidation {
|
||||||
if s.x.DataValidations == nil {
|
if s.x.DataValidations == nil {
|
||||||
s.x.DataValidations = sml.NewCT_DataValidations()
|
s.x.DataValidations = sml.NewCT_DataValidations()
|
||||||
}
|
}
|
||||||
@ -724,7 +740,7 @@ 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)))
|
||||||
if val.Type != formula.ResultTypeEmpty {
|
if val.Type != formula.ResultTypeEmpty {
|
||||||
if val.IsBoolean {
|
if val.IsBoolean {
|
||||||
cell.SetBool(val.ValueNumber != 0)
|
cell.SetBool(val.ValueNumber != 0)
|
||||||
} else {
|
} else {
|
||||||
@ -747,7 +763,7 @@ func (s *Sheet) setList(origin string, list formula.Result) error {
|
|||||||
sr := s.Row(cref.RowIdx)
|
sr := s.Row(cref.RowIdx)
|
||||||
for ic, val := range list.ValueList {
|
for ic, val := range list.ValueList {
|
||||||
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
cell := sr.Cell(reference.IndexToColumn(cref.ColumnIdx + uint32(ic)))
|
||||||
if val.Type != formula.ResultTypeEmpty {
|
if val.Type != formula.ResultTypeEmpty {
|
||||||
if val.IsBoolean {
|
if val.IsBoolean {
|
||||||
cell.SetBool(val.ValueNumber != 0)
|
cell.SetBool(val.ValueNumber != 0)
|
||||||
} else {
|
} else {
|
||||||
@ -860,8 +876,8 @@ func (s *Sheet) Sort(column string, firstRow uint32, order SortOrder) {
|
|||||||
cmp := Comparer{Order: order}
|
cmp := Comparer{Order: order}
|
||||||
sort.Slice(sheetData, func(i, j int) bool {
|
sort.Slice(sheetData, func(i, j int) bool {
|
||||||
return cmp.LessRows(column,
|
return cmp.LessRows(column,
|
||||||
Row{s.w, s.x, sheetData[i]},
|
Row{s.w, s, sheetData[i]},
|
||||||
Row{s.w, s.x, sheetData[j]})
|
Row{s.w, s, sheetData[j]})
|
||||||
})
|
})
|
||||||
|
|
||||||
// since we probably moved some rows, we need to go and fix up their row
|
// since we probably moved some rows, we need to go and fix up their row
|
||||||
@ -928,8 +944,8 @@ func (s *Sheet) RemoveColumn(column string) error {
|
|||||||
func (s *Sheet) updateAfterRemove(columnIdx uint32, updateType update.UpdateAction) error {
|
func (s *Sheet) updateAfterRemove(columnIdx uint32, updateType update.UpdateAction) error {
|
||||||
ownSheetName := s.Name()
|
ownSheetName := s.Name()
|
||||||
q := &update.UpdateQuery{
|
q := &update.UpdateQuery{
|
||||||
UpdateType: updateType,
|
UpdateType: updateType,
|
||||||
ColumnIdx: columnIdx,
|
ColumnIdx: columnIdx,
|
||||||
SheetToUpdate: ownSheetName,
|
SheetToUpdate: ownSheetName,
|
||||||
}
|
}
|
||||||
for _, sheet := range s.w.Sheets() {
|
for _, sheet := range s.w.Sheets() {
|
||||||
@ -1017,11 +1033,11 @@ func (s *Sheet) removeColumnFromNamedRanges(columnIdx uint32) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sheetTables := s.w.tables[startFromTable:startFromTable + numTables]
|
sheetTables := s.w.tables[startFromTable : startFromTable+numTables]
|
||||||
for tblIndex, tbl := range sheetTables {
|
for tblIndex, tbl := range sheetTables {
|
||||||
newTable := tbl
|
newTable := tbl
|
||||||
newTable.RefAttr = moveRangeLeft(newTable.RefAttr, columnIdx, false)
|
newTable.RefAttr = moveRangeLeft(newTable.RefAttr, columnIdx, false)
|
||||||
s.w.tables[startFromTable + tblIndex] = newTable
|
s.w.tables[startFromTable+tblIndex] = newTable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -357,7 +357,7 @@ func TestRemoveColumn(t *testing.T) {
|
|||||||
|
|
||||||
sheet.RemoveColumn("C")
|
sheet.RemoveColumn("C")
|
||||||
|
|
||||||
expected := []float64{5,4,2,1,0}
|
expected := []float64{5, 4, 2, 1, 0}
|
||||||
|
|
||||||
for i := 0; i <= 4; i++ {
|
for i := 0; i <= 4; i++ {
|
||||||
column := reference.IndexToColumn(uint32(i))
|
column := reference.IndexToColumn(uint32(i))
|
||||||
@ -369,3 +369,16 @@ func TestRemoveColumn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCellsWithEmpty(t *testing.T) {
|
||||||
|
wb := spreadsheet.New()
|
||||||
|
sheet := wb.AddSheet()
|
||||||
|
sheet.Cell("A1").SetNumber(1)
|
||||||
|
sheet.Cell("F2").SetNumber(1)
|
||||||
|
rows := sheet.Rows()
|
||||||
|
exp := 6
|
||||||
|
got := len(rows[0].CellsWithEmpty(sheet.MaxColumnIdx()))
|
||||||
|
if got != exp {
|
||||||
|
t.Errorf("expected %d cells in row, got %d", exp, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ package update
|
|||||||
|
|
||||||
// UpdateAction is the type for update types constants.
|
// UpdateAction is the type for update types constants.
|
||||||
type UpdateAction byte
|
type UpdateAction byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// UpdateActionRemoveColumn means updating references after removing a column.
|
// UpdateActionRemoveColumn means updating references after removing a column.
|
||||||
UpdateActionRemoveColumn UpdateAction = iota
|
UpdateActionRemoveColumn UpdateAction = iota
|
||||||
|
@ -237,7 +237,8 @@ func (wb *Workbook) CopySheet(ind int, copiedSheetName string) (Sheet, error) {
|
|||||||
wb.comments = append(wb.comments, &copiedComments)
|
wb.comments = append(wb.comments, &copiedComments)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Sheet{wb, &copiedSheet, &copiedWs}, nil
|
sheet := Sheet{wb, &copiedSheet, &copiedWs}
|
||||||
|
return sheet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopySheetByName copies the existing sheet with the name `name` and puts its copy with the name `copiedSheetName`.
|
// CopySheetByName copies the existing sheet with the name `name` and puts its copy with the name `copiedSheetName`.
|
||||||
@ -438,7 +439,8 @@ func (wb *Workbook) Sheets() []Sheet {
|
|||||||
ret := []Sheet{}
|
ret := []Sheet{}
|
||||||
for i, wks := range wb.xws {
|
for i, wks := range wb.xws {
|
||||||
r := wb.x.Sheets.Sheet[i]
|
r := wb.x.Sheets.Sheet[i]
|
||||||
ret = append(ret, Sheet{wb, r, wks})
|
sheet := Sheet{wb, r, wks}
|
||||||
|
ret = append(ret, sheet)
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user