Skip to content

Commit

Permalink
This fix #1682, removes table and content type parts when deleting th…
Browse files Browse the repository at this point in the history
…e table

- Move worksheet-related functions in one place
  • Loading branch information
xuri committed Oct 5, 2023
1 parent df032fc commit 95fc35f
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 194 deletions.
173 changes: 0 additions & 173 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
56 changes: 41 additions & 15 deletions sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 7 additions & 4 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 95fc35f

Please sign in to comment.