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. -->
|
||||
<!--- 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
|
||||
|
||||
|
|
240
vml.go
240
vml.go
|
@ -28,6 +28,7 @@ type FormControlType byte
|
|||
const (
|
||||
FormControlNote FormControlType = iota
|
||||
FormControlButton
|
||||
FormControlCheckbox
|
||||
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
|
||||
// worksheet name. For example, delete the comment in Sheet1!$A$30:
|
||||
// DeleteComment provides the method to delete comment in a worksheet by given
|
||||
// worksheet name and cell reference. For example, delete the comment in
|
||||
// Sheet1!$A$30:
|
||||
//
|
||||
// err := f.DeleteComment("Sheet1", "A30")
|
||||
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
|
||||
// in the folder xl/drawings.
|
||||
func (f *File) countVMLDrawing() int {
|
||||
|
@ -380,6 +456,8 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
|||
}
|
||||
drawingVML := "xl/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 {
|
||||
// The worksheet already has a VML relationships, use the relationships drawing ../drawings/vmlDrawing%d.vml.
|
||||
sheetRelationshipsDrawingVML = f.getSheetRelationshipsTargetByID(opts.Sheet, ws.LegacyDrawing.RID)
|
||||
|
@ -387,13 +465,7 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
|||
drawingVML = strings.ReplaceAll(sheetRelationshipsDrawingVML, "..", "xl")
|
||||
} else {
|
||||
// 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, "")
|
||||
if !opts.FormCtrl {
|
||||
sheetRelationshipsComments := "../comments" + strconv.Itoa(vmlID) + ".xml"
|
||||
f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "")
|
||||
}
|
||||
f.addSheetNameSpace(opts.Sheet, SourceRelationship)
|
||||
f.addSheetLegacyDrawing(opts.Sheet, rID)
|
||||
}
|
||||
|
@ -405,6 +477,10 @@ func (f *File) addVMLObject(opts vmlOptions) error {
|
|||
if err = f.addComment(commentsXML, opts); err != nil {
|
||||
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")
|
||||
}
|
||||
|
@ -475,75 +551,87 @@ func formCtrlText(opts *vmlOptions) []vmlFont {
|
|||
return font
|
||||
}
|
||||
|
||||
var (
|
||||
formCtrlPresets = map[FormControlType]struct {
|
||||
objectType string
|
||||
filled string
|
||||
fillColor string
|
||||
stroked string
|
||||
strokeColor string
|
||||
strokeButton string
|
||||
fill *vFill
|
||||
textHAlign string
|
||||
textVAlign string
|
||||
noThreeD *string
|
||||
firstButton *string
|
||||
shadow *vShadow
|
||||
}{
|
||||
FormControlNote: {
|
||||
objectType: "Note",
|
||||
filled: "",
|
||||
fillColor: "#FBF6D6",
|
||||
stroked: "",
|
||||
strokeColor: "#EDEAA1",
|
||||
strokeButton: "",
|
||||
fill: &vFill{
|
||||
Color2: "#FBFE82",
|
||||
Angle: -180,
|
||||
Type: "gradient",
|
||||
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
|
||||
},
|
||||
textHAlign: "",
|
||||
textVAlign: "",
|
||||
noThreeD: nil,
|
||||
firstButton: nil,
|
||||
shadow: &vShadow{On: "t", Color: "black", Obscured: "t"},
|
||||
var formCtrlPresets = map[FormControlType]struct {
|
||||
objectType string
|
||||
filled string
|
||||
fillColor string
|
||||
stroked string
|
||||
strokeColor string
|
||||
strokeButton string
|
||||
fill *vFill
|
||||
textHAlign string
|
||||
textVAlign string
|
||||
noThreeD *string
|
||||
firstButton *string
|
||||
shadow *vShadow
|
||||
}{
|
||||
FormControlNote: {
|
||||
objectType: "Note",
|
||||
filled: "",
|
||||
fillColor: "#FBF6D6",
|
||||
stroked: "",
|
||||
strokeColor: "#EDEAA1",
|
||||
strokeButton: "",
|
||||
fill: &vFill{
|
||||
Color2: "#FBFE82",
|
||||
Angle: -180,
|
||||
Type: "gradient",
|
||||
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
|
||||
},
|
||||
FormControlButton: {
|
||||
objectType: "Button",
|
||||
filled: "",
|
||||
fillColor: "buttonFace [67]",
|
||||
stroked: "",
|
||||
strokeColor: "windowText [64]",
|
||||
strokeButton: "t",
|
||||
fill: &vFill{
|
||||
Color2: "buttonFace [67]",
|
||||
Angle: -180,
|
||||
Type: "gradient",
|
||||
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
|
||||
},
|
||||
textHAlign: "Center",
|
||||
textVAlign: "Center",
|
||||
noThreeD: nil,
|
||||
firstButton: nil,
|
||||
shadow: nil,
|
||||
textHAlign: "",
|
||||
textVAlign: "",
|
||||
noThreeD: nil,
|
||||
firstButton: nil,
|
||||
shadow: &vShadow{On: "t", Color: "black", Obscured: "t"},
|
||||
},
|
||||
FormControlButton: {
|
||||
objectType: "Button",
|
||||
filled: "",
|
||||
fillColor: "buttonFace [67]",
|
||||
stroked: "",
|
||||
strokeColor: "windowText [64]",
|
||||
strokeButton: "t",
|
||||
fill: &vFill{
|
||||
Color2: "buttonFace [67]",
|
||||
Angle: -180,
|
||||
Type: "gradient",
|
||||
Fill: &oFill{Ext: "view", Type: "gradientUnscaled"},
|
||||
},
|
||||
FormControlRadio: {
|
||||
objectType: "Radio",
|
||||
filled: "f",
|
||||
fillColor: "window [65]",
|
||||
stroked: "f",
|
||||
strokeColor: "windowText [64]",
|
||||
strokeButton: "",
|
||||
fill: nil,
|
||||
textHAlign: "",
|
||||
textVAlign: "Center",
|
||||
noThreeD: stringPtr(""),
|
||||
firstButton: stringPtr(""),
|
||||
shadow: nil,
|
||||
},
|
||||
}
|
||||
)
|
||||
textHAlign: "Center",
|
||||
textVAlign: "Center",
|
||||
noThreeD: nil,
|
||||
firstButton: 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: {
|
||||
objectType: "Radio",
|
||||
filled: "f",
|
||||
fillColor: "window [65]",
|
||||
stroked: "f",
|
||||
strokeColor: "windowText [64]",
|
||||
strokeButton: "",
|
||||
fill: nil,
|
||||
textHAlign: "",
|
||||
textVAlign: "Center",
|
||||
noThreeD: stringPtr(""),
|
||||
firstButton: stringPtr(""),
|
||||
shadow: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -634,7 +722,7 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er
|
|||
if opts.FormCtrl {
|
||||
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")
|
||||
}
|
||||
s, _ := xml.Marshal(sp)
|
||||
|
|
|
@ -172,6 +172,20 @@ type decodeShape struct {
|
|||
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.
|
||||
type encodeShape struct {
|
||||
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")
|
||||
}
|
||||
|
||||
func TestAddFormControl(t *testing.T) {
|
||||
func TestFormControl(t *testing.T) {
|
||||
f := NewFile()
|
||||
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||
Cell: "D1",
|
||||
|
@ -185,12 +185,23 @@ func TestAddFormControl(t *testing.T) {
|
|||
}))
|
||||
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||
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,
|
||||
Text: "Option Button 1",
|
||||
Checked: true,
|
||||
}))
|
||||
assert.NoError(t, f.AddFormControl("Sheet1", FormControl{
|
||||
Cell: "A6",
|
||||
Cell: "A8",
|
||||
Type: FormControlRadio,
|
||||
Text: "Option Button 2",
|
||||
}))
|
||||
|
@ -221,4 +232,24 @@ func TestAddFormControl(t *testing.T) {
|
|||
Macro: "Button1_Click",
|
||||
}), newNoExistSheetError("SheetN"))
|
||||
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