From 7f30a6c9430476bcd5fc1662523ada0e95f60947 Mon Sep 17 00:00:00 2001 From: Ri Xu Date: Sun, 30 Apr 2017 20:03:43 +0800 Subject: [PATCH] - Initialize shape support: new function `AddShape()` added. Relate issue #38; - Drawing `nvPicPr` element ID property calculation changed; - go test updated --- README.md | 12 +- chart.go | 277 ++++++++++++++++---------------- excelize_test.go | 13 ++ picture.go | 51 +++--- shape.go | 375 ++++++++++++++++++++++++++++++++++++++++++++ sheet.go | 2 +- table.go | 3 - test/Workbook1.xlsx | Bin 23055 -> 22922 bytes xmlChart.go | 17 +- xmlDrawing.go | 137 +++++++++++++--- 10 files changed, 671 insertions(+), 216 deletions(-) create mode 100644 shape.go diff --git a/README.md b/README.md index 2d779504..b2c29790 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ func main() { // Set active sheet of the workbook. xlsx.SetActiveSheet(2) // Save xlsx file by the given path. - err := xlsx.WriteTo("/tmp/Workbook.xlsx") + err := xlsx.WriteTo("./Workbook.xlsx") if err != nil { fmt.Println(err) os.Exit(1) @@ -69,7 +69,7 @@ import ( ) func main() { - xlsx, err := excelize.OpenFile("/tmp/Workbook.xlsx") + xlsx, err := excelize.OpenFile("./Workbook.xlsx") if err != nil { fmt.Println(err) os.Exit(1) @@ -142,23 +142,23 @@ import ( ) func main() { - xlsx, err := excelize.OpenFile("/tmp/Workbook.xlsx") + xlsx, err := excelize.OpenFile("./Workbook.xlsx") if err != nil { fmt.Println(err) os.Exit(1) } // Insert a picture. - err = xlsx.AddPicture("Sheet1", "A2", "/tmp/image1.gif", "") + err = xlsx.AddPicture("Sheet1", "A2", "./image1.gif", "") if err != nil { fmt.Println(err) } // Insert a picture to sheet with scaling. - err = xlsx.AddPicture("Sheet1", "D2", "/tmp/image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`) + err = xlsx.AddPicture("Sheet1", "D2", "./image2.jpg", `{"x_scale": 0.5, "y_scale": 0.5}`) if err != nil { fmt.Println(err) } // Insert a picture offset in the cell with printing support. - err = xlsx.AddPicture("Sheet1", "H2", "/tmp/image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`) + err = xlsx.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`) if err != nil { fmt.Println(err) } diff --git a/chart.go b/chart.go index ed2efb62..d6f7fc07 100644 --- a/chart.go +++ b/chart.go @@ -3,7 +3,6 @@ package excelize import ( "encoding/json" "encoding/xml" - "fmt" "strconv" "strings" ) @@ -57,8 +56,8 @@ func parseFormatChartSet(formatSet string) *formatChart { // AddChart provides the method to add chart in a sheet by given chart format // set (such as offset, scale, aspect ratio setting and print settings) and -// properties set. Only support "pie" and "3Dpie" type chart currently. For -// example, create 3D bar chart with data Sheet1!$A$29:$D$32: +// properties set. For example, create 3D bar chart with data +// Sheet1!$A$29:$D$32: // // package main // @@ -81,7 +80,7 @@ func parseFormatChartSet(formatSet string) *formatChart { // } // xlsx.AddChart("SHEET1", "F2", `{"type":"bar3D","series":[{"name":"=Sheet1!$A$30","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$30:$D$30"},{"name":"=Sheet1!$A$31","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$31:$D$31"},{"name":"=Sheet1!$A$32","categories":"=Sheet1!$B$29:$D$29","values":"=Sheet1!$B$32:$D$32"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Fruit Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`) // // Save xlsx file by the given path. -// err := xlsx.WriteTo("./tmp/Workbook.xlsx") +// err := xlsx.WriteTo("./Workbook.xlsx") // if err != nil { // fmt.Println(err) // os.Exit(1) @@ -235,128 +234,126 @@ func (f *File) addChartContentTypePart(index int) { func (f *File) addChart(formatSet *formatChart) { count := f.countCharts() xlsxChartSpace := xlsxChartSpace{ - ChartSpace: cChartSpace{ - XMLNSc: NameSpaceDrawingMLChart, - XMLNSa: NameSpaceDrawingML, - XMLNSr: SourceRelationship, - XMLNSc16r2: SourceRelationshipChart201506, - Date1904: &attrValBool{Val: false}, - Lang: &attrValString{Val: "en-US"}, - RoundedCorners: &attrValBool{Val: false}, - Chart: cChart{ - Title: &cTitle{ - Tx: cTx{ - Rich: &cRich{ - P: aP{ - PPr: aPPr{ - DefRPr: aDefRPr{ - Kern: 1200, - Strike: "noStrike", - U: "none", - Sz: 1400, - SolidFill: &aSolidFill{ - SchemeClr: &aSchemeClr{ - Val: "tx1", - LumMod: &attrValInt{ - Val: 65000, - }, - LumOff: &attrValInt{ - Val: 35000, - }, - }, - }, - Ea: &aEa{ - Typeface: "+mn-ea", - }, - Cs: &aCs{ - Typeface: "+mn-cs", - }, - Latin: &aLatin{ - Typeface: "+mn-lt", - }, - }, - }, - R: &aR{ - RPr: aRPr{ - Lang: "en-US", - AltLang: "en-US", - }, - T: formatSet.Title.Name, - }, - }, - }, - }, - TxPr: cTxPr{ + XMLNSc: NameSpaceDrawingMLChart, + XMLNSa: NameSpaceDrawingML, + XMLNSr: SourceRelationship, + XMLNSc16r2: SourceRelationshipChart201506, + Date1904: &attrValBool{Val: false}, + Lang: &attrValString{Val: "en-US"}, + RoundedCorners: &attrValBool{Val: false}, + Chart: cChart{ + Title: &cTitle{ + Tx: cTx{ + Rich: &cRich{ P: aP{ - PPr: aPPr{ + PPr: &aPPr{ DefRPr: aDefRPr{ Kern: 1200, - U: "none", - Sz: 14000, Strike: "noStrike", + U: "none", + Sz: 1400, + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{ + Val: "tx1", + LumMod: &attrValInt{ + Val: 65000, + }, + LumOff: &attrValInt{ + Val: 35000, + }, + }, + }, + Ea: &aEa{ + Typeface: "+mn-ea", + }, + Cs: &aCs{ + Typeface: "+mn-cs", + }, + Latin: &aLatin{ + Typeface: "+mn-lt", + }, }, }, - EndParaRPr: &aEndParaRPr{ - Lang: "en-US", + R: &aR{ + RPr: aRPr{ + Lang: "en-US", + AltLang: "en-US", + }, + T: formatSet.Title.Name, }, }, }, }, - View3D: &cView3D{ - RotX: &attrValInt{Val: chartView3DRotX[formatSet.Type]}, - RotY: &attrValInt{Val: chartView3DRotY[formatSet.Type]}, - DepthPercent: &attrValInt{Val: chartView3DDepthPercent[formatSet.Type]}, - RAngAx: &attrValInt{Val: chartView3DRAngAx[formatSet.Type]}, - }, - Floor: &cThicknessSpPr{ - Thickness: &attrValInt{Val: 0}, - }, - SideWall: &cThicknessSpPr{ - Thickness: &attrValInt{Val: 0}, - }, - BackWall: &cThicknessSpPr{ - Thickness: &attrValInt{Val: 0}, - }, - PlotArea: &cPlotArea{}, - Legend: &cLegend{ - LegendPos: &attrValString{Val: chartLegendPosition[formatSet.Legend.Position]}, - Overlay: &attrValBool{Val: false}, + TxPr: cTxPr{ + P: aP{ + PPr: &aPPr{ + DefRPr: aDefRPr{ + Kern: 1200, + U: "none", + Sz: 14000, + Strike: "noStrike", + }, + }, + EndParaRPr: &aEndParaRPr{ + Lang: "en-US", + }, + }, }, + }, + View3D: &cView3D{ + RotX: &attrValInt{Val: chartView3DRotX[formatSet.Type]}, + RotY: &attrValInt{Val: chartView3DRotY[formatSet.Type]}, + DepthPercent: &attrValInt{Val: chartView3DDepthPercent[formatSet.Type]}, + RAngAx: &attrValInt{Val: chartView3DRAngAx[formatSet.Type]}, + }, + Floor: &cThicknessSpPr{ + Thickness: &attrValInt{Val: 0}, + }, + SideWall: &cThicknessSpPr{ + Thickness: &attrValInt{Val: 0}, + }, + BackWall: &cThicknessSpPr{ + Thickness: &attrValInt{Val: 0}, + }, + PlotArea: &cPlotArea{}, + Legend: &cLegend{ + LegendPos: &attrValString{Val: chartLegendPosition[formatSet.Legend.Position]}, + Overlay: &attrValBool{Val: false}, + }, - PlotVisOnly: &attrValBool{Val: false}, - DispBlanksAs: &attrValString{Val: formatSet.ShowBlanksAs}, - ShowDLblsOverMax: &attrValBool{Val: false}, + PlotVisOnly: &attrValBool{Val: false}, + DispBlanksAs: &attrValString{Val: formatSet.ShowBlanksAs}, + ShowDLblsOverMax: &attrValBool{Val: false}, + }, + SpPr: &cSpPr{ + SolidFill: &aSolidFill{ + SchemeClr: &aSchemeClr{Val: "bg1"}, }, - SpPr: &cSpPr{ + Ln: &aLn{ + W: 9525, + Cap: "flat", + Cmpd: "sng", + Algn: "ctr", SolidFill: &aSolidFill{ - SchemeClr: &aSchemeClr{Val: "bg1"}, - }, - Ln: &aLn{ - W: 9525, - Cap: "flat", - Cmpd: "sng", - Algn: "ctr", - SolidFill: &aSolidFill{ - SchemeClr: &aSchemeClr{Val: "tx1", - LumMod: &attrValInt{ - Val: 15000, - }, - LumOff: &attrValInt{ - Val: 85000, - }, + SchemeClr: &aSchemeClr{Val: "tx1", + LumMod: &attrValInt{ + Val: 15000, + }, + LumOff: &attrValInt{ + Val: 85000, }, }, }, }, - PrintSettings: &cPrintSettings{ - PageMargins: &cPageMargins{ - B: 0.75, - L: 0.7, - R: 0.7, - T: 0.7, - Header: 0.3, - Footer: 0.3, - }, + }, + PrintSettings: &cPrintSettings{ + PageMargins: &cPageMargins{ + B: 0.75, + L: 0.7, + R: 0.7, + T: 0.7, + Header: 0.3, + Footer: 0.3, }, }, } @@ -370,11 +367,11 @@ func (f *File) addChart(formatSet *formatChart) { Radar: f.drawRadarChart, Scatter: f.drawScatterChart, } - xlsxChartSpace.ChartSpace.Chart.PlotArea = plotAreaFunc[formatSet.Type](formatSet) + xlsxChartSpace.Chart.PlotArea = plotAreaFunc[formatSet.Type](formatSet) chart, _ := xml.Marshal(xlsxChartSpace) media := "xl/charts/chart" + strconv.Itoa(count+1) + ".xml" - f.saveFileList(media, string(chart[16:len(chart)-17])) + f.saveFileList(media, string(chart)) } // drawBarChart provides function to draw the c:plotArea element for bar chart @@ -809,7 +806,7 @@ func (f *File) drawPlotAreaTxPr() *cTxPr { AnchorCtr: true, }, P: aP{ - PPr: aPPr{ + PPr: &aPPr{ DefRPr: aDefRPr{ Sz: 900, B: false, @@ -846,29 +843,29 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) - content := encodeWsDr{} - content.WsDr.A = NameSpaceDrawingML - content.WsDr.Xdr = NameSpaceDrawingMLSpreadSheet + content := xlsxWsDr{} + content.A = NameSpaceDrawingML + content.Xdr = NameSpaceDrawingMLSpreadSheet cNvPrID := 1 _, ok := f.XLSX[drawingXML] if ok { // Append Model decodeWsDr := decodeWsDr{} xml.Unmarshal([]byte(f.readXML(drawingXML)), &decodeWsDr) - cNvPrID = len(decodeWsDr.TwoCellAnchor) + 1 + cNvPrID = len(decodeWsDr.OneCellAnchor) + len(decodeWsDr.TwoCellAnchor) + 1 for _, v := range decodeWsDr.OneCellAnchor { - content.WsDr.OneCellAnchor = append(content.WsDr.OneCellAnchor, &xlsxCellAnchor{ + content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, GraphicFrame: v.Content, }) } for _, v := range decodeWsDr.TwoCellAnchor { - content.WsDr.TwoCellAnchor = append(content.WsDr.TwoCellAnchor, &xlsxCellAnchor{ + content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, GraphicFrame: v.Content, }) } } - twoCellAnchor := xlsxCellAnchor{} + twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = "oneCell" from := xlsxFrom{} from.Col = colStart @@ -883,39 +880,31 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI twoCellAnchor.From = &from twoCellAnchor.To = &to - graphicFrame := graphicFrame{ - GraphicFrame: &xlsxGraphicFrame{ - NvGraphicFramePr: xlsxNvGraphicFramePr{ - CNvPr: &xlsxCNvPr{ - ID: cNvPrID, - Name: "Chart " + strconv.Itoa(cNvPrID), - }, + graphicFrame := xlsxGraphicFrame{ + NvGraphicFramePr: xlsxNvGraphicFramePr{ + CNvPr: &xlsxCNvPr{ + ID: f.countCharts() + f.countMedia() + 1, + Name: "Chart " + strconv.Itoa(cNvPrID), }, - Graphic: &xlsxGraphic{ - GraphicData: &xlsxGraphicData{ - URI: NameSpaceDrawingMLChart, - Chart: &xlsxChart{ - C: NameSpaceDrawingMLChart, - R: SourceRelationship, - RID: "rId" + strconv.Itoa(rID), - }, + }, + Graphic: &xlsxGraphic{ + GraphicData: &xlsxGraphicData{ + URI: NameSpaceDrawingMLChart, + Chart: &xlsxChart{ + C: NameSpaceDrawingMLChart, + R: SourceRelationship, + RID: "rId" + strconv.Itoa(rID), }, }, }, } graphic, _ := xml.Marshal(graphicFrame) - twoCellAnchor.GraphicFrame = string(graphic[14 : len(graphic)-15]) - twoCellAnchor.ClientData = &xlsxClientData{ + twoCellAnchor.GraphicFrame = string(graphic) + twoCellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: formatSet.FLocksWithSheet, FPrintsWithSheet: formatSet.FPrintsWithSheet, } - content.WsDr.TwoCellAnchor = append(content.WsDr.TwoCellAnchor, &twoCellAnchor) - output, err := xml.Marshal(content) - if err != nil { - fmt.Println(err) - } - // Create replacer with pairs as arguments and replace all pairs. - r := strings.NewReplacer("", "", "", "") - result := r.Replace(string(output)) - f.saveFileList(drawingXML, result) + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + output, _ := xml.Marshal(content) + f.saveFileList(drawingXML, string(output)) } diff --git a/excelize_test.go b/excelize_test.go index e8c3cc3c..7a8dcf39 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -486,6 +486,19 @@ func TestAddTable(t *testing.T) { } } +func TestAddShape(t *testing.T) { + xlsx, err := OpenFile("./test/Workbook_2.xlsx") + if err != nil { + t.Log(err) + } + xlsx.AddShape("Sheet1", "A30", `{"type":"rect","text":"Rectangle Shape"}`) + xlsx.AddShape("Sheet3", "H1", `{"type":"ellipseRibbon", "color":{"line":"#4286f4","fill":"#8eb9ff"}, "height": 90}`) + err = xlsx.Save() + if err != nil { + t.Log(err) + } +} + func TestAddChart(t *testing.T) { xlsx, err := OpenFile("./test/Workbook1.xlsx") if err != nil { diff --git a/picture.go b/picture.go index 4f650fb9..c04079b1 100644 --- a/picture.go +++ b/picture.go @@ -5,7 +5,6 @@ import ( "encoding/json" "encoding/xml" "errors" - "fmt" "image" "io/ioutil" "os" @@ -50,21 +49,21 @@ func parseFormatPictureSet(formatSet string) *formatPicture { // func main() { // xlsx := excelize.CreateFile() // // Insert a picture. -// err := xlsx.AddPicture("Sheet1", "A2", "/tmp/image1.jpg", "") +// err := xlsx.AddPicture("Sheet1", "A2", "./image1.jpg", "") // if err != nil { // fmt.Println(err) // } // // Insert a picture to sheet with scaling. -// err = xlsx.AddPicture("Sheet1", "D2", "/tmp/image1.png", `{"x_scale": 0.5, "y_scale": 0.5}`) +// err = xlsx.AddPicture("Sheet1", "D2", "./image1.png", `{"x_scale": 0.5, "y_scale": 0.5}`) // if err != nil { // fmt.Println(err) // } // // Insert a picture offset in the cell with printing support. -// err = xlsx.AddPicture("Sheet1", "H2", "/tmp/image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`) +// err = xlsx.AddPicture("Sheet1", "H2", "./image3.gif", `{"x_offset": 15, "y_offset": 10, "print_obj": true, "lock_aspect_ratio": false, "locked": false}`) // if err != nil { // fmt.Println(err) // } -// err = xlsx.WriteTo("/tmp/Workbook.xlsx") +// err = xlsx.WriteTo("./Workbook.xlsx") // if err != nil { // fmt.Println(err) // os.Exit(1) @@ -135,10 +134,7 @@ func (f *File) addSheetRelationships(sheet, relType, target, targetMode string) Target: target, TargetMode: targetMode, }) - output, err := xml.Marshal(sheetRels) - if err != nil { - fmt.Println(err) - } + output, _ := xml.Marshal(sheetRels) f.saveFileList(rels, string(output)) return rID } @@ -187,29 +183,29 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he width = int(float64(width) * formatSet.XScale) height = int(float64(height) * formatSet.YScale) colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.OffsetX, formatSet.OffsetY, width, height) - content := encodeWsDr{} - content.WsDr.A = NameSpaceDrawingML - content.WsDr.Xdr = NameSpaceDrawingMLSpreadSheet + content := xlsxWsDr{} + content.A = NameSpaceDrawingML + content.Xdr = NameSpaceDrawingMLSpreadSheet cNvPrID := 1 _, ok := f.XLSX[drawingXML] if ok { // Append Model decodeWsDr := decodeWsDr{} xml.Unmarshal([]byte(f.readXML(drawingXML)), &decodeWsDr) - cNvPrID = len(decodeWsDr.TwoCellAnchor) + 1 + cNvPrID = len(decodeWsDr.OneCellAnchor) + len(decodeWsDr.TwoCellAnchor) + 1 for _, v := range decodeWsDr.OneCellAnchor { - content.WsDr.OneCellAnchor = append(content.WsDr.OneCellAnchor, &xlsxCellAnchor{ + content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, GraphicFrame: v.Content, }) } for _, v := range decodeWsDr.TwoCellAnchor { - content.WsDr.TwoCellAnchor = append(content.WsDr.TwoCellAnchor, &xlsxCellAnchor{ + content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ EditAs: v.EditAs, GraphicFrame: v.Content, }) } } - twoCellAnchor := xlsxCellAnchor{} + twoCellAnchor := xdrCellAnchor{} twoCellAnchor.EditAs = "oneCell" from := xlsxFrom{} from.Col = colStart @@ -225,7 +221,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he twoCellAnchor.To = &to pic := xlsxPic{} pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = formatSet.NoChangeAspect - pic.NvPicPr.CNvPr.ID = cNvPrID + pic.NvPicPr.CNvPr.ID = f.countCharts() + f.countMedia() + 1 pic.NvPicPr.CNvPr.Descr = file pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID) pic.BlipFill.Blip.R = SourceRelationship @@ -233,19 +229,13 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he pic.SpPr.PrstGeom.Prst = "rect" twoCellAnchor.Pic = &pic - twoCellAnchor.ClientData = &xlsxClientData{ + twoCellAnchor.ClientData = &xdrClientData{ FLocksWithSheet: formatSet.FLocksWithSheet, FPrintsWithSheet: formatSet.FPrintsWithSheet, } - content.WsDr.TwoCellAnchor = append(content.WsDr.TwoCellAnchor, &twoCellAnchor) - output, err := xml.Marshal(content) - if err != nil { - fmt.Println(err) - } - // Create replacer with pairs as arguments and replace all pairs. - r := strings.NewReplacer("", "", "", "") - result := r.Replace(string(output)) - f.saveFileList(drawingXML, result) + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + output, _ := xml.Marshal(content) + f.saveFileList(drawingXML, string(output)) } // addDrawingRelationships provides function to add image part relationships in @@ -271,10 +261,7 @@ func (f *File) addDrawingRelationships(index int, relType string, target string) Type: relType, Target: target, }) - output, err := xml.Marshal(drawingRels) - if err != nil { - fmt.Println(err) - } + output, _ := xml.Marshal(drawingRels) f.saveFileList(rels, string(output)) return rID } @@ -356,7 +343,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet string, rID string) string // in XLSX by given worksheet and cell name. This function returns the file name // in XLSX and file contents as []byte data types. For example: // -// xlsx, err := excelize.OpenFile("/tmp/Workbook.xlsx") +// xlsx, err := excelize.OpenFile("./Workbook.xlsx") // if err != nil { // fmt.Println(err) // os.Exit(1) diff --git a/shape.go b/shape.go new file mode 100644 index 00000000..5994c31f --- /dev/null +++ b/shape.go @@ -0,0 +1,375 @@ +package excelize + +import ( + "encoding/json" + "encoding/xml" + "strconv" + "strings" +) + +// parseFormatShapeSet provides function to parse the format settings of the +// shape with default value. +func parseFormatShapeSet(formatSet string) *formatShape { + format := formatShape{ + Width: 160, + Height: 160, + Format: formatPicture{ + FPrintsWithSheet: true, + FLocksWithSheet: false, + NoChangeAspect: false, + OffsetX: 0, + OffsetY: 0, + XScale: 1.0, + YScale: 1.0, + }, + Text: " ", + } + json.Unmarshal([]byte(formatSet), &format) + return &format +} + +// AddShape provides the method to add shape in a sheet by given worksheet +// index, shape format set (such as offset, scale, aspect ratio setting and +// print settings) and properties set. For example, add text box (rect shape) in +// Sheet1: +// +// xlsx.AddShape("Sheet1", "G6", `{"type":"rect", "text":"Rectangle Shape", "color":{"line":"#4286F4","fill":"#8eb9ff"}, "width": 180, "height": 90}`) +// +// The following shows the type of chart supported by excelize: +// +// accentBorderCallout1 (Callout 1 with Border and Accent Shape) +// accentBorderCallout2 (Callout 2 with Border and Accent Shape) +// accentBorderCallout3 (Callout 3 with Border and Accent Shape) +// accentCallout1 (Callout 1 Shape) +// accentCallout2 (Callout 2 Shape) +// accentCallout3 (Callout 3 Shape) +// actionButtonBackPrevious (Back or Previous Button Shape) +// actionButtonBeginning (Beginning Button Shape) +// actionButtonBlank (Blank Button Shape) +// actionButtonDocument (Document Button Shape) +// actionButtonEnd (End Button Shape) +// actionButtonForwardNext (Forward or Next Button Shape) +// actionButtonHelp (Help Button Shape) +// actionButtonHome (Home Button Shape) +// actionButtonInformation (Information Button Shape) +// actionButtonMovie (Movie Button Shape) +// actionButtonReturn (Return Button Shape) +// actionButtonSound (Sound Button Shape) +// arc (Curved Arc Shape) +// bentArrow (Bent Arrow Shape) +// bentConnector2 (Bent Connector 2 Shape) +// bentConnector3 (Bent Connector 3 Shape) +// bentConnector4 (Bent Connector 4 Shape) +// bentConnector5 (Bent Connector 5 Shape) +// bentUpArrow (Bent Up Arrow Shape) +// bevel (Bevel Shape) +// blockArc (Block Arc Shape) +// borderCallout1 (Callout 1 with Border Shape) +// borderCallout2 (Callout 2 with Border Shape) +// borderCallout3 (Callout 3 with Border Shape) +// bracePair (Brace Pair Shape) +// bracketPair (Bracket Pair Shape) +// callout1 (Callout 1 Shape) +// callout2 (Callout 2 Shape) +// callout3 (Callout 3 Shape) +// can (Can Shape) +// chartPlus (Chart Plus Shape) +// chartStar (Chart Star Shape) +// chartX (Chart X Shape) +// chevron (Chevron Shape) +// chord (Chord Shape) +// circularArrow (Circular Arrow Shape) +// cloud (Cloud Shape) +// cloudCallout (Callout Cloud Shape) +// corner (Corner Shape) +// cornerTabs (Corner Tabs Shape) +// cube (Cube Shape) +// curvedConnector2 (Curved Connector 2 Shape) +// curvedConnector3 (Curved Connector 3 Shape) +// curvedConnector4 (Curved Connector 4 Shape) +// curvedConnector5 (Curved Connector 5 Shape) +// curvedDownArrow (Curved Down Arrow Shape) +// curvedLeftArrow (Curved Left Arrow Shape) +// curvedRightArrow (Curved Right Arrow Shape) +// curvedUpArrow (Curved Up Arrow Shape) +// decagon (Decagon Shape) +// diagStripe (Diagonal Stripe Shape) +// diamond (Diamond Shape) +// dodecagon (Dodecagon Shape) +// donut (Donut Shape) +// doubleWave (Double Wave Shape) +// downArrow (Down Arrow Shape) +// downArrowCallout (Callout Down Arrow Shape) +// ellipse (Ellipse Shape) +// ellipseRibbon (Ellipse Ribbon Shape) +// ellipseRibbon2 (Ellipse Ribbon 2 Shape) +// flowChartAlternateProcess (Alternate Process Flow Shape) +// flowChartCollate (Collate Flow Shape) +// flowChartConnector (Connector Flow Shape) +// flowChartDecision (Decision Flow Shape) +// flowChartDelay (Delay Flow Shape) +// flowChartDisplay (Display Flow Shape) +// flowChartDocument (Document Flow Shape) +// flowChartExtract (Extract Flow Shape) +// flowChartInputOutput (Input Output Flow Shape) +// flowChartInternalStorage (Internal Storage Flow Shape) +// flowChartMagneticDisk (Magnetic Disk Flow Shape) +// flowChartMagneticDrum (Magnetic Drum Flow Shape) +// flowChartMagneticTape (Magnetic Tape Flow Shape) +// flowChartManualInput (Manual Input Flow Shape) +// flowChartManualOperation (Manual Operation Flow Shape) +// flowChartMerge (Merge Flow Shape) +// flowChartMultidocument (Multi-Document Flow Shape) +// flowChartOfflineStorage (Offline Storage Flow Shape) +// flowChartOffpageConnector (Off-Page Connector Flow Shape) +// flowChartOnlineStorage (Online Storage Flow Shape) +// flowChartOr (Or Flow Shape) +// flowChartPredefinedProcess (Predefined Process Flow Shape) +// flowChartPreparation (Preparation Flow Shape) +// flowChartProcess (Process Flow Shape) +// flowChartPunchedCard (Punched Card Flow Shape) +// flowChartPunchedTape (Punched Tape Flow Shape) +// flowChartSort (Sort Flow Shape) +// flowChartSummingJunction (Summing Junction Flow Shape) +// flowChartTerminator (Terminator Flow Shape) +// foldedCorner (Folded Corner Shape) +// frame (Frame Shape) +// funnel (Funnel Shape) +// gear6 (Gear 6 Shape) +// gear9 (Gear 9 Shape) +// halfFrame (Half Frame Shape) +// heart (Heart Shape) +// heptagon (Heptagon Shape) +// hexagon (Hexagon Shape) +// homePlate (Home Plate Shape) +// horizontalScroll (Horizontal Scroll Shape) +// irregularSeal1 (Irregular Seal 1 Shape) +// irregularSeal2 (Irregular Seal 2 Shape) +// leftArrow (Left Arrow Shape) +// leftArrowCallout (Callout Left Arrow Shape) +// leftBrace (Left Brace Shape) +// leftBracket (Left Bracket Shape) +// leftCircularArrow (Left Circular Arrow Shape) +// leftRightArrow (Left Right Arrow Shape) +// leftRightArrowCallout (Callout Left Right Arrow Shape) +// leftRightCircularArrow (Left Right Circular Arrow Shape) +// leftRightRibbon (Left Right Ribbon Shape) +// leftRightUpArrow (Left Right Up Arrow Shape) +// leftUpArrow (Left Up Arrow Shape) +// lightningBolt (Lightning Bolt Shape) +// line (Line Shape) +// lineInv (Line Inverse Shape) +// mathDivide (Divide Math Shape) +// mathEqual (Equal Math Shape) +// mathMinus (Minus Math Shape) +// mathMultiply (Multiply Math Shape) +// mathNotEqual (Not Equal Math Shape) +// mathPlus (Plus Math Shape) +// moon (Moon Shape) +// nonIsoscelesTrapezoid (Non-Isosceles Trapezoid Shape) +// noSmoking (No Smoking Shape) +// notchedRightArrow (Notched Right Arrow Shape) +// octagon (Octagon Shape) +// parallelogram (Parallelogram Shape) +// pentagon (Pentagon Shape) +// pie (Pie Shape) +// pieWedge (Pie Wedge Shape) +// plaque (Plaque Shape) +// plaqueTabs (Plaque Tabs Shape) +// plus (Plus Shape) +// quadArrow (Quad-Arrow Shape) +// quadArrowCallout (Callout Quad-Arrow Shape) +// rect (Rectangle Shape) +// ribbon (Ribbon Shape) +// ribbon2 (Ribbon 2 Shape) +// rightArrow (Right Arrow Shape) +// rightArrowCallout (Callout Right Arrow Shape) +// rightBrace (Right Brace Shape) +// rightBracket (Right Bracket Shape) +// round1Rect (One Round Corner Rectangle Shape) +// round2DiagRect (Two Diagonal Round Corner Rectangle Shape) +// round2SameRect (Two Same-side Round Corner Rectangle Shape) +// roundRect (Round Corner Rectangle Shape) +// rtTriangle (Right Triangle Shape) +// smileyFace (Smiley Face Shape) +// snip1Rect (One Snip Corner Rectangle Shape) +// snip2DiagRect (Two Diagonal Snip Corner Rectangle Shape) +// snip2SameRect (Two Same-side Snip Corner Rectangle Shape) +// snipRoundRect (One Snip One Round Corner Rectangle Shape) +// squareTabs (Square Tabs Shape) +// star10 (Ten Pointed Star Shape) +// star12 (Twelve Pointed Star Shape) +// star16 (Sixteen Pointed Star Shape) +// star24 (Twenty Four Pointed Star Shape) +// star32 (Thirty Two Pointed Star Shape) +// star4 (Four Pointed Star Shape) +// star5 (Five Pointed Star Shape) +// star6 (Six Pointed Star Shape) +// star7 (Seven Pointed Star Shape) +// star8 (Eight Pointed Star Shape) +// straightConnector1 (Straight Connector 1 Shape) +// stripedRightArrow (Striped Right Arrow Shape) +// sun (Sun Shape) +// swooshArrow (Swoosh Arrow Shape) +// teardrop (Teardrop Shape) +// trapezoid (Trapezoid Shape) +// triangle (Triangle Shape) +// upArrow (Up Arrow Shape) +// upArrowCallout (Callout Up Arrow Shape) +// upDownArrow (Up Down Arrow Shape) +// upDownArrowCallout (Callout Up Down Arrow Shape) +// uturnArrow (U-Turn Arrow Shape) +// verticalScroll (Vertical Scroll Shape) +// wave (Wave Shape) +// wedgeEllipseCallout (Callout Wedge Ellipse Shape) +// wedgeRectCallout (Callout Wedge Rectangle Shape) +// wedgeRoundRectCallout (Callout Wedge Round Rectangle Shape) +// +func (f *File) AddShape(sheet, cell, format string) { + formatSet := parseFormatShapeSet(format) + // Read sheet data. + xlsx := f.workSheetReader(sheet) + // Add first shape for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. + drawingID := f.countDrawings() + 1 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + + if xlsx.Drawing != nil { + // The worksheet already has a shape or chart relationships, use the relationships drawing ../drawings/drawing%d.xml. + sheetRelationshipsDrawingXML = f.getSheetRelationshipsTargetByID(sheet, xlsx.Drawing.RID) + drawingID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../drawings/drawing"), ".xml")) + drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1) + } else { + // Add first shape for given sheet. + rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + f.addSheetDrawing(sheet, rID) + } + f.addDrawingShape(sheet, drawingXML, cell, formatSet) + f.addDrawingContentTypePart(drawingID) +} + +// addDrawingShape +func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *formatShape) { + cell = strings.ToUpper(cell) + fromCol := string(strings.Map(letterOnlyMapF, cell)) + fromRow, _ := strconv.Atoi(strings.Map(intOnlyMapF, cell)) + row := fromRow - 1 + col := titleToNumber(fromCol) + width := int(float64(formatSet.Width) * formatSet.Format.XScale) + height := int(float64(formatSet.Height) * formatSet.Format.YScale) + colStart, rowStart, _, _, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, formatSet.Format.OffsetX, formatSet.Format.OffsetY, width, height) + content := xlsxWsDr{} + content.A = NameSpaceDrawingML + content.Xdr = NameSpaceDrawingMLSpreadSheet + cNvPrID := 1 + _, ok := f.XLSX[drawingXML] + if ok { // Append Model + decodeWsDr := decodeWsDr{} + xml.Unmarshal([]byte(f.readXML(drawingXML)), &decodeWsDr) + cNvPrID = len(decodeWsDr.OneCellAnchor) + len(decodeWsDr.TwoCellAnchor) + 1 + for _, v := range decodeWsDr.OneCellAnchor { + content.OneCellAnchor = append(content.OneCellAnchor, &xdrCellAnchor{ + EditAs: v.EditAs, + GraphicFrame: v.Content, + }) + } + for _, v := range decodeWsDr.TwoCellAnchor { + content.TwoCellAnchor = append(content.TwoCellAnchor, &xdrCellAnchor{ + EditAs: v.EditAs, + GraphicFrame: v.Content, + }) + } + } + twoCellAnchor := xdrCellAnchor{} + twoCellAnchor.EditAs = "oneCell" + from := xlsxFrom{} + from.Col = colStart + from.ColOff = formatSet.Format.OffsetX * EMU + from.Row = rowStart + from.RowOff = formatSet.Format.OffsetY * EMU + to := xlsxTo{} + to.Col = colEnd + to.ColOff = x2 * EMU + to.Row = rowEnd + to.RowOff = y2 * EMU + twoCellAnchor.From = &from + twoCellAnchor.To = &to + shape := xdrSp{ + NvSpPr: &xdrNvSpPr{ + CNvPr: &xlsxCNvPr{ + ID: cNvPrID, + Name: "Shape " + strconv.Itoa(cNvPrID), + }, + CNvSpPr: &xdrCNvSpPr{ + TxBox: true, + }, + }, + SpPr: &xlsxSpPr{ + PrstGeom: xlsxPrstGeom{ + Prst: formatSet.Type, + }, + }, + Style: &xdrStyle{ + LnRef: setShapeRef(formatSet.Color.Line, 2), + FillRef: setShapeRef(formatSet.Color.Fill, 1), + EffectRef: setShapeRef(formatSet.Color.Effect, 0), + FontRef: &aFontRef{ + Idx: "minor", + SchemeClr: &attrValString{ + Val: "tx1", + }, + }, + }, + TxBody: &xdrTxBody{ + BodyPr: &aBodyPr{ + VertOverflow: "clip", + HorzOverflow: "clip", + Wrap: "none", + RtlCol: false, + Anchor: "t", + }, + P: &aP{ + R: &aR{ + RPr: aRPr{ + Lang: "en-US", + AltLang: "en-US", + Sz: 1100, + }, + T: formatSet.Text, + }, + EndParaRPr: &aEndParaRPr{ + Lang: "en-US", + }, + }, + }, + } + twoCellAnchor.Sp = &shape + twoCellAnchor.ClientData = &xdrClientData{ + FLocksWithSheet: formatSet.Format.FLocksWithSheet, + FPrintsWithSheet: formatSet.Format.FPrintsWithSheet, + } + content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + output, _ := xml.Marshal(content) + f.saveFileList(drawingXML, string(output)) +} + +// setShapeRef provides function to set color with hex model by given actual +// color value. +func setShapeRef(color string, i int) *aRef { + if color == "" { + return &aRef{ + Idx: 0, + ScrgbClr: &aScrgbClr{ + R: 0, + G: 0, + B: 0, + }, + } + } + return &aRef{ + Idx: i, + SrgbClr: &attrValString{ + Val: strings.Replace(strings.ToUpper(color), "#", "", -1), + }, + } +} diff --git a/sheet.go b/sheet.go index 59e7cda7..a0f5abec 100644 --- a/sheet.go +++ b/sheet.go @@ -291,7 +291,7 @@ func (f *File) GetSheetIndex(name string) int { // GetSheetMap provides function to get sheet map of XLSX. For example: // -// xlsx, err := excelize.OpenFile("/tmp/Workbook.xlsx") +// xlsx, err := excelize.OpenFile("./Workbook.xlsx") // if err != nil { // fmt.Println(err) // os.Exit(1) diff --git a/table.go b/table.go index d472ea0b..ce467fc1 100644 --- a/table.go +++ b/table.go @@ -52,11 +52,9 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) { vyAxis := vrow - 1 vxAxis := titleToNumber(vcol) if vxAxis < hxAxis { - hcell, vcell = vcell, hcell vxAxis, hxAxis = hxAxis, vxAxis } if vyAxis < hyAxis { - hcell, vcell = vcell, hcell vyAxis, hyAxis = hyAxis, vyAxis } tableID := f.countTables() + 1 @@ -156,7 +154,6 @@ func (f *File) addTable(sheet, tableXML string, hxAxis, hyAxis, vxAxis, vyAxis, // addTableContentTypePart provides function to add image part relationships // in the file [Content_Types].xml by given drawing index. func (f *File) addTableContentTypePart(index int) { - f.setContentTypePartImageExtensions() content := f.contentTypesReader() for _, v := range content.Overrides { if v.PartName == "/xl/tables/table"+strconv.Itoa(index)+".xml" { diff --git a/test/Workbook1.xlsx b/test/Workbook1.xlsx index ee52187124e68a3a95cee012b591cf9f1a574ad5..257ec27781172ba877379eaebc6fbcb9c3c6ce44 100644 GIT binary patch delta 3119 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