diff --git a/picture.go b/picture.go index 4e646eeac5..c289850727 100644 --- a/picture.go +++ b/picture.go @@ -251,29 +251,6 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error { return err } -// deleteSheetRelationships provides a function to delete relationships in -// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and -// relationship index. -func (f *File) deleteSheetRelationships(sheet, rID string) { - name, ok := f.getSheetXMLPath(sheet) - if !ok { - name = strings.ToLower(sheet) + ".xml" - } - rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels, _ := f.relsReader(rels) - if sheetRels == nil { - sheetRels = &xlsxRelationships{} - } - sheetRels.mu.Lock() - defer sheetRels.mu.Unlock() - for k, v := range sheetRels.Relationships { - if v.ID == rID { - sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...) - } - } - f.Relationships.Store(rels, sheetRels) -} - // addSheetLegacyDrawing provides a function to add legacy drawing element to // xl/worksheets/sheet%d.xml by given worksheet name and relationship index. func (f *File) addSheetLegacyDrawing(sheet string, rID int) { @@ -440,156 +417,6 @@ func (f *File) addMedia(file []byte, ext string) string { return media } -// setContentTypePartRelsExtensions provides a function to set the content -// type for relationship parts and the Main Document part. -func (f *File) setContentTypePartRelsExtensions() error { - var rels bool - content, err := f.contentTypesReader() - if err != nil { - return err - } - for _, v := range content.Defaults { - if v.Extension == "rels" { - rels = true - } - } - if !rels { - content.Defaults = append(content.Defaults, xlsxDefault{ - Extension: "rels", - ContentType: ContentTypeRelationships, - }) - } - return err -} - -// setContentTypePartImageExtensions provides a function to set the content -// type for relationship parts and the Main Document part. -func (f *File) setContentTypePartImageExtensions() error { - imageTypes := map[string]string{ - "bmp": "image/", "jpeg": "image/", "png": "image/", "gif": "image/", - "svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-", - "emz": "image/x-", "wmz": "image/x-", - } - content, err := f.contentTypesReader() - if err != nil { - return err - } - content.mu.Lock() - defer content.mu.Unlock() - for _, file := range content.Defaults { - delete(imageTypes, file.Extension) - } - for extension, prefix := range imageTypes { - content.Defaults = append(content.Defaults, xlsxDefault{ - Extension: extension, - ContentType: prefix + extension, - }) - } - return err -} - -// setContentTypePartVMLExtensions provides a function to set the content type -// for relationship parts and the Main Document part. -func (f *File) setContentTypePartVMLExtensions() error { - var vml bool - content, err := f.contentTypesReader() - if err != nil { - return err - } - content.mu.Lock() - defer content.mu.Unlock() - for _, v := range content.Defaults { - if v.Extension == "vml" { - vml = true - } - } - if !vml { - content.Defaults = append(content.Defaults, xlsxDefault{ - Extension: "vml", - ContentType: ContentTypeVML, - }) - } - return err -} - -// addContentTypePart provides a function to add content type part -// relationships in the file [Content_Types].xml by given index. -func (f *File) addContentTypePart(index int, contentType string) error { - setContentType := map[string]func() error{ - "comments": f.setContentTypePartVMLExtensions, - "drawings": f.setContentTypePartImageExtensions, - } - 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", - "pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml", - "pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml", - "sharedStrings": "/xl/sharedStrings.xml", - "slicer": "/xl/slicers/slicer" + strconv.Itoa(index) + ".xml", - "slicerCache": "/xl/slicerCaches/slicerCache" + strconv.Itoa(index) + ".xml", - } - contentTypes := map[string]string{ - "chart": ContentTypeDrawingML, - "chartsheet": ContentTypeSpreadSheetMLChartsheet, - "comments": ContentTypeSpreadSheetMLComments, - "drawings": ContentTypeDrawing, - "table": ContentTypeSpreadSheetMLTable, - "pivotTable": ContentTypeSpreadSheetMLPivotTable, - "pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition, - "sharedStrings": ContentTypeSpreadSheetMLSharedStrings, - "slicer": ContentTypeSlicer, - "slicerCache": ContentTypeSlicerCache, - } - s, ok := setContentType[contentType] - if ok { - if err := s(); err != nil { - return err - } - } - content, err := f.contentTypesReader() - if err != nil { - return err - } - content.mu.Lock() - defer content.mu.Unlock() - for _, v := range content.Overrides { - if v.PartName == partNames[contentType] { - return err - } - } - content.Overrides = append(content.Overrides, xlsxOverride{ - PartName: partNames[contentType], - ContentType: contentTypes[contentType], - }) - return f.setContentTypePartRelsExtensions() -} - -// getSheetRelationshipsTargetByID provides a function to get Target attribute -// value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and -// relationship index. -func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { - name, ok := f.getSheetXMLPath(sheet) - if !ok { - name = strings.ToLower(sheet) + ".xml" - } - rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" - sheetRels, _ := f.relsReader(rels) - if sheetRels == nil { - sheetRels = &xlsxRelationships{} - } - sheetRels.mu.Lock() - defer sheetRels.mu.Unlock() - for _, v := range sheetRels.Relationships { - if v.ID == rID { - return v.Target - } - } - return "" -} - // GetPictures provides a function to get picture meta info and raw content // embed in spreadsheet by given worksheet and cell name. This function // returns the image contents as []byte data types. This function is diff --git a/sheet.go b/sheet.go index 23ee77ec05..8f678b5150 100644 --- a/sheet.go +++ b/sheet.go @@ -576,7 +576,7 @@ func (f *File) DeleteSheet(sheet string) error { } } target := f.deleteSheetFromWorkbookRels(v.ID) - _ = f.deleteSheetFromContentTypes(target) + _ = f.removeContentTypesPart(ContentTypeSpreadSheetMLWorksheet, target) _ = f.deleteCalcChain(f.getSheetID(sheet), "") delete(f.sheetMap, v.Name) f.Pkg.Delete(sheetXML) @@ -626,24 +626,50 @@ func (f *File) deleteSheetFromWorkbookRels(rID string) string { return "" } -// deleteSheetFromContentTypes provides a function to remove worksheet -// relationships by given target name in the file [Content_Types].xml. -func (f *File) deleteSheetFromContentTypes(target string) error { - if !strings.HasPrefix(target, "/") { - target = "/xl/" + target +// deleteSheetRelationships provides a function to delete relationships in +// xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and +// relationship index. +func (f *File) deleteSheetRelationships(sheet, rID string) { + name, ok := f.getSheetXMLPath(sheet) + if !ok { + name = strings.ToLower(sheet) + ".xml" } - content, err := f.contentTypesReader() - if err != nil { - return err + rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + sheetRels, _ := f.relsReader(rels) + if sheetRels == nil { + sheetRels = &xlsxRelationships{} } - content.mu.Lock() - defer content.mu.Unlock() - for k, v := range content.Overrides { - if v.PartName == target { - content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) + sheetRels.mu.Lock() + defer sheetRels.mu.Unlock() + for k, v := range sheetRels.Relationships { + if v.ID == rID { + sheetRels.Relationships = append(sheetRels.Relationships[:k], sheetRels.Relationships[k+1:]...) } } - return err + f.Relationships.Store(rels, sheetRels) +} + +// getSheetRelationshipsTargetByID provides a function to get Target attribute +// value in xl/worksheets/_rels/sheet%d.xml.rels by given worksheet name and +// relationship index. +func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string { + name, ok := f.getSheetXMLPath(sheet) + if !ok { + name = strings.ToLower(sheet) + ".xml" + } + rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" + sheetRels, _ := f.relsReader(rels) + if sheetRels == nil { + sheetRels = &xlsxRelationships{} + } + sheetRels.mu.Lock() + defer sheetRels.mu.Unlock() + for _, v := range sheetRels.Relationships { + if v.ID == rID { + return v.Target + } + } + return "" } // CopySheet provides a function to duplicate a worksheet by gave source and diff --git a/sheet_test.go b/sheet_test.go index 935736d75e..6851dfcc78 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -561,12 +561,12 @@ func TestSetContentTypes(t *testing.T) { assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8") } -func TestDeleteSheetFromContentTypes(t *testing.T) { +func TestRemoveContentTypesPart(t *testing.T) { f := NewFile() // Test delete sheet from content types with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, f.deleteSheetFromContentTypes("/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.removeContentTypesPart(ContentTypeSpreadSheetMLWorksheet, "/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8") } func BenchmarkNewSheet(b *testing.B) { diff --git a/table.go b/table.go index d12c33b857..1efcd41447 100644 --- a/table.go +++ b/table.go @@ -150,10 +150,11 @@ func (f *File) GetTables(sheet string) ([]Table, error) { return tables, err } table := Table{ - rID: tbl.RID, - tID: t.ID, - Range: t.Ref, - Name: t.Name, + rID: tbl.RID, + tID: t.ID, + tableXML: tableXML, + Range: t.Ref, + Name: t.Name, } if t.TableStyleInfo != nil { table.StyleName = t.TableStyleInfo.Name @@ -186,6 +187,8 @@ func (f *File) DeleteTable(name string) error { for i, tbl := range ws.TableParts.TableParts { if tbl.RID == table.rID { ws.TableParts.TableParts = append(ws.TableParts.TableParts[:i], ws.TableParts.TableParts[i+1:]...) + f.Pkg.Delete(table.tableXML) + _ = f.removeContentTypesPart(ContentTypeSpreadSheetMLTable, "/"+table.tableXML) f.deleteSheetRelationships(sheet, tbl.RID) break } diff --git a/workbook.go b/workbook.go index 2810018c37..0e635d29ce 100644 --- a/workbook.go +++ b/workbook.go @@ -225,3 +225,150 @@ func (f *File) workBookWriter() { f.saveFileList(f.getWorkbookPath(), replaceRelationshipsBytes(f.replaceNameSpaceBytes(f.getWorkbookPath(), output))) } } + +// setContentTypePartRelsExtensions provides a function to set the content type +// for relationship parts and the Main Document part. +func (f *File) setContentTypePartRelsExtensions() error { + var rels bool + content, err := f.contentTypesReader() + if err != nil { + return err + } + for _, v := range content.Defaults { + if v.Extension == "rels" { + rels = true + } + } + if !rels { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "rels", + ContentType: ContentTypeRelationships, + }) + } + return err +} + +// setContentTypePartImageExtensions provides a function to set the content type +// for relationship parts and the Main Document part. +func (f *File) setContentTypePartImageExtensions() error { + imageTypes := map[string]string{ + "bmp": "image/", "jpeg": "image/", "png": "image/", "gif": "image/", + "svg": "image/", "tiff": "image/", "emf": "image/x-", "wmf": "image/x-", + "emz": "image/x-", "wmz": "image/x-", + } + content, err := f.contentTypesReader() + if err != nil { + return err + } + content.mu.Lock() + defer content.mu.Unlock() + for _, file := range content.Defaults { + delete(imageTypes, file.Extension) + } + for extension, prefix := range imageTypes { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: extension, + ContentType: prefix + extension, + }) + } + return err +} + +// setContentTypePartVMLExtensions provides a function to set the content type +// for relationship parts and the Main Document part. +func (f *File) setContentTypePartVMLExtensions() error { + var vml bool + content, err := f.contentTypesReader() + if err != nil { + return err + } + content.mu.Lock() + defer content.mu.Unlock() + for _, v := range content.Defaults { + if v.Extension == "vml" { + vml = true + } + } + if !vml { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "vml", + ContentType: ContentTypeVML, + }) + } + return err +} + +// addContentTypePart provides a function to add content type part relationships +// in the file [Content_Types].xml by given index and content type. +func (f *File) addContentTypePart(index int, contentType string) error { + setContentType := map[string]func() error{ + "comments": f.setContentTypePartVMLExtensions, + "drawings": f.setContentTypePartImageExtensions, + } + 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", + "pivotTable": "/xl/pivotTables/pivotTable" + strconv.Itoa(index) + ".xml", + "pivotCache": "/xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(index) + ".xml", + "sharedStrings": "/xl/sharedStrings.xml", + "slicer": "/xl/slicers/slicer" + strconv.Itoa(index) + ".xml", + "slicerCache": "/xl/slicerCaches/slicerCache" + strconv.Itoa(index) + ".xml", + } + contentTypes := map[string]string{ + "chart": ContentTypeDrawingML, + "chartsheet": ContentTypeSpreadSheetMLChartsheet, + "comments": ContentTypeSpreadSheetMLComments, + "drawings": ContentTypeDrawing, + "table": ContentTypeSpreadSheetMLTable, + "pivotTable": ContentTypeSpreadSheetMLPivotTable, + "pivotCache": ContentTypeSpreadSheetMLPivotCacheDefinition, + "sharedStrings": ContentTypeSpreadSheetMLSharedStrings, + "slicer": ContentTypeSlicer, + "slicerCache": ContentTypeSlicerCache, + } + s, ok := setContentType[contentType] + if ok { + if err := s(); err != nil { + return err + } + } + content, err := f.contentTypesReader() + if err != nil { + return err + } + content.mu.Lock() + defer content.mu.Unlock() + for _, v := range content.Overrides { + if v.PartName == partNames[contentType] { + return err + } + } + content.Overrides = append(content.Overrides, xlsxOverride{ + PartName: partNames[contentType], + ContentType: contentTypes[contentType], + }) + return f.setContentTypePartRelsExtensions() +} + +// removeContentTypesPart provides a function to remove relationships by given +// content type and part name in the file [Content_Types].xml. +func (f *File) removeContentTypesPart(contentType, partName string) error { + if !strings.HasPrefix(partName, "/") { + partName = "/xl/" + partName + } + content, err := f.contentTypesReader() + if err != nil { + return err + } + content.mu.Lock() + defer content.mu.Unlock() + for k, v := range content.Overrides { + if v.PartName == partName && v.ContentType == contentType { + content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) + } + } + return err +} diff --git a/xmlTable.go b/xmlTable.go index ff97df54c2..41a5bdb30e 100644 --- a/xmlTable.go +++ b/xmlTable.go @@ -229,6 +229,7 @@ type xlsxXMLCellPr struct { type Table struct { tID int rID string + tableXML string Range string Name string StyleName string