Resolve #451, support create chart sheet

This commit is contained in:
xuri 2020-03-28 23:47:26 +08:00
parent a75c6f63be
commit 6afc468a02
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
11 changed files with 239 additions and 79 deletions

View File

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

View File

@ -11,7 +11,9 @@ package excelize
import ( import (
"encoding/json" "encoding/json"
"encoding/xml"
"errors" "errors"
"fmt"
"strconv" "strconv"
"strings" "strings"
) )
@ -768,6 +770,72 @@ func (f *File) AddChart(sheet, cell, format string, combo ...string) error {
return err 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 // DeleteChart provides a function to delete chart in XLSX by given worksheet
// and cell name. // and cell name.
func (f *File) DeleteChart(sheet, cell string) (err error) { func (f *File) DeleteChart(sheet, cell string) (err error) {

View File

@ -200,12 +200,31 @@ func TestAddChart(t *testing.T) {
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx")))
// Test with unsupported chart type // 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") 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") 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") 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) { func TestDeleteChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -38,6 +38,26 @@ func (f *File) prepareDrawing(xlsx *xlsxWorksheet, drawingID int, sheet, drawing
return drawingID, drawingXML 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 // addChart provides a function to create chart as xl/charts/chart%d.xml by
// given format sets. // given format sets.
func (f *File) addChart(formatSet *formatChart, comboCharts []*formatChart) { 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 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 // deleteDrawing provides a function to delete chart graphic frame by given by
// given coordinates and graphic type. // given coordinates and graphic type.
func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err error) { func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) (err error) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ const (
SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" 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" SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition"
SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject" SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
@ -47,6 +48,17 @@ const (
NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/" NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/"
NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/" NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/"
NameSpaceDublinCoreMetadataIntiative = "http://purl.org/dc/dcmitype/" 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 // ExtURIConditionalFormattings is the extLst child element
// ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet 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 // ([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. // with cells and its extents are in EMU units.
type xdrCellAnchor struct { type xdrCellAnchor struct {
EditAs string `xml:"editAs,attr,omitempty"` EditAs string `xml:"editAs,attr,omitempty"`
Pos *xlsxPoint2D `xml:"xdr:pos"`
From *xlsxFrom `xml:"xdr:from"` From *xlsxFrom `xml:"xdr:from"`
To *xlsxTo `xml:"xdr:to"` To *xlsxTo `xml:"xdr:to"`
Ext *xlsxExt `xml:"xdr:ext"` Ext *xlsxExt `xml:"xdr:ext"`
@ -249,10 +262,18 @@ type xdrCellAnchor struct {
ClientData *xdrClientData `xml:"xdr:clientData"` 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 // xlsxWsDr directly maps the root element for a part of this content type shall
// wsDr. // wsDr.
type xlsxWsDr struct { type xlsxWsDr struct {
XMLName xml.Name `xml:"xdr:wsDr"` XMLName xml.Name `xml:"xdr:wsDr"`
AbsoluteAnchor []*xdrCellAnchor `xml:"xdr:absoluteAnchor"`
OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"` OneCellAnchor []*xdrCellAnchor `xml:"xdr:oneCellAnchor"`
TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"` TwoCellAnchor []*xdrCellAnchor `xml:"xdr:twoCellAnchor"`
A string `xml:"xmlns:a,attr,omitempty"` A string `xml:"xmlns:a,attr,omitempty"`

View File

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