Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support locale option for formattedValue & set locale numFmtMap #1525

Merged
merged 1 commit into from
May 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,15 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
return "", nil
}

// applyBuiltInNumFmt provides a function to returns a value after formatted
// with built-in number format code, or specified sort date format code.
func (f *File) applyBuiltInNumFmt(c *xlsxC, fmtCode string, numFmtID int, date1904 bool, cellType CellType) string {
if numFmtID == 14 && f.options != nil && f.options.ShortDateFmtCode != "" {
fmtCode = f.options.ShortDateFmtCode
}
return format(c.V, fmtCode, date1904, cellType, f.options)
}

// formattedValue provides a function to returns a value after formatted. If
// it is possible to apply a format to the cell value, it will do so, if not
// then an error will be returned, along with the raw value of the cell.
Expand Down Expand Up @@ -1366,14 +1375,14 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
date1904 = wb.WorkbookPr.Date1904
}
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
return format(c.V, fmtCode, date1904, cellType), err
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
}
if styleSheet.NumFmts == nil {
return c.V, err
}
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtID {
return format(c.V, xlsxFmt.FormatCode, date1904, cellType), err
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options), err
}
}
return c.V, err
Expand Down
4 changes: 2 additions & 2 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ func TestFormattedValue(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "311", result)

assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber))
assert.Equal(t, "0_0", format("0_0", "", false, CellTypeNumber, nil))

// Test format value with unsupported charset workbook
f.WorkBook = nil
Expand All @@ -887,7 +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")

assert.Equal(t, "text", format("text", "0", false, CellTypeNumber))
assert.Equal(t, "text", format("text", "0", false, CellTypeNumber, nil))
}

