Skip to content

Commit

Permalink
This closes #1590, add the Japanese calendar number format support
Browse files Browse the repository at this point in the history
- The `GetFormControl` now support to get text, rich-text and font format of the form controls
- Update the unit tests and the documentation
  • Loading branch information
xuri committed Jul 30, 2023
1 parent a07c8cd commit 5fe30eb
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 98 deletions.
15 changes: 12 additions & 3 deletions cell.go
Original file line number Diff line number Diff line change
Expand Up @@ -1368,15 +1368,24 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
if fmtCode, ok := f.getBuiltInNumFmtCode(numFmtID); ok {
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
}
return f.applyNumFmt(c, styleSheet, numFmtID, date1904, cellType), err
}

// applyNumFmt provides a function to returns formatted cell value with custom
// number format code.
func (f *File) applyNumFmt(c *xlsxC, styleSheet *xlsxStyleSheet, numFmtID int, date1904 bool, cellType CellType) string {
if styleSheet.NumFmts == nil {
return c.V, err
return c.V
}
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtID {
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options), err
if xlsxFmt.FormatCode16 != "" {
return format(c.V, xlsxFmt.FormatCode16, date1904, cellType, f.options)
}
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options)
}
}
return c.V, err
return c.V
}

// prepareCellStyle provides a function to prepare style index of cell in
Expand Down
8 changes: 8 additions & 0 deletions cell_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,14 @@ func TestFormattedValueNilWorkbookPr(t *testing.T) {
assert.Equal(t, "43528", result)
}

func TestApplyNumFmt(t *testing.T) {
f := NewFile()
assert.Equal(t, "\u4EE4\u548C\u5143年9月1日", f.applyNumFmt(&xlsxC{V: "43709"},
&xlsxStyleSheet{NumFmts: &xlsxNumFmts{NumFmt: []*xlsxNumFmt{
{NumFmtID: 164, FormatCode16: "[$-ja-JP-x-gannen,80]ggge\"\"m\"\"d\"\";@"},
}}}, 164, false, CellTypeNumber))
}

func TestSharedStringsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
assert.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/richardlehane/mscfb v1.0.4
github.com/stretchr/testify v1.8.0
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9
github.com/xuri/nfp v0.0.0-20230723160540-a7d120392641
github.com/xuri/nfp v0.0.0-20230730012209-aee513b45ff4
golang.org/x/crypto v0.11.0
golang.org/x/image v0.5.0
golang.org/x/net v0.12.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PK
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
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-20230723160540-a7d120392641 h1:1SQuQwUorWlROdGAbsAJrMInj02yCUsYFNi/MzTJ6cA=
github.com/xuri/nfp v0.0.0-20230723160540-a7d120392641/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
github.com/xuri/nfp v0.0.0-20230730012209-aee513b45ff4 h1:7TXNzvlvE0E/oLDazWm2Xip72G9Su+jRzvziSxwO6Ww=
github.com/xuri/nfp v0.0.0-20230730012209-aee513b45ff4/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=
Expand Down
181 changes: 109 additions & 72 deletions numfmt.go

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions numfmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,18 @@ func TestNumFmt(t *testing.T) {
{"44835.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0e15 01 2022 4:32 AM"},
{"44866.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0e1e 01 2022 4:32 AM"},
{"44896.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0e18 01 2022 4:32 AM"},
{"43709", "[$-411]ge\"\"m\"\"d\"\";@", "R1年9月1日"},
{"43709", "[$-411]gge\"\"m\"\"d\"\";@", "\u4EE41年9月1日"},
{"43709", "[$-411]ggge\"\"m\"\"d\"\";@", "\u4EE4\u548C1年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]ge\"\"m\"\"d\"\";@", "R1年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]gge\"\"m\"\"d\"\";@", "\u4EE4\u5143年9月1日"},
{"43709", "[$-ja-JP-x-gannen,80]ggge\"\"m\"\"d\"\";@", "\u4EE4\u548C\u5143年9月1日"},
{"43466.189571759256", "[$-411]ge\"\"m\"\"d\"\";@", "H31年1月1日"},
{"43466.189571759256", "[$-411]gge\"\"m\"\"d\"\";@", "\u5E7331年1月1日"},
{"43466.189571759256", "[$-411]ggge\"\"m\"\"d\"\";@", "\u5E73\u621031年1月1日"},
{"44896.18957170139", "[$-411]ge\"\"m\"\"d\"\";@", "R4年12月1日"},
{"44896.18957170139", "[$-411]gge\"\"m\"\"d\"\";@", "\u4EE44年12月1日"},
{"44896.18957170139", "[$-411]ggge\"\"m\"\"d\"\";@", "\u4EE4\u548C4年12月1日"},
{"44562.189571759256", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0f5f\u0fb3\u0f0b\u0f21 01 2022 4:32 \u0f66\u0f94\u0f0b\u0f51\u0fb2\u0f7c\u0f0b"},
{"44593.189571759256", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0f5f\u0fb3\u0f0b\u0f22 01 2022 4:32 \u0f66\u0f94\u0f0b\u0f51\u0fb2\u0f7c\u0f0b"},
{"44621.18957170139", "[$-51]mmm dd yyyy h:mm AM/PM", "\u0f5f\u0fb3\u0f0b\u0f23 01 2022 4:32 \u0f66\u0f94\u0f0b\u0f51\u0fb2\u0f7c\u0f0b"},
Expand Down
114 changes: 104 additions & 10 deletions vml.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,15 @@ func (f *File) DeleteFormControl(sheet, cell string) error {
for i, sp := range vml.Shape {
var shapeVal decodeShapeVal
if err = xml.Unmarshal([]byte(fmt.Sprintf("<shape>%s</shape>", sp.Val)), &shapeVal); err == nil &&
shapeVal.ClientData.ObjectType != "Note" && shapeVal.ClientData.Column == col-1 && shapeVal.ClientData.Row == row-1 {
vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...)
break
shapeVal.ClientData.ObjectType != "Note" && shapeVal.ClientData.Anchor != "" {
leftCol, topRow, err := extractAnchorCell(shapeVal.ClientData.Anchor)
if err != nil {
return err
}
if leftCol == col-1 && topRow == row-1 {
vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...)
break
}
}
}
f.VMLDrawing[drawingVML] = vml
Expand Down Expand Up @@ -454,7 +460,7 @@ func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
c, ok := f.Pkg.Load(path)
if ok && c != nil {
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
if err := f.xmlNewDecoder(bytes.NewReader(bytesReplace(namespaceStrictToTransitional(c.([]byte)), []byte("<br>\r\n"), []byte("<br></br>\r\n"), -1))).
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
return nil, err
}
Expand Down Expand Up @@ -574,6 +580,9 @@ func formCtrlText(opts *vmlOptions) []vmlFont {
if run.Font.Underline == "single" {
fnt.Content = "<u>" + fnt.Content + "</u>"
}
if run.Font.Underline == "double" {
fnt.Content = "<u class=\"font1\">" + fnt.Content + "</u>"
}
if run.Font.Italic {
fnt.Content = "<i>" + fnt.Content + "</i>"
}
Expand Down Expand Up @@ -765,8 +774,8 @@ func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor stri
ObjectType: preset.objectType,
Anchor: anchor,
AutoFill: preset.autoFill,
Row: row - 1,
Column: col - 1,
Row: intPtr(row - 1),
Column: intPtr(col - 1),
TextHAlign: preset.textHAlign,
TextVAlign: preset.textVAlign,
NoThreeD: preset.noThreeD,
Expand Down Expand Up @@ -885,8 +894,8 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er
}

// GetFormControls retrieves all form controls in a worksheet by a given
// worksheet name. Note that, this function does not support getting the width,
// height, text, rich text, and format currently.
// worksheet name. Note that, this function does not support getting the width
// and height of the form controls currently.
func (f *File) GetFormControls(sheet string) ([]FormControl, error) {
var formControls []FormControl
// Read sheet data
Expand Down Expand Up @@ -949,9 +958,18 @@ func extractFormControl(clientData string) (FormControl, error) {
return formControl, err
}
for formCtrlType, preset := range formCtrlPresets {
if shapeVal.ClientData.ObjectType == preset.objectType {
if shapeVal.ClientData.ObjectType == preset.objectType && shapeVal.ClientData.Anchor != "" {
formControl.Paragraph = extractVMLFont(shapeVal.TextBox.Div.Font)
if len(formControl.Paragraph) > 0 && formControl.Paragraph[0].Font == nil {
formControl.Text = formControl.Paragraph[0].Text
formControl.Paragraph = formControl.Paragraph[1:]
}
formControl.Type = formCtrlType
if formControl.Cell, err = CoordinatesToCellName(shapeVal.ClientData.Column+1, shapeVal.ClientData.Row+1); err != nil {
col, row, err := extractAnchorCell(shapeVal.ClientData.Anchor)
if err != nil {
return formControl, err
}
if formControl.Cell, err = CoordinatesToCellName(col+1, row+1); err != nil {
return formControl, err
}
formControl.Macro = shapeVal.ClientData.FmlaMacro
Expand All @@ -967,3 +985,79 @@ func extractFormControl(clientData string) (FormControl, error) {
}
return formControl, err
}

// extractAnchorCell extract left-top cell coordinates from given VML anchor
// comma-separated list values.
func extractAnchorCell(anchor string) (int, int, error) {
var (
leftCol, topRow int
err error
pos = strings.Split(anchor, ",")
)
if len(pos) != 8 {
return leftCol, topRow, ErrParameterInvalid
}
leftCol, err = strconv.Atoi(strings.TrimSpace(pos[0]))
if err != nil {
return leftCol, topRow, ErrColumnNumber
}
topRow, err = strconv.Atoi(strings.TrimSpace(pos[2]))
return leftCol, topRow, err
}

// extractVMLFont extract rich-text and font format from given VML font element.
func extractVMLFont(font []decodeVMLFont) []RichTextRun {
var runs []RichTextRun
extractU := func(u *decodeVMLFontU, run *RichTextRun) {
if u == nil {
return
}
run.Text += u.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Underline = "single"
if u.Class == "font1" {
run.Font.Underline = "double"
}
}
extractI := func(i *decodeVMLFontI, run *RichTextRun) {
if i == nil {
return
}
extractU(i.U, run)
run.Text += i.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Italic = true
}
extractB := func(b *decodeVMLFontB, run *RichTextRun) {
if b == nil {
return
}
extractI(b.I, run)
run.Text += b.Val
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Bold = true
}
for _, fnt := range font {
var run RichTextRun
extractB(fnt.B, &run)
extractI(fnt.I, &run)
extractU(fnt.U, &run)
run.Text += fnt.Val
if fnt.Face != "" || fnt.Size > 0 || fnt.Color != "" {
if run.Font == nil {
run.Font = &Font{}
}
run.Font.Family = fnt.Face
run.Font.Size = float64(fnt.Size / 20)
run.Font.Color = fnt.Color
}
runs = append(runs, run)
}
return runs
}
51 changes: 47 additions & 4 deletions vmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ type xClientData struct {
FmlaMacro string `xml:"x:FmlaMacro,omitempty"`
TextHAlign string `xml:"x:TextHAlign,omitempty"`
TextVAlign string `xml:"x:TextVAlign,omitempty"`
Row int `xml:"x:Row"`
Column int `xml:"x:Column"`
Row *int `xml:"x:Row"`
Column *int `xml:"x:Column"`
Checked int `xml:"x:Checked,omitempty"`
FmlaLink string `xml:"x:FmlaLink,omitempty"`
NoThreeD *string `xml:"x:NoThreeD"`
Expand Down Expand Up @@ -185,16 +185,59 @@ type decodeShape struct {
// decodeShapeVal defines the structure used to parse the sub-element of the
// shape in the file xl/drawings/vmlDrawing%d.vml.
type decodeShapeVal struct {
TextBox decodeVMLTextBox `xml:"textbox"`
ClientData decodeVMLClientData `xml:"ClientData"`
}

// decodeVMLFontU defines the structure used to parse the u element in the VML.
type decodeVMLFontU struct {
Class string `xml:"class,attr"`
Val string `xml:",chardata"`
}

// decodeVMLFontI defines the structure used to parse the i element in the VML.
type decodeVMLFontI struct {
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}

// decodeVMLFontB defines the structure used to parse the b element in the VML.
type decodeVMLFontB struct {
I *decodeVMLFontI `xml:"i"`
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}

// decodeVMLFont defines the structure used to parse the font element in the VML.
type decodeVMLFont struct {
Face string `xml:"face,attr,omitempty"`
Size uint `xml:"size,attr,omitempty"`
Color string `xml:"color,attr,omitempty"`
B *decodeVMLFontB `xml:"b"`
I *decodeVMLFontI `xml:"i"`
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}

// decodeVMLDiv defines the structure used to parse the div element in the VML.
type decodeVMLDiv struct {
Font []decodeVMLFont `xml:"font"`
}

// decodeVMLTextBox defines the structure used to parse the v:textbox element in
// the file xl/drawings/vmlDrawing%d.vml.
type decodeVMLTextBox struct {
Div decodeVMLDiv `xml:"div"`
}

// decodeVMLClientData defines the structure used to parse the x:ClientData
// element in the file xl/drawings/vmlDrawing%d.vml.
type decodeVMLClientData struct {
ObjectType string `xml:"ObjectType,attr"`
Anchor string
FmlaMacro string
Column int
Row int
Column *int
Row *int
Checked int
FmlaLink string
Val uint
Expand Down
Loading

0 comments on commit 5fe30eb

Please sign in to comment.