Skip to content

Commit bbdb83a

Browse files
authored
This closes #660, supports currency string, and switches argument for the number format code
- Support round millisecond for the date time - Update built-in number formats mapping - Update unit tests - Upgrade dependencies package
1 parent 7c221cf commit bbdb83a

File tree

8 files changed

+146
-58
lines changed

8 files changed

+146
-58
lines changed

col_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ func TestInsertCols(t *testing.T) {
407407
f := NewFile()
408408
sheet1 := f.GetSheetName(0)
409409

410-
fillCells(f, sheet1, 10, 10)
410+
assert.NoError(t, fillCells(f, sheet1, 10, 10))
411411

412412
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
413413
assert.NoError(t, f.MergeCell(sheet1, "A1", "C3"))
@@ -430,7 +430,7 @@ func TestRemoveCol(t *testing.T) {
430430
f := NewFile()
431431
sheet1 := f.GetSheetName(0)
432432

433-
fillCells(f, sheet1, 10, 15)
433+
assert.NoError(t, fillCells(f, sheet1, 10, 15))
434434

435435
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
436436
assert.NoError(t, f.SetCellHyperLink(sheet1, "C5", "https://github.com", "External"))

excelize_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -750,10 +750,10 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
750750
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}
751751
value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
752752
expected := [][]string{
753-
{"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"},
753+
{"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", "00:00.0", "37947.7500001", "37947.7500001"},
754754
{"-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"},
755-
{"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"},
756-
{"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"},
755+
{"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:05 AM", "00:10", "00:10:05", "12/30/99 00:10", "0 ", "0 ", "0.01 ", "0.01 ", "0.007", "0.007", "0.007", "0.007", "10:05", "0:10:05", "10:04.8", "0.007", "0.007"},
756+
{"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", "24:00.0", "2.1", "2.1"},
757757
{"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"},
758758
}
759759

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/richardlehane/mscfb v1.0.4
88
github.com/stretchr/testify v1.8.0
99
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9
10-
github.com/xuri/nfp v0.0.0-20230428090735-b50b0f0358f4
10+
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83
1111
golang.org/x/crypto v0.8.0
1212
golang.org/x/image v0.5.0
1313
golang.org/x/net v0.9.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
1717
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
1818
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9 h1:ge5g8vsTQclA5lXDi+PuiAFw5GMIlMHOB/5e1hsf96E=
1919
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
20-
github.com/xuri/nfp v0.0.0-20230428090735-b50b0f0358f4 h1:YoU/1S7L25dvNepEir3Fg2aU9iGmDyE4gWKoEswWXts=
21-
github.com/xuri/nfp v0.0.0-20230428090735-b50b0f0358f4/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
20+
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83 h1:xVwnvkzzi+OiwhIkWOXvh1skFI6bagk8OvGuazM80Rw=
21+
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
2222
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
2323
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
2424
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=

numfmt.go

Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ type numberFormat struct {
3636
section []nfp.Section
3737
t time.Time
3838
sectionIdx int
39-
date1904, isNumeric, hours, seconds bool
39+
date1904, isNumeric, hours, seconds, useMillisecond bool
4040
number float64
4141
ap, localCode, result, value, valueSectionType string
42+
switchArgument, currencyString string
4243
fracHolder, fracPadding, intHolder, intPadding, expBaseLen int
4344
percent int
4445
useCommaSep, usePointer, usePositive, useScientificNotation bool
@@ -47,6 +48,7 @@ type numberFormat struct {
4748
var (
4849
// supportedTokenTypes list the supported number format token types currently.
4950
supportedTokenTypes = []string{
51+
nfp.TokenSubTypeCurrencyString,
5052
nfp.TokenSubTypeLanguageInfo,
5153
nfp.TokenTypeColor,
5254
nfp.TokenTypeCurrencyLanguage,
@@ -58,23 +60,20 @@ var (
5860
nfp.TokenTypeHashPlaceHolder,
5961
nfp.TokenTypeLiteral,
6062
nfp.TokenTypePercent,
63+
nfp.TokenTypeSwitchArgument,
6164
nfp.TokenTypeTextPlaceHolder,
6265
nfp.TokenTypeThousandsSeparator,
6366
nfp.TokenTypeZeroPlaceHolder,
6467
}
6568
// supportedNumberTokenTypes list the supported number token types.
6669
supportedNumberTokenTypes = []string{
67-
nfp.TokenTypeColor,
68-
nfp.TokenTypeDecimalPoint,
70+
nfp.TokenTypeExponential,
6971
nfp.TokenTypeHashPlaceHolder,
70-
nfp.TokenTypeLiteral,
7172
nfp.TokenTypePercent,
72-
nfp.TokenTypeThousandsSeparator,
7373
nfp.TokenTypeZeroPlaceHolder,
7474
}
7575
// supportedDateTimeTokenTypes list the supported date and time token types.
7676
supportedDateTimeTokenTypes = []string{
77-
nfp.TokenTypeCurrencyLanguage,
7877
nfp.TokenTypeDateTimes,
7978
nfp.TokenTypeElapsedDateTimes,
8079
}
@@ -357,6 +356,30 @@ var (
357356
apFmtYi = "\ua3b8\ua111/\ua06f\ua2d2"
358357
// apFmtWelsh defined the AM/PM name in the Welsh.
359358
apFmtWelsh = "yb/yh"
359+
// switchArgumentFunc defined the switch argument printer function
360+
switchArgumentFunc = map[string]func(s string) string{
361+
"[DBNum1]": func(s string) string {
362+
r := strings.NewReplacer(
363+
"0", "\u25cb", "1", "\u4e00", "2", "\u4e8c", "3", "\u4e09", "4", "\u56db",
364+
"5", "\u4e94", "6", "\u516d", "7", "\u4e03", "8", "\u516b", "9", "\u4e5d",
365+
)
366+
return r.Replace(s)
367+
},
368+
"[DBNum2]": func(s string) string {
369+
r := strings.NewReplacer(
370+
"0", "\u96f6", "1", "\u58f9", "2", "\u8d30", "3", "\u53c1", "4", "\u8086",
371+
"5", "\u4f0d", "6", "\u9646", "7", "\u67d2", "8", "\u634c", "9", "\u7396",
372+
)
373+
return r.Replace(s)
374+
},
375+
"[DBNum3]": func(s string) string {
376+
r := strings.NewReplacer(
377+
"0", "\uff10", "1", "\uff11", "2", "\uff12", "3", "\uff13", "4", "\uff14",
378+
"5", "\uff15", "6", "\uff16", "7", "\uff17", "8", "\uff18", "9", "\uff19",
379+
)
380+
return r.Replace(s)
381+
},
382+
}
360383
)
361384

362385
// prepareNumberic split the number into two before and after parts by a
@@ -431,6 +454,9 @@ func (nf *numberFormat) getNumberFmtConf() {
431454
if token.TType == nfp.TokenTypeDecimalPoint {
432455
nf.usePointer = true
433456
}
457+
if token.TType == nfp.TokenTypeSwitchArgument {
458+
nf.switchArgument = token.TValue
459+
}
434460
if token.TType == nfp.TokenTypeZeroPlaceHolder {
435461
if nf.usePointer {
436462
if nf.useScientificNotation {
@@ -448,20 +474,34 @@ func (nf *numberFormat) getNumberFmtConf() {
448474
// printNumberLiteral apply literal tokens for the pre-formatted text.
449475
func (nf *numberFormat) printNumberLiteral(text string) string {
450476
var result string
451-
var useZeroPlaceHolder bool
477+
var useLiteral, useZeroPlaceHolder bool
452478
if nf.usePositive {
453479
result += "-"
454480
}
455-
for _, token := range nf.section[nf.sectionIdx].Items {
481+
for i, token := range nf.section[nf.sectionIdx].Items {
482+
if token.TType == nfp.TokenTypeCurrencyLanguage {
483+
if err := nf.currencyLanguageHandler(i, token); err != nil {
484+
return nf.value
485+
}
486+
result += nf.currencyString
487+
}
456488
if token.TType == nfp.TokenTypeLiteral {
489+
if useZeroPlaceHolder {
490+
useLiteral = true
491+
}
457492
result += token.TValue
458493
}
459-
if !useZeroPlaceHolder && token.TType == nfp.TokenTypeZeroPlaceHolder {
460-
useZeroPlaceHolder = true
461-
result += text
494+
if token.TType == nfp.TokenTypeZeroPlaceHolder {
495+
if useLiteral && useZeroPlaceHolder {
496+
return nf.value
497+
}
498+
if !useZeroPlaceHolder {
499+
useZeroPlaceHolder = true
500+
result += text
501+
}
462502
}
463503
}
464-
return result
504+
return nf.printSwitchArgument(result)
465505
}
466506

467507
// printCommaSep format number with thousands separator.
@@ -484,6 +524,17 @@ func printCommaSep(text string) string {
484524
return target.String()
485525
}
486526

527+
// printSwitchArgument format number with switch argument.
528+
func (nf *numberFormat) printSwitchArgument(text string) string {
529+
if nf.switchArgument == "" {
530+
return text
531+
}
532+
if fn, ok := switchArgumentFunc[nf.switchArgument]; ok {
533+
return fn(text)
534+
}
535+
return nf.value
536+
}
537+
487538
// printBigNumber format number which precision great than 15 with fraction
488539
// zero padding and percentage symbol.
489540
func (nf *numberFormat) printBigNumber(decimal float64, fracLen int) string {
@@ -561,14 +612,14 @@ func (nf *numberFormat) numberHandler() string {
561612

562613
// dateTimeHandler handling data and time number format expression for a
563614
// positive numeric.
564-
func (nf *numberFormat) dateTimeHandler() (result string) {
615+
func (nf *numberFormat) dateTimeHandler() string {
565616
nf.t, nf.hours, nf.seconds = timeFromExcelTime(nf.number, nf.date1904), false, false
566617
for i, token := range nf.section[nf.sectionIdx].Items {
567618
if token.TType == nfp.TokenTypeCurrencyLanguage {
568619
if err := nf.currencyLanguageHandler(i, token); err != nil {
569-
result = nf.value
570-
return
620+
return nf.value
571621
}
622+
nf.result += nf.currencyString
572623
}
573624
if token.TType == nfp.TokenTypeDateTimes {
574625
nf.dateTimesHandler(i, token)
@@ -583,15 +634,18 @@ func (nf *numberFormat) dateTimeHandler() (result string) {
583634
if token.TType == nfp.TokenTypeDecimalPoint {
584635
nf.result += "."
585636
}
637+
if token.TType == nfp.TokenTypeSwitchArgument {
638+
nf.switchArgument = token.TValue
639+
}
586640
if token.TType == nfp.TokenTypeZeroPlaceHolder {
587641
zeroHolderLen := len(token.TValue)
588642
if zeroHolderLen > 3 {
589643
zeroHolderLen = 3
590644
}
591-
nf.result += strings.Repeat("0", zeroHolderLen)
645+
nf.result += fmt.Sprintf("%03d", nf.t.Nanosecond()/1e6)[:zeroHolderLen]
592646
}
593647
}
594-
return nf.result
648+
return nf.printSwitchArgument(nf.result)
595649
}
596650

597651
// positiveHandler will be handling positive selection for a number format
@@ -609,13 +663,26 @@ func (nf *numberFormat) positiveHandler() string {
609663
if fmtNum || nf.number < 0 {
610664
return nf.value
611665
}
666+
var useDateTimeTokens bool
667+
for _, token := range nf.section[nf.sectionIdx].Items {
668+
if inStrSlice(supportedDateTimeTokenTypes, token.TType, false) != -1 {
669+
if useDateTimeTokens && nf.useMillisecond {
670+
return nf.value
671+
}
672+
useDateTimeTokens = true
673+
}
674+
if inStrSlice(supportedNumberTokenTypes, token.TType, false) != -1 {
675+
if token.TType == nfp.TokenTypeZeroPlaceHolder {
676+
nf.useMillisecond = true
677+
continue
678+
}
679+
return nf.value
680+
}
681+
}
612682
return nf.dateTimeHandler()
613683
}
614684
}
615-
if fmtNum {
616-
return nf.numberHandler()
617-
}
618-
return nf.value
685+
return nf.numberHandler()
619686
}
620687

621688
// currencyLanguageHandler will be handling currency and language types tokens
@@ -626,11 +693,16 @@ func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (err err
626693
err = ErrUnsupportedNumberFormat
627694
return
628695
}
629-
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
630-
err = ErrUnsupportedNumberFormat
631-
return
696+
if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
697+
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
698+
err = ErrUnsupportedNumberFormat
699+
return
700+
}
701+
nf.localCode = strings.ToUpper(part.Token.TValue)
702+
}
703+
if part.Token.TType == nfp.TokenSubTypeCurrencyString {
704+
nf.currencyString = part.Token.TValue
632705
}
633-
nf.localCode = strings.ToUpper(part.Token.TValue)
634706
}
635707
return
636708
}
@@ -1039,17 +1111,17 @@ func (nf *numberFormat) minutesHandler(token nfp.Token) {
10391111
// secondsHandler will be handling seconds in the date and times types tokens
10401112
// for a number format expression.
10411113
func (nf *numberFormat) secondsHandler(token nfp.Token) {
1042-
nf.seconds = strings.Contains(strings.ToUpper(token.TValue), "S")
1043-
if nf.seconds {
1044-
switch len(token.TValue) {
1045-
case 1:
1046-
nf.result += strconv.Itoa(nf.t.Second())
1047-
return
1048-
default:
1049-
nf.result += fmt.Sprintf("%02d", nf.t.Second())
1050-
return
1051-
}
1114+
if nf.seconds = strings.Contains(strings.ToUpper(token.TValue), "S"); !nf.seconds {
1115+
return
1116+
}
1117+
if !nf.useMillisecond {
1118+
nf.t = nf.t.Add(time.Duration(math.Round(float64(nf.t.Nanosecond())/1e9)) * time.Second)
10521119
}
1120+
if len(token.TValue) == 1 {
1121+
nf.result += strconv.Itoa(nf.t.Second())
1122+
return
1123+
}
1124+
nf.result += fmt.Sprintf("%02d", nf.t.Second())
10531125
}
10541126

10551127
// elapsedDateTimesHandler will be handling elapsed date and times types tokens
@@ -1114,23 +1186,15 @@ func (nf *numberFormat) secondsNext(i int) bool {
11141186
// negativeHandler will be handling negative selection for a number format
11151187
// expression.
11161188
func (nf *numberFormat) negativeHandler() (result string) {
1117-
fmtNum := true
11181189
for _, token := range nf.section[nf.sectionIdx].Items {
11191190
if inStrSlice(supportedTokenTypes, token.TType, true) == -1 || token.TType == nfp.TokenTypeGeneral {
11201191
return nf.value
11211192
}
1122-
if inStrSlice(supportedNumberTokenTypes, token.TType, true) != -1 {
1123-
continue
1124-
}
11251193
if inStrSlice(supportedDateTimeTokenTypes, token.TType, true) != -1 {
11261194
return nf.value
11271195
}
1128-
fmtNum = false
1129-
}
1130-
if fmtNum {
1131-
return nf.numberHandler()
11321196
}
1133-
return nf.value
1197+
return nf.numberHandler()
11341198
}
11351199

11361200
// zeroHandler will be handling zero selection for a number format expression.

0 commit comments

Comments
 (0)