Browse Source

Resolve #451, support create chart sheet

xuri 5 years ago
parent
commit
6afc468a02
11 changed files with 239 additions and 79 deletions
  1. 0 1
      LICENSE
  2. 68 0
      chart.go
  3. 21 2
      chart_test.go
  4. 63 0
      drawing.go
  5. 6 17
      excelize.go
  6. 9 7
      picture.go
  7. 8 8
      sheet.go
  8. 1 1
      styles.go
  9. 1 1
      xmlChartSheet.go
  10. 61 40
      xmlDrawing.go
  11. 1 2
      xmlWorksheet.go

+ 0 - 1
LICENSE

@@ -1,7 +1,6 @@
 BSD 3-Clause License
 
 Copyright (c) 2016-2020, 360 Enterprise Security Group, Endpoint Security, Inc.
-Copyright (c) 2011-2017, Geoffrey J. Teale
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without

+ 68 - 0
chart.go

@@ -11,7 +11,9 @@ package excelize
 
 import (
 	"encoding/json"
+	"encoding/xml"
 	"errors"
+	"fmt"
 	"strconv"
 	"strings"
 )
@@ -768,6 +770,72 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
 	return err
 }
 
+// AddChartSheet provides the method to create a chartsheet by given chart
+// format set (such as offset, scale, aspect ratio setting and print settings)
+// and properties set. In Excel a chartsheet is a worksheet that only contains
+// a chart.
+func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
+	// Check if the worksheet already exists
+	if f.GetSheetIndex(sheet) != 0 {
+		return errors.New("already existing name worksheet")
+	}
+	formatSet, err := parseFormatChartSet(format)
+	if err != nil {
+		return err
+	}
+	comboCharts := []*formatChart{}
+	for _, comboFormat := range combo {
+		comboChart, err := parseFormatChartSet(comboFormat)
+		if err != nil {
+			return err
+		}
+		if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
+			return errors.New("unsupported chart type " + comboChart.Type)
+		}
+		comboCharts = append(comboCharts, comboChart)
+	}
+	if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
+		return errors.New("unsupported chart type " + formatSet.Type)
+	}
+	cs := xlsxChartsheet{
+		SheetViews: []*xlsxChartsheetViews{{
+			SheetView: []*xlsxChartsheetView{{ZoomScaleAttr: 100, ZoomToFitAttr: true}}},
+		},
+	}
+	wb := f.workbookReader()
+	sheetID := 0
+	for _, v := range wb.Sheets.Sheet {
+		if v.SheetID > sheetID {
+			sheetID = v.SheetID
+		}
+	}
+	sheetID++
+	path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml"
+	f.sheetMap[trimSheetName(sheet)] = path
+	f.Sheet[path] = nil
+	drawingID := f.countDrawings() + 1
+	chartID := f.countCharts() + 1
+	drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
+	drawingID, drawingXML = f.prepareChartSheetDrawing(&cs, drawingID, sheet, drawingXML)
+	drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
+	drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
+	err = f.addSheetDrawingChart(sheet, drawingXML, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format)
+	if err != nil {
+		return err
+	}
+	f.addChart(formatSet, comboCharts)
+	f.addContentTypePart(chartID, "chart")
+	f.addContentTypePart(sheetID, "chartsheet")
+	f.addContentTypePart(drawingID, "drawings")
+	// Update xl/_rels/workbook.xml.rels
+	rID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipChartsheet, fmt.Sprintf("chartsheets/sheet%d.xml", sheetID), "")
+	// Update xl/workbook.xml
+	f.setWorkbook(sheet, sheetID, rID)
+	v, _ := xml.Marshal(cs)
+	f.saveFileList(path, replaceRelationshipsBytes(replaceWorkSheetsRelationshipsNameSpaceBytes(v)))
+	return err
+}
+
 // DeleteChart provides a function to delete chart in XLSX by given worksheet
 // and cell name.
 func (f *File) DeleteChart(sheet, cell string) (err error) {

+ 21 - 2
chart_test.go

@@ -200,12 +200,31 @@ func TestAddChart(t *testing.T) {
 	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx")))
 	// Test with unsupported chart type
 	assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"unknown","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"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"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":"left","show_legend_key":false},"title":{"name":"Bubble 3D 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"}`), "unsupported chart type unknown")
-	// Test add combo chart with invalid format set.
+	// Test add combo chart with invalid format set
 	assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"col","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"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"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":"left","show_legend_key":false},"title":{"name":"2D Column 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"}`, ""), "unexpected end of JSON input")
