Ref #660, support placeholder, padding and rounds numbers by specified number format code
- Remove built-in number formats functions - Update unit tests - Upgrade dependencies package
This commit is contained in:
parent
65fc25e7a6
commit
7c221cf295
4
cell.go
4
cell.go
|
@ -1365,8 +1365,8 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
|
|||
if wb != nil && wb.WorkbookPr != nil {
|
||||
date1904 = wb.WorkbookPr.Date1904
|
||||
}
|
||||
if ok := builtInNumFmtFunc[numFmtID]; ok != nil {
|
||||
return ok(c.V, builtInNumFmt[numFmtID], date1904, cellType), err
|
||||
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
|
||||
return format(c.V, fmtCode, date1904, cellType), err
|
||||
}
|
||||
if styleSheet.NumFmts == nil {
|
||||
return c.V, err
|
||||
|
|
|
@ -873,9 +873,7 @@ func TestFormattedValue(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, "311", result)
|
||||
|
||||
for _, fn := range builtInNumFmtFunc {
|
||||
assert.Equal(t, "0_0", fn("0_0", "", false, CellTypeNumber))
|
||||
}
|
||||
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber))
|
||||
|
||||
// Test format value with unsupported charset workbook
|
||||
f.WorkBook = nil
|
||||
|
@ -889,9 +887,7 @@ func TestFormattedValue(t *testing.T) {
|
|||
_, err = f.formattedValue(&xlsxC{S: 1, V: "43528"}, false, CellTypeNumber)
|
||||
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
|
||||
|
||||
for _, fn := range builtInNumFmtFunc {
|
||||
assert.Equal(t, fn("text", "0", false, CellTypeNumber), "text")
|
||||
}
|
||||
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber))
|
||||
}
|
||||
|
||||
func TestFormattedValueNilXfs(t *testing.T) {
|
||||
|
|
|
@ -747,33 +747,33 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
|
|||
|
||||
// Test only set fill and number format for a cell
|
||||
col := []string{"L", "M", "N", "O", "P"}
|
||||
data := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
|
||||
idxTbl := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}
|
||||
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
|
||||
expected := [][]string{
|
||||
{"37947.7500001", "37948", "37947.75", "37,948", "37947.75", "3794775%", "3794775.00%", "3.79E+04", "37947.7500001", "37947.7500001", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 pm", "6:00:00 pm", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37947.7500001", "37947.7500001", "37947.7500001", "37947.7500001", "00:00", "910746:00:00", "37947.7500001", "3.79E+04", "37947.7500001"},
|
||||
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-3.79E+04", "-37947.7500001"},
|
||||
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "0:10 am", "0:10:04 am", "00:10", "00:10:04", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0.007", "0.007", "0.007", "0.007", "10:04", "0:10:04", "0.007", "7.00E-03", "0.007"},
|
||||
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2.1", "2.1", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 am", "2:24:00 am", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2.1", "2.1", "2.1", "2.1", "24:00", "50:24:00", "2.1", "2.10E+00", "2.1"},
|
||||
{"37947.7500001", "37948", "37947.75", "37,948", "37,947.75", "3794775%", "3794775.00%", "3.79E+04", "37947.7500001", "37947.7500001", "11-22-03", "22-Nov-03", "22-Nov", "Nov-03", "6:00 pm", "6:00:00 pm", "18:00", "18:00:00", "11/22/03 18:00", "37,948 ", "37,948 ", "37,947.75 ", "37,947.75 ", "37947.7500001", "37947.7500001", "37947.7500001", "37947.7500001", "00:00", "910746:00:00", "0000.0", "37947.7500001", "37947.7500001"},
|
||||
{"-37947.7500001", "-37948", "-37947.75", "-37,948", "-37,947.75", "-3794775%", "-3794775.00%", "-3.79E+04", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "(37,948)", "(37,948)", "(37,947.75)", "(37,947.75)", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001", "-37947.7500001"},
|
||||
{"0.007", "0", "0.01", "0", "0.01", "1%", "0.70%", "7.00E-03", "0.007", "0.007", "12-30-99", "30-Dec-99", "30-Dec", "Dec-99", "0:10 am", "0:10:04 am", "00:10", "00:10:04", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0.007", "0.007", "0.007", "0.007", "10:04", "0:10:04", "1004.0", "0.007", "0.007"},
|
||||
{"2.1", "2", "2.10", "2", "2.10", "210%", "210.00%", "2.10E+00", "2.1", "2.1", "01-01-00", "1-Jan-00", "1-Jan", "Jan-00", "2:24 am", "2:24:00 am", "02:24", "02:24:00", "1/1/00 02:24", "2 ", "2 ", "2.10 ", "2.10 ", "2.1", "2.1", "2.1", "2.1", "24:00", "50:24:00", "2400.0", "2.1", "2.1"},
|
||||
{"String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String", "String"},
|
||||
}
|
||||
|
||||
for i, v := range value {
|
||||
for k, d := range data {
|
||||
c := col[i] + strconv.Itoa(k+1)
|
||||
for c, v := range value {
|
||||
for r, idx := range idxTbl {
|
||||
cell := col[c] + strconv.Itoa(r+1)
|
||||
var val float64
|
||||
val, err = strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
assert.NoError(t, f.SetCellValue("Sheet2", c, v))
|
||||
assert.NoError(t, f.SetCellValue("Sheet2", cell, v))
|
||||
} else {
|
||||
assert.NoError(t, f.SetCellValue("Sheet2", c, val))
|
||||
assert.NoError(t, f.SetCellValue("Sheet2", cell, val))
|
||||
}
|
||||
style, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 5}, NumFmt: d})
|
||||
style, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"FFFFFF", "E0EBF5"}, Shading: 5}, NumFmt: idx})
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.NoError(t, f.SetCellStyle("Sheet2", c, c, style))
|
||||
cellValue, err := f.GetCellValue("Sheet2", c)
|
||||
assert.Equal(t, expected[i][k], cellValue, fmt.Sprintf("Sheet2!%s value: %s, number format: %d", c, value[i], k))
|
||||
assert.NoError(t, f.SetCellStyle("Sheet2", cell, cell, style))
|
||||
cellValue, err := f.GetCellValue("Sheet2", cell)
|
||||
assert.Equal(t, expected[c][r], cellValue, fmt.Sprintf("Sheet2!%s value: %s, number format: %s c: %d r: %d", cell, value[c], builtInNumFmt[idx], c, r))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
@ -997,7 +997,7 @@ func TestConditionalFormat(t *testing.T) {
|
|||
f := NewFile()
|
||||
sheet1 := f.GetSheetName(0)
|
||||
|
||||
fillCells(f, sheet1, 10, 15)
|
||||
assert.NoError(t, fillCells(f, sheet1, 10, 15))
|
||||
|
||||
var format1, format2, format3, format4 int
|
||||
var err error
|
||||
|
@ -1612,15 +1612,16 @@ func prepareTestBook4() (*File, error) {
|
|||
return f, nil
|
||||
}
|
||||
|
||||
func fillCells(f *File, sheet string, colCount, rowCount int) {
|
||||
func fillCells(f *File, sheet string, colCount, rowCount int) error {
|
||||
for col := 1; col <= colCount; col++ {
|
||||
for row := 1; row <= rowCount; row++ {
|
||||
cell, _ := CoordinatesToCellName(col, row)
|
||||
if err := f.SetCellStr(sheet, cell, cell); err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func BenchmarkOpenFile(b *testing.B) {
|
||||
|
|
4
go.mod
4
go.mod
|
@ -6,8 +6,8 @@ require (
|
|||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/richardlehane/mscfb v1.0.4
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22
|
||||
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9
|
||||
github.com/xuri/nfp v0.0.0-20230428090735-b50b0f0358f4
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/image v0.5.0
|
||||
golang.org/x/net v0.9.0
|
||||
|
|
8
go.sum
8
go.sum
|
@ -15,10 +15,10 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
|||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
|
||||
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
|
||||
github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 h1:ge5g8vsTQclA5lXDi+PuiAFw5GMIlMHOB/5e1hsf96E=
|
||||
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/nfp v0.0.0-20230428090735-b50b0f0358f4 h1:YoU/1S7L25dvNepEir3Fg2aU9iGmDyE4gWKoEswWXts=
|
||||
github.com/xuri/nfp v0.0.0-20230428090735-b50b0f0358f4/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
|
|
305
numfmt.go
305
numfmt.go
|
@ -21,7 +21,8 @@ import (
|
|||
"github.com/xuri/nfp"
|
||||
)
|
||||
|
||||
// languageInfo defined the required fields of localization support for number format.
|
||||
// languageInfo defined the required fields of localization support for number
|
||||
// format.
|
||||
type languageInfo struct {
|
||||
apFmt string
|
||||
tags []string
|
||||
|
@ -31,13 +32,16 @@ type languageInfo struct {
|
|||
// numberFormat directly maps the number format parser runtime required
|
||||
// fields.
|
||||
type numberFormat struct {
|
||||
cellType CellType
|
||||
section []nfp.Section
|
||||
t time.Time
|
||||
sectionIdx int
|
||||
date1904, isNumeric, hours, seconds bool
|
||||
number float64
|
||||
ap, localCode, result, value, valueSectionType string
|
||||
cellType CellType
|
||||
section []nfp.Section
|
||||
t time.Time
|
||||
sectionIdx int
|
||||
date1904, isNumeric, hours, seconds bool
|
||||
number float64
|
||||
ap, localCode, result, value, valueSectionType string
|
||||
fracHolder, fracPadding, intHolder, intPadding, expBaseLen int
|
||||
percent int
|
||||
useCommaSep, usePointer, usePositive, useScientificNotation bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -47,12 +51,33 @@ var (
|
|||
nfp.TokenTypeColor,
|
||||
nfp.TokenTypeCurrencyLanguage,
|
||||
nfp.TokenTypeDateTimes,
|
||||
nfp.TokenTypeDecimalPoint,
|
||||
nfp.TokenTypeElapsedDateTimes,
|
||||
nfp.TokenTypeExponential,
|
||||
nfp.TokenTypeGeneral,
|
||||
nfp.TokenTypeHashPlaceHolder,
|
||||
nfp.TokenTypeLiteral,
|
||||
nfp.TokenTypePercent,
|
||||
nfp.TokenTypeTextPlaceHolder,
|
||||
nfp.TokenTypeThousandsSeparator,
|
||||
nfp.TokenTypeZeroPlaceHolder,
|
||||
}
|
||||
// supportedNumberTokenTypes list the supported number token types.
|
||||
supportedNumberTokenTypes = []string{
|
||||
nfp.TokenTypeColor,
|
||||
nfp.TokenTypeDecimalPoint,
|
||||
nfp.TokenTypeHashPlaceHolder,
|
||||
nfp.TokenTypeLiteral,
|
||||
nfp.TokenTypePercent,
|
||||
nfp.TokenTypeThousandsSeparator,
|
||||
nfp.TokenTypeZeroPlaceHolder,
|
||||
}
|
||||
// supportedDateTimeTokenTypes list the supported date and time token types.
|
||||
supportedDateTimeTokenTypes = []string{
|
||||
nfp.TokenTypeCurrencyLanguage,
|
||||
nfp.TokenTypeDateTimes,
|
||||
nfp.TokenTypeElapsedDateTimes,
|
||||
}
|
||||
// supportedLanguageInfo directly maps the supported language ID and tags.
|
||||
supportedLanguageInfo = map[string]languageInfo{
|
||||
"36": {tags: []string{"af"}, localMonth: localMonthsNameAfrikaans, apFmt: apFmtAfrikaans},
|
||||
|
@ -373,15 +398,172 @@ func format(value, numFmt string, date1904 bool, cellType CellType) string {
|
|||
return value
|
||||
}
|
||||
|
||||
// positiveHandler will be handling positive selection for a number format
|
||||
// expression.
|
||||
func (nf *numberFormat) positiveHandler() (result string) {
|
||||
// getNumberPartLen returns the length of integer and fraction parts for the
|
||||
// numeric.
|
||||
func getNumberPartLen(n float64) (int, int) {
|
||||
parts := strings.Split(strconv.FormatFloat(math.Abs(n), 'f', -1, 64), ".")
|
||||
if len(parts) == 2 {
|
||||
return len(parts[0]), len(parts[1])
|
||||
}
|
||||
return len(parts[0]), 0
|
||||
}
|
||||
|
||||
// getNumberFmtConf generate the number format padding and place holder
|
||||
// configurations.
|
||||
func (nf *numberFormat) getNumberFmtConf() {
|
||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||
if token.TType == nfp.TokenTypeHashPlaceHolder {
|
||||
if nf.usePointer {
|
||||
nf.fracHolder += len(token.TValue)
|
||||
} else {
|
||||
nf.intHolder += len(token.TValue)
|
||||
}
|
||||
}
|
||||
if token.TType == nfp.TokenTypeExponential {
|
||||
nf.useScientificNotation = true
|
||||
}
|
||||
if token.TType == nfp.TokenTypeThousandsSeparator {
|
||||
nf.useCommaSep = true
|
||||
}
|
||||
if token.TType == nfp.TokenTypePercent {
|
||||
nf.percent += len(token.TValue)
|
||||
}
|
||||
if token.TType == nfp.TokenTypeDecimalPoint {
|
||||
nf.usePointer = true
|
||||
}
|
||||
if token.TType == nfp.TokenTypeZeroPlaceHolder {
|
||||
if nf.usePointer {
|
||||
if nf.useScientificNotation {
|
||||
nf.expBaseLen += len(token.TValue)
|
||||
continue
|
||||
}
|
||||
nf.fracPadding += len(token.TValue)
|
||||
continue
|
||||
}
|
||||
nf.intPadding += len(token.TValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// printNumberLiteral apply literal tokens for the pre-formatted text.
|
||||
func (nf *numberFormat) printNumberLiteral(text string) string {
|
||||
var result string
|
||||
var useZeroPlaceHolder bool
|
||||
if nf.usePositive {
|
||||
result += "-"
|
||||
}
|
||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||
if token.TType == nfp.TokenTypeLiteral {
|
||||
result += token.TValue
|
||||
}
|
||||
if !useZeroPlaceHolder && token.TType == nfp.TokenTypeZeroPlaceHolder {
|
||||
useZeroPlaceHolder = true
|
||||
result += text
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// printCommaSep format number with thousands separator.
|
||||
func printCommaSep(text string) string {
|
||||
var (
|
||||
target strings.Builder
|
||||
subStr = strings.Split(text, ".")
|
||||
length = len(subStr[0])
|
||||
)
|
||||
for i := 0; i < length; i++ {
|
||||
if i > 0 && (length-i)%3 == 0 {
|
||||
target.WriteString(",")
|
||||
}
|
||||
target.WriteString(string(text[i]))
|
||||
}
|
||||
if len(subStr) == 2 {
|
||||
target.WriteString(".")
|
||||
target.WriteString(subStr[1])
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
|
||||
// printBigNumber format number which precision great than 15 with fraction
|
||||
// zero padding and percentage symbol.
|
||||
func (nf *numberFormat) printBigNumber(decimal float64, fracLen int) string {
|
||||
var exp float64
|
||||
if nf.percent > 0 {
|
||||
exp = 1
|
||||
}
|
||||
result := strings.TrimLeft(strconv.FormatFloat(decimal*math.Pow(100, exp), 'f', -1, 64), "-")
|
||||
if nf.useCommaSep {
|
||||
result = printCommaSep(result)
|
||||
}
|
||||
if fracLen > 0 {
|
||||
if parts := strings.Split(result, "."); len(parts) == 2 {
|
||||
fracPartLen := len(parts[1])
|
||||
if fracPartLen < fracLen {
|
||||
result = fmt.Sprintf("%s%s", result, strings.Repeat("0", fracLen-fracPartLen))
|
||||
}
|
||||
if fracPartLen > fracLen {
|
||||
result = fmt.Sprintf("%s.%s", parts[0], parts[1][:fracLen])
|
||||
}
|
||||
} else {
|
||||
result = fmt.Sprintf("%s.%s", result, strings.Repeat("0", fracLen))
|
||||
}
|
||||
}
|
||||
if nf.percent > 0 {
|
||||
return fmt.Sprintf("%s%%", result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// numberHandler handling number format expression for positive and negative
|
||||
// numeric.
|
||||
func (nf *numberFormat) numberHandler() string {
|
||||
var (
|
||||
num = nf.number
|
||||
intPart, fracPart = getNumberPartLen(nf.number)
|
||||
intLen, fracLen int
|
||||
result string
|
||||
)
|
||||
nf.getNumberFmtConf()
|
||||
if intLen = intPart; nf.intPadding > intPart {
|
||||
intLen = nf.intPadding
|
||||
}
|
||||
if fracLen = fracPart; fracPart > nf.fracHolder+nf.fracPadding {
|
||||
fracLen = nf.fracHolder + nf.fracPadding
|
||||
}
|
||||
if nf.fracPadding > fracPart {
|
||||
fracLen = nf.fracPadding
|
||||
}
|
||||
if isNum, precision, decimal := isNumeric(nf.value); isNum {
|
||||
if precision > 15 && intLen+fracLen > 15 {
|
||||
return nf.printNumberLiteral(nf.printBigNumber(decimal, fracLen))
|
||||
}
|
||||
}
|
||||
paddingLen := intLen + fracLen
|
||||
if fracLen > 0 {
|
||||
paddingLen++
|
||||
}
|
||||
flag := "f"
|
||||
if nf.useScientificNotation {
|
||||
if nf.expBaseLen != 2 {
|
||||
return nf.value
|
||||
}
|
||||
flag = "E"
|
||||
}
|
||||
fmtCode := fmt.Sprintf("%%0%d.%d%s%s", paddingLen, fracLen, flag, strings.Repeat("%%", nf.percent))
|
||||
if nf.percent > 0 {
|
||||
num *= math.Pow(100, float64(nf.percent))
|
||||
}
|
||||
if result = fmt.Sprintf(fmtCode, math.Abs(num)); nf.useCommaSep {
|
||||
result = printCommaSep(result)
|
||||
}
|
||||
return nf.printNumberLiteral(result)
|
||||
}
|
||||
|
||||
// dateTimeHandler handling data and time number format expression for a
|
||||
// positive numeric.
|
||||
func (nf *numberFormat) dateTimeHandler() (result string) {
|
||||
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
|
||||
for i, token := range nf.section[nf.sectionIdx].Items {
|
||||
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
|
||||
result = nf.value
|
||||
return
|
||||
}
|
||||
if token.TType == nfp.TokenTypeCurrencyLanguage {
|
||||
if err := nf.currencyLanguageHandler(i, token); err != nil {
|
||||
result = nf.value
|
||||
|
@ -398,27 +580,46 @@ func (nf *numberFormat) positiveHandler() (result string) {
|
|||
nf.result += token.TValue
|
||||
continue
|
||||
}
|
||||
if token.TType == nfp.TokenTypeZeroPlaceHolder && token.TValue == strings.Repeat("0", len(token.TValue)) {
|
||||
if isNum, precision, decimal := isNumeric(nf.value); isNum {
|
||||
if nf.number < 1 {
|
||||
nf.result += "0"
|
||||
continue
|
||||
}
|
||||
if precision > 15 {
|
||||
nf.result += strconv.FormatFloat(decimal, 'f', -1, 64)
|
||||
} else {
|
||||
nf.result += fmt.Sprintf("%.f", nf.number)
|
||||
}
|
||||
continue
|
||||
if token.TType == nfp.TokenTypeDecimalPoint {
|
||||
nf.result += "."
|
||||
}
|
||||
if token.TType == nfp.TokenTypeZeroPlaceHolder {
|
||||
zeroHolderLen := len(token.TValue)
|
||||
if zeroHolderLen > 3 {
|
||||
zeroHolderLen = 3
|
||||
}
|
||||
nf.result += strings.Repeat("0", zeroHolderLen)
|
||||
}
|
||||
}
|
||||
result = nf.result
|
||||
return
|
||||
return nf.result
|
||||
}
|
||||
|
||||
// currencyLanguageHandler will be handling currency and language types tokens for a number
|
||||
// format expression.
|
||||
// positiveHandler will be handling positive selection for a number format
|
||||
// expression.
|
||||
func (nf *numberFormat) positiveHandler() string {
|
||||
var fmtNum bool
|
||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
|
||||
return nf.value
|
||||
}
|
||||
if inStrSlice(supportedNumberTokenTypes, token.TType, true) != -1 {
|
||||
fmtNum = true
|
||||
}
|
||||
if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 {
|
||||
if fmtNum || nf.number < 0 {
|
||||
return nf.value
|
||||
}
|
||||
return nf.dateTimeHandler()
|
||||
}
|
||||
}
|
||||
if fmtNum {
|
||||
return nf.numberHandler()
|
||||
}
|
||||
return nf.value
|
||||
}
|
||||
|
||||
// currencyLanguageHandler will be handling currency and language types tokens
|
||||
// for a number format expression.
|
||||
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err error) {
|
||||
for _, part := range token.Parts {
|
||||
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
|
||||
|
@ -566,7 +767,8 @@ func localMonthsNameKorean(t time.Time, abbr int) string {
|
|||
return strconv.Itoa(int(t.Month()))
|
||||
}
|
||||
|
||||
// localMonthsNameTraditionalMongolian returns the Traditional Mongolian name of the month.
|
||||
// localMonthsNameTraditionalMongolian returns the Traditional Mongolian name of
|
||||
// the month.
|
||||
func localMonthsNameTraditionalMongolian(t time.Time, abbr int) string {
|
||||
if abbr == 5 {
|
||||
return "M"
|
||||
|
@ -912,32 +1114,23 @@ func (nf *numberFormat) secondsNext(i int) bool {
|
|||
// negativeHandler will be handling negative selection for a number format
|
||||
// expression.
|
||||
func (nf *numberFormat) negativeHandler() (result string) {
|
||||
fmtNum := true
|
||||
for _, token := range nf.section[nf.sectionIdx].Items {
|
||||
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
|
||||
result = nf.value
|
||||
return
|
||||
return nf.value
|
||||
}
|
||||
if token.TType == nfp.TokenTypeLiteral {
|
||||
nf.result += token.TValue
|
||||
if inStrSlice(supportedNumberTokenTypes, token.TType, true) != -1 {
|
||||
continue
|
||||
}
|
||||
if token.TType == nfp.TokenTypeZeroPlaceHolder && token.TValue == strings.Repeat("0", len(token.TValue)) {
|
||||
if isNum, precision, decimal := isNumeric(nf.value); isNum {
|
||||
if math.Abs(nf.number) < 1 {
|
||||
nf.result += "0"
|
||||
continue
|
||||
}
|
||||
if precision > 15 {
|
||||
nf.result += strings.TrimLeft(strconv.FormatFloat(decimal, 'f', -1, 64), "-")
|
||||
} else {
|
||||
nf.result += fmt.Sprintf("%.f", math.Abs(nf.number))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 {
|
||||
return nf.value
|
||||
}
|
||||
fmtNum = false
|
||||
}
|
||||
result = nf.result
|
||||
return
|
||||
if fmtNum {
|
||||
return nf.numberHandler()
|
||||
}
|
||||
return nf.value
|
||||
}
|
||||
|
||||
// zeroHandler will be handling zero selection for a number format expression.
|
||||
|
@ -973,6 +1166,16 @@ func (nf *numberFormat) getValueSectionType(value string) (float64, string) {
|
|||
return number, nfp.TokenSectionPositive
|
||||
}
|
||||
if number < 0 {
|
||||
var hasNeg bool
|
||||
for _, sec := range nf.section {
|
||||
if sec.Type == nfp.TokenSectionNegative {
|
||||
hasNeg = true
|
||||
}
|
||||
}
|
||||
if !hasNeg {
|
||||
nf.usePositive = true
|
||||
return number, nfp.TokenSectionPositive
|
||||
}
|
||||
return number, nfp.TokenSectionNegative
|
||||
}
|
||||
return number, nfp.TokenSectionZero
|
||||
|
|
|
@ -1004,6 +1004,40 @@ func TestNumFmt(t *testing.T) {
|
|||
{"-8.0450685976001E+21", "0_);[Red]\\(0\\)", "(8045068597600100000000)"},
|
||||
{"-8.0450685976001E-21", "0_);[Red]\\(0\\)", "(0)"},
|
||||
{"-8.04506", "0_);[Red]\\(0\\)", "(8)"},
|
||||
{"1234.5678", "0", "1235"},
|
||||
{"1234.5678", "0.00", "1234.57"},
|
||||
{"1234.5678", "#,##0", "1,235"},
|
||||
{"1234.5678", "#,##0.00", "1,234.57"},
|
||||
{"1234.5678", "0%", "123457%"},
|
||||
{"1234.5678", "#,##0 ;(#,##0)", "1,235 "},
|
||||
{"1234.5678", "#,##0 ;[red](#,##0)", "1,235 "},
|
||||
{"1234.5678", "#,##0.00;(#,##0.00)", "1,234.57"},
|
||||
{"1234.5678", "#,##0.00;[red](#,##0.00)", "1,234.57"},
|
||||
{"-1234.5678", "0.00", "-1234.57"},
|
||||
{"-1234.5678", "0.00;-0.00", "-1234.57"},
|
||||
{"-1234.5678", "0.00%%", "-12345678.00%%"},
|
||||
{"2.1", "mmss.0000", "2400.000"},
|
||||
{"1234.5678", "0.00###", "1234.5678"},
|
||||
{"1234.5678", "00000.00###", "01234.5678"},
|
||||
{"-1234.5678", "00000.00###;;", ""},
|
||||
{"1234.5678", "0.00000", "1234.56780"},
|
||||
{"8.8888666665555487", "0.00000", "8.88887"},
|
||||
{"8.8888666665555493e+19", "#,000.00", "88,888,666,665,555,500,000.00"},
|
||||
{"8.8888666665555493e+19", "0.00000", "88888666665555500000.00000"},
|
||||
{"37947.7500001", "0.00000000E+00", "3.79477500E+04"},
|
||||
{"1.234E-16", "0.00000000000000000000", "0.00000000000000012340"},
|
||||
{"1.234E-16", "0.000000000000000000", "0.000000000000000123"},
|
||||
{"1.234E-16", "0.000000000000000000%", "0.000000000000012340%"},
|
||||
{"1.234E-16", "0.000000000000000000%%%%", "0.000000000000012340%"},
|
||||
// Unsupported number format
|
||||
{"37947.7500001", "0.00000000E+000", "37947.7500001"},
|
||||
// Invalid number format
|
||||
{"123", "x0.00s", "123"},
|
||||
{"-123", "x0.00s", "-123"},
|
||||
{"-1234.5678", ";E+;", "-1234.5678"},
|
||||
{"1234.5678", "E+;", "1234.5678"},
|
||||
{"1234.5678", "00000.00###s", "1234.5678"},
|
||||
{"-1234.5678", "00000.00###;s;", "-1234.5678"},
|
||||
} {
|
||||
result := format(item[0], item[1], false, CellTypeNumber)
|
||||
assert.Equal(t, item[2], result, item)
|
||||
|
|
|
@ -1114,7 +1114,7 @@ func TestNumberFormats(t *testing.T) {
|
|||
assert.NoError(t, f.SetCellValue("Sheet1", cell, value))
|
||||
result, err := f.GetCellValue("Sheet1", cell)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, result)
|
||||
assert.Equal(t, expected, result, cell)
|
||||
}
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))
|
||||
}
|
||||
|
|
211
styles.go
211
styles.go
|
@ -34,7 +34,7 @@ var builtInNumFmt = map[int]string{
|
|||
4: "#,##0.00",
|
||||
9: "0%",
|
||||
10: "0.00%",
|
||||
11: "0.00e+00",
|
||||
11: "0.00E+00",
|
||||
12: "# ?/?",
|
||||
13: "# ??/??",
|
||||
14: "mm-dd-yy",
|
||||
|
@ -48,8 +48,8 @@ var builtInNumFmt = map[int]string{
|
|||
22: "m/d/yy hh:mm",
|
||||
37: "#,##0 ;(#,##0)",
|
||||
38: "#,##0 ;[red](#,##0)",
|
||||
39: "#,##0.00;(#,##0.00)",
|
||||
40: "#,##0.00;[red](#,##0.00)",
|
||||
39: "#,##0.00 ;(#,##0.00)",
|
||||
40: "#,##0.00 ;[red](#,##0.00)",
|
||||
41: `_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)`,
|
||||
42: `_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)`,
|
||||
43: `_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)`,
|
||||
|
@ -57,7 +57,7 @@ var builtInNumFmt = map[int]string{
|
|||
45: "mm:ss",
|
||||
46: "[h]:mm:ss",
|
||||
47: "mmss.0",
|
||||
48: "##0.0e+0",
|
||||
48: "##0.0E+0",
|
||||
49: "@",
|
||||
}
|
||||
|
||||
|
@ -751,43 +751,6 @@ var currencyNumFmt = map[int]string{
|
|||
634: "[$ZWR]\\ #,##0.00",
|
||||
}
|
||||
|
||||
// builtInNumFmtFunc defined the format conversion functions map. Partial format
|
||||
// code doesn't support currently and will return original string.
|
||||
var builtInNumFmtFunc = map[int]func(v, format string, date1904 bool, cellType CellType) string{
|
||||
0: format,
|
||||
1: formatToInt,
|
||||
2: formatToFloat,
|
||||
3: formatToIntSeparator,
|
||||
4: formatToFloat,
|
||||
9: formatToC,
|
||||
10: formatToD,
|
||||
11: formatToE,
|
||||
12: format, // Doesn't support currently
|
||||
13: format, // Doesn't support currently
|
||||
14: format,
|
||||
15: format,
|
||||
16: format,
|
||||
17: format,
|
||||
18: format,
|
||||
19: format,
|
||||
20: format,
|
||||
21: format,
|
||||
22: format,
|
||||
37: formatToA,
|
||||
38: formatToA,
|
||||
39: formatToB,
|
||||
40: formatToB,
|
||||
41: format, // Doesn't support currently
|
||||
42: format, // Doesn't support currently
|
||||
43: format, // Doesn't support currently
|
||||
44: format, // Doesn't support currently
|
||||
45: format,
|
||||
46: format,
|
||||
47: format,
|
||||
48: formatToE,
|
||||
49: format,
|
||||
}
|
||||
|
||||
// validType defined the list of valid validation types.
|
||||
var validType = map[string]string{
|
||||
"cell": "cellIs",
|
||||
|
@ -869,172 +832,6 @@ var operatorType = map[string]string{
|
|||
"greaterThanOrEqual": "greater than or equal to",
|
||||
}
|
||||
|
||||
// printCommaSep format number with thousands separator.
|
||||
func printCommaSep(text string) string {
|
||||
var (
|
||||
target strings.Builder
|
||||
subStr = strings.Split(text, ".")
|
||||
length = len(subStr[0])
|
||||
)
|
||||
for i := 0; i < length; i++ {
|
||||
if i > 0 && (length-i)%3 == 0 {
|
||||
target.WriteString(",")
|
||||
}
|
||||
target.WriteString(string(text[i]))
|
||||
}
|
||||
if len(subStr) == 2 {
|
||||
target.WriteString(".")
|
||||
target.WriteString(subStr[1])
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
|
||||
// formatToInt provides a function to convert original string to integer
|
||||
// format as string type by given built-in number formats code and cell
|
||||
// string.
|
||||
func formatToInt(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
return strconv.FormatFloat(math.Round(f), 'f', -1, 64)
|
||||
}
|
||||
|
||||
// formatToFloat provides a function to convert original string to float
|
||||
// format as string type by given built-in number formats code and cell
|
||||
// string.
|
||||
func formatToFloat(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
source := strconv.FormatFloat(f, 'f', -1, 64)
|
||||
if !strings.Contains(source, ".") {
|
||||
return source + ".00"
|
||||
}
|
||||
return fmt.Sprintf("%.2f", f)
|
||||
}
|
||||
|
||||
// formatToIntSeparator provides a function to convert original string to
|
||||
// integer format as string type by given built-in number formats code and cell
|
||||
// string.
|
||||
func formatToIntSeparator(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
return printCommaSep(strconv.FormatFloat(math.Round(f), 'f', -1, 64))
|
||||
}
|
||||
|
||||
// formatToA provides a function to convert original string to special format
|
||||
// as string type by given built-in number formats code and cell string.
|
||||
func formatToA(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
var target strings.Builder
|
||||
if f < 0 {
|
||||
target.WriteString("(")
|
||||
}
|
||||
target.WriteString(printCommaSep(strconv.FormatFloat(math.Abs(math.Round(f)), 'f', -1, 64)))
|
||||
if f < 0 {
|
||||
target.WriteString(")")
|
||||
} else {
|
||||
target.WriteString(" ")
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
|
||||
// formatToB provides a function to convert original string to special format
|
||||
// as string type by given built-in number formats code and cell string.
|
||||
func formatToB(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
var target strings.Builder
|
||||
if f < 0 {
|
||||
target.WriteString("(")
|
||||
}
|
||||
source := strconv.FormatFloat(math.Abs(f), 'f', -1, 64)
|
||||
var text string
|
||||
if !strings.Contains(source, ".") {
|
||||
text = printCommaSep(source + ".00")
|
||||
} else {
|
||||
text = printCommaSep(fmt.Sprintf("%.2f", math.Abs(f)))
|
||||
}
|
||||
target.WriteString(text)
|
||||
if f < 0 {
|
||||
target.WriteString(")")
|
||||
} else {
|
||||
target.WriteString(" ")
|
||||
}
|
||||
return target.String()
|
||||
}
|
||||
|
||||
// formatToC provides a function to convert original string to special format
|
||||
// as string type by given built-in number formats code and cell string.
|
||||
func formatToC(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
source := strconv.FormatFloat(f, 'f', -1, 64)
|
||||
if !strings.Contains(source, ".") {
|
||||
return source + "00%"
|
||||
}
|
||||
return fmt.Sprintf("%.f%%", f*100)
|
||||
}
|
||||
|
||||
// formatToD provides a function to convert original string to special format
|
||||
// as string type by given built-in number formats code and cell string.
|
||||
func formatToD(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
source := strconv.FormatFloat(f, 'f', -1, 64)
|
||||
if !strings.Contains(source, ".") {
|
||||
return source + "00.00%"
|
||||
}
|
||||
return fmt.Sprintf("%.2f%%", f*100)
|
||||
}
|
||||
|
||||
// formatToE provides a function to convert original string to special format
|
||||
// as string type by given built-in number formats code and cell string.
|
||||
func formatToE(v, format string, date1904 bool, cellType CellType) string {
|
||||
if strings.Contains(v, "_") || cellType == CellTypeSharedString || cellType == CellTypeInlineString {
|
||||
return v
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return v
|
||||
}
|
||||
return fmt.Sprintf("%.2E", f)
|
||||
}
|
||||
|
||||
// stylesReader provides a function to get the pointer to the structure after
|
||||
// deserialization of xl/styles.xml.
|
||||
func (f *File) stylesReader() (*xlsxStyleSheet, error) {
|
||||
|
|
Loading…
Reference in New Issue