Skip to content

Commit 5fe30eb

Browse files
committed
This closes #1590, add the Japanese calendar number format support
- The `GetFormControl` now support to get text, rich-text and font format of the form controls - Update the unit tests and the documentation
1 parent a07c8cd commit 5fe30eb

File tree

10 files changed

+347
-98
lines changed

10 files changed

+347
-98
lines changed

cell.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,15 +1368,24 @@ func (f *File) formattedValue(c *xlsxC, raw bool, cellType CellType) (string, er
13681368
if fmtCode, ok := f.getBuiltInNumFmtCode(numFmtID); ok {
13691369
return f.applyBuiltInNumFmt(c, fmtCode, numFmtID, date1904, cellType), err
13701370
}
1371+
return f.applyNumFmt(c, styleSheet, numFmtID, date1904, cellType), err
1372+
}
1373+
1374+
// applyNumFmt provides a function to returns formatted cell value with custom
1375+
// number format code.
1376+
func (f *File) applyNumFmt(c *xlsxC, styleSheet *xlsxStyleSheet, numFmtID int, date1904 bool, cellType CellType) string {
13711377
if styleSheet.NumFmts == nil {
1372-
return c.V, err
1378+
return c.V
13731379
}
13741380
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
13751381
if xlsxFmt.NumFmtID == numFmtID {
1376-
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options), err
1382+
if xlsxFmt.FormatCode16 != "" {
1383+
return format(c.V, xlsxFmt.FormatCode16, date1904, cellType, f.options)
1384+
}
1385+
return format(c.V, xlsxFmt.FormatCode, date1904, cellType, f.options)
13771386
}
13781387
}
1379-
return c.V, err
1388+
return c.V
13801389
}
13811390

13821391
// prepareCellStyle provides a function to prepare style index of cell in

cell_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,14 @@ func TestFormattedValueNilWorkbookPr(t *testing.T) {
927927
assert.Equal(t, "43528", result)
928928
}
929929

930+
func TestApplyNumFmt(t *testing.T) {
931+
f := NewFile()
932+
assert.Equal(t, "\u4EE4\u548C\u5143年9月1日", f.applyNumFmt(&xlsxC{V: "43709"},
933+
&xlsxStyleSheet{NumFmts: &xlsxNumFmts{NumFmt: []*xlsxNumFmt{
934+
{NumFmtID: 164, FormatCode16: "[$-ja-JP-x-gannen,80]ggge\"\"m\"\"d\"\";@"},
935+
}}}, 164, false, CellTypeNumber))
936+
}
937+
930938
func TestSharedStringsError(t *testing.T) {
931939
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipXMLSizeLimit: 128})
932940
assert.NoError(t, err)

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-20230723160540-a7d120392641
10+
github.com/xuri/nfp v0.0.0-20230730012209-aee513b45ff4
1111
golang.org/x/crypto v0.11.0
1212
golang.org/x/image v0.5.0
1313
golang.org/x/net v0.12.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-20230723160540-a7d120392641 h1:1SQuQwUorWlROdGAbsAJrMInj02yCUsYFNi/MzTJ6cA=
21-
github.com/xuri/nfp v0.0.0-20230723160540-a7d120392641/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
20+
github.com/xuri/nfp v0.0.0-20230730012209-aee513b45ff4 h1:7TXNzvlvE0E/oLDazWm2Xip72G9Su+jRzvziSxwO6Ww=
21+
github.com/xuri/nfp v0.0.0-20230730012209-aee513b45ff4/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: 109 additions & 72 deletions
Large diffs are not rendered by default.

