forked from p30928647/excelize
This fixed incorrect formula calculation exception expected result
- Simplify and remove duplicate code for optimization - Update documentation comments with typo fix - Handle error return to save the workbook - Add file path length limitation details in the error message
This commit is contained in:
parent
e3fb2d7bad
commit
121ac17ca0
323
calc.go
323
calc.go
|
@ -380,17 +380,17 @@ type formulaFuncs struct {
|
||||||
// BESSELJ
|
// BESSELJ
|
||||||
// BESSELK
|
// BESSELK
|
||||||
// BESSELY
|
// BESSELY
|
||||||
// BETADIST
|
|
||||||
// BETA.DIST
|
// BETA.DIST
|
||||||
// BETAINV
|
|
||||||
// BETA.INV
|
// BETA.INV
|
||||||
|
// BETADIST
|
||||||
|
// BETAINV
|
||||||
// BIN2DEC
|
// BIN2DEC
|
||||||
// BIN2HEX
|
// BIN2HEX
|
||||||
// BIN2OCT
|
// BIN2OCT
|
||||||
// BINOMDIST
|
|
||||||
// BINOM.DIST
|
// BINOM.DIST
|
||||||
// BINOM.DIST.RANGE
|
// BINOM.DIST.RANGE
|
||||||
// BINOM.INV
|
// BINOM.INV
|
||||||
|
// BINOMDIST
|
||||||
// BITAND
|
// BITAND
|
||||||
// BITLSHIFT
|
// BITLSHIFT
|
||||||
// BITOR
|
// BITOR
|
||||||
|
@ -402,12 +402,12 @@ type formulaFuncs struct {
|
||||||
// CHAR
|
// CHAR
|
||||||
// CHIDIST
|
// CHIDIST
|
||||||
// CHIINV
|
// CHIINV
|
||||||
// CHITEST
|
|
||||||
// CHISQ.DIST
|
// CHISQ.DIST
|
||||||
// CHISQ.DIST.RT
|
// CHISQ.DIST.RT
|
||||||
// CHISQ.INV
|
// CHISQ.INV
|
||||||
// CHISQ.INV.RT
|
// CHISQ.INV.RT
|
||||||
// CHISQ.TEST
|
// CHISQ.TEST
|
||||||
|
// CHITEST
|
||||||
// CHOOSE
|
// CHOOSE
|
||||||
// CLEAN
|
// CLEAN
|
||||||
// CODE
|
// CODE
|
||||||
|
@ -477,8 +477,8 @@ type formulaFuncs struct {
|
||||||
// DURATION
|
// DURATION
|
||||||
// DVAR
|
// DVAR
|
||||||
// DVARP
|
// DVARP
|
||||||
// EFFECT
|
|
||||||
// EDATE
|
// EDATE
|
||||||
|
// EFFECT
|
||||||
// ENCODEURL
|
// ENCODEURL
|
||||||
// EOMONTH
|
// EOMONTH
|
||||||
// ERF
|
// ERF
|
||||||
|
@ -492,16 +492,17 @@ type formulaFuncs struct {
|
||||||
// EXP
|
// EXP
|
||||||
// EXPON.DIST
|
// EXPON.DIST
|
||||||
// EXPONDIST
|
// EXPONDIST
|
||||||
|
// F.DIST
|
||||||
|
// F.DIST.RT
|
||||||
|
// F.INV
|
||||||
|
// F.INV.RT
|
||||||
|
// F.TEST
|
||||||
// FACT
|
// FACT
|
||||||
// FACTDOUBLE
|
// FACTDOUBLE
|
||||||
// FALSE
|
// FALSE
|
||||||
// F.DIST
|
|
||||||
// F.DIST.RT
|
|
||||||
// FDIST
|
// FDIST
|
||||||
// FIND
|
// FIND
|
||||||
// FINDB
|
// FINDB
|
||||||
// F.INV
|
|
||||||
// F.INV.RT
|
|
||||||
// FINV
|
// FINV
|
||||||
// FISHER
|
// FISHER
|
||||||
// FISHERINV
|
// FISHERINV
|
||||||
|
@ -510,14 +511,13 @@ type formulaFuncs struct {
|
||||||
// FLOOR.MATH
|
// FLOOR.MATH
|
||||||
// FLOOR.PRECISE
|
// FLOOR.PRECISE
|
||||||
// FORMULATEXT
|
// FORMULATEXT
|
||||||
// F.TEST
|
|
||||||
// FTEST
|
// FTEST
|
||||||
// FV
|
// FV
|
||||||
// FVSCHEDULE
|
// FVSCHEDULE
|
||||||
// GAMMA
|
// GAMMA
|
||||||
// GAMMA.DIST
|
// GAMMA.DIST
|
||||||
// GAMMADIST
|
|
||||||
// GAMMA.INV
|
// GAMMA.INV
|
||||||
|
// GAMMADIST
|
||||||
// GAMMAINV
|
// GAMMAINV
|
||||||
// GAMMALN
|
// GAMMALN
|
||||||
// GAMMALN.PRECISE
|
// GAMMALN.PRECISE
|
||||||
|
@ -579,12 +579,12 @@ type formulaFuncs struct {
|
||||||
// ISNA
|
// ISNA
|
||||||
// ISNONTEXT
|
// ISNONTEXT
|
||||||
// ISNUMBER
|
// ISNUMBER
|
||||||
// ISODD
|
|
||||||
// ISREF
|
|
||||||
// ISTEXT
|
|
||||||
// ISO.CEILING
|
// ISO.CEILING
|
||||||
|
// ISODD
|
||||||
// ISOWEEKNUM
|
// ISOWEEKNUM
|
||||||
// ISPMT
|
// ISPMT
|
||||||
|
// ISREF
|
||||||
|
// ISTEXT
|
||||||
// KURT
|
// KURT
|
||||||
// LARGE
|
// LARGE
|
||||||
// LCM
|
// LCM
|
||||||
|
@ -597,8 +597,8 @@ type formulaFuncs struct {
|
||||||
// LOG10
|
// LOG10
|
||||||
// LOGINV
|
// LOGINV
|
||||||
// LOGNORM.DIST
|
// LOGNORM.DIST
|
||||||
// LOGNORMDIST
|
|
||||||
// LOGNORM.INV
|
// LOGNORM.INV
|
||||||
|
// LOGNORMDIST
|
||||||
// LOOKUP
|
// LOOKUP
|
||||||
// LOWER
|
// LOWER
|
||||||
// MATCH
|
// MATCH
|
||||||
|
@ -633,12 +633,12 @@ type formulaFuncs struct {
|
||||||
// NETWORKDAYS.INTL
|
// NETWORKDAYS.INTL
|
||||||
// NOMINAL
|
// NOMINAL
|
||||||
// NORM.DIST
|
// NORM.DIST
|
||||||
// NORMDIST
|
|
||||||
// NORM.INV
|
// NORM.INV
|
||||||
// NORMINV
|
|
||||||
// NORM.S.DIST
|
// NORM.S.DIST
|
||||||
// NORMSDIST
|
|
||||||
// NORM.S.INV
|
// NORM.S.INV
|
||||||
|
// NORMDIST
|
||||||
|
// NORMINV
|
||||||
|
// NORMSDIST
|
||||||
// NORMSINV
|
// NORMSINV
|
||||||
// NOT
|
// NOT
|
||||||
// NOW
|
// NOW
|
||||||
|
@ -652,19 +652,19 @@ type formulaFuncs struct {
|
||||||
// OR
|
// OR
|
||||||
// PDURATION
|
// PDURATION
|
||||||
// PEARSON
|
// PEARSON
|
||||||
|
// PERCENTILE
|
||||||
// PERCENTILE.EXC
|
// PERCENTILE.EXC
|
||||||
// PERCENTILE.INC
|
// PERCENTILE.INC
|
||||||
// PERCENTILE
|
// PERCENTRANK
|
||||||
// PERCENTRANK.EXC
|
// PERCENTRANK.EXC
|
||||||
// PERCENTRANK.INC
|
// PERCENTRANK.INC
|
||||||
// PERCENTRANK
|
|
||||||
// PERMUT
|
// PERMUT
|
||||||
// PERMUTATIONA
|
// PERMUTATIONA
|
||||||
// PHI
|
// PHI
|
||||||
// PI
|
// PI
|
||||||
// PMT
|
// PMT
|
||||||
// POISSON.DIST
|
|
||||||
// POISSON
|
// POISSON
|
||||||
|
// POISSON.DIST
|
||||||
// POWER
|
// POWER
|
||||||
// PPMT
|
// PPMT
|
||||||
// PRICE
|
// PRICE
|
||||||
|
@ -734,20 +734,21 @@ type formulaFuncs struct {
|
||||||
// SWITCH
|
// SWITCH
|
||||||
// SYD
|
// SYD
|
||||||
// T
|
// T
|
||||||
|
// T.DIST
|
||||||
|
// T.DIST.2T
|
||||||
|
// T.DIST.RT
|
||||||
|
// T.INV
|
||||||
|
// T.INV.2T
|
||||||
|
// T.TEST
|
||||||
// TAN
|
// TAN
|
||||||
// TANH
|
// TANH
|
||||||
// TBILLEQ
|
// TBILLEQ
|
||||||
// TBILLPRICE
|
// TBILLPRICE
|
||||||
// TBILLYIELD
|
// TBILLYIELD
|
||||||
// T.DIST
|
|
||||||
// T.DIST.2T
|
|
||||||
// T.DIST.RT
|
|
||||||
// TDIST
|
// TDIST
|
||||||
// TEXTJOIN
|
// TEXTJOIN
|
||||||
// TIME
|
// TIME
|
||||||
// TIMEVALUE
|
// TIMEVALUE
|
||||||
// T.INV
|
|
||||||
// T.INV.2T
|
|
||||||
// TINV
|
// TINV
|
||||||
// TODAY
|
// TODAY
|
||||||
// TRANSPOSE
|
// TRANSPOSE
|
||||||
|
@ -756,7 +757,6 @@ type formulaFuncs struct {
|
||||||
// TRIMMEAN
|
// TRIMMEAN
|
||||||
// TRUE
|
// TRUE
|
||||||
// TRUNC
|
// TRUNC
|
||||||
// T.TEST
|
|
||||||
// TTEST
|
// TTEST
|
||||||
// TYPE
|
// TYPE
|
||||||
// UNICHAR
|
// UNICHAR
|
||||||
|
@ -14162,17 +14162,23 @@ func (fn *formulaFuncs) COLUMN(argsList *list.List) formulaArg {
|
||||||
return newNumberFormulaArg(float64(col))
|
return newNumberFormulaArg(float64(col))
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcColumnsMinMax calculation min and max value for given formula arguments
|
// calcColsRowsMinMax calculation min and max value for given formula arguments
|
||||||
// sequence of the formula function COLUMNS.
|
// sequence of the formula functions COLUMNS and ROWS.
|
||||||
func calcColumnsMinMax(argsList *list.List) (min, max int) {
|
func calcColsRowsMinMax(cols bool, argsList *list.List) (min, max int) {
|
||||||
|
getVal := func(cols bool, cell cellRef) int {
|
||||||
|
if cols {
|
||||||
|
return cell.Col
|
||||||
|
}
|
||||||
|
return cell.Row
|
||||||
|
}
|
||||||
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
|
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
|
||||||
crs := argsList.Front().Value.(formulaArg).cellRanges
|
crs := argsList.Front().Value.(formulaArg).cellRanges
|
||||||
for cr := crs.Front(); cr != nil; cr = cr.Next() {
|
for cr := crs.Front(); cr != nil; cr = cr.Next() {
|
||||||
if min == 0 {
|
if min == 0 {
|
||||||
min = cr.Value.(cellRange).From.Col
|
min = getVal(cols, cr.Value.(cellRange).From)
|
||||||
}
|
}
|
||||||
if max < cr.Value.(cellRange).To.Col {
|
if max < getVal(cols, cr.Value.(cellRange).To) {
|
||||||
max = cr.Value.(cellRange).To.Col
|
max = getVal(cols, cr.Value.(cellRange).To)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14180,10 +14186,10 @@ func calcColumnsMinMax(argsList *list.List) (min, max int) {
|
||||||
cr := argsList.Front().Value.(formulaArg).cellRefs
|
cr := argsList.Front().Value.(formulaArg).cellRefs
|
||||||
for refs := cr.Front(); refs != nil; refs = refs.Next() {
|
for refs := cr.Front(); refs != nil; refs = refs.Next() {
|
||||||
if min == 0 {
|
if min == 0 {
|
||||||
min = refs.Value.(cellRef).Col
|
min = getVal(cols, refs.Value.(cellRef))
|
||||||
}
|
}
|
||||||
if max < refs.Value.(cellRef).Col {
|
if max < getVal(cols, refs.Value.(cellRef)) {
|
||||||
max = refs.Value.(cellRef).Col
|
max = getVal(cols, refs.Value.(cellRef))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14198,7 +14204,7 @@ func (fn *formulaFuncs) COLUMNS(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 1 {
|
if argsList.Len() != 1 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument")
|
||||||
}
|
}
|
||||||
min, max := calcColumnsMinMax(argsList)
|
min, max := calcColsRowsMinMax(true, argsList)
|
||||||
if max == MaxColumns {
|
if max == MaxColumns {
|
||||||
return newNumberFormulaArg(float64(MaxColumns))
|
return newNumberFormulaArg(float64(MaxColumns))
|
||||||
}
|
}
|
||||||
|
@ -14411,8 +14417,8 @@ func (fn *formulaFuncs) TRANSPOSE(argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "TRANSPOSE requires 1 argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "TRANSPOSE requires 1 argument")
|
||||||
}
|
}
|
||||||
args := argsList.Back().Value.(formulaArg).ToList()
|
args := argsList.Back().Value.(formulaArg).ToList()
|
||||||
rmin, rmax := calcRowsMinMax(argsList)
|
rmin, rmax := calcColsRowsMinMax(false, argsList)
|
||||||
cmin, cmax := calcColumnsMinMax(argsList)
|
cmin, cmax := calcColsRowsMinMax(true, argsList)
|
||||||
cols, rows := cmax-cmin+1, rmax-rmin+1
|
cols, rows := cmax-cmin+1, rmax-rmin+1
|
||||||
src := make([][]formulaArg, 0)
|
src := make([][]formulaArg, 0)
|
||||||
for i := 0; i < len(args); i += cols {
|
for i := 0; i < len(args); i += cols {
|
||||||
|
@ -14931,34 +14937,6 @@ func (fn *formulaFuncs) ROW(argsList *list.List) formulaArg {
|
||||||
return newNumberFormulaArg(float64(row))
|
return newNumberFormulaArg(float64(row))
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcRowsMinMax calculation min and max value for given formula arguments
|
|
||||||
// sequence of the formula function ROWS.
|
|
||||||
func calcRowsMinMax(argsList *list.List) (min, max int) {
|
|
||||||
if argsList.Front().Value.(formulaArg).cellRanges != nil && argsList.Front().Value.(formulaArg).cellRanges.Len() > 0 {
|
|
||||||
crs := argsList.Front().Value.(formulaArg).cellRanges
|
|
||||||
for cr := crs.Front(); cr != nil; cr = cr.Next() {
|
|
||||||
if min == 0 {
|
|
||||||
min = cr.Value.(cellRange).From.Row
|
|
||||||
}
|
|
||||||
if max < cr.Value.(cellRange).To.Row {
|
|
||||||
max = cr.Value.(cellRange).To.Row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if argsList.Front().Value.(formulaArg).cellRefs != nil && argsList.Front().Value.(formulaArg).cellRefs.Len() > 0 {
|
|
||||||
cr := argsList.Front().Value.(formulaArg).cellRefs
|
|
||||||
for refs := cr.Front(); refs != nil; refs = refs.Next() {
|
|
||||||
if min == 0 {
|
|
||||||
min = refs.Value.(cellRef).Row
|
|
||||||
}
|
|
||||||
if max < refs.Value.(cellRef).Row {
|
|
||||||
max = refs.Value.(cellRef).Row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ROWS function takes an Excel range and returns the number of rows that are
|
// ROWS function takes an Excel range and returns the number of rows that are
|
||||||
// contained within the range. The syntax of the function is:
|
// contained within the range. The syntax of the function is:
|
||||||
//
|
//
|
||||||
|
@ -14967,7 +14945,7 @@ func (fn *formulaFuncs) ROWS(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 1 {
|
if argsList.Len() != 1 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument")
|
||||||
}
|
}
|
||||||
min, max := calcRowsMinMax(argsList)
|
min, max := calcColsRowsMinMax(false, argsList)
|
||||||
if max == TotalRows {
|
if max == TotalRows {
|
||||||
return newStringFormulaArg(strconv.Itoa(TotalRows))
|
return newStringFormulaArg(strconv.Itoa(TotalRows))
|
||||||
}
|
}
|
||||||
|
@ -15649,35 +15627,35 @@ func (fn *formulaFuncs) prepareDataValueArgs(n int, argsList *list.List) formula
|
||||||
return newListFormulaArg(dataValues)
|
return newListFormulaArg(dataValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DISC function calculates the Discount Rate for a security. The syntax of
|
// discIntrate is an implementation of the formula functions DISC and INTRATE.
|
||||||
// the function is:
|
func (fn *formulaFuncs) discIntrate(name string, argsList *list.List) formulaArg {
|
||||||
//
|
|
||||||
// DISC(settlement,maturity,pr,redemption,[basis])
|
|
||||||
func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg {
|
|
||||||
if argsList.Len() != 4 && argsList.Len() != 5 {
|
if argsList.Len() != 4 && argsList.Len() != 5 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "DISC requires 4 or 5 arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 4 or 5 arguments", name))
|
||||||
}
|
}
|
||||||
args := fn.prepareDataValueArgs(2, argsList)
|
args := fn.prepareDataValueArgs(2, argsList)
|
||||||
if args.Type != ArgList {
|
if args.Type != ArgList {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
settlement, maturity := args.List[0], args.List[1]
|
settlement, maturity, argName := args.List[0], args.List[1], "pr"
|
||||||
if maturity.Number <= settlement.Number {
|
if maturity.Number <= settlement.Number {
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "DISC requires maturity > settlement")
|
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires maturity > settlement", name))
|
||||||
}
|
}
|
||||||
pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
prInvestment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
||||||
if pr.Type != ArgNumber {
|
if prInvestment.Type != ArgNumber {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
}
|
}
|
||||||
if pr.Number <= 0 {
|
if prInvestment.Number <= 0 {
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "DISC requires pr > 0")
|
if name == "INTRATE" {
|
||||||
|
argName = "investment"
|
||||||
|
}
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires %s > 0", name, argName))
|
||||||
}
|
}
|
||||||
redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||||
if redemption.Type != ArgNumber {
|
if redemption.Type != ArgNumber {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
}
|
}
|
||||||
if redemption.Number <= 0 {
|
if redemption.Number <= 0 {
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "DISC requires redemption > 0")
|
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
|
||||||
}
|
}
|
||||||
basis := newNumberFormulaArg(0)
|
basis := newNumberFormulaArg(0)
|
||||||
if argsList.Len() == 5 {
|
if argsList.Len() == 5 {
|
||||||
|
@ -15689,7 +15667,18 @@ func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg {
|
||||||
if frac.Type != ArgNumber {
|
if frac.Type != ArgNumber {
|
||||||
return frac
|
return frac
|
||||||
}
|
}
|
||||||
return newNumberFormulaArg((redemption.Number - pr.Number) / redemption.Number / frac.Number)
|
if name == "INTRATE" {
|
||||||
|
return newNumberFormulaArg((redemption.Number - prInvestment.Number) / prInvestment.Number / frac.Number)
|
||||||
|
}
|
||||||
|
return newNumberFormulaArg((redemption.Number - prInvestment.Number) / redemption.Number / frac.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DISC function calculates the Discount Rate for a security. The syntax of
|
||||||
|
// the function is:
|
||||||
|
//
|
||||||
|
// DISC(settlement,maturity,pr,redemption,[basis])
|
||||||
|
func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg {
|
||||||
|
return fn.discIntrate("DISC", argsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DOLLARDE function converts a dollar value in fractional notation, into a
|
// DOLLARDE function converts a dollar value in fractional notation, into a
|
||||||
|
@ -16007,42 +15996,7 @@ func (fn *formulaFuncs) FVSCHEDULE(argsList *list.List) formulaArg {
|
||||||
//
|
//
|
||||||
// INTRATE(settlement,maturity,investment,redemption,[basis])
|
// INTRATE(settlement,maturity,investment,redemption,[basis])
|
||||||
func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg {
|
func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 4 && argsList.Len() != 5 {
|
return fn.discIntrate("INTRATE", argsList)
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "INTRATE requires 4 or 5 arguments")
|
|
||||||
}
|
|
||||||
args := fn.prepareDataValueArgs(2, argsList)
|
|
||||||
if args.Type != ArgList {
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
settlement, maturity := args.List[0], args.List[1]
|
|
||||||
if maturity.Number <= settlement.Number {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires maturity > settlement")
|
|
||||||
}
|
|
||||||
investment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if investment.Type != ArgNumber {
|
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
||||||
}
|
|
||||||
if investment.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires investment > 0")
|
|
||||||
}
|
|
||||||
redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if redemption.Type != ArgNumber {
|
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
|
||||||
}
|
|
||||||
if redemption.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires redemption > 0")
|
|
||||||
}
|
|
||||||
basis := newNumberFormulaArg(0)
|
|
||||||
if argsList.Len() == 5 {
|
|
||||||
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number))
|
|
||||||
if frac.Type != ArgNumber {
|
|
||||||
return frac
|
|
||||||
}
|
|
||||||
return newNumberFormulaArg((redemption.Number - investment.Number) / investment.Number / frac.Number)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPMT function calculates the interest payment, during a specific period of a
|
// IPMT function calculates the interest payment, during a specific period of a
|
||||||
|
@ -16756,13 +16710,50 @@ func (fn *formulaFuncs) price(settlement, maturity, rate, yld, redemption, frequ
|
||||||
return newNumberFormulaArg(ret)
|
return newNumberFormulaArg(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRICE function calculates the price, per $100 face value of a security that
|
// checkPriceYieldArgs checking and prepare arguments for the formula functions
|
||||||
// pays periodic interest. The syntax of the function is:
|
// PRICE and YIELD.
|
||||||
//
|
func checkPriceYieldArgs(name string, rate, prYld, redemption, frequency formulaArg) formulaArg {
|
||||||
// PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis])
|
if rate.Type != ArgNumber {
|
||||||
func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg {
|
return rate
|
||||||
|
}
|
||||||
|
if rate.Number < 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
|
||||||
|
}
|
||||||
|
if prYld.Type != ArgNumber {
|
||||||
|
return prYld
|
||||||
|
}
|
||||||
|
if redemption.Type != ArgNumber {
|
||||||
|
return redemption
|
||||||
|
}
|
||||||
|
if name == "PRICE" {
|
||||||
|
if prYld.Number < 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0")
|
||||||
|
}
|
||||||
|
if redemption.Number <= 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if name == "YIELD" {
|
||||||
|
if prYld.Number <= 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, "YIELD requires pr > 0")
|
||||||
|
}
|
||||||
|
if redemption.Number < 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, "YIELD requires redemption >= 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if frequency.Type != ArgNumber {
|
||||||
|
return frequency
|
||||||
|
}
|
||||||
|
if !validateFrequency(frequency.Number) {
|
||||||
|
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||||
|
}
|
||||||
|
return newEmptyFormulaArg()
|
||||||
|
}
|
||||||
|
|
||||||
|
// priceYield is an implementation of the formula functions PRICE and YIELD.
|
||||||
|
func (fn *formulaFuncs) priceYield(name string, argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 6 && argsList.Len() != 7 {
|
if argsList.Len() != 6 && argsList.Len() != 7 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "PRICE requires 6 or 7 arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 or 7 arguments", name))
|
||||||
}
|
}
|
||||||
args := fn.prepareDataValueArgs(2, argsList)
|
args := fn.prepareDataValueArgs(2, argsList)
|
||||||
if args.Type != ArgList {
|
if args.Type != ArgList {
|
||||||
|
@ -16770,32 +16761,11 @@ func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg {
|
||||||
}
|
}
|
||||||
settlement, maturity := args.List[0], args.List[1]
|
settlement, maturity := args.List[0], args.List[1]
|
||||||
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
||||||
if rate.Type != ArgNumber {
|
prYld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||||
return rate
|
|
||||||
}
|
|
||||||
if rate.Number < 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0")
|
|
||||||
}
|
|
||||||
yld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if yld.Type != ArgNumber {
|
|
||||||
return yld
|
|
||||||
}
|
|
||||||
if yld.Number < 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires yld >= 0")
|
|
||||||
}
|
|
||||||
redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||||
if redemption.Type != ArgNumber {
|
|
||||||
return redemption
|
|
||||||
}
|
|
||||||
if redemption.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption > 0")
|
|
||||||
}
|
|
||||||
frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
||||||
if frequency.Type != ArgNumber {
|
if arg := checkPriceYieldArgs(name, rate, prYld, redemption, frequency); arg.Type != ArgEmpty {
|
||||||
return frequency
|
return arg
|
||||||
}
|
|
||||||
if !validateFrequency(frequency.Number) {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
|
||||||
}
|
}
|
||||||
basis := newNumberFormulaArg(0)
|
basis := newNumberFormulaArg(0)
|
||||||
if argsList.Len() == 7 {
|
if argsList.Len() == 7 {
|
||||||
|
@ -16803,7 +16773,18 @@ func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fn.price(settlement, maturity, rate, yld, redemption, frequency, basis)
|
if name == "PRICE" {
|
||||||
|
return fn.price(settlement, maturity, rate, prYld, redemption, frequency, basis)
|
||||||
|
}
|
||||||
|
return fn.yield(settlement, maturity, rate, prYld, redemption, frequency, basis)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRICE function calculates the price, per $100 face value of a security that
|
||||||
|
// pays periodic interest. The syntax of the function is:
|
||||||
|
//
|
||||||
|
// PRICE(settlement,maturity,rate,yld,redemption,frequency,[basis])
|
||||||
|
func (fn *formulaFuncs) PRICE(argsList *list.List) formulaArg {
|
||||||
|
return fn.priceYield("PRICE", argsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRICEDISC function calculates the price, per $100 face value of a
|
// PRICEDISC function calculates the price, per $100 face value of a
|
||||||
|
@ -17535,49 +17516,7 @@ func (fn *formulaFuncs) yield(settlement, maturity, rate, pr, redemption, freque
|
||||||
//
|
//
|
||||||
// YIELD(settlement,maturity,rate,pr,redemption,frequency,[basis])
|
// YIELD(settlement,maturity,rate,pr,redemption,frequency,[basis])
|
||||||
func (fn *formulaFuncs) YIELD(argsList *list.List) formulaArg {
|
func (fn *formulaFuncs) YIELD(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 6 && argsList.Len() != 7 {
|
return fn.priceYield("YIELD", argsList)
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "YIELD requires 6 or 7 arguments")
|
|
||||||
}
|
|
||||||
args := fn.prepareDataValueArgs(2, argsList)
|
|
||||||
if args.Type != ArgList {
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
settlement, maturity := args.List[0], args.List[1]
|
|
||||||
rate := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if rate.Type != ArgNumber {
|
|
||||||
return rate
|
|
||||||
}
|
|
||||||
if rate.Number < 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0")
|
|
||||||
}
|
|
||||||
pr := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if pr.Type != ArgNumber {
|
|
||||||
return pr
|
|
||||||
}
|
|
||||||
if pr.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires pr > 0")
|
|
||||||
}
|
|
||||||
redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if redemption.Type != ArgNumber {
|
|
||||||
return redemption
|
|
||||||
}
|
|
||||||
if redemption.Number < 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, "PRICE requires redemption >= 0")
|
|
||||||
}
|
|
||||||
frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
|
|
||||||
if frequency.Type != ArgNumber {
|
|
||||||
return frequency
|
|
||||||
}
|
|
||||||
if !validateFrequency(frequency.Number) {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
|
||||||
}
|
|
||||||
basis := newNumberFormulaArg(0)
|
|
||||||
if argsList.Len() == 7 {
|
|
||||||
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
|
|
||||||
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fn.yield(settlement, maturity, rate, pr, redemption, frequency, basis)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// YIELDDISC function calculates the annual yield of a discounted security.
|
// YIELDDISC function calculates the annual yield of a discounted security.
|
||||||
|
|
|
@ -4334,9 +4334,9 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,\"\")": {"#NUM!", "#NUM!"},
|
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,\"\")": {"#NUM!", "#NUM!"},
|
||||||
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,3)": {"#NUM!", "#NUM!"},
|
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,3)": {"#NUM!", "#NUM!"},
|
||||||
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,5)": {"#NUM!", "invalid basis"},
|
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,100,4,5)": {"#NUM!", "invalid basis"},
|
||||||
"=YIELD(\"01/01/2010\",\"06/30/2015\",-1,101,100,4)": {"#NUM!", "PRICE requires rate >= 0"},
|
"=YIELD(\"01/01/2010\",\"06/30/2015\",-1,101,100,4)": {"#NUM!", "YIELD requires rate >= 0"},
|
||||||
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,0,100,4)": {"#NUM!", "PRICE requires pr > 0"},
|
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,0,100,4)": {"#NUM!", "YIELD requires pr > 0"},
|
||||||
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,-1,4)": {"#NUM!", "PRICE requires redemption >= 0"},
|
"=YIELD(\"01/01/2010\",\"06/30/2015\",10%,101,-1,4)": {"#NUM!", "YIELD requires redemption >= 0"},
|
||||||
// YIELDDISC
|
// YIELDDISC
|
||||||
"=YIELDDISC()": {"#VALUE!", "YIELDDISC requires 4 or 5 arguments"},
|
"=YIELDDISC()": {"#VALUE!", "YIELDDISC requires 4 or 5 arguments"},
|
||||||
"=YIELDDISC(\"\",\"06/30/2017\",97,100,0)": {"#VALUE!", "#VALUE!"},
|
"=YIELDDISC(\"\",\"06/30/2017\",97,100,0)": {"#VALUE!", "#VALUE!"},
|
||||||
|
|
4
date.go
4
date.go
|
@ -114,7 +114,7 @@ func julianDateToGregorianTime(part1, part2 float64) time.Time {
|
||||||
// "Communications of the ACM" in 1968 (published in CACM, volume 11, number
|
// "Communications of the ACM" in 1968 (published in CACM, volume 11, number
|
||||||
// 10, October 1968, p.657). None of those programmers seems to have found it
|
// 10, October 1968, p.657). None of those programmers seems to have found it
|
||||||
// necessary to explain the constants or variable names set out by Henry F.
|
// necessary to explain the constants or variable names set out by Henry F.
|
||||||
// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and
|
// Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that journal and
|
||||||
// expand an explanation here - that day is not today.
|
// expand an explanation here - that day is not today.
|
||||||
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
|
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
|
||||||
l := jd + 68569
|
l := jd + 68569
|
||||||
|
@ -163,7 +163,7 @@ func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
|
||||||
return date.Truncate(time.Second)
|
return date.Truncate(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExcelDateToTime converts a float-based excel date representation to a time.Time.
|
// ExcelDateToTime converts a float-based Excel date representation to a time.Time.
|
||||||
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
|
func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
|
||||||
if excelDate < 0 {
|
if excelDate < 0 {
|
||||||
return time.Time{}, newInvalidExcelDateError(excelDate)
|
return time.Time{}, newInvalidExcelDateError(excelDate)
|
||||||
|
|
|
@ -143,7 +143,7 @@ var (
|
||||||
ErrWorkbookFileFormat = errors.New("unsupported workbook file format")
|
ErrWorkbookFileFormat = errors.New("unsupported workbook file format")
|
||||||
// ErrMaxFilePathLength defined the error message on receive the file path
|
// ErrMaxFilePathLength defined the error message on receive the file path
|
||||||
// length overflow.
|
// length overflow.
|
||||||
ErrMaxFilePathLength = errors.New("file path length exceeds maximum limit")
|
ErrMaxFilePathLength = fmt.Errorf("file path length exceeds maximum limit %d characters", MaxFilePathLength)
|
||||||
// ErrUnknownEncryptMechanism defined the error message on unsupported
|
// ErrUnknownEncryptMechanism defined the error message on unsupported
|
||||||
// encryption mechanism.
|
// encryption mechanism.
|
||||||
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
|
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
|
||||||
|
|
|
@ -60,7 +60,7 @@ type File struct {
|
||||||
// the spreadsheet from non-UTF-8 encoding.
|
// the spreadsheet from non-UTF-8 encoding.
|
||||||
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
|
type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, err error)
|
||||||
|
|
||||||
// Options define the options for o`pen and reading spreadsheet.
|
// Options define the options for opening and reading the spreadsheet.
|
||||||
//
|
//
|
||||||
// MaxCalcIterations specifies the maximum iterations for iterative
|
// MaxCalcIterations specifies the maximum iterations for iterative
|
||||||
// calculation, the default value is 0.
|
// calculation, the default value is 0.
|
||||||
|
@ -70,7 +70,7 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
|
||||||
// RawCellValue specifies if apply the number format for the cell value or get
|
// RawCellValue specifies if apply the number format for the cell value or get
|
||||||
// the raw value.
|
// the raw value.
|
||||||
//
|
//
|
||||||
// UnzipSizeLimit specifies the unzip size limit in bytes on open the
|
// UnzipSizeLimit specifies to unzip size limit in bytes on open the
|
||||||
// spreadsheet, this value should be greater than or equal to
|
// spreadsheet, this value should be greater than or equal to
|
||||||
// UnzipXMLSizeLimit, the default size limit is 16GB.
|
// UnzipXMLSizeLimit, the default size limit is 16GB.
|
||||||
//
|
//
|
||||||
|
@ -106,7 +106,7 @@ type Options struct {
|
||||||
CultureInfo CultureName
|
CultureInfo CultureName
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenFile take the name of an spreadsheet file and returns a populated
|
// OpenFile take the name of a spreadsheet file and returns a populated
|
||||||
// spreadsheet file struct for it. For example, open spreadsheet with
|
// spreadsheet file struct for it. For example, open spreadsheet with
|
||||||
// password protection:
|
// password protection:
|
||||||
//
|
//
|
||||||
|
|
4
hsl.go
4
hsl.go
|
@ -60,7 +60,7 @@ func hslModel(c color.Color) color.Color {
|
||||||
return HSL{h, s, l}
|
return HSL{h, s, l}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RGBToHSL converts an RGB triple to a HSL triple.
|
// RGBToHSL converts an RGB triple to an HSL triple.
|
||||||
func RGBToHSL(r, g, b uint8) (h, s, l float64) {
|
func RGBToHSL(r, g, b uint8) (h, s, l float64) {
|
||||||
fR := float64(r) / 255
|
fR := float64(r) / 255
|
||||||
fG := float64(g) / 255
|
fG := float64(g) / 255
|
||||||
|
@ -95,7 +95,7 @@ func RGBToHSL(r, g, b uint8) (h, s, l float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSLToRGB converts an HSL triple to a RGB triple.
|
// HSLToRGB converts an HSL triple to an RGB triple.
|
||||||
func HSLToRGB(h, s, l float64) (r, g, b uint8) {
|
func HSLToRGB(h, s, l float64) (r, g, b uint8) {
|
||||||
var fR, fG, fB float64
|
var fR, fG, fB float64
|
||||||
if s == 0 {
|
if s == 0 {
|
||||||
|
|
38
numfmt.go
38
numfmt.go
|
@ -1136,7 +1136,7 @@ func getNumberPartLen(n float64) (int, int) {
|
||||||
return len(parts[0]), 0
|
return len(parts[0]), 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNumberFmtConf generate the number format padding and place holder
|
// getNumberFmtConf generate the number format padding and placeholder
|
||||||
// configurations.
|
// configurations.
|
||||||
func (nf *numberFormat) getNumberFmtConf() {
|
func (nf *numberFormat) getNumberFmtConf() {
|
||||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||||
|
@ -1183,9 +1183,9 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
|
||||||
if nf.usePositive {
|
if nf.usePositive {
|
||||||
result += "-"
|
result += "-"
|
||||||
}
|
}
|
||||||
for i, token := range nf.section[nf.sectionIdx].Items {
|
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||||
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
||||||
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
|
if err, changeNumFmtCode := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode {
|
||||||
return nf.value
|
return nf.value
|
||||||
}
|
}
|
||||||
result += nf.currencyString
|
result += nf.currencyString
|
||||||
|
@ -1321,7 +1321,7 @@ func (nf *numberFormat) dateTimeHandler() string {
|
||||||
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
|
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
|
||||||
for i, token := range nf.section[nf.sectionIdx].Items {
|
for i, token := range nf.section[nf.sectionIdx].Items {
|
||||||
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
||||||
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
|
if err, changeNumFmtCode := nf.currencyLanguageHandler(token); err != nil || changeNumFmtCode {
|
||||||
return nf.value
|
return nf.value
|
||||||
}
|
}
|
||||||
nf.result += nf.currencyString
|
nf.result += nf.currencyString
|
||||||
|
@ -1392,7 +1392,7 @@ func (nf *numberFormat) positiveHandler() string {
|
||||||
|
|
||||||
// currencyLanguageHandler will be handling currency and language types tokens
|
// currencyLanguageHandler will be handling currency and language types tokens
|
||||||
// for a number format expression.
|
// for a number format expression.
|
||||||
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (error, bool) {
|
func (nf *numberFormat) currencyLanguageHandler(token nfp.Token) (error, bool) {
|
||||||
for _, part := range token.Parts {
|
for _, part := range token.Parts {
|
||||||
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
|
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
|
||||||
return ErrUnsupportedNumberFormat, false
|
return ErrUnsupportedNumberFormat, false
|
||||||
|
@ -1491,7 +1491,7 @@ func localMonthsNameFrench(t time.Time, abbr int) string {
|
||||||
// localMonthsNameIrish returns the Irish name of the month.
|
// localMonthsNameIrish returns the Irish name of the month.
|
||||||
func localMonthsNameIrish(t time.Time, abbr int) string {
|
func localMonthsNameIrish(t time.Time, abbr int) string {
|
||||||
if abbr == 3 {
|
if abbr == 3 {
|
||||||
return monthNamesIrishAbbr[int(t.Month()-1)]
|
return monthNamesIrishAbbr[(t.Month() - 1)]
|
||||||
}
|
}
|
||||||
if abbr == 4 {
|
if abbr == 4 {
|
||||||
return monthNamesIrish[int(t.Month())-1]
|
return monthNamesIrish[int(t.Month())-1]
|
||||||
|
@ -1524,7 +1524,7 @@ func localMonthsNameGerman(t time.Time, abbr int) string {
|
||||||
// localMonthsNameChinese1 returns the Chinese name of the month.
|
// localMonthsNameChinese1 returns the Chinese name of the month.
|
||||||
func localMonthsNameChinese1(t time.Time, abbr int) string {
|
func localMonthsNameChinese1(t time.Time, abbr int) string {
|
||||||
if abbr == 3 {
|
if abbr == 3 {
|
||||||
return monthNamesChineseAbbrPlus[int(t.Month())]
|
return monthNamesChineseAbbrPlus[t.Month()]
|
||||||
}
|
}
|
||||||
if abbr == 4 {
|
if abbr == 4 {
|
||||||
return monthNamesChinesePlus[int(t.Month())-1]
|
return monthNamesChinesePlus[int(t.Month())-1]
|
||||||
|
@ -1543,7 +1543,7 @@ func localMonthsNameChinese2(t time.Time, abbr int) string {
|
||||||
// localMonthsNameChinese3 returns the Chinese name of the month.
|
// localMonthsNameChinese3 returns the Chinese name of the month.
|
||||||
func localMonthsNameChinese3(t time.Time, abbr int) string {
|
func localMonthsNameChinese3(t time.Time, abbr int) string {
|
||||||
if abbr == 3 || abbr == 4 {
|
if abbr == 3 || abbr == 4 {
|
||||||
return monthNamesChineseAbbrPlus[int(t.Month())]
|
return monthNamesChineseAbbrPlus[t.Month()]
|
||||||
}
|
}
|
||||||
return strconv.Itoa(int(t.Month()))
|
return strconv.Itoa(int(t.Month()))
|
||||||
}
|
}
|
||||||
|
@ -1551,7 +1551,7 @@ func localMonthsNameChinese3(t time.Time, abbr int) string {
|
||||||
// localMonthsNameKorean returns the Korean name of the month.
|
// localMonthsNameKorean returns the Korean name of the month.
|
||||||
func localMonthsNameKorean(t time.Time, abbr int) string {
|
func localMonthsNameKorean(t time.Time, abbr int) string {
|
||||||
if abbr == 3 || abbr == 4 {
|
if abbr == 3 || abbr == 4 {
|
||||||
return monthNamesKoreanAbbrPlus[int(t.Month())]
|
return monthNamesKoreanAbbrPlus[t.Month()]
|
||||||
}
|
}
|
||||||
return strconv.Itoa(int(t.Month()))
|
return strconv.Itoa(int(t.Month()))
|
||||||
}
|
}
|
||||||
|
@ -1562,7 +1562,7 @@ func localMonthsNameTraditionalMongolian(t time.Time, abbr int) string {
|
||||||
if abbr == 5 {
|
if abbr == 5 {
|
||||||
return "M"
|
return "M"
|
||||||
}
|
}
|
||||||
return monthNamesTradMongolian[int(t.Month()-1)]
|
return monthNamesTradMongolian[t.Month()-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// localMonthsNameRussian returns the Russian name of the month.
|
// localMonthsNameRussian returns the Russian name of the month.
|
||||||
|
@ -1642,12 +1642,12 @@ func localMonthsNameWelsh(t time.Time, abbr int) string {
|
||||||
// localMonthsNameVietnamese returns the Vietnamese name of the month.
|
// localMonthsNameVietnamese returns the Vietnamese name of the month.
|
||||||
func localMonthsNameVietnamese(t time.Time, abbr int) string {
|
func localMonthsNameVietnamese(t time.Time, abbr int) string {
|
||||||
if abbr == 3 {
|
if abbr == 3 {
|
||||||
return monthNamesVietnameseAbbr3[int(t.Month()-1)]
|
return monthNamesVietnameseAbbr3[t.Month()-1]
|
||||||
}
|
}
|
||||||
if abbr == 5 {
|
if abbr == 5 {
|
||||||
return monthNamesVietnameseAbbr5[int(t.Month()-1)]
|
return monthNamesVietnameseAbbr5[t.Month()-1]
|
||||||
}
|
}
|
||||||
return monthNamesVietnamese[int(t.Month()-1)]
|
return monthNamesVietnamese[t.Month()-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// localMonthsNameWolof returns the Wolof name of the month.
|
// localMonthsNameWolof returns the Wolof name of the month.
|
||||||
|
@ -1675,7 +1675,7 @@ func localMonthsNameXhosa(t time.Time, abbr int) string {
|
||||||
// localMonthsNameYi returns the Yi name of the month.
|
// localMonthsNameYi returns the Yi name of the month.
|
||||||
func localMonthsNameYi(t time.Time, abbr int) string {
|
func localMonthsNameYi(t time.Time, abbr int) string {
|
||||||
if abbr == 3 || abbr == 4 {
|
if abbr == 3 || abbr == 4 {
|
||||||
return monthNamesYiSuffix[int(t.Month()-1)]
|
return monthNamesYiSuffix[t.Month()-1]
|
||||||
}
|
}
|
||||||
return string([]rune(monthNamesYi[int(t.Month())-1])[:1])
|
return string([]rune(monthNamesYi[int(t.Month())-1])[:1])
|
||||||
}
|
}
|
||||||
|
@ -1683,7 +1683,7 @@ func localMonthsNameYi(t time.Time, abbr int) string {
|
||||||
// localMonthsNameZulu returns the Zulu name of the month.
|
// localMonthsNameZulu returns the Zulu name of the month.
|
||||||
func localMonthsNameZulu(t time.Time, abbr int) string {
|
func localMonthsNameZulu(t time.Time, abbr int) string {
|
||||||
if abbr == 3 {
|
if abbr == 3 {
|
||||||
return monthNamesZuluAbbr[int(t.Month()-1)]
|
return monthNamesZuluAbbr[t.Month()-1]
|
||||||
}
|
}
|
||||||
if abbr == 4 {
|
if abbr == 4 {
|
||||||
return monthNamesZulu[int(t.Month())-1]
|
return monthNamesZulu[int(t.Month())-1]
|
||||||
|
@ -1737,8 +1737,8 @@ func (nf *numberFormat) dateTimesHandler(i int, token nfp.Token) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nf.yearsHandler(i, token)
|
nf.yearsHandler(token)
|
||||||
nf.daysHandler(i, token)
|
nf.daysHandler(token)
|
||||||
nf.hoursHandler(i, token)
|
nf.hoursHandler(i, token)
|
||||||
nf.minutesHandler(token)
|
nf.minutesHandler(token)
|
||||||
nf.secondsHandler(token)
|
nf.secondsHandler(token)
|
||||||
|
@ -1746,7 +1746,7 @@ func (nf *numberFormat) dateTimesHandler(i int, token nfp.Token) {
|
||||||
|
|
||||||
// yearsHandler will be handling years in the date and times types tokens for a
|
// yearsHandler will be handling years in the date and times types tokens for a
|
||||||
// number format expression.
|
// number format expression.
|
||||||
func (nf *numberFormat) yearsHandler(i int, token nfp.Token) {
|
func (nf *numberFormat) yearsHandler(token nfp.Token) {
|
||||||
years := strings.Contains(strings.ToUpper(token.TValue), "Y")
|
years := strings.Contains(strings.ToUpper(token.TValue), "Y")
|
||||||
if years && len(token.TValue) <= 2 {
|
if years && len(token.TValue) <= 2 {
|
||||||
nf.result += strconv.Itoa(nf.t.Year())[2:]
|
nf.result += strconv.Itoa(nf.t.Year())[2:]
|
||||||
|
@ -1760,7 +1760,7 @@ func (nf *numberFormat) yearsHandler(i int, token nfp.Token) {
|
||||||
|
|
||||||
// daysHandler will be handling days in the date and times types tokens for a
|
// daysHandler will be handling days in the date and times types tokens for a
|
||||||
// number format expression.
|
// number format expression.
|
||||||
func (nf *numberFormat) daysHandler(i int, token nfp.Token) {
|
func (nf *numberFormat) daysHandler(token nfp.Token) {
|
||||||
if strings.Contains(strings.ToUpper(token.TValue), "D") {
|
if strings.Contains(strings.ToUpper(token.TValue), "D") {
|
||||||
switch len(token.TValue) {
|
switch len(token.TValue) {
|
||||||
case 1:
|
case 1:
|
||||||
|
|
|
@ -1093,7 +1093,7 @@ func TestNumFmt(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nf := numberFormat{}
|
nf := numberFormat{}
|
||||||
err, changeNumFmtCode := nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}})
|
err, changeNumFmtCode := nf.currencyLanguageHandler(nfp.Token{Parts: []nfp.Part{{}}})
|
||||||
assert.Equal(t, ErrUnsupportedNumberFormat, err)
|
assert.Equal(t, ErrUnsupportedNumberFormat, err)
|
||||||
assert.False(t, changeNumFmtCode)
|
assert.False(t, changeNumFmtCode)
|
||||||
}
|
}
|
||||||
|
|
6
rows.go
6
rows.go
|
@ -79,7 +79,7 @@ type Rows struct {
|
||||||
curRowOpts, seekRowOpts RowOpts
|
curRowOpts, seekRowOpts RowOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next will return true if find the next row element.
|
// Next will return true if it finds the next row element.
|
||||||
func (rows *Rows) Next() bool {
|
func (rows *Rows) Next() bool {
|
||||||
rows.seekRow++
|
rows.seekRow++
|
||||||
if rows.curRow >= rows.seekRow {
|
if rows.curRow >= rows.seekRow {
|
||||||
|
@ -297,7 +297,9 @@ func (f *File) getFromStringItem(index int) string {
|
||||||
}
|
}
|
||||||
needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings)
|
needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings)
|
||||||
if needClose && err == nil {
|
if needClose && err == nil {
|
||||||
defer tempFile.Close()
|
defer func() {
|
||||||
|
err = tempFile.Close()
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
f.sharedStringItem = [][]uint{}
|
f.sharedStringItem = [][]uint{}
|
||||||
f.sharedStringTemp, _ = os.CreateTemp(os.TempDir(), "excelize-")
|
f.sharedStringTemp, _ = os.CreateTemp(os.TempDir(), "excelize-")
|
||||||
|
|
109
rows_test.go
109
rows_test.go
|
@ -416,6 +416,23 @@ func TestInsertRowsInEmptyFile(t *testing.T) {
|
||||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx")))
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRowInEmptyFile.xlsx")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareTestBook2() (*File, error) {
|
||||||
|
f := NewFile()
|
||||||
|
for cell, val := range map[string]string{
|
||||||
|
"A1": "A1 Value",
|
||||||
|
"A2": "A2 Value",
|
||||||
|
"A3": "A3 Value",
|
||||||
|
"B1": "B1 Value",
|
||||||
|
"B2": "B2 Value",
|
||||||
|
"B3": "B3 Value",
|
||||||
|
} {
|
||||||
|
if err := f.SetCellStr("Sheet1", cell, val); err != nil {
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestDuplicateRowFromSingleRow(t *testing.T) {
|
func TestDuplicateRowFromSingleRow(t *testing.T) {
|
||||||
const sheet = "Sheet1"
|
const sheet = "Sheet1"
|
||||||
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
||||||
|
@ -512,7 +529,6 @@ func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) {
|
||||||
func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
|
func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
|
||||||
const sheet = "Sheet1"
|
const sheet = "Sheet1"
|
||||||
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
||||||
|
|
||||||
cells := map[string]string{
|
cells := map[string]string{
|
||||||
"A1": "A1 Value",
|
"A1": "A1 Value",
|
||||||
"A2": "A2 Value",
|
"A2": "A2 Value",
|
||||||
|
@ -521,18 +537,9 @@ func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
|
||||||
"B2": "B2 Value",
|
"B2": "B2 Value",
|
||||||
"B3": "B3 Value",
|
"B3": "B3 Value",
|
||||||
}
|
}
|
||||||
|
|
||||||
newFileWithDefaults := func() *File {
|
|
||||||
f := NewFile()
|
|
||||||
for cell, val := range cells {
|
|
||||||
assert.NoError(t, f.SetCellStr(sheet, cell, val))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("FirstOfMultipleRows", func(t *testing.T) {
|
t.Run("FirstOfMultipleRows", func(t *testing.T) {
|
||||||
f := newFileWithDefaults()
|
f, err := prepareTestBook2()
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, f.DuplicateRow(sheet, 1))
|
assert.NoError(t, f.DuplicateRow(sheet, 1))
|
||||||
|
|
||||||
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FirstOfMultipleRows"))) {
|
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FirstOfMultipleRows"))) {
|
||||||
|
@ -635,18 +642,9 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
|
||||||
"B2": "B2 Value",
|
"B2": "B2 Value",
|
||||||
"B3": "B3 Value",
|
"B3": "B3 Value",
|
||||||
}
|
}
|
||||||
|
|
||||||
newFileWithDefaults := func() *File {
|
|
||||||
f := NewFile()
|
|
||||||
for cell, val := range cells {
|
|
||||||
assert.NoError(t, f.SetCellStr(sheet, cell, val))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("WithLargeOffsetToMiddleOfData", func(t *testing.T) {
|
t.Run("WithLargeOffsetToMiddleOfData", func(t *testing.T) {
|
||||||
f := newFileWithDefaults()
|
f, err := prepareTestBook2()
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 3))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 3))
|
||||||
|
|
||||||
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToMiddleOfData"))) {
|
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToMiddleOfData"))) {
|
||||||
|
@ -671,7 +669,6 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
|
||||||
func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
|
func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
|
||||||
const sheet = "Sheet1"
|
const sheet = "Sheet1"
|
||||||
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
||||||
|
|
||||||
cells := map[string]string{
|
cells := map[string]string{
|
||||||
"A1": "A1 Value",
|
"A1": "A1 Value",
|
||||||
"A2": "A2 Value",
|
"A2": "A2 Value",
|
||||||
|
@ -680,18 +677,9 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
|
||||||
"B2": "B2 Value",
|
"B2": "B2 Value",
|
||||||
"B3": "B3 Value",
|
"B3": "B3 Value",
|
||||||
}
|
}
|
||||||
|
|
||||||
newFileWithDefaults := func() *File {
|
|
||||||
f := NewFile()
|
|
||||||
for cell, val := range cells {
|
|
||||||
assert.NoError(t, f.SetCellStr(sheet, cell, val))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) {
|
t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) {
|
||||||
f := newFileWithDefaults()
|
f, err := prepareTestBook2()
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 7))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 7))
|
||||||
|
|
||||||
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToEmptyRows"))) {
|
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToEmptyRows"))) {
|
||||||
|
@ -716,7 +704,6 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
|
||||||
func TestDuplicateRowInsertBefore(t *testing.T) {
|
func TestDuplicateRowInsertBefore(t *testing.T) {
|
||||||
const sheet = "Sheet1"
|
const sheet = "Sheet1"
|
||||||
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
||||||
|
|
||||||
cells := map[string]string{
|
cells := map[string]string{
|
||||||
"A1": "A1 Value",
|
"A1": "A1 Value",
|
||||||
"A2": "A2 Value",
|
"A2": "A2 Value",
|
||||||
|
@ -725,18 +712,9 @@ func TestDuplicateRowInsertBefore(t *testing.T) {
|
||||||
"B2": "B2 Value",
|
"B2": "B2 Value",
|
||||||
"B3": "B3 Value",
|
"B3": "B3 Value",
|
||||||
}
|
}
|
||||||
|
|
||||||
newFileWithDefaults := func() *File {
|
|
||||||
f := NewFile()
|
|
||||||
for cell, val := range cells {
|
|
||||||
assert.NoError(t, f.SetCellStr(sheet, cell, val))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("InsertBefore", func(t *testing.T) {
|
t.Run("InsertBefore", func(t *testing.T) {
|
||||||
f := newFileWithDefaults()
|
f, err := prepareTestBook2()
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 10, 4))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 10, 4))
|
||||||
|
|
||||||
|
@ -763,7 +741,6 @@ func TestDuplicateRowInsertBefore(t *testing.T) {
|
||||||
func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
|
func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
|
||||||
const sheet = "Sheet1"
|
const sheet = "Sheet1"
|
||||||
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
||||||
|
|
||||||
cells := map[string]string{
|
cells := map[string]string{
|
||||||
"A1": "A1 Value",
|
"A1": "A1 Value",
|
||||||
"A2": "A2 Value",
|
"A2": "A2 Value",
|
||||||
|
@ -772,18 +749,9 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
|
||||||
"B2": "B2 Value",
|
"B2": "B2 Value",
|
||||||
"B3": "B3 Value",
|
"B3": "B3 Value",
|
||||||
}
|
}
|
||||||
|
|
||||||
newFileWithDefaults := func() *File {
|
|
||||||
f := NewFile()
|
|
||||||
for cell, val := range cells {
|
|
||||||
assert.NoError(t, f.SetCellStr(sheet, cell, val))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
|
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
|
||||||
f := newFileWithDefaults()
|
f, err := prepareTestBook2()
|
||||||
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 3, 1))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 3, 1))
|
||||||
|
|
||||||
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithLargeOffset"))) {
|
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithLargeOffset"))) {
|
||||||
|
@ -809,28 +777,11 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
|
||||||
func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
|
func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
|
||||||
const sheet = "Sheet1"
|
const sheet = "Sheet1"
|
||||||
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
|
||||||
|
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
|
||||||
cells := map[string]string{
|
f, err := prepareTestBook2()
|
||||||
"A1": "A1 Value",
|
assert.NoError(t, err)
|
||||||
"A2": "A2 Value",
|
|
||||||
"A3": "A3 Value",
|
|
||||||
"B1": "B1 Value",
|
|
||||||
"B2": "B2 Value",
|
|
||||||
"B3": "B3 Value",
|
|
||||||
}
|
|
||||||
|
|
||||||
newFileWithDefaults := func() *File {
|
|
||||||
f := NewFile()
|
|
||||||
for cell, val := range cells {
|
|
||||||
assert.NoError(t, f.SetCellStr(sheet, cell, val))
|
|
||||||
}
|
|
||||||
assert.NoError(t, f.MergeCell(sheet, "B2", "C2"))
|
assert.NoError(t, f.MergeCell(sheet, "B2", "C2"))
|
||||||
assert.NoError(t, f.MergeCell(sheet, "C6", "C8"))
|
assert.NoError(t, f.MergeCell(sheet, "C6", "C8"))
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
|
|
||||||
f := newFileWithDefaults()
|
|
||||||
|
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 2, 1))
|
||||||
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 8))
|
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 8))
|
||||||
|
|
|
@ -1309,7 +1309,7 @@ func newNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
|
||||||
if !ok {
|
if !ok {
|
||||||
fc, currency := currencyNumFmt[style.NumFmt]
|
fc, currency := currencyNumFmt[style.NumFmt]
|
||||||
if !currency {
|
if !currency {
|
||||||
return setLangNumFmt(styleSheet, style)
|
return setLangNumFmt(style)
|
||||||
}
|
}
|
||||||
fc = strings.ReplaceAll(fc, "0.00", dp)
|
fc = strings.ReplaceAll(fc, "0.00", dp)
|
||||||
if style.NegRed {
|
if style.NegRed {
|
||||||
|
@ -1375,7 +1375,7 @@ func getCustomNumFmtID(styleSheet *xlsxStyleSheet, style *Style) (customNumFmtID
|
||||||
}
|
}
|
||||||
|
|
||||||
// setLangNumFmt provides a function to set number format code with language.
|
// setLangNumFmt provides a function to set number format code with language.
|
||||||
func setLangNumFmt(styleSheet *xlsxStyleSheet, style *Style) int {
|
func setLangNumFmt(style *Style) int {
|
||||||
if (27 <= style.NumFmt && style.NumFmt <= 36) || (50 <= style.NumFmt && style.NumFmt <= 81) {
|
if (27 <= style.NumFmt && style.NumFmt <= 36) || (50 <= style.NumFmt && style.NumFmt <= 81) {
|
||||||
return style.NumFmt
|
return style.NumFmt
|
||||||
}
|
}
|
||||||
|
@ -1585,7 +1585,7 @@ func newBorders(style *Style) *xlsxBorder {
|
||||||
return &border
|
return &border
|
||||||
}
|
}
|
||||||
|
|
||||||
// setCellXfs provides a function to set describes all of the formatting for a
|
// setCellXfs provides a function to set describes all the formatting for a
|
||||||
// cell.
|
// cell.
|
||||||
func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) {
|
func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) {
|
||||||
var xf xlsxXf
|
var xf xlsxXf
|
||||||
|
@ -2451,7 +2451,7 @@ func extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatO
|
||||||
if ext.URI == ExtURIConditionalFormattings {
|
if ext.URI == ExtURIConditionalFormattings {
|
||||||
decodeCondFmts := new(decodeX14ConditionalFormattings)
|
decodeCondFmts := new(decodeX14ConditionalFormattings)
|
||||||
if err := xml.Unmarshal([]byte(ext.Content), &decodeCondFmts); err == nil {
|
if err := xml.Unmarshal([]byte(ext.Content), &decodeCondFmts); err == nil {
|
||||||
condFmts := []decodeX14ConditionalFormatting{}
|
var condFmts []decodeX14ConditionalFormatting
|
||||||
if err = xml.Unmarshal([]byte(decodeCondFmts.Content), &condFmts); err == nil {
|
if err = xml.Unmarshal([]byte(decodeCondFmts.Content), &condFmts); err == nil {
|
||||||
extractDataBarRule(condFmts)
|
extractDataBarRule(condFmts)
|
||||||
}
|
}
|
||||||
|
|
4
table.go
4
table.go
|
@ -320,7 +320,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
|
||||||
// x == *b // ends with b
|
// x == *b // ends with b
|
||||||
// x != *b // doesn't end with b
|
// x != *b // doesn't end with b
|
||||||
// x == *b* // contains b
|
// x == *b* // contains b
|
||||||
// x != *b* // doesn't contains b
|
// x != *b* // doesn't contain b
|
||||||
//
|
//
|
||||||
// You can also use '*' to match any character or number and '?' to match any
|
// You can also use '*' to match any character or number and '?' to match any
|
||||||
// single character or number. No other regular expression quantifier is
|
// single character or number. No other regular expression quantifier is
|
||||||
|
@ -538,7 +538,7 @@ func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, str
|
||||||
}
|
}
|
||||||
token := tokens[2]
|
token := tokens[2]
|
||||||
// Special handling for Blanks/NonBlanks.
|
// Special handling for Blanks/NonBlanks.
|
||||||
re := blankFormat.MatchString((strings.ToLower(token)))
|
re := blankFormat.MatchString(strings.ToLower(token))
|
||||||
if re {
|
if re {
|
||||||
// Only allow Equals or NotEqual in this context.
|
// Only allow Equals or NotEqual in this context.
|
||||||
if operator != 2 && operator != 5 {
|
if operator != 2 && operator != 5 {
|
||||||
|
|
|
@ -199,7 +199,7 @@ type decodeSpPr struct {
|
||||||
// decodePic elements encompass the definition of pictures within the
|
// decodePic elements encompass the definition of pictures within the
|
||||||
// DrawingML framework. While pictures are in many ways very similar to shapes
|
// DrawingML framework. While pictures are in many ways very similar to shapes
|
||||||
// they have specific properties that are unique in order to optimize for
|
// they have specific properties that are unique in order to optimize for
|
||||||
// picture- specific scenarios.
|
// picture-specific scenarios.
|
||||||
type decodePic struct {
|
type decodePic struct {
|
||||||
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
|
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
|
||||||
BlipFill decodeBlipFill `xml:"blipFill"`
|
BlipFill decodeBlipFill `xml:"blipFill"`
|
||||||
|
|
16
xmlStyles.go
16
xmlStyles.go
|
@ -65,10 +65,10 @@ type xlsxLine struct {
|
||||||
|
|
||||||
// xlsxColor is a common mapping used for both the fgColor and bgColor elements.
|
// xlsxColor is a common mapping used for both the fgColor and bgColor elements.
|
||||||
// Foreground color of the cell fill pattern. Cell fill patterns operate with
|
// Foreground color of the cell fill pattern. Cell fill patterns operate with
|
||||||
// two colors: a background color and a foreground color. These combine together
|
// two colors: a background color and a foreground color. These combine
|
||||||
// to make a patterned cell fill. Background color of the cell fill pattern.
|
// to make a patterned cell fill. Background color of the cell fill pattern.
|
||||||
// Cell fill patterns operate with two colors: a background color and a
|
// Cell fill patterns operate with two colors: a background color and a
|
||||||
// foreground color. These combine together to make a patterned cell fill.
|
// foreground color. These combine to make a patterned cell fill.
|
||||||
type xlsxColor struct {
|
type xlsxColor struct {
|
||||||
Auto bool `xml:"auto,attr,omitempty"`
|
Auto bool `xml:"auto,attr,omitempty"`
|
||||||
RGB string `xml:"rgb,attr,omitempty"`
|
RGB string `xml:"rgb,attr,omitempty"`
|
||||||
|
@ -103,7 +103,7 @@ type xlsxFont struct {
|
||||||
Scheme *attrValString `xml:"scheme"`
|
Scheme *attrValString `xml:"scheme"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxFills directly maps the fills element. This element defines the cell
|
// xlsxFills directly maps the fills' element. This element defines the cell
|
||||||
// fills portion of the Styles part, consisting of a sequence of fill records. A
|
// fills portion of the Styles part, consisting of a sequence of fill records. A
|
||||||
// cell fill consists of a background color, foreground color, and pattern to be
|
// cell fill consists of a background color, foreground color, and pattern to be
|
||||||
// applied across the cell.
|
// applied across the cell.
|
||||||
|
@ -147,7 +147,7 @@ type xlsxGradientFillStop struct {
|
||||||
Color xlsxColor `xml:"color,omitempty"`
|
Color xlsxColor `xml:"color,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxBorders directly maps the borders element. This element contains borders
|
// xlsxBorders directly maps the borders' element. This element contains borders
|
||||||
// formatting information, specifying all border definitions for all cells in
|
// formatting information, specifying all border definitions for all cells in
|
||||||
// the workbook.
|
// the workbook.
|
||||||
type xlsxBorders struct {
|
type xlsxBorders struct {
|
||||||
|
@ -205,7 +205,7 @@ type xlsxCellStyleXfs struct {
|
||||||
Xf []xlsxXf `xml:"xf,omitempty"`
|
Xf []xlsxXf `xml:"xf,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxXf directly maps the xf element. A single xf element describes all of the
|
// xlsxXf directly maps the xf element. A single xf element describes all the
|
||||||
// formatting for a cell.
|
// formatting for a cell.
|
||||||
type xlsxXf struct {
|
type xlsxXf struct {
|
||||||
NumFmtID *int `xml:"numFmtId,attr"`
|
NumFmtID *int `xml:"numFmtId,attr"`
|
||||||
|
@ -236,8 +236,8 @@ type xlsxCellXfs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxDxfs directly maps the dxfs element. This element contains the master
|
// xlsxDxfs directly maps the dxfs element. This element contains the master
|
||||||
// differential formatting records (dxf's) which define formatting for all non-
|
// differential formatting records (dxf's) which define formatting for all
|
||||||
// cell formatting in this workbook. Whereas xf records fully specify a
|
// non-cell formatting in this workbook. Whereas xf records fully specify a
|
||||||
// particular aspect of formatting (e.g., cell borders) by referencing those
|
// particular aspect of formatting (e.g., cell borders) by referencing those
|
||||||
// formatting definitions elsewhere in the Styles part, dxf records specify
|
// formatting definitions elsewhere in the Styles part, dxf records specify
|
||||||
// incremental (or differential) aspects of formatting directly inline within
|
// incremental (or differential) aspects of formatting directly inline within
|
||||||
|
@ -304,7 +304,7 @@ type xlsxNumFmt struct {
|
||||||
FormatCode string `xml:"formatCode,attr,omitempty"`
|
FormatCode string `xml:"formatCode,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// xlsxStyleColors directly maps the colors element. Color information
|
// xlsxStyleColors directly maps the colors' element. Color information
|
||||||
// associated with this stylesheet. This collection is written whenever the
|
// associated with this stylesheet. This collection is written whenever the
|
||||||
// legacy color palette has been modified (backwards compatibility settings) or
|
// legacy color palette has been modified (backwards compatibility settings) or
|
||||||
// a custom color has been selected while using this workbook.
|
// a custom color has been selected while using this workbook.
|
||||||
|
|
|
@ -212,7 +212,7 @@ type xlsxPivotCache struct {
|
||||||
// document are specified in the markup specification and can be used to store
|
// document are specified in the markup specification and can be used to store
|
||||||
// extensions to the markup specification, whether those are future version
|
// extensions to the markup specification, whether those are future version
|
||||||
// extensions of the markup specification or are private extensions implemented
|
// extensions of the markup specification or are private extensions implemented
|
||||||
// independently from the markup specification. Markup within an extension might
|
// independently of the markup specification. Markup within an extension might
|
||||||
// not be understood by a consumer.
|
// not be understood by a consumer.
|
||||||
type xlsxExtLst struct {
|
type xlsxExtLst struct {
|
||||||
Ext string `xml:",innerxml"`
|
Ext string `xml:",innerxml"`
|
||||||
|
@ -229,7 +229,7 @@ type xlsxDefinedNames struct {
|
||||||
// xlsxDefinedName directly maps the definedName element from the namespace
|
// xlsxDefinedName directly maps the definedName element from the namespace
|
||||||
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
|
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
|
||||||
// defines a defined name within this workbook. A defined name is descriptive
|
// defines a defined name within this workbook. A defined name is descriptive
|
||||||
// text that is used to represents a cell, range of cells, formula, or constant
|
// text that is used to represent a cell, range of cells, formula, or constant
|
||||||
// value. For a descriptions of the attributes see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname
|
// value. For a descriptions of the attributes see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname
|
||||||
type xlsxDefinedName struct {
|
type xlsxDefinedName struct {
|
||||||
Comment string `xml:"comment,attr,omitempty"`
|
Comment string `xml:"comment,attr,omitempty"`
|
||||||
|
|
|
@ -427,7 +427,7 @@ type xlsxDataValidations struct {
|
||||||
DataValidation []*DataValidation `xml:"dataValidation"`
|
DataValidation []*DataValidation `xml:"dataValidation"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataValidation directly maps the a single item of data validation defined
|
// DataValidation directly maps the single item of data validation defined
|
||||||
// on a range of the worksheet.
|
// on a range of the worksheet.
|
||||||
type DataValidation struct {
|
type DataValidation struct {
|
||||||
AllowBlank bool `xml:"allowBlank,attr"`
|
AllowBlank bool `xml:"allowBlank,attr"`
|
||||||
|
|
Loading…
Reference in New Issue