func TestFormattedValueNilXfs(t *testing.T) {
Expand Down
15 changes: 15 additions & 0 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,27 @@ type charsetTranscoderFn func(charset string, input io.Reader) (rdr io.Reader, e
// temporary directory when the file size is over this value, this value
// should be less than or equal to UnzipSizeLimit, the default value is
// 16MB.
//
// ShortDateFmtCode specifies the short date number format code. In the
// spreadsheet applications, date formats display date and time serial numbers
// as date values. Date formats that begin with an asterisk (*) respond to
// changes in regional date and time settings that are specified for the
// operating system. Formats without an asterisk are not affected by operating
// system settings. The ShortDateFmtCode used for specifies apply date formats
// that begin with an asterisk.
//
// LongDateFmtCode specifies the long date number format code.
//
// LongTimeFmtCode specifies the long time number format code.
type Options struct {
MaxCalcIterations uint
Password string
RawCellValue bool
UnzipSizeLimit int64
UnzipXMLSizeLimit int64
ShortDateFmtCode string
LongDateFmtCode string
LongTimeFmtCode string
}

// OpenFile take the name of an spreadsheet file and returns a populated
Expand Down
3 changes: 2 additions & 1 deletion file.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
// For example:
//
// f := NewFile()
func NewFile() *File {
func NewFile(opts ...Options) *File {
f := newFile()
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
Expand All @@ -49,6 +49,7 @@ func NewFile() *File {
ws, _ := f.workSheetReader("Sheet1")
f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
f.Theme, _ = f.themeReader()
f.options = getOptions(opts...)
return f
}

Expand Down
33 changes: 23 additions & 10 deletions numfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type languageInfo struct {
// numberFormat directly maps the number format parser runtime required
// fields.
type numberFormat struct {
opts *Options
cellType CellType
section []nfp.Section
t time.Time
Expand Down Expand Up @@ -396,9 +397,9 @@ func (nf *numberFormat) prepareNumberic(value string) {
// format provides a function to return a string parse by number format
// expression. If the given number format is not supported, this will return
// the original cell value.
func format(value, numFmt string, date1904 bool, cellType CellType) string {
func format(value, numFmt string, date1904 bool, cellType CellType, opts *Options) string {
p := nfp.NumberFormatParser()
nf := numberFormat{section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
nf := numberFormat{opts: opts, section: p.Parse(numFmt), value: value, date1904: date1904, cellType: cellType}
nf.number, nf.valueSectionType = nf.getValueSectionType(value)
nf.prepareNumberic(value)
for i, section := range nf.section {
Expand Down Expand Up @@ -480,7 +481,7 @@ func (nf *numberFormat) printNumberLiteral(text string) string {
}
for i, token := range nf.section[nf.sectionIdx].Items {
if token.TType == nfp.TokenTypeCurrencyLanguage {
if err := nf.currencyLanguageHandler(i, token); err != nil {
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
return nf.value
}
result += nf.currencyString
Expand Down Expand Up @@ -616,7 +617,7 @@ func (nf *numberFormat) dateTimeHandler() string {
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 := nf.currencyLanguageHandler(i, token); err != nil {
if err, changeNumFmtCode := nf.currencyLanguageHandler(i, token); err != nil || changeNumFmtCode {
return nf.value
}
nf.result += nf.currencyString
Expand Down Expand Up @@ -687,24 +688,36 @@ func (nf *numberFormat) positiveHandler() string {

// 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) {
func (nf *numberFormat) currencyLanguageHandler(i int, token nfp.Token) (error, bool) {
for _, part := range token.Parts {
if inStrSlice(supportedTokenTypes, part.Token.TType, true) == -1 {
err = ErrUnsupportedNumberFormat
return
return ErrUnsupportedNumberFormat, false
}
if part.Token.TType == nfp.TokenSubTypeLanguageInfo {
if strings.EqualFold(part.Token.TValue, "F800") { // [$-x-sysdate]
if nf.opts != nil && nf.opts.LongDateFmtCode != "" {
nf.value = format(nf.value, nf.opts.LongDateFmtCode, nf.date1904, nf.cellType, nf.opts)
return nil, true
}
part.Token.TValue = "409"
}
if strings.EqualFold(part.Token.TValue, "F400") { // [$-x-systime]
if nf.opts != nil && nf.opts.LongTimeFmtCode != "" {
nf.value = format(nf.value, nf.opts.LongTimeFmtCode, nf.date1904, nf.cellType, nf.opts)
return nil, true
}
part.Token.TValue = "409"
}
if _, ok := supportedLanguageInfo[strings.ToUpper(part.Token.TValue)]; !ok {
err = ErrUnsupportedNumberFormat
return
return ErrUnsupportedNumberFormat, false
}
nf.localCode = strings.ToUpper(part.Token.TValue)
}
if part.Token.TType == nfp.TokenSubTypeCurrencyString {
nf.currencyString = part.Token.TValue
}
}
return
return nil, false
}

// localAmPm return AM/PM name by supported language ID.
Expand Down
23 changes: 20 additions & 3 deletions numfmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,8 @@ func TestNumFmt(t *testing.T) {
{"44835.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "O 01 2022 4:32 AM"},
{"44866.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "N 01 2022 4:32 AM"},
{"44896.18957170139", "[$-435]mmmmm dd yyyy h:mm AM/PM", "D 01 2022 4:32 AM"},
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "Tuesday, March 19, 2019"},
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37 PM"},
{"text_", "General", "text_"},
{"text_", "\"=====\"@@@\"--\"@\"----\"", "=====text_text_text_--text_----"},
{"0.0450685976001E+21", "0_);[Red]\\(0\\)", "45068597600100000000"},
Expand Down Expand Up @@ -1061,9 +1063,22 @@ func TestNumFmt(t *testing.T) {
{"1234.5678", "0.0xxx00", "1234.5678"},
{"-1234.5678", "00000.00###;s;", "-1234.5678"},
} {
result := format(item[0], item[1], false, CellTypeNumber)
result := format(item[0], item[1], false, CellTypeNumber, nil)
assert.Equal(t, item[2], result, item)
}
// Test format number with specified date and time format code
for _, item := range [][]string{
{"43543.503206018519", "[$-F800]dddd, mmmm dd, yyyy", "2019年3月19日"},
{"43543.503206018519", "[$-F400]h:mm:ss AM/PM", "12:04:37"},
} {
result := format(item[0], item[1], false, CellTypeNumber, &Options{
ShortDateFmtCode: "yyyy/m/d",
LongDateFmtCode: "yyyy\"\"M\"\"d\"\"",
LongTimeFmtCode: "H:mm:ss",
})
assert.Equal(t, item[2], result, item)
}
// Test format number with string data type cell value
for _, cellType := range []CellType{CellTypeSharedString, CellTypeInlineString} {
for _, item := range [][]string{
{"1234.5678", "General", "1234.5678"},
Expand All @@ -1073,10 +1088,12 @@ func TestNumFmt(t *testing.T) {
{"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"},
{"1234.5678", "\"text\"@", "text1234.5678"},
} {
result := format(item[0], item[1], false, cellType)
result := format(item[0], item[1], false, cellType, nil)
assert.Equal(t, item[2], result, item)
}
}
nf := numberFormat{}
assert.Equal(t, ErrUnsupportedNumberFormat, nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}}))
err, changeNumFmtCode := nf.currencyLanguageHandler(0, nfp.Token{Parts: []nfp.Part{{}}})
assert.Equal(t, ErrUnsupportedNumberFormat, err)
assert.False(t, changeNumFmtCode)
}
9 changes: 9 additions & 0 deletions rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,15 @@ func TestNumberFormats(t *testing.T) {
assert.Equal(t, expected, result, cell)
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNumberFormats.xlsx")))

f = NewFile(Options{ShortDateFmtCode: "yyyy/m/d"})
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 43543.503206018519))
numFmt14, err := f.NewStyle(&Style{NumFmt: 14})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", numFmt14))
result, err := f.GetCellValue("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, "2019/3/19", result, "A1")
}

func BenchmarkRows(b *testing.B) {
Expand Down