numfmt_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,18 @@ func TestNumFmt(t *testing.T) {
420420
{"44835.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0e15 01 2022 4:32 AM"},
421421
{"44866.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0e1e 01 2022 4:32 AM"},
422422
{"44896.18957170139", "[$-41E]mmmmm dd yyyy h:mm AM/PM", "\u0e18 01 2022 4:32 AM"},
423+
{"43709", "[$-411]ge\"\"m\"\"d\"\";@", "R1年9月1日"},
424+
{"43709", "[$-411]gge\"\"m\"\"d\"\";@", "\u4EE41年9月1日"},
425+
{"43709", "[$-411]ggge\"\"m\"\"d\"\";@", "\u4EE4\u548C1年9月1日"},
426+
{"43709", "[$-ja-JP-x-gannen,80]ge\"\"m\"\"d\"\";@", "R1年9月1日"},
427+
{"43709", "[$-ja-JP-x-gannen,80]gge\"\"m\"\"d\"\";@", "\u4EE4\u5143年9月1日"},
428+
{"43709", "[$-ja-JP-x-gannen,80]ggge\"\"m\"\"d\"\";@", "\u4EE4\u548C\u5143年9月1日"},
429+
{"43466.189571759256", "[$-411]ge\"\"m\"\"d\"\";@", "H31年1月1日"},
430+
{"43466.189571759256", "[$-411]gge\"\"m\"\"d\"\";@", "\u5E7331年1月1日"},
431+
{"43466.189571759256", "[$-411]ggge\"\"m\"\"d\"\";@", "\u5E73\u621031年1月1日"},
432+
{"44896.18957170139", "[$-411]ge\"\"m\"\"d\"\";@", "R4年12月1日"},
433+
{"44896.18957170139", "[$-411]gge\"\"m\"\"d\"\";@", "\u4EE44年12月1日"},
434+
{"44896.18957170139", "[$-411]ggge\"\"m\"\"d\"\";@", "\u4EE4\u548C4年12月1日"},
423435
{"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"},
424436
{"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"},
425437
{"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"},

vml.go

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,15 @@ func (f *File) DeleteFormControl(sheet, cell string) error {
420420
for i, sp := range vml.Shape {
421421
var shapeVal decodeShapeVal
422422
if err = xml.Unmarshal([]byte(fmt.Sprintf("<shape>%s</shape>", sp.Val)), &shapeVal); err == nil &&
423-
shapeVal.ClientData.ObjectType != "Note" && shapeVal.ClientData.Column == col-1 && shapeVal.ClientData.Row == row-1 {
424-
vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...)
425-
break
423+
shapeVal.ClientData.ObjectType != "Note" && shapeVal.ClientData.Anchor != "" {
424+
leftCol, topRow, err := extractAnchorCell(shapeVal.ClientData.Anchor)
425+
if err != nil {
426+
return err
427+
}
428+
if leftCol == col-1 && topRow == row-1 {
429+
vml.Shape = append(vml.Shape[:i], vml.Shape[i+1:]...)
430+
break
431+
}
426432
}
427433
}
428434
f.VMLDrawing[drawingVML] = vml
@@ -454,7 +460,7 @@ func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
454460
c, ok := f.Pkg.Load(path)
455461
if ok && c != nil {
456462
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
457-
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
463+
if err := f.xmlNewDecoder(bytes.NewReader(bytesReplace(namespaceStrictToTransitional(c.([]byte)), []byte("<br>\r\n"), []byte("<br></br>\r\n"), -1))).
458464
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
459465
return nil, err
460466
}
@@ -574,6 +580,9 @@ func formCtrlText(opts *vmlOptions) []vmlFont {
574580
if run.Font.Underline == "single" {
575581
fnt.Content = "<u>" + fnt.Content + "</u>"
576582
}
583+
if run.Font.Underline == "double" {
584+
fnt.Content = "<u class=\"font1\">" + fnt.Content + "</u>"
585+
}
577586
if run.Font.Italic {
578587
fnt.Content = "<i>" + fnt.Content + "</i>"
579588
}
@@ -765,8 +774,8 @@ func (f *File) addFormCtrlShape(preset formCtrlPreset, col, row int, anchor stri
765774
ObjectType: preset.objectType,
766775
Anchor: anchor,
767776
AutoFill: preset.autoFill,
768-
Row: row - 1,
769-
Column: col - 1,
777+
Row: intPtr(row - 1),
778+
Column: intPtr(col - 1),
770779
TextHAlign: preset.textHAlign,
771780
TextVAlign: preset.textVAlign,
772781
NoThreeD: preset.noThreeD,
@@ -885,8 +894,8 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er
885894
}
886895

887896
// GetFormControls retrieves all form controls in a worksheet by a given
888-
// worksheet name. Note that, this function does not support getting the width,
889-
// height, text, rich text, and format currently.
897+
// worksheet name. Note that, this function does not support getting the width
898+
// and height of the form controls currently.
890899
func (f *File) GetFormControls(sheet string) ([]FormControl, error) {
891900
var formControls []FormControl
892901
// Read sheet data
@@ -949,9 +958,18 @@ func extractFormControl(clientData string) (FormControl, error) {
949958
return formControl, err
950959
}
951960
for formCtrlType, preset := range formCtrlPresets {
952-
if shapeVal.ClientData.ObjectType == preset.objectType {
961+
if shapeVal.ClientData.ObjectType == preset.objectType && shapeVal.ClientData.Anchor != "" {
962+
formControl.Paragraph = extractVMLFont(shapeVal.TextBox.Div.Font)
963+
if len(formControl.Paragraph) > 0 && formControl.Paragraph[0].Font == nil {
964+
formControl.Text = formControl.Paragraph[0].Text
965+
formControl.Paragraph = formControl.Paragraph[1:]
966+
}
953967
formControl.Type = formCtrlType
954-
if formControl.Cell, err = CoordinatesToCellName(shapeVal.ClientData.Column+1, shapeVal.ClientData.Row+1); err != nil {
968+
col, row, err := extractAnchorCell(shapeVal.ClientData.Anchor)
969+
if err != nil {
970+
return formControl, err
971+
}
972+
if formControl.Cell, err = CoordinatesToCellName(col+1, row+1); err != nil {
955973
return formControl, err
956974
}
957975
formControl.Macro = shapeVal.ClientData.FmlaMacro
@@ -967,3 +985,79 @@ func extractFormControl(clientData string) (FormControl, error) {
967985
}
968986
return formControl, err
969987
}
988+
989+
// extractAnchorCell extract left-top cell coordinates from given VML anchor
990+
// comma-separated list values.
991+
func extractAnchorCell(anchor string) (int, int, error) {
992+
var (
993+
leftCol, topRow int
994+
err error
995+
pos = strings.Split(anchor, ",")
996+
)
997+
if len(pos) != 8 {
998+
return leftCol, topRow, ErrParameterInvalid
999+
}
1000+
leftCol, err = strconv.Atoi(strings.TrimSpace(pos[0]))
1001+
if err != nil {
1002+
return leftCol, topRow, ErrColumnNumber
1003+
}
1004+
topRow, err = strconv.Atoi(strings.TrimSpace(pos[2]))
1005+
return leftCol, topRow, err
1006+
}
1007+
1008+
// extractVMLFont extract rich-text and font format from given VML font element.
1009+
func extractVMLFont(font []decodeVMLFont) []RichTextRun {
1010+
var runs []RichTextRun
1011+
extractU := func(u *decodeVMLFontU, run *RichTextRun) {
1012+
if u == nil {
1013+
return
1014+
}
1015+
run.Text += u.Val
1016+
if run.Font == nil {
1017+
run.Font = &Font{}
1018+
}
1019+
run.Font.Underline = "single"
1020+
if u.Class == "font1" {
1021+
run.Font.Underline = "double"
1022+
}
1023+
}
1024+
extractI := func(i *decodeVMLFontI, run *RichTextRun) {
1025+
if i == nil {
1026+
return
1027+
}
1028+
extractU(i.U, run)
1029+
run.Text += i.Val
1030+
if run.Font == nil {
1031+
run.Font = &Font{}
1032+
}
1033+
run.Font.Italic = true
1034+
}
1035+
extractB := func(b *decodeVMLFontB, run *RichTextRun) {
1036+
if b == nil {
1037+
return
1038+
}
1039+
extractI(b.I, run)
1040+
run.Text += b.Val
1041+
if run.Font == nil {
1042+
run.Font = &Font{}
1043+
}
1044+
run.Font.Bold = true
1045+
}
1046+
for _, fnt := range font {
1047+
var run RichTextRun
1048+
extractB(fnt.B, &run)
1049+
extractI(fnt.I, &run)
1050+
extractU(fnt.U, &run)
1051+
run.Text += fnt.Val
1052+
if fnt.Face != "" || fnt.Size > 0 || fnt.Color != "" {
1053+
if run.Font == nil {
1054+
run.Font = &Font{}
1055+
}
1056+
run.Font.Family = fnt.Face
1057+
run.Font.Size = float64(fnt.Size / 20)
1058+
run.Font.Color = fnt.Color
1059+
}
1060+
runs = append(runs, run)
1061+
}
1062+
return runs
1063+
}

vmlDrawing.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ type xClientData struct {
137137
FmlaMacro string `xml:"x:FmlaMacro,omitempty"`
138138
TextHAlign string `xml:"x:TextHAlign,omitempty"`
139139
TextVAlign string `xml:"x:TextVAlign,omitempty"`
140-
Row int `xml:"x:Row"`
141-
Column int `xml:"x:Column"`
140+
Row *int `xml:"x:Row"`
141+
Column *int `xml:"x:Column"`
142142
Checked int `xml:"x:Checked,omitempty"`
143143
FmlaLink string `xml:"x:FmlaLink,omitempty"`
144144
NoThreeD *string `xml:"x:NoThreeD"`
@@ -185,16 +185,59 @@ type decodeShape struct {
185185
// decodeShapeVal defines the structure used to parse the sub-element of the
186186
// shape in the file xl/drawings/vmlDrawing%d.vml.
187187
type decodeShapeVal struct {
188+
TextBox decodeVMLTextBox `xml:"textbox"`
188189
ClientData decodeVMLClientData `xml:"ClientData"`
189190
}
190191

192+
// decodeVMLFontU defines the structure used to parse the u element in the VML.
193+
type decodeVMLFontU struct {
194+
Class string `xml:"class,attr"`
195+
Val string `xml:",chardata"`
196+
}
197+
198+
// decodeVMLFontI defines the structure used to parse the i element in the VML.
199+
type decodeVMLFontI struct {
200+
U *decodeVMLFontU `xml:"u"`
201+
Val string `xml:",chardata"`
202+
}
203+
204+
// decodeVMLFontB defines the structure used to parse the b element in the VML.
205+
type decodeVMLFontB struct {
206+
I *decodeVMLFontI `xml:"i"`
207+
U *decodeVMLFontU `xml:"u"`
208+
Val string `xml:",chardata"`
209+
}
210+
211+
// decodeVMLFont defines the structure used to parse the font element in the VML.
212+
type decodeVMLFont struct {
213+
Face string `xml:"face,attr,omitempty"`
214+
Size uint `xml:"size,attr,omitempty"`
215+
Color string `xml:"color,attr,omitempty"`
216+
B *decodeVMLFontB `xml:"b"`
217+
I *decodeVMLFontI `xml:"i"`
218+
U *decodeVMLFontU `xml:"u"`
219+
Val string `xml:",chardata"`
220+
}
221+
222+
// decodeVMLDiv defines the structure used to parse the div element in the VML.
223+
type decodeVMLDiv struct {
224+
Font []decodeVMLFont `xml:"font"`
225+
}
226+
227+
// decodeVMLTextBox defines the structure used to parse the v:textbox element in
228+
// the file xl/drawings/vmlDrawing%d.vml.
229+
type decodeVMLTextBox struct {
230+
Div decodeVMLDiv `xml:"div"`
231+
}
232+
191233
// decodeVMLClientData defines the structure used to parse the x:ClientData
192234
// element in the file xl/drawings/vmlDrawing%d.vml.
193235
type decodeVMLClientData struct {
194236
ObjectType string `xml:"ObjectType,attr"`
237+
Anchor string
195238
FmlaMacro string
196-
Column int
197-
Row int
239+
Column *int
240+
Row *int
198241
Checked int
199242
FmlaLink string
200243
Val uint

0 commit comments

Comments
 (0)