From 8922f659788187afa6d0a5d3248e999c2c1bb846 Mon Sep 17 00:00:00 2001 From: xuri Date: Mon, 16 Sep 2019 01:17:35 +0800 Subject: [PATCH] Combine functions: workBookRelsWriter, drawingRelsWriter into relsWriter; drawingRelsReader, workbookRelsReader, workSheetRelsReader into relsReader; addDrawingRelationships, addSheetRelationships into addRels --- README.md | 2 +- README_zh.md | 2 +- cell.go | 4 +- chart.go | 7 ++- comment.go | 8 ++- excelize.go | 33 ++++++++--- excelize.png | Bin 27196 -> 0 bytes excelize.svg | 1 + file.go | 9 +-- picture.go | 136 +++++++++--------------------------------- shape.go | 4 +- sheet.go | 85 +++++++-------------------- table.go | 4 +- xmlDrawing.go | 2 + xmlPivotCache.go | 4 +- xmlPivotTable.go | 150 ++++++++++++++++++++++++----------------------- xmlWorkbook.go | 12 ++-- 17 files changed, 190 insertions(+), 273 deletions(-) delete mode 100644 excelize.png create mode 100644 excelize.svg diff --git a/README.md b/README.md index 7f9cf70..998a4c1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Excelize logo

+

Excelize logo

Build Status diff --git a/README_zh.md b/README_zh.md index 6c2b190..d4cac66 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,4 +1,4 @@ -

Excelize logo

+

Excelize logo

