// BESSELJ
// BESSELK
// BESSELY
-// BETADIST
// BETA.DIST
-// BETAINV
// BETA.INV
+// BETADIST
+// BETAINV
// BIN2DEC
// BIN2HEX
// BIN2OCT
-// BINOMDIST
// BINOM.DIST
// BINOM.DIST.RANGE
// BINOM.INV
+// BINOMDIST
// BITAND
// BITLSHIFT
// BITOR
// CHAR
// CHIDIST
// CHIINV
-// CHITEST
// CHISQ.DIST
// CHISQ.DIST.RT
// CHISQ.INV
// CHISQ.INV.RT
// CHISQ.TEST
+// CHITEST
// CHOOSE
// CLEAN
// CODE
// DURATION
// DVAR
// DVARP
-// EFFECT
// EDATE
+// EFFECT
// ENCODEURL
// EOMONTH
// ERF
// EXP
// EXPON.DIST
// EXPONDIST
+// F.DIST
+// F.DIST.RT
+// F.INV
+// F.INV.RT
+// F.TEST
// FACT
// FACTDOUBLE
// FALSE
-// F.DIST
-// F.DIST.RT
// FDIST
// FIND
// FINDB
-// F.INV
-// F.INV.RT
// FINV
// FISHER
// FISHERINV
// FLOOR.MATH
// FLOOR.PRECISE
// FORMULATEXT
-// F.TEST
// FTEST
// FV
// FVSCHEDULE
// GAMMA
// GAMMA.DIST
-// GAMMADIST
// GAMMA.INV
+// GAMMADIST
// GAMMAINV
// GAMMALN
// GAMMALN.PRECISE
// ISNA
// ISNONTEXT
// ISNUMBER
-// ISODD
-// ISREF
-// ISTEXT
// ISO.CEILING
+// ISODD
// ISOWEEKNUM
// ISPMT
+// ISREF
+// ISTEXT
// KURT
// LARGE
// LCM
// LOG10
// LOGINV
// LOGNORM.DIST
-// LOGNORMDIST
// LOGNORM.INV
+// LOGNORMDIST
// LOOKUP
// LOWER
// MATCH
// NETWORKDAYS.INTL
// NOMINAL
// NORM.DIST
-// NORMDIST
// NORM.INV
-// NORMINV
// NORM.S.DIST
-// NORMSDIST
// NORM.S.INV
+// NORMDIST
+// NORMINV
+// NORMSDIST
// NORMSINV
// NOT
// NOW
// OR
// PDURATION
// PEARSON
+// PERCENTILE
// PERCENTILE.EXC
// PERCENTILE.INC
-// PERCENTILE
+// PERCENTRANK
// PERCENTRANK.EXC
// PERCENTRANK.INC
-// PERCENTRANK
// PERMUT
// PERMUTATIONA
// PHI
// PI
// PMT
-// POISSON.DIST
// POISSON
+// POISSON.DIST
// POWER
// PPMT
// PRICE
// SWITCH
// SYD
// T
+// T.DIST
+// T.DIST.2T
+// T.DIST.RT
+// T.INV
+// T.INV.2T
+// T.TEST
// TAN
// TANH
// TBILLEQ
// TBILLPRICE
// TBILLYIELD
-// T.DIST
-// T.DIST.2T
-// T.DIST.RT
// TDIST
// TEXTJOIN
// TIME
// TIMEVALUE
-// T.INV
-// T.INV.2T
// TINV
// TODAY
// TRANSPOSE
// TRIMMEAN
// TRUE
// TRUNC
-// T.TEST
// TTEST
// TYPE
// UNICHAR
return newNumberFormulaArg(float64(col))
}
-// calcColumnsMinMax calculation min and max value for given formula arguments
-// sequence of the formula function COLUMNS.
-func calcColumnsMinMax(argsList *list.List) (min, max int) {
+// calcColsRowsMinMax calculation min and max value for given formula arguments
+// sequence of the formula functions COLUMNS and ROWS.
+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 {
crs := argsList.Front().Value.(formulaArg).cellRanges
for cr := crs.Front(); cr != nil; cr = cr.Next() {
if min == 0 {
- min = cr.Value.(cellRange).From.Col
+ min = getVal(cols, cr.Value.(cellRange).From)
}
- if max < cr.Value.(cellRange).To.Col {
- max = cr.Value.(cellRange).To.Col
+ if max < getVal(cols, cr.Value.(cellRange).To) {
+ max = getVal(cols, cr.Value.(cellRange).To)
}
}
}
cr := argsList.Front().Value.(formulaArg).cellRefs
for refs := cr.Front(); refs != nil; refs = refs.Next() {
if min == 0 {
- min = refs.Value.(cellRef).Col
+ min = getVal(cols, refs.Value.(cellRef))
}
- if max < refs.Value.(cellRef).Col {
- max = refs.Value.(cellRef).Col
+ if max < getVal(cols, refs.Value.(cellRef)) {
+ max = getVal(cols, refs.Value.(cellRef))
}
}
}
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "COLUMNS requires 1 argument")
}
- min, max := calcColumnsMinMax(argsList)
+ min, max := calcColsRowsMinMax(true, argsList)
if max == MaxColumns {
return newNumberFormulaArg(float64(MaxColumns))
}
return newErrorFormulaArg(formulaErrorVALUE, "TRANSPOSE requires 1 argument")
}
args := argsList.Back().Value.(formulaArg).ToList()
- rmin, rmax := calcRowsMinMax(argsList)
- cmin, cmax := calcColumnsMinMax(argsList)
+ rmin, rmax := calcColsRowsMinMax(false, argsList)
+ cmin, cmax := calcColsRowsMinMax(true, argsList)
cols, rows := cmax-cmin+1, rmax-rmin+1
src := make([][]formulaArg, 0)
for i := 0; i < len(args); i += cols {
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
// contained within the range. The syntax of the function is:
//
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "ROWS requires 1 argument")
}
- min, max := calcRowsMinMax(argsList)
+ min, max := calcColsRowsMinMax(false, argsList)
if max == TotalRows {
return newStringFormulaArg(strconv.Itoa(TotalRows))
}
return newListFormulaArg(dataValues)
}
-// 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 {
+// discIntrate is an implementation of the formula functions DISC and INTRATE.
+func (fn *formulaFuncs) discIntrate(name string, argsList *list.List) formulaArg {
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)
if args.Type != ArgList {
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 {
- 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()
- if pr.Type != ArgNumber {
+ prInvestment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
+ if prInvestment.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
- if pr.Number <= 0 {
- return newErrorFormulaArg(formulaErrorNUM, "DISC requires pr > 0")
+ if prInvestment.Number <= 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()
if redemption.Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
if redemption.Number <= 0 {
- return newErrorFormulaArg(formulaErrorNUM, "DISC requires redemption > 0")
+ return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires redemption > 0", name))
}
basis := newNumberFormulaArg(0)
if argsList.Len() == 5 {
if frac.Type != ArgNumber {
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
//
// INTRATE(settlement,maturity,investment,redemption,[basis])
func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg {
- if argsList.Len() != 4 && argsList.Len() != 5 {
- 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)
+ return fn.discIntrate("INTRATE", argsList)
}
// IPMT function calculates the interest payment, during a specific period of a
return newNumberFormulaArg(ret)
}
-// 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 {
- if argsList.Len() != 6 && argsList.Len() != 7 {
- return newErrorFormulaArg(formulaErrorVALUE, "PRICE 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()
+// checkPriceYieldArgs checking and prepare arguments for the formula functions
+// PRICE and YIELD.
+func checkPriceYieldArgs(name string, rate, prYld, redemption, frequency formulaArg) formulaArg {
if rate.Type != ArgNumber {
return rate
}
if rate.Number < 0 {
- return newErrorFormulaArg(formulaErrorNUM, "PRICE requires rate >= 0")
+ return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s requires rate >= 0", name))
}
- 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")
+ if prYld.Type != ArgNumber {
+ return prYld
}
- 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")
+ 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")
+ }
}
- 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)
}
+ 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 {
+ return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 6 or 7 arguments", name))
+ }
+ 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()
+ prYld := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber()
+ redemption := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
+ frequency := argsList.Front().Next().Next().Next().Next().Next().Value.(formulaArg).ToNumber()
+ if arg := checkPriceYieldArgs(name, rate, prYld, redemption, frequency); arg.Type != ArgEmpty {
+ return arg
+ }
basis := newNumberFormulaArg(0)
if argsList.Len() == 7 {
if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber {
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
//
// YIELD(settlement,maturity,rate,pr,redemption,frequency,[basis])
func (fn *formulaFuncs) YIELD(argsList *list.List) formulaArg {
- if argsList.Len() != 6 && argsList.Len() != 7 {
- 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)
+ return fn.priceYield("YIELD", argsList)
}
// YIELDDISC function calculates the annual yield of a discounted security.
"=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,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\",10%,0,100,4)": {"#NUM!", "PRICE 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\",-1,101,100,4)": {"#NUM!", "YIELD requires rate >= 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!", "YIELD requires redemption >= 0"},
// YIELDDISC
"=YIELDDISC()": {"#VALUE!", "YIELDDISC requires 4 or 5 arguments"},
"=YIELDDISC(\"\",\"06/30/2017\",97,100,0)": {"#VALUE!", "#VALUE!"},
// "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
// 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.
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569
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) {
if excelDate < 0 {
return time.Time{}, newInvalidExcelDateError(excelDate)
ErrWorkbookFileFormat = errors.New("unsupported workbook file format")
// ErrMaxFilePathLength defined the error message on receive the file path
// 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
// encryption mechanism.
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
// the spreadsheet from non-UTF-8 encoding.
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
// calculation, the default value is 0.
// RawCellValue specifies if apply the number format for the cell value or get
// 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
// UnzipXMLSizeLimit, the default size limit is 16GB.
//
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
// password protection:
//
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) {
fR := float64(r) / 255
fG := float64(g) / 255
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) {
var fR, fG, fB float64
if s == 0 {
return len(parts[0]), 0
}
-// getNumberFmtConf generate the number format padding and place holder
+// getNumberFmtConf generate the number format padding and placeholder
// configurations.
func (nf *numberFormat) getNumberFmtConf() {
for _, token := range nf.section[nf.sectionIdx].Items {
if nf.usePositive {
result += "-"
}
- for i, token := range nf.section[nf.sectionIdx].Items {
+ for _, token := range nf.section[nf.sectionIdx].Items {
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
}
result += nf.currencyString
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
for i, token := range nf.section[nf.sectionIdx].Items {
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
}
nf.result += nf.currencyString
// currencyLanguageHandler will be handling currency and language types tokens
// 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 {
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
return ErrUnsupportedNumberFormat, false
// localMonthsNameIrish returns the Irish name of the month.
func localMonthsNameIrish(t time.Time, abbr int) string {
if abbr == 3 {
- return monthNamesIrishAbbr[int(t.Month()-1)]
+ return monthNamesIrishAbbr[(t.Month() - 1)]
}
if abbr == 4 {
return monthNamesIrish[int(t.Month())-1]
// localMonthsNameChinese1 returns the Chinese name of the month.
func localMonthsNameChinese1(t time.Time, abbr int) string {
if abbr == 3 {
- return monthNamesChineseAbbrPlus[int(t.Month())]
+ return monthNamesChineseAbbrPlus[t.Month()]
}
if abbr == 4 {
return monthNamesChinesePlus[int(t.Month())-1]
// localMonthsNameChinese3 returns the Chinese name of the month.
func localMonthsNameChinese3(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
- return monthNamesChineseAbbrPlus[int(t.Month())]
+ return monthNamesChineseAbbrPlus[t.Month()]
}
return strconv.Itoa(int(t.Month()))
}
// localMonthsNameKorean returns the Korean name of the month.
func localMonthsNameKorean(t time.Time, abbr int) string {
if abbr == 3 || abbr == 4 {
- return monthNamesKoreanAbbrPlus[int(t.Month())]
+ return monthNamesKoreanAbbrPlus[t.Month()]
}
return strconv.Itoa(int(t.Month()))
}
if abbr == 5 {
return "M"
}
- return monthNamesTradMongolian[int(t.Month()-1)]
+ return monthNamesTradMongolian[t.Month()-1]
}
// localMonthsNameRussian returns the Russian name of the month.
// localMonthsNameVietnamese returns the Vietnamese name of the month.
func localMonthsNameVietnamese(t time.Time, abbr int) string {
if abbr == 3 {
- return monthNamesVietnameseAbbr3[int(t.Month()-1)]
+ return monthNamesVietnameseAbbr3[t.Month()-1]
}
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.
// localMonthsNameYi returns the Yi name of the month.
func localMonthsNameYi(t time.Time, abbr int) string {
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])
}
// localMonthsNameZulu returns the Zulu name of the month.
func localMonthsNameZulu(t time.Time, abbr int) string {
if abbr == 3 {
- return monthNamesZuluAbbr[int(t.Month()-1)]
+ return monthNamesZuluAbbr[t.Month()-1]
}
if abbr == 4 {
return monthNamesZulu[int(t.Month())-1]
return
}
}
- nf.yearsHandler(i, token)
- nf.daysHandler(i, token)
+ nf.yearsHandler(token)
+ nf.daysHandler(token)
nf.hoursHandler(i, token)
nf.minutesHandler(token)
nf.secondsHandler(token)
// yearsHandler will be handling years in the date and times types tokens for a
// 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")
if years && len(token.TValue) <= 2 {
nf.result += strconv.Itoa(nf.t.Year())[2:]
// daysHandler will be handling days in the date and times types tokens for a
// 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") {
switch len(token.TValue) {
case 1:
}
}
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.False(t, changeNumFmtCode)
}
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 {
rows.seekRow++
if rows.curRow >= rows.seekRow {
}
needClose, decoder, tempFile, err := f.xmlDecoder(defaultXMLPathSharedStrings)
if needClose && err == nil {
- defer tempFile.Close()
+ defer func() {
+ err = tempFile.Close()
+ }()
}
f.sharedStringItem = [][]uint{}
f.sharedStringTemp, _ = os.CreateTemp(os.TempDir(), "excelize-")
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) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
-
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 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))
- }
- return f
- }
-
t.Run("FirstOfMultipleRows", func(t *testing.T) {
- f := newFileWithDefaults()
-
+ f, err := prepareTestBook2()
+ assert.NoError(t, err)
assert.NoError(t, f.DuplicateRow(sheet, 1))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "FirstOfMultipleRows"))) {
"B2": "B2 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) {
- f := newFileWithDefaults()
-
+ f, err := prepareTestBook2()
+ assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 3))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToMiddleOfData"))) {
func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
-
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 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))
- }
- return f
- }
-
t.Run("WithLargeOffsetToEmptyRows", func(t *testing.T) {
- f := newFileWithDefaults()
-
+ f, err := prepareTestBook2()
+ assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 1, 7))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "WithLargeOffsetToEmptyRows"))) {
func TestDuplicateRowInsertBefore(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
-
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 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))
- }
- return f
- }
-
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, 10, 4))
func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
-
cells := map[string]string{
"A1": "A1 Value",
"A2": "A2 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))
- }
- return f
- }
-
t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
- f := newFileWithDefaults()
-
+ f, err := prepareTestBook2()
+ assert.NoError(t, err)
assert.NoError(t, f.DuplicateRowTo(sheet, 3, 1))
if !assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, "InsertBeforeWithLargeOffset"))) {
func TestDuplicateRowInsertBeforeWithMergeCells(t *testing.T) {
const sheet = "Sheet1"
outFile := filepath.Join("test", "TestDuplicateRow.%s.xlsx")
-
- cells := map[string]string{
- "A1": "A1 Value",
- "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))
- }
+ t.Run("InsertBeforeWithLargeOffset", func(t *testing.T) {
+ f, err := prepareTestBook2()
+ assert.NoError(t, err)
assert.NoError(t, f.MergeCell(sheet, "B2", "C2"))
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, 1, 8))
if !ok {
fc, currency := currencyNumFmt[style.NumFmt]
if !currency {
- return setLangNumFmt(styleSheet, style)
+ return setLangNumFmt(style)
}
fc = strings.ReplaceAll(fc, "0.00", dp)
if style.NegRed {
}
// 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) {
return style.NumFmt
}
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.
func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) {
var xf xlsxXf
if ext.URI == ExtURIConditionalFormattings {
decodeCondFmts := new(decodeX14ConditionalFormattings)
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 {
extractDataBarRule(condFmts)
}
// x == *b // ends with b
// x != *b // doesn't end with 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
// single character or number. No other regular expression quantifier is
}
token := tokens[2]
// Special handling for Blanks/NonBlanks.
- re := blankFormat.MatchString((strings.ToLower(token)))
+ re := blankFormat.MatchString(strings.ToLower(token))
if re {
// Only allow Equals or NotEqual in this context.
if operator != 2 && operator != 5 {
// decodePic elements encompass the definition of pictures within the
// DrawingML framework. While pictures are in many ways very similar to shapes
// they have specific properties that are unique in order to optimize for
-// picture- specific scenarios.
+// picture-specific scenarios.
type decodePic struct {
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
BlipFill decodeBlipFill `xml:"blipFill"`
// 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
-// 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.
// 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 {
Auto bool `xml:"auto,attr,omitempty"`
RGB string `xml:"rgb,attr,omitempty"`
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
// cell fill consists of a background color, foreground color, and pattern to be
// applied across the cell.
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
// the workbook.
type xlsxBorders struct {
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.
type xlsxXf struct {
NumFmtID *int `xml:"numFmtId,attr"`
}
// xlsxDxfs directly maps the dxfs element. This element contains the master
-// differential formatting records (dxf's) which define formatting for all non-
-// cell formatting in this workbook. Whereas xf records fully specify a
+// differential formatting records (dxf's) which define formatting for all
+// non-cell formatting in this workbook. Whereas xf records fully specify a
// particular aspect of formatting (e.g., cell borders) by referencing those
// formatting definitions elsewhere in the Styles part, dxf records specify
// incremental (or differential) aspects of formatting directly inline within
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
// legacy color palette has been modified (backwards compatibility settings) or
// a custom color has been selected while using this workbook.
// document are specified in the markup specification and can be used to store
// extensions to the markup specification, whether those are future version
// 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.
type xlsxExtLst struct {
Ext string `xml:",innerxml"`
// xlsxDefinedName directly maps the definedName element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
// 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
type xlsxDefinedName struct {
Comment string `xml:"comment,attr,omitempty"`
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.
type DataValidation struct {
AllowBlank bool `xml:"allowBlank,attr"`