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:
xuri 2023-05-30 00:14:44 +08:00
parent e3fb2d7bad
commit 121ac17ca0
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
16 changed files with 214 additions and 322 deletions

323
calc.go
View File

@ -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.

View File

@ -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!"},

View File

@ -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)

View File

@ -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")

View File

@ -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
View File

@ -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 {

View File

@ -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:

View File

@ -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)
} }

View File

@ -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-")

View File

@ -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))

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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.

View File

@ -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"`

View File

@ -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"`