Build Status diff --git a/cell.go b/cell.go index e897379..1da46aa 100644 --- a/cell.go +++ b/cell.go @@ -378,7 +378,9 @@ func (f *File) SetCellHyperLink(sheet, axis, link, linkType string) error { linkData = xlsxHyperlink{ Ref: axis, } - rID := f.addSheetRelationships(sheet, SourceRelationshipHyperLink, link, linkType) + sheetPath, _ := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipHyperLink, link, linkType) linkData.RID = "rId" + strconv.Itoa(rID) case "Location": linkData = xlsxHyperlink{ diff --git a/chart.go b/chart.go index e1eb81f..db2df1e 100644 --- a/chart.go +++ b/chart.go @@ -727,7 +727,8 @@ func (f *File) AddChart(sheet, cell, format string) error { chartID := f.countCharts() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML) - drawingRID := f.addDrawingRelationships(drawingID, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") + drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" + drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") err = f.addDrawingChart(sheet, drawingXML, cell, formatSet.Dimension.Width, formatSet.Dimension.Height, drawingRID, &formatSet.Format) if err != nil { return err @@ -761,7 +762,9 @@ func (f *File) prepareDrawing(xlsx *xlsxWorksheet, drawingID int, sheet, drawing drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1) } else { // Add first picture for given sheet. - rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + sheetPath, _ := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetDrawing(sheet, rID) } return drawingID, drawingXML diff --git a/comment.go b/comment.go index 97e0e9b..7f3b10d 100644 --- a/comment.go +++ b/comment.go @@ -60,7 +60,7 @@ func (f *File) GetComments() (comments map[string][]Comment) { // given worksheet index. func (f *File) getSheetComments(sheetID int) string { var rels = "xl/worksheets/_rels/sheet" + strconv.Itoa(sheetID) + ".xml.rels" - if sheetRels := f.workSheetRelsReader(rels); sheetRels != nil { + if sheetRels := f.relsReader(rels); sheetRels != nil { for _, v := range sheetRels.Relationships { if v.Type == SourceRelationshipComments { return v.Target @@ -98,8 +98,10 @@ func (f *File) AddComment(sheet, cell, format string) error { drawingVML = strings.Replace(sheetRelationshipsDrawingVML, "..", "xl", -1) } else { // Add first comment for given sheet. - rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") - f.addSheetRelationships(sheet, SourceRelationshipComments, sheetRelationshipsComments, "") + sheetPath, _ := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingVML, sheetRelationshipsDrawingVML, "") + f.addRels(sheetRels, SourceRelationshipComments, sheetRelationshipsComments, "") f.addSheetLegacyDrawing(sheet, rID) } commentsXML := "xl/comments" + strconv.Itoa(commentID) + ".xml" diff --git a/excelize.go b/excelize.go index b734e57..4d46b94 100644 --- a/excelize.go +++ b/excelize.go @@ -31,7 +31,6 @@ type File struct { CalcChain *xlsxCalcChain Comments map[string]*xlsxComments ContentTypes *xlsxTypes - DrawingRels map[string]*xlsxWorkbookRels Drawings map[string]*xlsxWsDr Path string SharedStrings *xlsxSST @@ -42,8 +41,7 @@ type File struct { DecodeVMLDrawing map[string]*decodeVmlDrawing VMLDrawing map[string]*vmlDrawing WorkBook *xlsxWorkbook - WorkBookRels *xlsxWorkbookRels - WorkSheetRels map[string]*xlsxWorkbookRels + Relationships map[string]*xlsxRelationships XLSX map[string][]byte } @@ -93,13 +91,12 @@ func OpenReader(r io.Reader) (*File, error) { f := &File{ checked: make(map[string]bool), Comments: make(map[string]*xlsxComments), - DrawingRels: make(map[string]*xlsxWorkbookRels), Drawings: make(map[string]*xlsxWsDr), Sheet: make(map[string]*xlsxWorksheet), SheetCount: sheetCount, DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), VMLDrawing: make(map[string]*vmlDrawing), - WorkSheetRels: make(map[string]*xlsxWorkbookRels), + Relationships: make(map[string]*xlsxRelationships), XLSX: file, } f.CalcChain = f.calcChainReader() @@ -176,6 +173,28 @@ func checkSheet(xlsx *xlsxWorksheet) { xlsx.SheetData = sheetData } +// addRels provides a function to add relationships by given XML path, +// relationship type, target and target mode. +func (f *File) addRels(relPath, relType, target, targetMode string) int { + rels := f.relsReader(relPath) + rID := 0 + if rels == nil { + rels = &xlsxRelationships{} + } + rID = len(rels.Relationships) + 1 + var ID bytes.Buffer + ID.WriteString("rId") + ID.WriteString(strconv.Itoa(rID)) + rels.Relationships = append(rels.Relationships, xlsxRelationship{ + ID: ID.String(), + Type: relType, + Target: target, + TargetMode: targetMode, + }) + f.Relationships[relPath] = rels + return rID +} + // replaceWorkSheetsRelationshipsNameSpaceBytes provides a function to replace // xl/worksheets/sheet%d.xml XML tags to self-closing for compatible Microsoft // Office Excel 2007. @@ -265,7 +284,7 @@ func (f *File) AddVBAProject(bin string) error { return errors.New("unsupported VBA project extension") } f.setContentTypePartVBAProjectExtensions() - wb := f.workbookRelsReader() + wb := f.relsReader("xl/_rels/workbook.xml.rels") var rID int var ok bool for _, rel := range wb.Relationships { @@ -280,7 +299,7 @@ func (f *File) AddVBAProject(bin string) error { } rID++ if !ok { - wb.Relationships = append(wb.Relationships, xlsxWorkbookRelation{ + wb.Relationships = append(wb.Relationships, xlsxRelationship{ ID: "rId" + strconv.Itoa(rID), Target: "vbaProject.bin", Type: SourceRelationshipVBAProject, diff --git a/excelize.png b/excelize.png deleted file mode 100644 index 8ba520e91b975fc240f37cda9a381ced765bc954..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27196 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 logo \ No newline at end of file diff --git a/file.go b/file.go index 46f1f62..2e0d27b 100644 --- a/file.go +++ b/file.go @@ -42,14 +42,13 @@ func NewFile() *File { f.CalcChain = f.calcChainReader() f.Comments = make(map[string]*xlsxComments) f.ContentTypes = f.contentTypesReader() - f.DrawingRels = make(map[string]*xlsxWorkbookRels) f.Drawings = make(map[string]*xlsxWsDr) f.Styles = f.stylesReader() f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) f.VMLDrawing = make(map[string]*vmlDrawing) f.WorkBook = f.workbookReader() - f.WorkBookRels = f.workbookRelsReader() - f.WorkSheetRels = make(map[string]*xlsxWorkbookRels) + f.Relationships = make(map[string]*xlsxRelationships) + f.Relationships["xl/_rels/workbook.xml.rels"] = f.relsReader("xl/_rels/workbook.xml.rels") f.Sheet["xl/worksheets/sheet1.xml"], _ = f.workSheetReader("Sheet1") f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml" f.Theme = f.themeReader() @@ -97,13 +96,11 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) { f.calcChainWriter() f.commentsWriter() f.contentTypesWriter() - f.drawingRelsWriter() f.drawingsWriter() f.vmlDrawingWriter() f.workBookWriter() - f.workBookRelsWriter() f.workSheetWriter() - f.workSheetRelsWriter() + f.relsWriter() f.styleSheetWriter() for path, content := range f.XLSX { diff --git a/picture.go b/picture.go index a5904ff..518463a 100644 --- a/picture.go +++ b/picture.go @@ -155,14 +155,15 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string, drawingID := f.countDrawings() + 1 drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" drawingID, drawingXML = f.prepareDrawing(xlsx, drawingID, sheet, drawingXML) + drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" mediaStr := ".." + strings.TrimPrefix(f.addMedia(file, ext), "xl") - drawingRID := f.addDrawingRelationships(drawingID, SourceRelationshipImage, mediaStr, hyperlinkType) + drawingRID := f.addRels(drawingRels, SourceRelationshipImage, mediaStr, hyperlinkType) // Add picture with hyperlink. if formatSet.Hyperlink != "" && formatSet.HyperlinkType != "" { if formatSet.HyperlinkType == "External" { hyperlinkType = formatSet.HyperlinkType } - drawingHyperlinkRID = f.addDrawingRelationships(drawingID, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType) + drawingHyperlinkRID = f.addRels(drawingRels, SourceRelationshipHyperLink, formatSet.Hyperlink, hyperlinkType) } err = f.addDrawingPicture(sheet, drawingXML, cell, name, img.Width, img.Height, drawingRID, drawingHyperlinkRID, formatSet) if err != nil { @@ -172,37 +173,6 @@ func (f *File) AddPictureFromBytes(sheet, cell, format, name, extension string, return err } -// addSheetRelationships provides a function to add -// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name, relationship -// type and target. -func (f *File) addSheetRelationships(sheet, relType, target, targetMode string) int { - name, ok := f.sheetMap[trimSheetName(sheet)] - if !ok { - name = strings.ToLower(sheet) + ".xml" - } - var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels := f.workSheetRelsReader(rels) - if sheetRels == nil { - sheetRels = &xlsxWorkbookRels{} - } - var rID = 1 - var ID bytes.Buffer - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) - ID.Reset() - rID = len(sheetRels.Relationships) + 1 - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) - sheetRels.Relationships = append(sheetRels.Relationships, xlsxWorkbookRelation{ - ID: ID.String(), - Type: relType, - Target: target, - TargetMode: targetMode, - }) - f.WorkSheetRels[rels] = sheetRels - return rID -} - // deleteSheetRelationships provides a function to delete relationships in // xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and // relationship index. @@ -212,16 +182,16 @@ func (f *File) deleteSheetRelationships(sheet, rID string) { name = strings.ToLower(sheet) + ".xml" } var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels := f.workSheetRelsReader(rels) + sheetRels := f.relsReader(rels) if sheetRels == nil { - sheetRels = &xlsxWorkbookRels{} + sheetRels = &xlsxRelationships{} } for k, v := range sheetRels.Relationships { if v.ID == rID { sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...) } } - f.WorkSheetRels[rels] = sheetRels + f.Relationships[rels] = sheetRels } // addSheetLegacyDrawing provides a function to add legacy drawing element to @@ -325,33 +295,6 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he return err } -// addDrawingRelationships provides a function to add image part relationships -// in the file xl/drawings/_rels/drawing%d.xml.rels by given drawing index, -// relationship type and target. -func (f *File) addDrawingRelationships(index int, relType, target, targetMode string) int { - var rels = "xl/drawings/_rels/drawing" + strconv.Itoa(index) + ".xml.rels" - var rID = 1 - var ID bytes.Buffer - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) - drawingRels := f.drawingRelsReader(rels) - if drawingRels == nil { - drawingRels = &xlsxWorkbookRels{} - } - ID.Reset() - rID = len(drawingRels.Relationships) + 1 - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) - drawingRels.Relationships = append(drawingRels.Relationships, xlsxWorkbookRelation{ - ID: ID.String(), - Type: relType, - Target: target, - TargetMode: targetMode, - }) - f.DrawingRels[rels] = drawingRels - return rID -} - // countMedia provides a function to get media files count storage in the // folder xl/media/image. func (f *File) countMedia() int { @@ -429,16 +372,20 @@ func (f *File) addContentTypePart(index int, contentType string) { "drawings": f.setContentTypePartImageExtensions, } partNames := map[string]string{ - "chart": "/xl/charts/chart" + 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", + "chart": "/xl/charts/chart" + 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", + "pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml", + "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", + "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", } s, ok := setContentType[contentType] if ok { @@ -465,9 +412,9 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { name = strings.ToLower(sheet) + ".xml" } var rels = "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels := f.workSheetRelsReader(rels) + sheetRels := f.relsReader(rels) if sheetRels == nil { - sheetRels = &xlsxWorkbookRels{} + sheetRels = &xlsxRelationships{} } for _, v := range sheetRels.Relationships { if v.ID == rID { @@ -529,12 +476,12 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) for _, anchor := range wsDr.TwoCellAnchor { if anchor.From != nil && anchor.Pic != nil { if anchor.From.Col == col && anchor.From.Row == row { - xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, + xlsxRelationship := f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed) - _, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)] + _, ok := supportImageTypes[filepath.Ext(xlsxRelationship.Target)] if ok { - return filepath.Base(xlsxWorkbookRelation.Target), - []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, + return filepath.Base(xlsxRelationship.Target), + []byte(f.XLSX[strings.Replace(xlsxRelationship.Target, "..", "xl", -1)]), nil } } @@ -548,10 +495,10 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) _ = xml.Unmarshal([]byte(""+anchor.Content+""), &decodeTwoCellAnchor) if decodeTwoCellAnchor.From != nil && decodeTwoCellAnchor.Pic != nil { if decodeTwoCellAnchor.From.Col == col && decodeTwoCellAnchor.From.Row == row { - xlsxWorkbookRelation := f.getDrawingRelationships(drawingRelationships, decodeTwoCellAnchor.Pic.BlipFill.Blip.Embed) - _, ok := supportImageTypes[filepath.Ext(xlsxWorkbookRelation.Target)] + xlsxRelationship := f.getDrawingRelationships(drawingRelationships, decodeTwoCellAnchor.Pic.BlipFill.Blip.Embed) + _, ok := supportImageTypes[filepath.Ext(xlsxRelationship.Target)] if ok { - return filepath.Base(xlsxWorkbookRelation.Target), []byte(f.XLSX[strings.Replace(xlsxWorkbookRelation.Target, "..", "xl", -1)]), nil + return filepath.Base(xlsxRelationship.Target), []byte(f.XLSX[strings.Replace(xlsxRelationship.Target, "..", "xl", -1)]), nil } } } @@ -562,8 +509,8 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string) // getDrawingRelationships provides a function to get drawing relationships // from xl/drawings/_rels/drawing%s.xml.rels by given file name and // relationship ID. -func (f *File) getDrawingRelationships(rels, rID string) *xlsxWorkbookRelation { - if drawingRels := f.drawingRelsReader(rels); drawingRels != nil { +func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship { + if drawingRels := f.relsReader(rels); drawingRels != nil { for _, v := range drawingRels.Relationships { if v.ID == rID { return &v @@ -573,31 +520,6 @@ func (f *File) getDrawingRelationships(rels, rID string) *xlsxWorkbookRelation { return nil } -// drawingRelsReader provides a function to get the pointer to the structure -// after deserialization of xl/drawings/_rels/drawing%d.xml.rels. -func (f *File) drawingRelsReader(rel string) *xlsxWorkbookRels { - if f.DrawingRels[rel] == nil { - _, ok := f.XLSX[rel] - if ok { - d := xlsxWorkbookRels{} - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(rel)), &d) - f.DrawingRels[rel] = &d - } - } - return f.DrawingRels[rel] -} - -// drawingRelsWriter provides a function to save -// xl/drawings/_rels/drawing%d.xml.rels after serialize structure. -func (f *File) drawingRelsWriter() { - for path, d := range f.DrawingRels { - if d != nil { - v, _ := xml.Marshal(d) - f.saveFileList(path, v) - } - } -} - // drawingsWriter provides a function to save xl/drawings/drawing%d.xml after // serialize structure. func (f *File) drawingsWriter() { diff --git a/shape.go b/shape.go index 8d95849..e6a2ff3 100644 --- a/shape.go +++ b/shape.go @@ -275,7 +275,9 @@ func (f *File) AddShape(sheet, cell, format string) error { drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1) } else { // Add first shape for given sheet. - rID := f.addSheetRelationships(sheet, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") + name, _ := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "") f.addSheetDrawing(sheet, rID) } err = f.addDrawingShape(sheet, drawingXML, cell, formatSet) diff --git a/sheet.go b/sheet.go index ed6d888..951baf9 100644 --- a/sheet.go +++ b/sheet.go @@ -52,7 +52,7 @@ func (f *File) NewSheet(name string) int { // Create new sheet /xl/worksheets/sheet%d.xml f.setSheet(sheetID, name) // Update xl/_rels/workbook.xml.rels - rID := f.addXlsxWorkbookRels(sheetID) + rID := f.addRels("xl/_rels/workbook.xml.rels", SourceRelationshipWorkSheet, fmt.Sprintf("worksheets/sheet%d.xml", sheetID), "") // Update xl/workbook.xml f.setWorkbook(name, sheetID, rID) return sheetID @@ -163,50 +163,18 @@ func (f *File) setWorkbook(name string, sheetID, rid int) { }) } -// workbookRelsReader provides a function to read and unmarshal workbook -// relationships of XLSX file. -func (f *File) workbookRelsReader() *xlsxWorkbookRels { - if f.WorkBookRels == nil { - var content xlsxWorkbookRels - _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/_rels/workbook.xml.rels")), &content) - f.WorkBookRels = &content - } - return f.WorkBookRels -} - -// workBookRelsWriter provides a function to save xl/_rels/workbook.xml.rels after +// relsWriter provides a function to save relationships after // serialize structure. -func (f *File) workBookRelsWriter() { - if f.WorkBookRels != nil { - output, _ := xml.Marshal(f.WorkBookRels) - f.saveFileList("xl/_rels/workbook.xml.rels", output) - } -} - -// addXlsxWorkbookRels update workbook relationships property of XLSX. -func (f *File) addXlsxWorkbookRels(sheet int) int { - content := f.workbookRelsReader() - rID := 0 - for _, v := range content.Relationships { - t, _ := strconv.Atoi(strings.TrimPrefix(v.ID, "rId")) - if t > rID { - rID = t +func (f *File) relsWriter() { + for path, rel := range f.Relationships { + if rel != nil { + output, _ := xml.Marshal(rel) + if strings.HasPrefix(path, "xl/worksheets/sheet/rels/sheet") { + output = replaceWorkSheetsRelationshipsNameSpaceBytes(output) + } + f.saveFileList(path, replaceRelationshipsBytes(output)) } } - rID++ - ID := bytes.Buffer{} - ID.WriteString("rId") - ID.WriteString(strconv.Itoa(rID)) - target := bytes.Buffer{} - target.WriteString("worksheets/sheet") - target.WriteString(strconv.Itoa(sheet)) - target.WriteString(".xml") - content.Relationships = append(content.Relationships, xlsxWorkbookRelation{ - ID: ID.String(), - Target: target.String(), - Type: SourceRelationshipWorkSheet, - }) - return rID } // setAppXML update docProps/app.xml file of XML. @@ -365,7 +333,7 @@ func (f *File) GetSheetMap() map[int]string { // of XLSX. func (f *File) getSheetMap() map[string]string { content := f.workbookReader() - rels := f.workbookRelsReader() + rels := f.relsReader("xl/_rels/workbook.xml.rels") maps := map[string]string{} for _, v := range content.Sheets.Sheet { for _, rel := range rels.Relationships { @@ -396,7 +364,9 @@ func (f *File) SetSheetBackground(sheet, picture string) error { } file, _ := ioutil.ReadFile(picture) name := f.addMedia(file, ext) - rID := f.addSheetRelationships(sheet, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") + sheetPath, _ := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") f.addSheetPicture(sheet, rID) f.setContentTypePartImageExtensions() return err @@ -413,7 +383,7 @@ func (f *File) DeleteSheet(name string) { } sheetName := trimSheetName(name) wb := f.workbookReader() - wbRels := f.workbookRelsReader() + wbRels := f.relsReader("xl/_rels/workbook.xml.rels") for idx, sheet := range wb.Sheets.Sheet { if sheet.Name == sheetName { wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...) @@ -443,7 +413,7 @@ func (f *File) DeleteSheet(name string) { // relationships by given relationships ID in the file // xl/_rels/workbook.xml.rels. func (f *File) deleteSheetFromWorkbookRels(rID string) string { - content := f.workbookRelsReader() + content := f.relsReader("xl/_rels/workbook.xml.rels") for k, v := range content.Relationships { if v.ID == rID { content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...) @@ -1387,29 +1357,18 @@ func (f *File) UngroupSheets() error { return nil } -// workSheetRelsReader provides a function to get the pointer to the structure +// relsReader provides a function to get the pointer to the structure // after deserialization of xl/worksheets/_rels/sheet%d.xml.rels. -func (f *File) workSheetRelsReader(path string) *xlsxWorkbookRels { - if f.WorkSheetRels[path] == nil { +func (f *File) relsReader(path string) *xlsxRelationships { + if f.Relationships[path] == nil { _, ok := f.XLSX[path] if ok { - c := xlsxWorkbookRels{} + c := xlsxRelationships{} _ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML(path)), &c) - f.WorkSheetRels[path] = &c - } - } - return f.WorkSheetRels[path] -} - -// workSheetRelsWriter provides a function to save -// xl/worksheets/_rels/sheet%d.xml.rels after serialize structure. -func (f *File) workSheetRelsWriter() { - for p, r := range f.WorkSheetRels { - if r != nil { - v, _ := xml.Marshal(r) - f.saveFileList(p, v) + f.Relationships[path] = &c } } + return f.Relationships[path] } // fillSheetData ensures there are enough rows, and columns in the chosen diff --git a/table.go b/table.go index 45a1622..d26f8fd 100644 --- a/table.go +++ b/table.go @@ -77,7 +77,9 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error { sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml" tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1) // Add first table for given sheet. - rID := f.addSheetRelationships(sheet, SourceRelationshipTable, sheetRelationshipsTableXML, "") + sheetPath, _ := f.sheetMap[trimSheetName(sheet)] + sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels" + rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "") f.addSheetTable(sheet, rID) err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet) if err != nil { diff --git a/xmlDrawing.go b/xmlDrawing.go index ade6261..4338c5e 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -22,6 +22,8 @@ const ( 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" diff --git a/xmlPivotCache.go b/xmlPivotCache.go index 9e07931..0c00832 100644 --- a/xmlPivotCache.go +++ b/xmlPivotCache.go @@ -2,11 +2,11 @@ package excelize import "encoding/xml" -// pivotCacheDefinition represents the pivotCacheDefinition part. This part +// xlsxPivotCacheDefinition represents the pivotCacheDefinition part. This part // defines each field in the source data, including the name, the string // resources of the instance data (for shared items), and information about // the type of data that appears in the field. -type xmlPivotCacheDefinition struct { +type xlsxPivotCacheDefinition struct { XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotCacheDefinition"` RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` Invalid bool `xml:"invalid,attr,omitempty"` diff --git a/xmlPivotTable.go b/xmlPivotTable.go index 16c469f..6f2a8e7 100644 --- a/xmlPivotTable.go +++ b/xmlPivotTable.go @@ -15,78 +15,84 @@ import "encoding/xml" // non-null PivotTables. There exists one pivotTableDefinition for each // PivotTableDefinition part type xlsxPivotTableDefinition struct { - XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"` - Name string `xml:"name,attr"` - CacheID int `xml:"cacheId,attr"` - DataOnRows bool `xml:"dataOnRows,attr"` - DataPosition int `xml:"dataPosition,attr"` - DataCaption string `xml:"dataCaption,attr"` - GrandTotalCaption string `xml:"grandTotalCaption,attr"` - ErrorCaption string `xml:"errorCaption,attr"` - ShowError bool `xml:"showError,attr"` - MissingCaption string `xml:"missingCaption,attr"` - ShowMissing bool `xml:"showMissing,attr"` - PageStyle string `xml:"pageStyle,attr"` - PivotTableStyle string `xml:"pivotTableStyle,attr"` - VacatedStyle string `xml:"vacatedStyle,attr"` - Tag string `xml:"tag,attr"` - UpdatedVersion int `xml:"updatedVersion,attr"` - MinRefreshableVersion int `xml:"minRefreshableVersion,attr"` - AsteriskTotals bool `xml:"asteriskTotals,attr"` - ShowItems bool `xml:"showItems,attr"` - EditData bool `xml:"editData,attr"` - DisableFieldList bool `xml:"disableFieldList,attr"` - ShowCalcMbrs bool `xml:"showCalcMbrs,attr"` - VisualTotals bool `xml:"visualTotals,attr"` - ShowMultipleLabel bool `xml:"showMultipleLabel,attr"` - ShowDataDropDown bool `xml:"showDataDropDown,attr"` - ShowDrill bool `xml:"showDrill,attr"` - PrintDrill bool `xml:"printDrill,attr"` - ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr"` - ShowDataTips bool `xml:"showDataTips,attr"` - EnableWizard bool `xml:"enableWizard,attr"` - EnableDrill bool `xml:"enableDrill,attr"` - EnableFieldProperties bool `xml:"enableFieldProperties,attr"` - PreserveFormatting bool `xml:"preserveFormatting,attr"` - UseAutoFormatting bool `xml:"useAutoFormatting,attr"` - PageWrap int `xml:"pageWrap,attr"` - PageOverThenDown bool `xml:"pageOverThenDown,attr"` - SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr"` - RowGrandTotals bool `xml:"rowGrandTotals,attr"` - ColGrandTotals bool `xml:"colGrandTotals,attr"` - FieldPrintTitles bool `xml:"fieldPrintTitles,attr"` - ItemPrintTitles bool `xml:"itemPrintTitles,attr"` - MergeItem bool `xml:"mergeItem,attr"` - ShowDropZones bool `xml:"showDropZones,attr"` - CreatedVersion int `xml:"createdVersion,attr"` - Indent int `xml:"indent,attr"` - ShowEmptyRow bool `xml:"showEmptyRow,attr"` - ShowEmptyCol bool `xml:"showEmptyCol,attr"` - ShowHeaders bool `xml:"showHeaders,attr"` - Compact bool `xml:"compact,attr"` - Outline bool `xml:"outline,attr"` - OutlineData bool `xml:"outlineData,attr"` - CompactData bool `xml:"compactData,attr"` - Published bool `xml:"published,attr"` - GridDropZones bool `xml:"gridDropZones,attr"` - Immersive bool `xml:"immersive,attr"` - MultipleFieldFilters bool `xml:"multipleFieldFilters,attr"` - ChartFormat int `xml:"chartFormat,attr"` - RowHeaderCaption string `xml:"rowHeaderCaption,attr"` - ColHeaderCaption string `xml:"colHeaderCaption,attr"` - FieldListSortAscending bool `xml:"fieldListSortAscending,attr"` - MdxSubqueries bool `xml:"mdxSubqueries,attr"` - CustomListSort bool `xml:"customListSort,attr"` - Location *xlsxLocation `xml:"location"` - PivotFields *xlsxPivotFields `xml:"pivotFields"` - RowFields *xlsxRowFields `xml:"rowFields"` - RowItems *xlsxRowItems `xml:"rowItems"` - ColFields *xlsxColFields `xml:"colFields"` - ColItems *xlsxColItems `xml:"colItems"` - PageFields *xlsxPageFields `xml:"pageFields"` - DataFields *xlsxDataFields `xml:"dataFields"` - ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"` - PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"` + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main pivotTableDefinition"` + Name string `xml:"name,attr"` + CacheID int `xml:"cacheId,attr"` + ApplyNumberFormats bool `xml:"applyNumberFormats,attr,omitempty"` + ApplyBorderFormats bool `xml:"applyBorderFormats,attr,omitempty"` + ApplyFontFormats bool `xml:"applyFontFormats,attr,omitempty"` + ApplyPatternFormats bool `xml:"applyPatternFormats,attr,omitempty"` + ApplyAlignmentFormats bool `xml:"applyAlignmentFormats,attr,omitempty"` + ApplyWidthHeightFormats bool `xml:"applyWidthHeightFormats,attr,omitempty"` + DataOnRows bool `xml:"dataOnRows,attr,omitempty"` + DataPosition int `xml:"dataPosition,attr,omitempty"` + DataCaption string `xml:"dataCaption,attr"` + GrandTotalCaption string `xml:"grandTotalCaption,attr,omitempty"` + ErrorCaption string `xml:"errorCaption,attr,omitempty"` + ShowError bool `xml:"showError,attr,omitempty"` + MissingCaption string `xml:"missingCaption,attr,omitempty"` + ShowMissing bool `xml:"showMissing,attr,omitempty"` + PageStyle string `xml:"pageStyle,attr,omitempty"` + PivotTableStyle string `xml:"pivotTableStyle,attr,omitempty"` + VacatedStyle string `xml:"vacatedStyle,attr,omitempty"` + Tag string `xml:"tag,attr,omitempty"` + UpdatedVersion int `xml:"updatedVersion,attr"` + MinRefreshableVersion int `xml:"minRefreshableVersion,attr"` + AsteriskTotals bool `xml:"asteriskTotals,attr,omitempty"` + ShowItems bool `xml:"showItems,attr,omitempty"` + EditData bool `xml:"editData,attr,omitempty"` + DisableFieldList bool `xml:"disableFieldList,attr,omitempty"` + ShowCalcMbrs bool `xml:"showCalcMbrs,attr,omitempty"` + VisualTotals bool `xml:"visualTotals,attr,omitempty"` + ShowMultipleLabel bool `xml:"showMultipleLabel,attr,omitempty"` + ShowDataDropDown bool `xml:"showDataDropDown,attr,omitempty"` + ShowDrill bool `xml:"showDrill,attr,omitempty"` + PrintDrill bool `xml:"printDrill,attr,omitempty"` + ShowMemberPropertyTips bool `xml:"showMemberPropertyTips,attr,omitempty"` + ShowDataTips bool `xml:"showDataTips,attr,omitempty"` + EnableWizard bool `xml:"enableWizard,attr,omitempty"` + EnableDrill bool `xml:"enableDrill,attr,omitempty"` + EnableFieldProperties bool `xml:"enableFieldProperties,attr,omitempty"` + PreserveFormatting bool `xml:"preserveFormatting,attr,omitempty"` + UseAutoFormatting bool `xml:"useAutoFormatting,attr"` + PageWrap int `xml:"pageWrap,attr,omitempty"` + PageOverThenDown bool `xml:"pageOverThenDown,attr,omitempty"` + SubtotalHiddenItems bool `xml:"subtotalHiddenItems,attr,omitempty"` + RowGrandTotals bool `xml:"rowGrandTotals,attr,omitempty"` + ColGrandTotals bool `xml:"colGrandTotals,attr,omitempty"` + FieldPrintTitles bool `xml:"fieldPrintTitles,attr,omitempty"` + ItemPrintTitles bool `xml:"itemPrintTitles,attr"` + MergeItem bool `xml:"mergeItem,attr,omitempty"` + ShowDropZones bool `xml:"showDropZones,attr,omitempty"` + CreatedVersion int `xml:"createdVersion,attr"` + Indent int `xml:"indent,attr,omitempty"` + ShowEmptyRow bool `xml:"showEmptyRow,attr,omitempty"` + ShowEmptyCol bool `xml:"showEmptyCol,attr,omitempty"` + ShowHeaders bool `xml:"showHeaders,attr,omitempty"` + Compact bool `xml:"compact,attr,omitempty"` + Outline bool `xml:"outline,attr,omitempty"` + OutlineData bool `xml:"outlineData,attr,omitempty"` + CompactData bool `xml:"compactData,attr,omitempty"` + Published bool `xml:"published,attr,omitempty"` + GridDropZones bool `xml:"gridDropZones,attr"` + Immersive bool `xml:"immersive,attr,omitempty"` + MultipleFieldFilters bool `xml:"multipleFieldFilters,attr,omitempty"` + ChartFormat int `xml:"chartFormat,attr,omitempty"` + RowHeaderCaption string `xml:"rowHeaderCaption,attr,omitempty"` + ColHeaderCaption string `xml:"colHeaderCaption,attr,omitempty"` + FieldListSortAscending bool `xml:"fieldListSortAscending,attr,omitempty"` + MdxSubqueries bool `xml:"mdxSubqueries,attr,omitempty"` + CustomListSort bool `xml:"customListSort,attr,omitempty"` + Location *xlsxLocation `xml:"location"` + PivotFields *xlsxPivotFields `xml:"pivotFields"` + RowFields *xlsxRowFields `xml:"rowFields"` + RowItems *xlsxRowItems `xml:"rowItems"` + ColFields *xlsxColFields `xml:"colFields"` + ColItems *xlsxColItems `xml:"colItems"` + PageFields *xlsxPageFields `xml:"pageFields"` + DataFields *xlsxDataFields `xml:"dataFields"` + ConditionalFormats *xlsxConditionalFormats `xml:"conditionalFormats"` + PivotTableStyleInfo *xlsxPivotTableStyleInfo `xml:"pivotTableStyleInfo"` } // xlsxLocation represents location information for the PivotTable. diff --git a/xmlWorkbook.go b/xmlWorkbook.go index 8150e29..765563b 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -11,14 +11,14 @@ package excelize import "encoding/xml" -// xmlxWorkbookRels contains xmlxWorkbookRelations which maps sheet id and sheet XML. -type xlsxWorkbookRels struct { - XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"` - Relationships []xlsxWorkbookRelation `xml:"Relationship"` +// xlsxRelationships describe references from parts to other internal resources in the package or to external resources. +type xlsxRelationships struct { + XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"` + Relationships []xlsxRelationship `xml:"Relationship"` } -// xmlxWorkbookRelation maps sheet id and xl/worksheets/_rels/sheet%d.xml.rels -type xlsxWorkbookRelation struct { +// xlsxRelationship contains relations which maps id and XML. +type xlsxRelationship struct { ID string `xml:"Id,attr"` Target string `xml:",attr"` Type string `xml:",attr"`