-	// Test add combo chart with unsupported chart type.
+	// Test add combo chart with unsupported chart type
 	assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"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":"left","show_legend_key":false},"title":{"name":"Bar of Pie 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","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"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":"left","show_legend_key":false},"title":{"name":"Bar of Pie 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","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown")
 }
 
+func TestAddChartSheet(t *testing.T) {
+	categories := map[string]string{"A2": "Small", "A3": "Normal", "A4": "Large", "B1": "Apple", "C1": "Orange", "D1": "Pear"}
+	values := map[string]int{"B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8}
+	f := NewFile()
+	for k, v := range categories {
+		assert.NoError(t, f.SetCellValue("Sheet1", k, v))
+	}
+	for k, v := range values {
+		assert.NoError(t, f.SetCellValue("Sheet1", k, v))
+	}
+	assert.NoError(t, f.AddChartSheet("Chart1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`))
+
+	assert.EqualError(t, f.AddChartSheet("Sheet1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "already existing name worksheet")
+	// Test with unsupported chart type
+	assert.EqualError(t, f.AddChartSheet("Chart2", `{"type":"unknown","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "unsupported chart type unknown")
+
+	assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx")))
+}
+
 func TestDeleteChart(t *testing.T) {
 	f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
 	assert.NoError(t, err)

+ 63 - 0
drawing.go

@@ -38,6 +38,26 @@ func (f *File) prepareDrawing(xlsx *xlsxWorksheet, drawingID int, sheet, drawing
 	return drawingID, drawingXML
 }
 
+// prepareChartSheetDrawing provides a function to prepare drawing ID and XML
+// by given drawingID, worksheet name and default drawingXML.
+func (f *File) prepareChartSheetDrawing(xlsx *xlsxChartsheet, drawingID int, sheet, drawingXML string) (int, string) {
+	sheetRelationshipsDrawingXML := "../drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
+	if xlsx.Drawing != nil {
+		// The worksheet already has a picture 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 picture for given sheet.
+		sheetRels := "xl/chartsheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/chartsheets/") + ".rels"
+		rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
+		xlsx.Drawing = &xlsxDrawing{
+			RID: "rId" + strconv.Itoa(rID),
+		}
+	}
+	return drawingID, drawingXML
+}
+
 // addChart provides a function to create chart as xl/charts/chart%d.xml by
 // given format sets.
 func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) {
@@ -1209,6 +1229,49 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
 	return err
 }
 
+// addSheetDrawingChart provides a function to add chart graphic frame for
+// chartsheet by given sheet, drawingXML, width, height, relationship index
+// and format sets.
+func (f *File) addSheetDrawingChart(sheet, drawingXML string, width, height, rID int, formatSet *formatPicture) (err error) {
+	width = int(float64(width) * formatSet.XScale)
+	height = int(float64(height) * formatSet.YScale)
+
+	content, cNvPrID := f.drawingParser(drawingXML)
+	absoluteAnchor := xdrCellAnchor{
+		EditAs: formatSet.Positioning,
+		Pos:    &xlsxPoint2D{},
+		Ext:    &xlsxExt{},
+	}
+
+	graphicFrame := xlsxGraphicFrame{
+		NvGraphicFramePr: xlsxNvGraphicFramePr{
+			CNvPr: &xlsxCNvPr{
+				ID:   cNvPrID,
+				Name: "Chart " + strconv.Itoa(cNvPrID),
+			},
+		},
+		Graphic: &xlsxGraphic{
+			GraphicData: &xlsxGraphicData{
+				URI: NameSpaceDrawingMLChart,
+				Chart: &xlsxChart{
+					C:   NameSpaceDrawingMLChart,
+					R:   SourceRelationship,
+					RID: "rId" + strconv.Itoa(rID),
+				},
+			},
+		},
+	}
+	graphic, _ := xml.Marshal(graphicFrame)
+	absoluteAnchor.GraphicFrame = string(graphic)
+	absoluteAnchor.ClientData = &xdrClientData{
+		FLocksWithSheet:  formatSet.FLocksWithSheet,
+		FPrintsWithSheet: formatSet.FPrintsWithSheet,
+	}
+	content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
+	f.Drawings[drawingXML] = content
+	return err
+}
+
 // deleteDrawing provides a function to delete chart graphic frame by given by
 // given coordinates and graphic type.
 func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err error) {

+ 6 - 17
excelize.go

@@ -228,21 +228,10 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
 }
 
 // replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace
-// xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft
-// Office Excel 2007.
-func replaceWorkSheetsRelationshipsNameSpaceBytes(workbookMarshal []byte) []byte {
-	var oldXmlns = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
-	var newXmlns = []byte(`<worksheet` + templateNamespaceIDMap)
-	workbookMarshal = bytes.Replace(workbookMarshal, oldXmlns, newXmlns, -1)
-	return workbookMarshal
-}
-
-// replaceStyleRelationshipsNameSpaceBytes provides a function to replace
-// xl/styles.xml XML tags to self-closing for compatible Microsoft Office
-// Excel 2007.
-func replaceStyleRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
-	var oldXmlns = []byte(`<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
-	var newXmlns = []byte(`<styleSheet` + templateNamespaceIDMap)
+// XML tags to self-closing for compatible Microsoft Office Excel 2007.
+func replaceWorkSheetsRelationshipsNameSpaceBytes(contentMarshal []byte) []byte {
+	var oldXmlns = []byte(` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">`)
+	var newXmlns = []byte(templateNamespaceIDMap)
 	contentMarshal = bytes.Replace(contentMarshal, oldXmlns, newXmlns, -1)
 	return contentMarshal
 }
@@ -354,13 +343,13 @@ func (f *File) setContentTypePartVBAProjectExtensions() {
 	}
 	for idx, o := range content.Overrides {
 		if o.PartName == "/xl/workbook.xml" {
-			content.Overrides[idx].ContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
+			content.Overrides[idx].ContentType = ContentTypeMacro
 		}
 	}
 	if !ok {
 		content.Defaults = append(content.Defaults, xlsxDefault{
 			Extension:   "bin",
-			ContentType: "application/vnd.ms-office.vbaProject",
+			ContentType: ContentTypeVBA,
 		})
 	}
 }

+ 9 - 7
picture.go

@@ -354,7 +354,7 @@ func (f *File) setContentTypePartVMLExtensions() {
 	if !vml {
 		content.Defaults = append(content.Defaults, xlsxDefault{
 			Extension:   "vml",
-			ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing",
+			ContentType: ContentTypeVML,
 		})
 	}
 }
@@ -368,6 +368,7 @@ func (f *File) addContentTypePart(index int, contentType string) {
 	}
 	partNames := map[string]string{
 		"chart":      "/xl/charts/chart" + strconv.Itoa(index) + ".xml",
+		"chartsheet": "/xl/chartsheets/sheet" + strconv.Itoa(index) + ".xml",
 		"comments":   "/xl/comments" + strconv.Itoa(index) + ".xml",
 		"drawings":   "/xl/drawings/drawing" + strconv.Itoa(index) + ".xml",
 		"table":      "/xl/tables/table" + strconv.Itoa(index) + ".xml",
@@ -375,12 +376,13 @@ func (f *File) addContentTypePart(index int, contentType string) {
 		"pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml",
 	}
 	contentTypes := map[string]string{
-		"chart":      "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
-		"comments":   "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
-		"drawings":   "application/vnd.openxmlformats-officedocument.drawing+xml",
-		"table":      "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml",
-		"pivotTable": "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml",
-		"pivotCache": "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml",
+		"chart":      ContentTypeDrawingML,
+		"chartsheet": ContentTypeSpreadSheetMLChartsheet,
+		"comments":   ContentTypeSpreadSheetMLComments,
+		"drawings":   ContentTypeDrawing,
+		"table":      ContentTypeSpreadSheetMLTable,
+		"pivotTable": ContentTypeSpreadSheetMLPivotTable,
+		"pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition,
 	}
 	s, ok := setContentType[contentType]
 	if ok {

+ 8 - 8
sheet.go

@@ -50,7 +50,7 @@ func (f *File) NewSheet(name string) int {
 	// Update docProps/app.xml
 	f.setAppXML()
 	// Update [Content_Types].xml
-	f.setContentTypes(sheetID)
+	f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet)
 	// Create new sheet /xl/worksheets/sheet%d.xml
 	f.setSheet(sheetID, name)
 	// Update xl/_rels/workbook.xml.rels
@@ -151,11 +151,11 @@ func trimCell(column []xlsxC) []xlsxC {
 
 // setContentTypes provides a function to read and update property of contents
 // type of XLSX.
-func (f *File) setContentTypes(index int) {
+func (f *File) setContentTypes(partName, contentType string) {
 	content := f.contentTypesReader()
 	content.Overrides = append(content.Overrides, xlsxOverride{
-		PartName:    "/xl/worksheets/sheet" + strconv.Itoa(index) + ".xml",
-		ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
+		PartName:    partName,
+		ContentType: contentType,
 	})
 }
 
@@ -336,8 +336,8 @@ func (f *File) GetSheetIndex(name string) int {
 	return 0
 }
 
-// GetSheetMap provides a function to get worksheet name and index map of XLSX.
-// For example:
+// GetSheetMap provides a function to get worksheet and chartsheet name and
+// index map of XLSX. For example:
 //
 //    f, err := excelize.OpenFile("Book1.xlsx")
 //    if err != nil {
@@ -358,8 +358,8 @@ func (f *File) GetSheetMap() map[int]string {
 	return sheetMap
 }
 
-// getSheetMap provides a function to get worksheet name and XML file path map
-// of XLSX.
+// getSheetMap provides a function to get worksheet and chartsheet name and
+// XML file path map of XLSX.
 func (f *File) getSheetMap() map[string]string {
 	content := f.workbookReader()
 	rels := f.relsReader("xl/_rels/workbook.xml.rels")

+ 1 - 1
styles.go

@@ -1018,7 +1018,7 @@ func (f *File) stylesReader() *xlsxStyleSheet {
 func (f *File) styleSheetWriter() {
 	if f.Styles != nil {
 		output, _ := xml.Marshal(f.Styles)
-		f.saveFileList("xl/styles.xml", replaceStyleRelationshipsNameSpaceBytes(output))
+		f.saveFileList("xl/styles.xml", replaceWorkSheetsRelationshipsNameSpaceBytes(output))
 	}
 }
 

+ 1 - 1
xmlChartSheet.go

@@ -24,7 +24,7 @@ type xlsxChartsheet struct {
 	PageMargins      *xlsxPageMargins             `xml:"pageMargins"`
 	PageSetup        []*xlsxPageSetUp             `xml:"pageSetup"`
 	HeaderFooter     *xlsxHeaderFooter            `xml:"headerFooter"`
-	Drawing          []*xlsxDrawing               `xml:"drawing"`
+	Drawing          *xlsxDrawing                 `xml:"drawing"`
 	DrawingHF        []*xlsxDrawingHF             `xml:"drawingHF"`
 	Picture          []*xlsxPicture               `xml:"picture"`
 	WebPublishItems  []*xlsxInnerXML              `xml:"webPublishItems"`

+ 61 - 40
xmlDrawing.go

@@ -13,40 +13,52 @@ import "encoding/xml"
 
 // Source relationship and namespace.
 const (
-	SourceRelationship                   = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
-	SourceRelationshipChart              = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
-	SourceRelationshipComments           = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
-	SourceRelationshipImage              = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
-	SourceRelationshipTable              = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
-	SourceRelationshipDrawingML          = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
-	SourceRelationshipDrawingVML         = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
-	SourceRelationshipHyperLink          = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
-	SourceRelationshipWorkSheet          = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
-	SourceRelationshipPivotTable         = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
-	SourceRelationshipPivotCache         = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
-	SourceRelationshipVBAProject         = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
-	SourceRelationshipChart201506        = "http://schemas.microsoft.com/office/drawing/2015/06/chart"
-	SourceRelationshipChart20070802      = "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"
-	SourceRelationshipChart2014          = "http://schemas.microsoft.com/office/drawing/2014/chart"
-	SourceRelationshipCompatibility      = "http://schemas.openxmlformats.org/markup-compatibility/2006"
-	NameSpaceDrawingML                   = "http://schemas.openxmlformats.org/drawingml/2006/main"
-	NameSpaceDrawingMLChart              = "http://schemas.openxmlformats.org/drawingml/2006/chart"
-	NameSpaceDrawingMLSpreadSheet        = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
-	NameSpaceSpreadSheet                 = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
-	NameSpaceSpreadSheetX14              = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
-	NameSpaceSpreadSheetX15              = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
-	NameSpaceSpreadSheetExcel2006Main    = "http://schemas.microsoft.com/office/excel/2006/main"
-	NameSpaceMacExcel2008Main            = "http://schemas.microsoft.com/office/mac/excel/2008/main"
-	NameSpaceXML                         = "http://www.w3.org/XML/1998/namespace"
-	NameSpaceXMLSchemaInstance           = "http://www.w3.org/2001/XMLSchema-instance"
-	StrictSourceRelationship             = "http://purl.oclc.org/ooxml/officeDocument/relationships"
-	StrictSourceRelationshipChart        = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
-	StrictSourceRelationshipComments     = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
-	StrictSourceRelationshipImage        = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
-	StrictNameSpaceSpreadSheet           = "http://purl.oclc.org/ooxml/spreadsheetml/main"
-	NameSpaceDublinCore                  = "http://purl.org/dc/elements/1.1/"
-	NameSpaceDublinCoreTerms             = "http://purl.org/dc/terms/"
-	NameSpaceDublinCoreMetadataIntiative = "http://purl.org/dc/dcmitype/"
+	SourceRelationship                           = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
+	SourceRelationshipChart                      = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart"
+	SourceRelationshipComments                   = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
+	SourceRelationshipImage                      = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
+	SourceRelationshipTable                      = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
+	SourceRelationshipDrawingML                  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"
+	SourceRelationshipDrawingVML                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
+	SourceRelationshipHyperLink                  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
+	SourceRelationshipWorkSheet                  = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
+	SourceRelationshipChartsheet                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet"
+	SourceRelationshipPivotTable                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
+	SourceRelationshipPivotCache                 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
+	SourceRelationshipVBAProject                 = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
+	SourceRelationshipChart201506                = "http://schemas.microsoft.com/office/drawing/2015/06/chart"
+	SourceRelationshipChart20070802              = "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"
+	SourceRelationshipChart2014                  = "http://schemas.microsoft.com/office/drawing/2014/chart"
+	SourceRelationshipCompatibility              = "http://schemas.openxmlformats.org/markup-compatibility/2006"
+	NameSpaceDrawingML                           = "http://schemas.openxmlformats.org/drawingml/2006/main"
+	NameSpaceDrawingMLChart                      = "http://schemas.openxmlformats.org/drawingml/2006/chart"
+	NameSpaceDrawingMLSpreadSheet                = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
+	NameSpaceSpreadSheet                         = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
+	NameSpaceSpreadSheetX14                      = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
+	NameSpaceSpreadSheetX15                      = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
+	NameSpaceSpreadSheetExcel2006Main            = "http://schemas.microsoft.com/office/excel/2006/main"
+	NameSpaceMacExcel2008Main                    = "http://schemas.microsoft.com/office/mac/excel/2008/main"
+	NameSpaceXML                                 = "http://www.w3.org/XML/1998/namespace"
+	NameSpaceXMLSchemaInstance                   = "http://www.w3.org/2001/XMLSchema-instance"
+	StrictSourceRelationship                     = "http://purl.oclc.org/ooxml/officeDocument/relationships"
+	StrictSourceRelationshipChart                = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart"
+	StrictSourceRelationshipComments             = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments"
+	StrictSourceRelationshipImage                = "http://purl.oclc.org/ooxml/officeDocument/relationships/image"
+	StrictNameSpaceSpreadSheet                   = "http://purl.oclc.org/ooxml/spreadsheetml/main"
+	NameSpaceDublinCore                          = "http://purl.org/dc/elements/1.1/"
+	NameSpaceDublinCoreTerms                     = "http://purl.org/dc/terms/"
+	NameSpaceDublinCoreMetadataIntiative         = "http://purl.org/dc/dcmitype/"
+	ContentTypeDrawing                           = "application/vnd.openxmlformats-officedocument.drawing+xml"
+	ContentTypeDrawingML                         = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
+	ContentTypeMacro                             = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
+	ContentTypeSpreadSheetMLChartsheet           = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"
+	ContentTypeSpreadSheetMLComments             = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"
+	ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"
+	ContentTypeSpreadSheetMLPivotTable           = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"
+	ContentTypeSpreadSheetMLTable                = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
+	ContentTypeSpreadSheetMLWorksheet            = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
+	ContentTypeVBA                               = "application/vnd.ms-office.vbaProject"
+	ContentTypeVML                               = "application/vnd.openxmlformats-officedocument.vmlDrawing"
 	// ExtURIConditionalFormattings is the extLst child element
 	// ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet element
 	// ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by the addition of
@@ -240,6 +252,7 @@ type xdrClientData struct {
 // with cells and its extents are in EMU units.
 type xdrCellAnchor struct {
 	EditAs       string         `xml:"editAs,attr,omitempty"`
+	Pos          *xlsxPoint2D   `xml:"xdr:pos"`
 	From         *xlsxFrom      `xml:"xdr:from"`
 	To           *xlsxTo        `xml:"xdr:to"`
 	Ext          *xlsxExt       `xml:"xdr:ext"`
@@ -249,15 +262,23 @@ type xdrCellAnchor struct {
 	ClientData   *xdrClientData `xml:"xdr:clientData"`
 }
 
+// xlsxPoint2D describes the position of a drawing element within a spreadsheet.
+type xlsxPoint2D struct {
+	XMLName xml.Name `xml:"xdr:pos"`
+	X       int      `xml:"x,attr"`
+	Y       int      `xml:"y,attr"`
+}
+
 // xlsxWsDr directly maps the root element for a part of this content type shall
 // wsDr.
 type xlsxWsDr struct {
-	XMLName       xml.Name         `xml:"xdr:wsDr"`
-	OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
-	TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
-	A             string           `xml:"xmlns:a,attr,omitempty"`
-	Xdr           string           `xml:"xmlns:xdr,attr,omitempty"`
-	R             string           `xml:"xmlns:r,attr,omitempty"`
+	XMLName        xml.Name         `xml:"xdr:wsDr"`
+	AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"`
+	OneCellAnchor  []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
+	TwoCellAnchor  []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
+	A              string           `xml:"xmlns:a,attr,omitempty"`
+	Xdr            string           `xml:"xmlns:xdr,attr,omitempty"`
+	R              string           `xml:"xmlns:r,attr,omitempty"`
 }
 
 // xlsxGraphicFrame (Graphic Frame) directly maps the xdr:graphicFrame element.

+ 1 - 2
xmlWorksheet.go

@@ -12,8 +12,7 @@ package excelize
 import "encoding/xml"
 
 // xlsxWorksheet directly maps the worksheet element in the namespace
-// http://schemas.openxmlformats.org/spreadsheetml/2006/main - currently I have
-// not checked it for completeness - it does as much as I need.
+// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
 type xlsxWorksheet struct {
 	XMLName               xml.Name                     `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
 	SheetPr               *xlsxSheetPr                 `xml:"sheetPr"`