This related for #810, add new functions DeleteSlicer and GetSlicers (#1943)

- Update unit tests
This commit is contained in:
zhangyimingdatiancai 2024-08-23 10:47:47 +08:00 committed by GitHub
parent 9a38657515
commit c805be1f6f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 658 additions and 42 deletions

View File

@ -264,6 +264,12 @@ func newInvalidStyleID(styleID int) error {
return fmt.Errorf("invalid style ID %d", styleID) return fmt.Errorf("invalid style ID %d", styleID)
} }
// newNoExistSlicerError defined the error message on receiving the non existing
// slicer name.
func newNoExistSlicerError(name string) error {
return fmt.Errorf("slicer %s does not exist", name)
}
// newNoExistTableError defined the error message on receiving the non existing // newNoExistTableError defined the error message on receiving the non existing
// table name. // table name.
func newNoExistTableError(name string) error { func newNoExistTableError(name string) error {

View File

@ -785,12 +785,11 @@ func (f *File) getPivotTableDataRange(opts *PivotTableOptions) error {
opts.pivotDataRange = opts.DataRange opts.pivotDataRange = opts.DataRange
return nil return nil
} }
for _, sheetName := range f.GetSheetList() { tbls, err := f.getTables()
tables, err := f.GetTables(sheetName) if err != nil {
e := ErrSheetNotExist{sheetName} return err
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() { }
return err for sheetName, tables := range tbls {
}
for _, table := range tables { for _, table := range tables {
if table.Name == opts.DataRange { if table.Name == opts.DataRange {
opts.pivotDataRange, opts.namedDataRange = fmt.Sprintf("%s!%s", sheetName, table.Range), true opts.pivotDataRange, opts.namedDataRange = fmt.Sprintf("%s!%s", sheetName, table.Range), true
@ -1016,8 +1015,8 @@ func (f *File) DeletePivotTable(sheet, name string) error {
return err return err
} }
pivotTableCaches := map[string]int{} pivotTableCaches := map[string]int{}
for _, sheetName := range f.GetSheetList() { pivotTables, _ := f.getPivotTables()
sheetPivotTables, _ := f.GetPivotTables(sheetName) for _, sheetPivotTables := range pivotTables {
for _, sheetPivotTable := range sheetPivotTables { for _, sheetPivotTable := range sheetPivotTables {
pivotTableCaches[sheetPivotTable.pivotCacheXML]++ pivotTableCaches[sheetPivotTable.pivotCacheXML]++
} }
@ -1038,3 +1037,17 @@ func (f *File) DeletePivotTable(sheet, name string) error {
} }
return newNoExistTableError(name) return newNoExistTableError(name)
} }
// getPivotTables provides a function to get all pivot tables in a workbook.
func (f *File) getPivotTables() (map[string][]PivotTableOptions, error) {
pivotTables := map[string][]PivotTableOptions{}
for _, sheetName := range f.GetSheetList() {
pts, err := f.GetPivotTables(sheetName)
e := ErrSheetNotExist{sheetName}
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
return pivotTables, err
}
pivotTables[sheetName] = append(pivotTables[sheetName], pts...)
}
return pivotTables, nil
}

View File

@ -343,6 +343,8 @@ func TestPivotTable(t *testing.T) {
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset) f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
_, err = f.GetPivotTables("Sheet1") _, err = f.GetPivotTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
_, err = f.getPivotTables()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
} }

357
slicer.go
View File

@ -53,17 +53,23 @@ import (
// //
// Format specifies the format of the slicer, this setting is optional. // Format specifies the format of the slicer, this setting is optional.
type SlicerOptions struct { type SlicerOptions struct {
Name string slicerXML string
Cell string slicerCacheXML string
TableSheet string slicerCacheName string
TableName string slicerSheetName string
Caption string slicerSheetRID string
Macro string drawingXML string
Width uint Name string
Height uint Cell string
DisplayHeader *bool TableSheet string
ItemDesc bool TableName string
Format GraphicOptions Caption string
Macro string
Width uint
Height uint
DisplayHeader *bool
ItemDesc bool
Format GraphicOptions
} }
// AddSlicer function inserts a slicer by giving the worksheet name and slicer // AddSlicer function inserts a slicer by giving the worksheet name and slicer
@ -99,7 +105,7 @@ func (f *File) AddSlicer(sheet string, opts *SlicerOptions) error {
if err != nil { if err != nil {
return err return err
} }
slicerCacheName, err := f.setSlicerCache(sheet, colIdx, opts, table, pivotTable) slicerCacheName, err := f.setSlicerCache(colIdx, opts, table, pivotTable)
if err != nil { if err != nil {
return err return err
} }
@ -224,7 +230,6 @@ func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
slicerID = f.countSlicers() + 1 slicerID = f.countSlicers() + 1
ws, err = f.workSheetReader(sheet) ws, err = f.workSheetReader(sheet)
decodeExtLst = new(decodeExtLst) decodeExtLst = new(decodeExtLst)
slicerList = new(decodeSlicerList)
) )
if err != nil { if err != nil {
return slicerID, err return slicerID, err
@ -236,6 +241,7 @@ func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
} }
for _, ext := range decodeExtLst.Ext { for _, ext := range decodeExtLst.Ext {
if ext.URI == extURI { if ext.URI == extURI {
slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList) _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer { for _, slicer := range slicerList.Slicer {
if slicer.RID != "" { if slicer.RID != "" {
@ -390,14 +396,13 @@ func (f *File) genSlicerCacheName(name string) string {
// setSlicerCache check if a slicer cache already exists or add a new slicer // setSlicerCache check if a slicer cache already exists or add a new slicer
// cache by giving the column index, slicer, table options, and returns the // cache by giving the column index, slicer, table options, and returns the
// slicer cache name. // slicer cache name.
func (f *File) setSlicerCache(sheet string, colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) (string, error) { func (f *File) setSlicerCache(colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) (string, error) {
var ok bool var ok bool
var slicerCacheName string var slicerCacheName string
f.Pkg.Range(func(k, v interface{}) bool { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") { if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
slicerCache := &xlsxSlicerCacheDefinition{} slicerCache, err := f.slicerCacheReader(k.(string))
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))). if err != nil {
Decode(slicerCache); err != nil && err != io.EOF {
return true return true
} }
if pivotTable != nil && slicerCache.PivotTables != nil { if pivotTable != nil && slicerCache.PivotTables != nil {
@ -449,6 +454,20 @@ func (f *File) slicerReader(slicerXML string) (*xlsxSlicers, error) {
return slicer, nil return slicer, nil
} }
// slicerCacheReader provides a function to get the pointer to the structure
// after deserialization of xl/slicerCaches/slicerCache%d.xml.
func (f *File) slicerCacheReader(slicerCacheXML string) (*xlsxSlicerCacheDefinition, error) {
content, ok := f.Pkg.Load(slicerCacheXML)
slicerCache := &xlsxSlicerCacheDefinition{}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(slicerCache); err != nil && err != io.EOF {
return nil, err
}
}
return slicerCache, nil
}
// timelineReader provides a function to get the pointer to the structure // timelineReader provides a function to get the pointer to the structure
// after deserialization of xl/timelines/timeline%d.xml. // after deserialization of xl/timelines/timeline%d.xml.
func (f *File) timelineReader(timelineXML string) (*xlsxTimelines, error) { func (f *File) timelineReader(timelineXML string) (*xlsxTimelines, error) {
@ -586,6 +605,7 @@ func (f *File) addDrawingSlicer(sheet, slicerName string, ns xml.Attr, opts *Sli
return err return err
} }
graphicFrame := xlsxGraphicFrame{ graphicFrame := xlsxGraphicFrame{
Macro: opts.Macro,
NvGraphicFramePr: xlsxNvGraphicFramePr{ NvGraphicFramePr: xlsxNvGraphicFramePr{
CNvPr: &xlsxCNvPr{ CNvPr: &xlsxCNvPr{
ID: cNvPrID, ID: cNvPrID,
@ -725,3 +745,306 @@ func (f *File) addWorkbookSlicerCache(slicerCacheID int, URI string) error {
wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")} wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
return err return err
} }
// GetSlicers provides the method to get all slicers in a worksheet by a given
// worksheet name. Note that, this function does not support getting the height,
// width, and graphic options of the slicer shape currently.
func (f *File) GetSlicers(sheet string) ([]SlicerOptions, error) {
var (
slicers []SlicerOptions
ws, err = f.workSheetReader(sheet)
decodeExtLst = new(decodeExtLst)
)
if err != nil {
return slicers, err
}
if ws.ExtLst == nil {
return slicers, err
}
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.TrimPrefix(strings.ReplaceAll(target, "..", "xl"), "/")
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return slicers, err
}
for _, ext := range decodeExtLst.Ext {
if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 {
slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(&slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID != "" {
opts, err := f.getSlicers(sheet, slicer.RID, drawingXML)
if err != nil {
return slicers, err
}
slicers = append(slicers, opts...)
}
}
}
}
return slicers, err
}
// getSlicerCache provides a function to get a slicer cache by given slicer
// cache name and slicer options.
func (f *File) getSlicerCache(slicerCacheName string, opt *SlicerOptions) *xlsxSlicerCacheDefinition {
var (
err error
slicerCache *xlsxSlicerCacheDefinition
)
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
slicerCache, err = f.slicerCacheReader(k.(string))
if err != nil {
return true
}
if slicerCache.Name == slicerCacheName {
opt.slicerCacheXML = k.(string)
return false
}
}
return true
})
return slicerCache
}
// getSlicers provides a function to get slicers options by given worksheet
// name, slicer part relationship ID and drawing part path.
func (f *File) getSlicers(sheet, rID, drawingXML string) ([]SlicerOptions, error) {
var (
opts []SlicerOptions
sheetRelationshipsSlicerXML = f.getSheetRelationshipsTargetByID(sheet, rID)
slicerXML = strings.ReplaceAll(sheetRelationshipsSlicerXML, "..", "xl")
slicers, err = f.slicerReader(slicerXML)
)
if err != nil {
return opts, err
}
for _, slicer := range slicers.Slicer {
opt := SlicerOptions{
slicerXML: slicerXML,
slicerCacheName: slicer.Cache,
slicerSheetName: sheet,
slicerSheetRID: rID,
drawingXML: drawingXML,
Name: slicer.Name,
Caption: slicer.Caption,
DisplayHeader: slicer.ShowCaption,
}
slicerCache := f.getSlicerCache(slicer.Cache, &opt)
if slicerCache == nil {
return opts, err
}
if err := f.extractTableSlicer(slicerCache, &opt); err != nil {
return opts, err
}
if err := f.extractPivotTableSlicer(slicerCache, &opt); err != nil {
return opts, err
}
if err = f.extractSlicerCellAnchor(drawingXML, &opt); err != nil {
return opts, err
}
opts = append(opts, opt)
}
return opts, err
}
// extractTableSlicer extract table slicer options from slicer cache.
func (f *File) extractTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error {
if slicerCache.ExtLst != nil {
tables, err := f.getTables()
if err != nil {
return err
}
ext := new(xlsxExt)
_ = f.xmlNewDecoder(strings.NewReader(slicerCache.ExtLst.Ext)).Decode(ext)
if ext.URI == ExtURISlicerCacheDefinition {
tableSlicerCache := new(decodeTableSlicerCache)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(tableSlicerCache)
opt.ItemDesc = tableSlicerCache.SortOrder == "descending"
for sheetName, sheetTables := range tables {
for _, table := range sheetTables {
if tableSlicerCache.TableID == table.tID {
opt.TableName = table.Name
opt.TableSheet = sheetName
}
}
}
}
}
return nil
}
// extractPivotTableSlicer extract pivot table slicer options from slicer cache.
func (f *File) extractPivotTableSlicer(slicerCache *xlsxSlicerCacheDefinition, opt *SlicerOptions) error {
pivotTables, err := f.getPivotTables()
if err != nil {
return err
}
if slicerCache.PivotTables != nil {
for _, pt := range slicerCache.PivotTables.PivotTable {
opt.TableName = pt.Name
for sheetName, sheetPivotTables := range pivotTables {
for _, pivotTable := range sheetPivotTables {
if opt.TableName == pivotTable.Name {
opt.TableSheet = sheetName
}
}
}
}
if slicerCache.Data != nil && slicerCache.Data.Tabular != nil {
opt.ItemDesc = slicerCache.Data.Tabular.SortOrder == "descending"
}
}
return nil
}
// extractSlicerCellAnchor extract slicer drawing object from two cell anchor by
// giving drawing part path and slicer options.
func (f *File) extractSlicerCellAnchor(drawingXML string, opt *SlicerOptions) error {
var (
wsDr *xlsxWsDr
deCellAnchor = new(decodeCellAnchor)
deChoice = new(decodeChoice)
err error
)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return err
}
wsDr.mu.Lock()
defer wsDr.mu.Unlock()
cond := func(ac *xlsxAlternateContent) bool {
if ac != nil {
_ = f.xmlNewDecoder(strings.NewReader(ac.Content)).Decode(&deChoice)
if deChoice.XMLNSSle15 == NameSpaceDrawingMLSlicerX15.Value || deChoice.XMLNSA14 == NameSpaceDrawingMLA14.Value {
if deChoice.GraphicFrame.NvGraphicFramePr.CNvPr.Name == opt.Name {
return true
}
}
}
return false
}
for _, anchor := range wsDr.TwoCellAnchor {
for _, ac := range anchor.AlternateContent {
if cond(ac) {
if anchor.From != nil {
opt.Macro = deChoice.GraphicFrame.Macro
if opt.Cell, err = CoordinatesToCellName(anchor.From.Col+1, anchor.From.Row+1); err != nil {
return err
}
}
return err
}
}
_ = f.xmlNewDecoder(strings.NewReader("<decodeCellAnchor>" + anchor.GraphicFrame + "</decodeCellAnchor>")).Decode(&deCellAnchor)
for _, ac := range deCellAnchor.AlternateContent {
if cond(ac) {
if deCellAnchor.From != nil {
opt.Macro = deChoice.GraphicFrame.Macro
if opt.Cell, err = CoordinatesToCellName(deCellAnchor.From.Col+1, deCellAnchor.From.Row+1); err != nil {
return err
}
}
return err
}
}
}
return err
}
// getAllSlicers provides a function to get all slicers in a workbook.
func (f *File) getAllSlicers() (map[string][]SlicerOptions, error) {
slicers := map[string][]SlicerOptions{}
for _, sheetName := range f.GetSheetList() {
sles, err := f.GetSlicers(sheetName)
e := ErrSheetNotExist{sheetName}
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
return slicers, err
}
slicers[sheetName] = append(slicers[sheetName], sles...)
}
return slicers, nil
}
// DeleteSlicer provides the method to delete a slicer by a given slicer name.
func (f *File) DeleteSlicer(name string) error {
sles, err := f.getAllSlicers()
if err != nil {
return err
}
for _, slicers := range sles {
for _, slicer := range slicers {
if slicer.Name != name {
continue
}
_ = f.deleteSlicer(slicer)
return f.deleteSlicerCache(sles, slicer)
}
}
return newNoExistSlicerError(name)
}
// getSlicers provides a function to delete slicer by given slicer options.
func (f *File) deleteSlicer(opts SlicerOptions) error {
slicers, err := f.slicerReader(opts.slicerXML)
if err != nil {
return err
}
for i := 0; i < len(slicers.Slicer); i++ {
if slicers.Slicer[i].Name == opts.Name {
slicers.Slicer = append(slicers.Slicer[:i], slicers.Slicer[i+1:]...)
i--
}
}
if len(slicers.Slicer) == 0 {
var (
extLstBytes []byte
ws, err = f.workSheetReader(opts.slicerSheetName)
decodeExtLst = new(decodeExtLst)
)
if err != nil {
return err
}
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for i, ext := range decodeExtLst.Ext {
if ext.URI == ExtURISlicerListX14 || ext.URI == ExtURISlicerListX15 {
slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID == opts.slicerSheetRID {
decodeExtLst.Ext = append(decodeExtLst.Ext[:i], decodeExtLst.Ext[i+1:]...)
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
f.Pkg.Delete(opts.slicerXML)
_ = f.removeContentTypesPart(ContentTypeSlicer, "/"+opts.slicerXML)
f.deleteSheetRelationships(opts.slicerSheetName, opts.slicerSheetRID)
return err
}
}
}
}
}
output, err := xml.Marshal(slicers)
f.saveFileList(opts.slicerXML, output)
return err
}
// deleteSlicerCache provides a function to delete the slicer cache by giving
// slicer options if the slicer cache is no longer used.
func (f *File) deleteSlicerCache(sles map[string][]SlicerOptions, opts SlicerOptions) error {
for _, slicers := range sles {
for _, slicer := range slicers {
if slicer.Name != opts.Name && slicer.slicerCacheName == opts.slicerCacheName {
return nil
}
}
}
if err := f.DeleteDefinedName(&DefinedName{Name: opts.slicerCacheName}); err != nil {
return err
}
f.Pkg.Delete(opts.slicerCacheXML)
return f.removeContentTypesPart(ContentTypeSlicerCache, "/"+opts.slicerCacheXML)
}

View File

@ -5,12 +5,13 @@ import (
"math/rand" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestAddSlicer(t *testing.T) { func TestSlicer(t *testing.T) {
f := NewFile() f := NewFile()
disable, colName := false, "_!@#$%^&*()-+=|\\/<>" disable, colName := false, "_!@#$%^&*()-+=|\\/<>"
assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName)) assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName))
@ -45,8 +46,29 @@ func TestAddSlicer(t *testing.T) {
DisplayHeader: &disable, DisplayHeader: &disable,
ItemDesc: true, ItemDesc: true,
})) }))
// Test get table slicers
slicers, err := f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "Column1", slicers[0].Name)
assert.Equal(t, "E1", slicers[0].Cell)
assert.Equal(t, "Sheet1", slicers[0].TableSheet)
assert.Equal(t, "Table1", slicers[0].TableName)
assert.Equal(t, "Column1", slicers[0].Caption)
assert.Equal(t, "Column1 1", slicers[1].Name)
assert.Equal(t, "I1", slicers[1].Cell)
assert.Equal(t, "Sheet1", slicers[1].TableSheet)
assert.Equal(t, "Table1", slicers[1].TableName)
assert.Equal(t, "Column1", slicers[1].Caption)
assert.Equal(t, colName, slicers[2].Name)
assert.Equal(t, "M1", slicers[2].Cell)
assert.Equal(t, "Sheet1", slicers[2].TableSheet)
assert.Equal(t, "Table1", slicers[2].TableName)
assert.Equal(t, colName, slicers[2].Caption)
assert.Equal(t, "Button1_Click", slicers[2].Macro)
assert.False(t, *slicers[2].DisplayHeader)
assert.True(t, slicers[2].ItemDesc)
// Test create two pivot tables in a new worksheet // Test create two pivot tables in a new worksheet
_, err := f.NewSheet("Sheet2") _, err = f.NewSheet("Sheet2")
assert.NoError(t, err) assert.NoError(t, err)
// Create some data in a sheet // Create some data in a sheet
month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"} month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
@ -116,6 +138,25 @@ func TestAddSlicer(t *testing.T) {
Caption: "Region", Caption: "Region",
ItemDesc: true, ItemDesc: true,
})) }))
// Test get pivot table slicers
slicers, err = f.GetSlicers("Sheet2")
assert.NoError(t, err)
assert.Equal(t, "Month", slicers[0].Name)
assert.Equal(t, "G42", slicers[0].Cell)
assert.Equal(t, "Sheet2", slicers[0].TableSheet)
assert.Equal(t, "PivotTable1", slicers[0].TableName)
assert.Equal(t, "Month", slicers[0].Caption)
assert.Equal(t, "Month 1", slicers[1].Name)
assert.Equal(t, "K42", slicers[1].Cell)
assert.Equal(t, "Sheet2", slicers[1].TableSheet)
assert.Equal(t, "PivotTable1", slicers[1].TableName)
assert.Equal(t, "Month", slicers[1].Caption)
assert.Equal(t, "Region", slicers[2].Name)
assert.Equal(t, "O42", slicers[2].Cell)
assert.Equal(t, "Sheet2", slicers[2].TableSheet)
assert.Equal(t, "PivotTable2", slicers[2].TableName)
assert.Equal(t, "Region", slicers[2].Caption)
assert.True(t, slicers[2].ItemDesc)
// Test add a table slicer with empty slicer options // Test add a table slicer with empty slicer options
assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil)) assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil))
// Test add a table slicer with invalid slicer options // Test add a table slicer with invalid slicer options
@ -167,6 +208,48 @@ func TestAddSlicer(t *testing.T) {
}), "XML syntax error on line 1: invalid UTF-8") }), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
// Test open a workbook and get already exist slicers
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
slicers, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Equal(t, "Column1", slicers[0].Name)
assert.Equal(t, "E1", slicers[0].Cell)
assert.Equal(t, "Sheet1", slicers[0].TableSheet)
assert.Equal(t, "Table1", slicers[0].TableName)
assert.Equal(t, "Column1", slicers[0].Caption)
assert.Equal(t, "Column1 1", slicers[1].Name)
assert.Equal(t, "I1", slicers[1].Cell)
assert.Equal(t, "Sheet1", slicers[1].TableSheet)
assert.Equal(t, "Table1", slicers[1].TableName)
assert.Equal(t, "Column1", slicers[1].Caption)
assert.Equal(t, colName, slicers[2].Name)
assert.Equal(t, "M1", slicers[2].Cell)
assert.Equal(t, "Sheet1", slicers[2].TableSheet)
assert.Equal(t, "Table1", slicers[2].TableName)
assert.Equal(t, colName, slicers[2].Caption)
assert.Equal(t, "Button1_Click", slicers[2].Macro)
assert.False(t, *slicers[2].DisplayHeader)
assert.True(t, slicers[2].ItemDesc)
slicers, err = f.GetSlicers("Sheet2")
assert.NoError(t, err)
assert.Equal(t, "Month", slicers[0].Name)
assert.Equal(t, "G42", slicers[0].Cell)
assert.Equal(t, "Sheet2", slicers[0].TableSheet)
assert.Equal(t, "PivotTable1", slicers[0].TableName)
assert.Equal(t, "Month", slicers[0].Caption)
assert.Equal(t, "Month 1", slicers[1].Name)
assert.Equal(t, "K42", slicers[1].Cell)
assert.Equal(t, "Sheet2", slicers[1].TableSheet)
assert.Equal(t, "PivotTable1", slicers[1].TableName)
assert.Equal(t, "Month", slicers[1].Caption)
assert.Equal(t, "Region", slicers[2].Name)
assert.Equal(t, "O42", slicers[2].Cell)
assert.Equal(t, "Sheet2", slicers[2].TableSheet)
assert.Equal(t, "PivotTable2", slicers[2].TableName)
assert.Equal(t, "Region", slicers[2].Caption)
assert.True(t, slicers[2].ItemDesc)
// Test add a pivot table slicer with workbook which contains timeline // Test add a pivot table slicer with workbook which contains timeline
f, err = OpenFile(workbookPath) f, err = OpenFile(workbookPath)
assert.NoError(t, err) assert.NoError(t, err)
@ -274,6 +357,113 @@ func TestAddSlicer(t *testing.T) {
Caption: "Column1", Caption: "Column1",
}), "XML syntax error on line 1: invalid UTF-8") }), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile()
// Test get sheet slicers without slicer
slicers, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Empty(t, slicers)
// Test get sheet slicers with not exist worksheet name
_, err = f.GetSlicers("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close())
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
// Test get sheet slicers with unsupported charset slicer cache
f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
// Test get sheet slicers with unsupported charset slicer
f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get sheet slicers with invalid worksheet extension list
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = "<>"
_, err = f.GetSlicers("Sheet1")
assert.Error(t, err)
assert.NoError(t, f.Close())
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
// Test get sheet slicers without slicer cache
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
f.Pkg.Delete(k.(string))
}
return true
})
slicers, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
assert.Empty(t, slicers)
assert.NoError(t, f.Close())
// Test open a workbook and get sheet slicer with invalid cell reference in the drawing part
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`<wsDr xmlns="%s"><twoCellAnchor><from><col>-1</col><row>-1</row></from><mc:AlternateContent><mc:Choice xmlns:sle15="%s"><graphicFrame><nvGraphicFramePr><cNvPr id="2" name="Column1"/></nvGraphicFramePr></graphicFrame></mc:Choice></mc:AlternateContent></twoCellAnchor></wsDr>`, NameSpaceDrawingMLSpreadSheet.Value, NameSpaceDrawingMLSlicerX15.Value)))
_, err = f.GetSlicers("Sheet1")
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
// Test get sheet slicer without slicer shape in the drawing part
f.Drawings.Delete("xl/drawings/drawing1.xml")
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(fmt.Sprintf(`<wsDr xmlns="%s"><twoCellAnchor/></wsDr>`, NameSpaceDrawingMLSpreadSheet.Value)))
_, err = f.GetSlicers("Sheet1")
assert.NoError(t, err)
f.Drawings.Delete("xl/drawings/drawing1.xml")
// Test get sheet slicers with unsupported charset drawing part
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get sheet slicers with unsupported charset table
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test get sheet slicers with unsupported charset pivot table
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
_, err = f.GetSlicers("Sheet2")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
// Test create a workbook and get sheet slicer with invalid cell reference in the drawing part
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}))
drawing, ok := f.Drawings.Load("xl/drawings/drawing1.xml")
assert.True(t, ok)
drawing.(*xlsxWsDr).TwoCellAnchor[0].From = &xlsxFrom{Col: -1, Row: -1}
_, err = f.GetSlicers("Sheet1")
assert.Equal(t, newCoordinatesToCellNameError(0, 0), err)
assert.NoError(t, f.Close())
// Test open a workbook and delete slicers
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
for _, name := range []string{colName, "Column1 1", "Column1"} {
assert.NoError(t, f.DeleteSlicer(name))
}
for _, name := range []string{"Month", "Month 1", "Region"} {
assert.NoError(t, f.DeleteSlicer(name))
}
// Test delete slicer with no exits slicer name
assert.Equal(t, newNoExistSlicerError("x"), f.DeleteSlicer("x"))
assert.NoError(t, f.Close())
// Test open a workbook and delete sheet slicer with unsupported charset slicer cache
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
f.Pkg.Store("xl/slicers/slicer1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteSlicer("Column1"), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
} }
func TestAddSheetSlicer(t *testing.T) { func TestAddSheetSlicer(t *testing.T) {
@ -296,36 +486,81 @@ func TestAddSheetTableSlicer(t *testing.T) {
func TestSetSlicerCache(t *testing.T) { func TestSetSlicerCache(t *testing.T) {
f := NewFile() f := NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset) f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset)
_, err := f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{}, nil) _, err := f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile() f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer2" sourceName="B1"><extLst><ext uri="%s"/></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer2" sourceName="B1"><extLst><ext uri="%s"/></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{}, nil) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile() f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"><extLst><ext uri="%s"/></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"><extLst><ext uri="%s"/></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{}, nil) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile() f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"><extLst><ext uri="%s"><tableSlicerCache tableId="1" column="2"/></ext></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"><extLst><ext uri="%s"><tableSlicerCache tableId="1" column="2"/></ext></extLst></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition)))
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{tID: 1}, nil) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile() f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value))) f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(`<slicerCacheDefinition xmlns="%s" name="Slicer1" sourceName="B1"></slicerCacheDefinition>`, NameSpaceSpreadSheetX14.Value)))
_, err = f.setSlicerCache("Sheet1", 1, &SlicerOptions{}, &Table{tID: 1}, nil) _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
} }
func TestDeleteSlicer(t *testing.T) {
f, slicerXML := NewFile(), "xl/slicers/slicer1.xml"
assert.NoError(t, f.AddTable("Sheet1", &Table{
Name: "Table1",
Range: "A1:D5",
}))
assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{
Name: "Column1",
Cell: "E1",
TableSheet: "Sheet1",
TableName: "Table1",
Caption: "Column1",
}))
// Test delete sheet slicers with invalid worksheet extension list
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = "<>"
assert.Error(t, f.deleteSlicer(SlicerOptions{
slicerXML: slicerXML,
slicerSheetName: "Sheet1",
Name: "Column1",
}))
// Test delete slicer with unsupported charset worksheet
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSlicer(SlicerOptions{
slicerXML: slicerXML,
slicerSheetName: "Sheet1",
Name: "Column1",
}), "XML syntax error on line 1: invalid UTF-8")
// Test delete slicer with unsupported charset slicer
f.Pkg.Store(slicerXML, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSlicer(SlicerOptions{slicerXML: slicerXML}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestDeleteSlicerCache(t *testing.T) {
f := NewFile()
// Test delete slicer cache with unsupported charset workbook
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSlicerCache(nil, SlicerOptions{}), "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, f.Close())
}
func TestAddSlicerCache(t *testing.T) { func TestAddSlicerCache(t *testing.T) {
f := NewFile() f := NewFile()
f.ContentTypes = nil f.ContentTypes = nil

View File

@ -173,11 +173,11 @@ func (f *File) DeleteTable(name string) error {
if err := checkDefinedName(name); err != nil { if err := checkDefinedName(name); err != nil {
return err return err
} }
for _, sheet := range f.GetSheetList() { tbls, err := f.getTables()
tables, err := f.GetTables(sheet) if err != nil {
if err != nil { return err
return err }
} for sheet, tables := range tbls {
for _, table := range tables { for _, table := range tables {
if table.Name != name { if table.Name != name {
continue continue
@ -201,6 +201,20 @@ func (f *File) DeleteTable(name string) error {
return newNoExistTableError(name) return newNoExistTableError(name)
} }
// getTables provides a function to get all tables in a workbook.
func (f *File) getTables() (map[string][]Table, error) {
tables := map[string][]Table{}
for _, sheetName := range f.GetSheetList() {
tbls, err := f.GetTables(sheetName)
e := ErrSheetNotExist{sheetName}
if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
return tables, err
}
tables[sheetName] = append(tables[sheetName], tbls...)
}
return tables, nil
}
// countTables provides a function to get table files count storage in the // countTables provides a function to get table files count storage in the
// folder xl/tables. // folder xl/tables.
func (f *File) countTables() int { func (f *File) countTables() int {

View File

@ -24,7 +24,7 @@ type decodeCellAnchor struct {
Sp *decodeSp `xml:"sp"` Sp *decodeSp `xml:"sp"`
Pic *decodePic `xml:"pic"` Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"` ClientData *decodeClientData `xml:"clientData"`
AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"`
Content string `xml:",innerxml"` Content string `xml:",innerxml"`
} }
@ -46,6 +46,28 @@ type decodeCellAnchorPos struct {
ClientData *xlsxInnerXML `xml:"clientData"` ClientData *xlsxInnerXML `xml:"clientData"`
} }
// decodeChoice defines the structure used to deserialize the mc:Choice element.
type decodeChoice struct {
XMLName xml.Name `xml:"Choice"`
XMLNSA14 string `xml:"a14,attr"`
XMLNSSle15 string `xml:"sle15,attr"`
Requires string `xml:"Requires,attr"`
GraphicFrame decodeGraphicFrame `xml:"graphicFrame"`
}
// decodeGraphicFrame defines the structure used to deserialize the
// xdr:graphicFrame element.
type decodeGraphicFrame struct {
Macro string `xml:"macro,attr"`
NvGraphicFramePr decodeNvGraphicFramePr `xml:"nvGraphicFramePr"`
}
// decodeNvGraphicFramePr defines the structure used to deserialize the
// xdr:nvGraphicFramePr element.
type decodeNvGraphicFramePr struct {
CNvPr decodeCNvPr `xml:"cNvPr"`
}
// decodeSp defines the structure used to deserialize the sp element. // decodeSp defines the structure used to deserialize the sp element.
type decodeSp struct { type decodeSp struct {
Macro string `xml:"macro,attr,omitempty"` Macro string `xml:"macro,attr,omitempty"`
@ -56,7 +78,7 @@ type decodeSp struct {
SpPr *decodeSpPr `xml:"spPr"` SpPr *decodeSpPr `xml:"spPr"`
} }
// decodeSp (Non-Visual Properties for a Shape) directly maps the nvSpPr // decodeNvSpPr (Non-Visual Properties for a Shape) directly maps the nvSpPr
// element. This element specifies all non-visual properties for a shape. This // element. This element specifies all non-visual properties for a shape. This
// element is a container for the non-visual identification properties, shape // element is a container for the non-visual identification properties, shape
// properties and application properties that are to be associated with a // properties and application properties that are to be associated with a

View File

@ -149,9 +149,10 @@ type xlsxX15SlicerCaches struct {
// decodeTableSlicerCache defines the structure used to parse the // decodeTableSlicerCache defines the structure used to parse the
// x15:tableSlicerCache element of the table slicer cache. // x15:tableSlicerCache element of the table slicer cache.
type decodeTableSlicerCache struct { type decodeTableSlicerCache struct {
XMLName xml.Name `xml:"tableSlicerCache"` XMLName xml.Name `xml:"tableSlicerCache"`
TableID int `xml:"tableId,attr"` TableID int `xml:"tableId,attr"`
Column int `xml:"column,attr"` Column int `xml:"column,attr"`
SortOrder string `xml:"sortOrder,attr"`
} }
// decodeSlicerList defines the structure used to parse the x14:slicerList // decodeSlicerList defines the structure used to parse the x14:slicerList