forked from p30928647/excelize
This closes #301, support delete and add radio button form control
- New exported function `DeleteFormControl` has been added - Update unit tests - Fix comments was missing after form control added - Update pull request templates
This commit is contained in:
parent
2c8dc5c150
commit
b667987084
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<!--- Please describe in detail how you tested your changes. -->
|
<!--- Please describe in detail how you tested your changes. -->
|
||||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||||
<!--- see how your change affects other areas of the code, etc. -->
|
<!--- See how your change affects other areas of the code, etc. -->
|
||||||
|
|
||||||
## Types of changes
|
## Types of changes
|
||||||
|
|
||||||
|
|
116
vml.go
116
vml.go
|
@ -28,6 +28,7 @@ type FormControlType byte
|
||||||
const (
|
const (
|
||||||
FormControlNote FormControlType = iota
|
FormControlNote FormControlType = iota
|
||||||
FormControlButton
|
FormControlButton
|
||||||
|
FormControlCheckbox
|
||||||
FormControlRadio
|
FormControlRadio
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,8 +115,9 @@ func (f *File) AddComment(sheet string, opts Comment) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteComment provides the method to delete comment in a sheet by given
|
// DeleteComment provides the method to delete comment in a worksheet by given
|
||||||
// worksheet name. For example, delete the comment in Sheet1!$A$30:
|
// worksheet name and cell reference. For example, delete the comment in
|
||||||
|
// Sheet1!$A$30:
|
||||||
//
|
//
|
||||||
// err := f.DeleteComment("Sheet1", "A30")
|
// err := f.DeleteComment("Sheet1", "A30")
|
||||||
func (f *File) DeleteComment(sheet, cell string) error {
|
func (f *File) DeleteComment(sheet, cell string) error {
|
||||||
|
@ -315,6 +317,80 @@ func (f *File) AddFormControl(sheet string, opts FormControl) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteFormControl provides the method to delete form control in a worksheet
|
||||||
|
// by given worksheet name and cell reference. For example, delete the form
|
||||||
|
// control in Sheet1!$A$30:
|
||||||
|
//
|
||||||
|
// err := f.DeleteFormControl("Sheet1", "A30")
|
||||||
|
func (f *File) DeleteFormControl(sheet, cell string) error {
|
||||||
|
ws, err := f.workSheetReader(sheet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
col, row, err := CellNameToCoordinates(cell)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ws.LegacyDrawing == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sheetRelationshipsDrawingVML := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID)
|
||||||
|
vmlID, _ := strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingVML, "../drawings/vmlDrawing"), ".vml"))
|
||||||
|
drawingVML := strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
||||||
|
vml := f.VMLDrawing[drawingVML]
|
||||||
|
if vml == nil {
|
||||||
|
vml = &vmlDrawing{
|
||||||
|
XMLNSv: "urn:schemas-microsoft-com:vml",
|
||||||
|
XMLNSo: "urn:schemas-microsoft-com:office:office",
|
||||||
|
XMLNSx: "urn:schemas-microsoft-com:office:excel",
|
||||||
|
XMLNSmv: "http://macVmlSchemaUri",
|
||||||
|
ShapeLayout: &xlsxShapeLayout{
|
||||||
|
Ext: "edit", IDmap: &xlsxIDmap{Ext: "edit", Data: vmlID},
|
||||||
|
},
|
||||||
|
ShapeType: &xlsxShapeType{
|
||||||
|
Stroke: &xlsxStroke{JoinStyle: "miter"},
|
||||||
|
VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// load exist VML shapes from xl/drawings/vmlDrawing%d.vml
|
||||||
|
d, err := f.decodeVMLDrawingReader(drawingVML)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if d != nil {
|
||||||
|
vml.ShapeType.ID = d.ShapeType.ID
|
||||||
|
vml.ShapeType.CoordSize = d.ShapeType.CoordSize
|
||||||
|
vml.ShapeType.Spt = d.ShapeType.Spt
|
||||||
|
vml.ShapeType.Path = d.ShapeType.Path
|
||||||
|
for _, v := range d.Shape {
|
||||||
|
s := xlsxShape{
|
||||||
|
ID: v.ID,
|
||||||
|
Type: v.Type,
|
||||||
|
Style: v.Style,
|
||||||
|
Button: v.Button,
|
||||||
|
Filled: v.Filled,
|
||||||
|
FillColor: v.FillColor,
|
||||||
|
InsetMode: v.InsetMode,
|
||||||
|
Stroked: v.Stroked,
|
||||||
|
StrokeColor: v.StrokeColor,
|
||||||
|
Val: v.Val,
|
||||||
|
}
|
||||||
|
vml.Shape = append(vml.Shape, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.VMLDrawing[drawingVML] = vml
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// countVMLDrawing provides a function to get VML drawing files count storage
|
// countVMLDrawing provides a function to get VML drawing files count storage
|
||||||
// in the folder xl/drawings.
|
// in the folder xl/drawings.
|
||||||
func (f *File) countVMLDrawing() int {
|
func (f *File) countVMLDrawing() int {
|
||||||
|
@ -380,6 +456,8 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
||||||
}
|
}
|
||||||
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
drawingVML := "xl/drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
||||||
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
sheetRelationshipsDrawingVML := "../drawings/vmlDrawing" + strconv.Itoa(vmlID) + ".vml"
|
||||||
|
sheetXMLPath, _ := f.getSheetXMLPath(opts.Sheet)
|
||||||
|
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
||||||
if ws.LegacyDrawing != nil {
|
if ws.LegacyDrawing != nil {
|
||||||
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
||||||
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(opts.Sheet, ws.LegacyDrawing.RID)
|
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(opts.Sheet, ws.LegacyDrawing.RID)
|
||||||
|
@ -387,13 +465,7 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
||||||
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
||||||
} else {
|
} else {
|
||||||
// Add first VML drawing for given sheet.
|
// Add first VML drawing for given sheet.
|
||||||
sheetXMLPath, _ := f.getSheetXMLPath(opts.Sheet)
|
|
||||||
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
|
|
||||||
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
|
rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "")
|
||||||
if !opts.FormCtrl {
|
|
||||||
sheetRelationshipsComments := "../comments" + strconv.Itoa(vmlID) + ".xml"
|
|
||||||
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
|
|
||||||
}
|
|
||||||
f.addSheetNameSpace(opts.Sheet, SourceRelationship)
|
f.addSheetNameSpace(opts.Sheet, SourceRelationship)
|
||||||
f.addSheetLegacyDrawing(opts.Sheet, rID)
|
f.addSheetLegacyDrawing(opts.Sheet, rID)
|
||||||
}
|
}
|
||||||
|
@ -405,6 +477,10 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
||||||
if err = f.addComment(commentsXML, opts); err != nil {
|
if err = f.addComment(commentsXML, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if sheetXMLPath, ok := f.getSheetXMLPath(opts.Sheet); ok && f.getSheetComments(filepath.Base(sheetXMLPath)) == "" {
|
||||||
|
sheetRelationshipsComments := "../comments" + strconv.Itoa(vmlID) + ".xml"
|
||||||
|
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return f.addContentTypePart(vmlID, "comments")
|
return f.addContentTypePart(vmlID, "comments")
|
||||||
}
|
}
|
||||||
|
@ -475,8 +551,7 @@ func formCtrlText(opts *vmlOptions) []vmlFont {
|
||||||
return font
|
return font
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var formCtrlPresets = map[FormControlType]struct {
|
||||||
formCtrlPresets = map[FormControlType]struct {
|
|
||||||
objectType string
|
objectType string
|
||||||
filled string
|
filled string
|
||||||
fillColor string
|
fillColor string
|
||||||
|
@ -489,7 +564,7 @@ var (
|
||||||
noThreeD *string
|
noThreeD *string
|
||||||
firstButton *string
|
firstButton *string
|
||||||
shadow *vShadow
|
shadow *vShadow
|
||||||
}{
|
}{
|
||||||
FormControlNote: {
|
FormControlNote: {
|
||||||
objectType: "Note",
|
objectType: "Note",
|
||||||
filled: "",
|
filled: "",
|
||||||
|
@ -528,6 +603,20 @@ var (
|
||||||
firstButton: nil,
|
firstButton: nil,
|
||||||
shadow: nil,
|
shadow: nil,
|
||||||
},
|
},
|
||||||
|
FormControlCheckbox: {
|
||||||
|
objectType: "Checkbox",
|
||||||
|
filled: "f",
|
||||||
|
fillColor: "window [65]",
|
||||||
|
stroked: "f",
|
||||||
|
strokeColor: "windowText [64]",
|
||||||
|
strokeButton: "",
|
||||||
|
fill: nil,
|
||||||
|
textHAlign: "",
|
||||||
|
textVAlign: "Center",
|
||||||
|
noThreeD: stringPtr(""),
|
||||||
|
firstButton: nil,
|
||||||
|
shadow: nil,
|
||||||
|
},
|
||||||
FormControlRadio: {
|
FormControlRadio: {
|
||||||
objectType: "Radio",
|
objectType: "Radio",
|
||||||
filled: "f",
|
filled: "f",
|
||||||
|
@ -542,8 +631,7 @@ var (
|
||||||
firstButton: stringPtr(""),
|
firstButton: stringPtr(""),
|
||||||
shadow: nil,
|
shadow: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// addDrawingVML provides a function to create VML drawing XML as
|
// addDrawingVML provides a function to create VML drawing XML as
|
||||||
// xl/drawings/vmlDrawing%d.vml by given data ID, XML path and VML options. The
|
// xl/drawings/vmlDrawing%d.vml by given data ID, XML path and VML options. The
|
||||||
|
@ -634,7 +722,7 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er
|
||||||
if opts.FormCtrl {
|
if opts.FormCtrl {
|
||||||
sp.ClientData.FmlaMacro = opts.Macro
|
sp.ClientData.FmlaMacro = opts.Macro
|
||||||
}
|
}
|
||||||
if opts.Type == FormControlRadio && opts.Checked {
|
if (opts.Type == FormControlCheckbox || opts.Type == FormControlRadio) && opts.Checked {
|
||||||
sp.ClientData.Checked = stringPtr("1")
|
sp.ClientData.Checked = stringPtr("1")
|
||||||
}
|
}
|
||||||
s, _ := xml.Marshal(sp)
|
s, _ := xml.Marshal(sp)
|
||||||
|
|
|
@ -172,6 +172,20 @@ type decodeShape struct {
|
||||||
Val string `xml:",innerxml"`
|
Val string `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeShapeVal defines the structure used to parse the sub-element of the
|
||||||
|
// shape in the file xl/drawings/vmlDrawing%d.vml.
|
||||||
|
type decodeShapeVal struct {
|
||||||
|
ClientData decodeVMLClientData `xml:"ClientData"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
Column int
|
||||||
|
Row int
|
||||||
|
}
|
||||||
|
|
||||||
// encodeShape defines the structure used to re-serialization shape element.
|
// encodeShape defines the structure used to re-serialization shape element.
|
||||||
type encodeShape struct {
|
type encodeShape struct {
|
||||||
Fill *vFill `xml:"v:fill"`
|
Fill *vFill `xml:"v:fill"`
|
||||||
|
|
35
vml_test.go
35
vml_test.go
|
@ -155,7 +155,7 @@ func TestAddDrawingVML(t *testing.T) {
|
||||||
assert.EqualError(t, f.addDrawingVML(0, "xl/drawings/vmlDrawing1.vml", &vmlOptions{Cell: "A1"}), "XML syntax error on line 1: invalid UTF-8")
|
assert.EqualError(t, f.addDrawingVML(0, "xl/drawings/vmlDrawing1.vml", &vmlOptions{Cell: "A1"}), "XML syntax error on line 1: invalid UTF-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddFormControl(t *testing.T) {
|
func TestFormControl(t *testing.T) {
|
||||||
f := NewFile()
|
f := NewFile()
|
||||||
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||||
Cell: "D1",
|
Cell: "D1",
|
||||||
|
@ -185,12 +185,23 @@ func TestAddFormControl(t *testing.T) {
|
||||||
}))
|
}))
|
||||||
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||||
Cell: "A5",
|
Cell: "A5",
|
||||||
|
Type: FormControlCheckbox,
|
||||||
|
Text: "Check Box 1",
|
||||||
|
Checked: true,
|
||||||
|
}))
|
||||||
|
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||||
|
Cell: "A6",
|
||||||
|
Type: FormControlCheckbox,
|
||||||
|
Text: "Check Box 2",
|
||||||
|
}))
|
||||||
|
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||||
|
Cell: "A7",
|
||||||
Type: FormControlRadio,
|
Type: FormControlRadio,
|
||||||
Text: "Option Button 1",
|
Text: "Option Button 1",
|
||||||
Checked: true,
|
Checked: true,
|
||||||
}))
|
}))
|
||||||
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||||
Cell: "A6",
|
Cell: "A8",
|
||||||
Type: FormControlRadio,
|
Type: FormControlRadio,
|
||||||
Text: "Option Button 2",
|
Text: "Option Button 2",
|
||||||
}))
|
}))
|
||||||
|
@ -221,4 +232,24 @@ func TestAddFormControl(t *testing.T) {
|
||||||
Macro: "Button1_Click",
|
Macro: "Button1_Click",
|
||||||
}), newNoExistSheetError("SheetN"))
|
}), newNoExistSheetError("SheetN"))
|
||||||
assert.NoError(t, f.Close())
|
assert.NoError(t, f.Close())
|
||||||
|
// Test delete form control
|
||||||
|
f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NoError(t, f.DeleteFormControl("Sheet1", "D1"))
|
||||||
|
assert.NoError(t, f.DeleteFormControl("Sheet1", "A1"))
|
||||||
|
// Test delete form control on not exists worksheet
|
||||||
|
assert.Equal(t, f.DeleteFormControl("SheetN", "A1"), newNoExistSheetError("SheetN"))
|
||||||
|
// Test delete form control on not exists worksheet
|
||||||
|
assert.Equal(t, f.DeleteFormControl("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
|
||||||
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteFormControl.xlsm")))
|
||||||
|
assert.NoError(t, f.Close())
|
||||||
|
// Test delete form control with expected element
|
||||||
|
f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
|
||||||
|
assert.Error(t, f.DeleteFormControl("Sheet1", "A1"), "XML syntax error on line 1: invalid UTF-8")
|
||||||
|
assert.NoError(t, f.Close())
|
||||||
|
// Test delete form control on a worksheet without form control
|
||||||
|
f = NewFile()
|
||||||
|
assert.NoError(t, f.DeleteFormControl("Sheet1", "A1"))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue