diff --git a/errors.go b/errors.go
index b460dfd..b12b06c 100644
--- a/errors.go
+++ b/errors.go
@@ -264,6 +264,12 @@ func newInvalidStyleID(styleID int) error {
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
// table name.
func newNoExistTableError(name string) error {
diff --git a/pivotTable.go b/pivotTable.go
index 490b487..6205c54 100644
--- a/pivotTable.go
+++ b/pivotTable.go
@@ -785,12 +785,11 @@ func (f *File) getPivotTableDataRange(opts *PivotTableOptions) error {
opts.pivotDataRange = opts.DataRange
return nil
}
- for _, sheetName := range f.GetSheetList() {
- tables, err := f.GetTables(sheetName)
- e := ErrSheetNotExist{sheetName}
- if err != nil && err.Error() != newNotWorksheetError(sheetName).Error() && err.Error() != e.Error() {
- return err
- }
+ tbls, err := f.getTables()
+ if err != nil {
+ return err
+ }
+ for sheetName, tables := range tbls {
for _, table := range tables {
if table.Name == opts.DataRange {
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
}
pivotTableCaches := map[string]int{}
- for _, sheetName := range f.GetSheetList() {
- sheetPivotTables, _ := f.GetPivotTables(sheetName)
+ pivotTables, _ := f.getPivotTables()
+ for _, sheetPivotTables := range pivotTables {
for _, sheetPivotTable := range sheetPivotTables {
pivotTableCaches[sheetPivotTable.pivotCacheXML]++
}
@@ -1038,3 +1037,17 @@ func (f *File) DeletePivotTable(sheet, name string) error {
}
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
+}
diff --git a/pivotTable_test.go b/pivotTable_test.go
index 58b1dbe..50f95bf 100644
--- a/pivotTable_test.go
+++ b/pivotTable_test.go
@@ -343,6 +343,8 @@ func TestPivotTable(t *testing.T) {
f.Pkg.Store("xl/pivotTables/pivotTable1.xml", MacintoshCyrillicCharset)
_, err = f.GetPivotTables("Sheet1")
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())
}
diff --git a/slicer.go b/slicer.go
index a7f26ed..7b4a2d8 100644
--- a/slicer.go
+++ b/slicer.go
@@ -53,17 +53,23 @@ import (
//
// Format specifies the format of the slicer, this setting is optional.
type SlicerOptions struct {
- Name string
- Cell string
- TableSheet string
- TableName string
- Caption string
- Macro string
- Width uint
- Height uint
- DisplayHeader *bool
- ItemDesc bool
- Format GraphicOptions
+ slicerXML string
+ slicerCacheXML string
+ slicerCacheName string
+ slicerSheetName string
+ slicerSheetRID string
+ drawingXML string
+ Name string
+ Cell string
+ TableSheet string
+ TableName string
+ 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
@@ -99,7 +105,7 @@ func (f *File) AddSlicer(sheet string, opts *SlicerOptions) error {
if err != nil {
return err
}
- slicerCacheName, err := f.setSlicerCache(sheet, colIdx, opts, table, pivotTable)
+ slicerCacheName, err := f.setSlicerCache(colIdx, opts, table, pivotTable)
if err != nil {
return err
}
@@ -224,7 +230,6 @@ func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
slicerID = f.countSlicers() + 1
ws, err = f.workSheetReader(sheet)
decodeExtLst = new(decodeExtLst)
- slicerList = new(decodeSlicerList)
)
if err != nil {
return slicerID, err
@@ -236,6 +241,7 @@ func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
}
for _, ext := range decodeExtLst.Ext {
if ext.URI == extURI {
+ slicerList := new(decodeSlicerList)
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer {
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
// cache by giving the column index, slicer, table options, and returns the
// 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 slicerCacheName string
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
- slicerCache := &xlsxSlicerCacheDefinition{}
- if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
- Decode(slicerCache); err != nil && err != io.EOF {
+ slicerCache, err := f.slicerCacheReader(k.(string))
+ if err != nil {
return true
}
if pivotTable != nil && slicerCache.PivotTables != nil {
@@ -449,6 +454,20 @@ func (f *File) slicerReader(slicerXML string) (*xlsxSlicers, error) {
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
// after deserialization of xl/timelines/timeline%d.xml.
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
}
graphicFrame := xlsxGraphicFrame{
+ Macro: opts.Macro,
NvGraphicFramePr: xlsxNvGraphicFramePr{
CNvPr: &xlsxCNvPr{
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), ""), "")}
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("" + ws.ExtLst.Ext + "")).
+ 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("" + anchor.GraphicFrame + "")).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("" + ws.ExtLst.Ext + "")).
+ 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), ""), "")}
+ 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)
+}
diff --git a/slicer_test.go b/slicer_test.go
index da6fa91..5a79a80 100644
--- a/slicer_test.go
+++ b/slicer_test.go
@@ -5,12 +5,13 @@ import (
"math/rand"
"os"
"path/filepath"
+ "strings"
"testing"
"github.com/stretchr/testify/assert"
)
-func TestAddSlicer(t *testing.T) {
+func TestSlicer(t *testing.T) {
f := NewFile()
disable, colName := false, "_!@#$%^&*()-+=|\\/<>"
assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName))
@@ -45,8 +46,29 @@ func TestAddSlicer(t *testing.T) {
DisplayHeader: &disable,
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
- _, err := f.NewSheet("Sheet2")
+ _, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
// Create some data in a sheet
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",
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
assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil))
// 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")
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
f, err = OpenFile(workbookPath)
assert.NoError(t, err)
@@ -274,6 +357,113 @@ func TestAddSlicer(t *testing.T) {
Caption: "Column1",
}), "XML syntax error on line 1: invalid UTF-8")
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(`-1-1
`, 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(``, 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) {
@@ -296,36 +486,81 @@ func TestAddSheetTableSlicer(t *testing.T) {
func TestSetSlicerCache(t *testing.T) {
f := NewFile()
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, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, 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, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, 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, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, 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, f.Close())
f = NewFile()
f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, 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, 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) {
f := NewFile()
f.ContentTypes = nil
diff --git a/table.go b/table.go
index 5ca7894..aec7b26 100644
--- a/table.go
+++ b/table.go
@@ -173,11 +173,11 @@ func (f *File) DeleteTable(name string) error {
if err := checkDefinedName(name); err != nil {
return err
}
- for _, sheet := range f.GetSheetList() {
- tables, err := f.GetTables(sheet)
- if err != nil {
- return err
- }
+ tbls, err := f.getTables()
+ if err != nil {
+ return err
+ }
+ for sheet, tables := range tbls {
for _, table := range tables {
if table.Name != name {
continue
@@ -201,6 +201,20 @@ func (f *File) DeleteTable(name string) error {
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
// folder xl/tables.
func (f *File) countTables() int {
diff --git a/xmlDecodeDrawing.go b/xmlDecodeDrawing.go
index 5c900fc..a59e7c4 100644
--- a/xmlDecodeDrawing.go
+++ b/xmlDecodeDrawing.go
@@ -24,7 +24,7 @@ type decodeCellAnchor struct {
Sp *decodeSp `xml:"sp"`
Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"`
- AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"`
+ AlternateContent []*xlsxAlternateContent `xml:"AlternateContent"`
Content string `xml:",innerxml"`
}
@@ -46,6 +46,28 @@ type decodeCellAnchorPos struct {
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.
type decodeSp struct {
Macro string `xml:"macro,attr,omitempty"`
@@ -56,7 +78,7 @@ type decodeSp struct {
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 is a container for the non-visual identification properties, shape
// properties and application properties that are to be associated with a
diff --git a/xmlSlicers.go b/xmlSlicers.go
index 6e68897..5c20923 100644
--- a/xmlSlicers.go
+++ b/xmlSlicers.go
@@ -149,9 +149,10 @@ type xlsxX15SlicerCaches struct {
// decodeTableSlicerCache defines the structure used to parse the
// x15:tableSlicerCache element of the table slicer cache.
type decodeTableSlicerCache struct {
- XMLName xml.Name `xml:"tableSlicerCache"`
- TableID int `xml:"tableId,attr"`
- Column int `xml:"column,attr"`
+ XMLName xml.Name `xml:"tableSlicerCache"`
+ TableID int `xml:"tableId,attr"`
+ Column int `xml:"column,attr"`
+ SortOrder string `xml:"sortOrder,attr"`
}
// decodeSlicerList defines the structure used to parse the x14:slicerList