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 user defined numFmtId to numFmtCode mapping #1524

Closed
wants to merge 1 commit into from
Closed
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
4 changes: 4 additions & 0 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,10 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
numFmtCode, ok := runNumFmtIdToCodeHook(f, numFmtID)
if ok {
return format(c.V, numFmtCode, date1904, cellType), err
}
if ok := builtInNumFmtFunc[numFmtID]; ok != nil {
return ok(c.V, builtInNumFmt[numFmtID], date1904, cellType), err
}
Expand Down
69 changes: 69 additions & 0 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,75 @@ func TestFormattedValue(t *testing.T) {
}
}

func TestFormattedValueWithWpsFile(t *testing.T) {

testWpsIdCodeMap := map[int]string{
14: "yyyy/m/d",
31: "yyyy\"年\"m\"月\"d\"日\"",
}

testWpsIdCodeMapHook := func(info NumFmtIdToCodeHookInfo, numFmtId int) (code string, ok bool) {
isWps := false
allAttrs:
for _, attrs := range info.XmlAttr {
for _, attr := range attrs {
if strings.Contains(attr.Value, "wps") {
isWps = true
break allAttrs
}
}
}
if !isWps {
return
}
code, ok = testWpsIdCodeMap[numFmtId]
return
}

testCases := []struct {
name string
cellName string
v string
numFmtIdCodeHook NumFmtIdToCodeHook
}{
{
name: "yy/m/d cell",
cellName: "A1",
v: "23/1/1",
},
{
name: "yyyy/m/d cell",
cellName: "A2",
v: "01-01-23",
},
{
name: "yyyy/m/d cell has hook",
cellName: "A2",
v: "2023/1/1",
numFmtIdCodeHook: testWpsIdCodeMapHook,
},
{
name: "yyyy\"年\"m\"月\"d\"日\" cell",
cellName: "A3",
v: "2023年1月1日",
numFmtIdCodeHook: testWpsIdCodeMapHook,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "NumFmtCases.xlsx"), Options{
NumFmtIdToCodeHook: testCase.numFmtIdCodeHook,
})
if !assert.NoError(t, err) {
t.FailNow()
}
val, err := f.GetCellValue("Sheet1", testCase.cellName)
assert.NoError(t, err)
assert.Equal(t, testCase.v, val)
})
}
}

func TestFormattedValueNilXfs(t *testing.T) {
// Set the CellXfs to nil and verify that the formattedValue function does not crash
f := NewFile()
Expand Down
14 changes: 9 additions & 5 deletions excelize.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,16 @@ 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.
//
// NumFmtIdToCodeHook User-defined mapping function for numFmtId to numFmtCode
// if NumFmtIdToCodeHook function is nil will use default mapping
type Options struct {
MaxCalcIterations uint
Password string
RawCellValue bool
UnzipSizeLimit int64
UnzipXMLSizeLimit int64
MaxCalcIterations uint
Password string
RawCellValue bool
UnzipSizeLimit int64
UnzipXMLSizeLimit int64
NumFmtIdToCodeHook NumFmtIdToCodeHook
}

// OpenFile take the name of an spreadsheet file and returns a populated
Expand Down
23 changes: 23 additions & 0 deletions hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package excelize

import "encoding/xml"

type NumFmtIdToCodeHookInfo struct {
XmlAttr map[string][]xml.Attr
NumFmt []*xlsxNumFmt
}

// NumFmtIdToCodeHook User-defined mapping function for numFmtId to numFmtCode
// if return ok is false, will use default mapping
type NumFmtIdToCodeHook func(info NumFmtIdToCodeHookInfo, numFmtId int) (numFmtCode string, ok bool)

func runNumFmtIdToCodeHook(file *File, numFmtId int) (numFmtCode string, ok bool) {
if file == nil || file.options == nil || file.options.NumFmtIdToCodeHook == nil {
return
}
hookInfo := NumFmtIdToCodeHookInfo{XmlAttr: file.xmlAttr}
if file.Styles != nil && file.Styles.NumFmts != nil {
hookInfo.NumFmt = file.Styles.NumFmts.NumFmt
}
return file.options.NumFmtIdToCodeHook(hookInfo, numFmtId)
}
Binary file added test/NumFmtCases.xlsx
Binary file not shown.