excelize/slicer.go
3zmx 50e23df865
ref #65, support _xlfn.ANCHORARRAY formula function (#1784)
- Initial formula array calculation support
- Update unit test and documentation
2024-01-18 15:31:43 +08:00

728 lines
25 KiB
Go

// Copyright 2016 - 2024 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
// Package excelize providing a set of functions that allow you to write to and
// read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
// writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
// Supports complex components by high compatibility, and provided streaming
// API for generating or reading data from a worksheet with huge amounts of
// data. This library needs Go version 1.18 or later.
package excelize
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"sort"
"strconv"
"strings"
"unicode"
)
// SlicerOptions represents the settings of the slicer.
//
// Name specifies the slicer name, should be an existing field name of the given
// table or pivot table, this setting is required.
//
// Cell specifies the left top cell coordinates the position for inserting the
// slicer, this setting is required.
//
// TableSheet specifies the worksheet name of the table or pivot table, this
// setting is required.
//
// TableName specifies the name of the table or pivot table, this setting is
// required.
//
// Caption specifies the caption of the slicer, this setting is optional.
//
// Macro used for set macro for the slicer, the workbook extension should be
// XLSM or XLTM.
//
// Width specifies the width of the slicer, this setting is optional.
//
// Height specifies the height of the slicer, this setting is optional.
//
// DisplayHeader specifies if display header of the slicer, this setting is
// optional, the default setting is display.
//
// ItemDesc specifies descending (Z-A) item sorting, this setting is optional,
// and the default setting is false (represents ascending).
//
// 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
}
// AddSlicer function inserts a slicer by giving the worksheet name and slicer
// settings.
//
// For example, insert a slicer on the Sheet1!E1 with field Column1 for the
// table named Table1:
//
// err := f.AddSlicer("Sheet1", &excelize.SlicerOptions{
// Name: "Column1",
// Cell: "E1",
// TableSheet: "Sheet1",
// TableName: "Table1",
// Caption: "Column1",
// Width: 200,
// Height: 200,
// })
func (f *File) AddSlicer(sheet string, opts *SlicerOptions) error {
opts, err := parseSlicerOptions(opts)
if err != nil {
return err
}
table, pivotTable, colIdx, err := f.getSlicerSource(opts)
if err != nil {
return err
}
extURI, ns := ExtURISlicerListX14, NameSpaceDrawingMLA14
if table != nil {
extURI = ExtURISlicerListX15
ns = NameSpaceDrawingMLSlicerX15
}
slicerID, err := f.addSheetSlicer(sheet, extURI)
if err != nil {
return err
}
slicerCacheName, err := f.setSlicerCache(sheet, colIdx, opts, table, pivotTable)
if err != nil {
return err
}
slicerName := f.genSlicerName(opts.Name)
if err := f.addDrawingSlicer(sheet, slicerName, ns, opts); err != nil {
return err
}
return f.addSlicer(slicerID, xlsxSlicer{
Name: slicerName,
Cache: slicerCacheName,
Caption: opts.Caption,
ShowCaption: opts.DisplayHeader,
RowHeight: 251883,
})
}
// parseSlicerOptions provides a function to parse the format settings of the
// slicer with default value.
func parseSlicerOptions(opts *SlicerOptions) (*SlicerOptions, error) {
if opts == nil {
return nil, ErrParameterRequired
}
if opts.Name == "" || opts.Cell == "" || opts.TableSheet == "" || opts.TableName == "" {
return nil, ErrParameterInvalid
}
if opts.Width == 0 {
opts.Width = defaultSlicerWidth
}
if opts.Height == 0 {
opts.Height = defaultSlicerHeight
}
if opts.Format.PrintObject == nil {
opts.Format.PrintObject = boolPtr(true)
}
if opts.Format.Locked == nil {
opts.Format.Locked = boolPtr(false)
}
if opts.Format.ScaleX == 0 {
opts.Format.ScaleX = defaultDrawingScale
}
if opts.Format.ScaleY == 0 {
opts.Format.ScaleY = defaultDrawingScale
}
return opts, nil
}
// countSlicers provides a function to get slicer files count storage in the
// folder xl/slicers.
func (f *File) countSlicers() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicers/slicer") {
count++
}
return true
})
return count
}
// countSlicerCache provides a function to get slicer cache files count storage
// in the folder xl/SlicerCaches.
func (f *File) countSlicerCache() int {
count := 0
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicerCaches/slicerCache") {
count++
}
return true
})
return count
}
// getSlicerSource returns the slicer data source table or pivot table settings
// and the index of the given slicer fields in the table or pivot table
// column.
func (f *File) getSlicerSource(opts *SlicerOptions) (*Table, *PivotTableOptions, int, error) {
var (
table *Table
pivotTable *PivotTableOptions
colIdx int
err error
dataRange string
tables []Table
pivotTables []PivotTableOptions
)
if tables, err = f.GetTables(opts.TableSheet); err != nil {
return table, pivotTable, colIdx, err
}
for _, tbl := range tables {
if tbl.Name == opts.TableName {
table = &tbl
dataRange = fmt.Sprintf("%s!%s", opts.TableSheet, tbl.Range)
break
}
}
if table == nil {
if pivotTables, err = f.GetPivotTables(opts.TableSheet); err != nil {
return table, pivotTable, colIdx, err
}
for _, tbl := range pivotTables {
if tbl.Name == opts.TableName {
pivotTable = &tbl
dataRange = tbl.DataRange
break
}
}
if pivotTable == nil {
return table, pivotTable, colIdx, newNoExistTableError(opts.TableName)
}
}
order, _ := f.getTableFieldsOrder(&PivotTableOptions{DataRange: dataRange})
if colIdx = inStrSlice(order, opts.Name, true); colIdx == -1 {
return table, pivotTable, colIdx, newInvalidSlicerNameError(opts.Name)
}
return table, pivotTable, colIdx, err
}
// addSheetSlicer adds a new slicer and updates the namespace and relationships
// parts of the worksheet by giving the worksheet name.
func (f *File) addSheetSlicer(sheet, extURI string) (int, error) {
var (
slicerID = f.countSlicers() + 1
ws, err = f.workSheetReader(sheet)
decodeExtLst = new(decodeExtLst)
slicerList = new(decodeSlicerList)
)
if err != nil {
return slicerID, err
}
if ws.ExtLst != nil {
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return slicerID, err
}
for _, ext := range decodeExtLst.Ext {
if ext.URI == extURI {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID != "" {
sheetRelationshipsDrawingXML := f.getSheetRelationshipsTargetByID(sheet, slicer.RID)
slicerID, _ = strconv.Atoi(strings.TrimSuffix(strings.TrimPrefix(sheetRelationshipsDrawingXML, "../slicers/slicer"), ".xml"))
return slicerID, err
}
}
}
}
}
sheetRelationshipsSlicerXML := "../slicers/slicer" + strconv.Itoa(slicerID) + ".xml"
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipSlicer, sheetRelationshipsSlicerXML, "")
f.addSheetNameSpace(sheet, NameSpaceSpreadSheetX14)
return slicerID, f.addSheetTableSlicer(ws, rID, extURI)
}
// addSheetTableSlicer adds a new table slicer for the worksheet by giving the
// worksheet relationships ID and extension URI.
func (f *File) addSheetTableSlicer(ws *xlsxWorksheet, rID int, extURI string) error {
var (
decodeExtLst = new(decodeExtLst)
err error
slicerListBytes, extLstBytes []byte
)
if ws.ExtLst != nil {
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + ws.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
}
slicerListBytes, _ = xml.Marshal(&xlsxX14SlicerList{
Slicer: []*xlsxX14Slicer{{RID: "rId" + strconv.Itoa(rID)}},
})
ext := &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: extURI, Content: string(slicerListBytes),
}
if extURI == ExtURISlicerListX15 {
ext.xmlns = []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}}
}
decodeExtLst.Ext = append(decodeExtLst.Ext, ext)
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(worksheetExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
return err
}
// addSlicer adds a new slicer to the workbook by giving the slicer ID and
// settings.
func (f *File) addSlicer(slicerID int, slicer xlsxSlicer) error {
slicerXML := "xl/slicers/slicer" + strconv.Itoa(slicerID) + ".xml"
slicers, err := f.slicerReader(slicerXML)
if err != nil {
return err
}
if err := f.addContentTypePart(slicerID, "slicer"); err != nil {
return err
}
slicers.Slicer = append(slicers.Slicer, slicer)
output, err := xml.Marshal(slicers)
f.saveFileList(slicerXML, output)
return err
}
// genSlicerName generates a unique slicer cache name by giving the slicer name.
func (f *File) genSlicerName(name string) string {
var (
cnt int
slicerName string
names []string
)
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/slicers/slicer") {
slicers, err := f.slicerReader(k.(string))
if err != nil {
return true
}
for _, slicer := range slicers.Slicer {
names = append(names, slicer.Name)
}
}
if strings.Contains(k.(string), "xl/timelines/timeline") {
timelines, err := f.timelineReader(k.(string))
if err != nil {
return true
}
for _, timeline := range timelines.Timeline {
names = append(names, timeline.Name)
}
}
return true
})
slicerName = name
for {
tmp := slicerName
if cnt > 0 {
tmp = fmt.Sprintf("%s %d", slicerName, cnt)
}
if inStrSlice(names, tmp, true) == -1 {
slicerName = tmp
break
}
cnt++
}
return slicerName
}
// genSlicerCacheName generates a unique slicer cache name by giving the slicer name.
func (f *File) genSlicerCacheName(name string) string {
var (
cnt int
definedNames []string
slicerCacheName string
)
for _, dn := range f.GetDefinedName() {
if dn.Scope == "Workbook" {
definedNames = append(definedNames, dn.Name)
}
}
for i, c := range name {
if unicode.IsLetter(c) {
slicerCacheName += string(c)
continue
}
if i > 0 && (unicode.IsDigit(c) || c == '.') {
slicerCacheName += string(c)
continue
}
slicerCacheName += "_"
}
slicerCacheName = fmt.Sprintf("Slicer_%s", slicerCacheName)
for {
tmp := slicerCacheName
if cnt > 0 {
tmp = fmt.Sprintf("%s%d", slicerCacheName, cnt)
}
if inStrSlice(definedNames, tmp, true) == -1 {
slicerCacheName = tmp
break
}
cnt++
}
return slicerCacheName
}
// 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) {
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 {
return true
}
if pivotTable != nil && slicerCache.PivotTables != nil {
for _, tbl := range slicerCache.PivotTables.PivotTable {
if tbl.Name == pivotTable.Name {
ok, slicerCacheName = true, slicerCache.Name
return false
}
}
}
if table == nil || slicerCache.ExtLst == nil {
return true
}
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)
if tableSlicerCache.TableID == table.tID && tableSlicerCache.Column == colIdx+1 {
ok, slicerCacheName = true, slicerCache.Name
return false
}
}
}
return true
})
if ok {
return slicerCacheName, nil
}
slicerCacheName = f.genSlicerCacheName(opts.Name)
return slicerCacheName, f.addSlicerCache(slicerCacheName, colIdx, opts, table, pivotTable)
}
// slicerReader provides a function to get the pointer to the structure
// after deserialization of xl/slicers/slicer%d.xml.
func (f *File) slicerReader(slicerXML string) (*xlsxSlicers, error) {
content, ok := f.Pkg.Load(slicerXML)
slicer := &xlsxSlicers{
XMLNSXMC: SourceRelationshipCompatibility.Value,
XMLNSX: NameSpaceSpreadSheet.Value,
XMLNSXR10: NameSpaceSpreadSheetXR10.Value,
}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(slicer); err != nil && err != io.EOF {
return nil, err
}
}
return slicer, 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) {
content, ok := f.Pkg.Load(timelineXML)
timeline := &xlsxTimelines{
XMLNSXMC: SourceRelationshipCompatibility.Value,
XMLNSX: NameSpaceSpreadSheet.Value,
XMLNSXR10: NameSpaceSpreadSheetXR10.Value,
}
if ok && content != nil {
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(timeline); err != nil && err != io.EOF {
return nil, err
}
}
return timeline, nil
}
// addSlicerCache adds a new slicer cache by giving the slicer cache name,
// column index, slicer, and table or pivot table options.
func (f *File) addSlicerCache(slicerCacheName string, colIdx int, opts *SlicerOptions, table *Table, pivotTable *PivotTableOptions) error {
var (
sortOrder string
slicerCacheBytes, tableSlicerBytes, extLstBytes []byte
extURI = ExtURISlicerCachesX14
slicerCacheID = f.countSlicerCache() + 1
decodeExtLst = new(decodeExtLst)
slicerCache = xlsxSlicerCacheDefinition{
XMLNSXMC: SourceRelationshipCompatibility.Value,
XMLNSX: NameSpaceSpreadSheet.Value,
XMLNSX15: NameSpaceSpreadSheetX15.Value,
XMLNSXR10: NameSpaceSpreadSheetXR10.Value,
Name: slicerCacheName,
SourceName: opts.Name,
}
)
if opts.ItemDesc {
sortOrder = "descending"
}
if pivotTable != nil {
pivotCacheID, err := f.addPivotCacheSlicer(pivotTable)
if err != nil {
return err
}
slicerCache.PivotTables = &xlsxSlicerCachePivotTables{
PivotTable: []xlsxSlicerCachePivotTable{
{TabID: f.getSheetID(opts.TableSheet), Name: pivotTable.Name},
},
}
slicerCache.Data = &xlsxSlicerCacheData{
Tabular: &xlsxTabularSlicerCache{
PivotCacheID: pivotCacheID,
SortOrder: sortOrder,
ShowMissing: boolPtr(false),
Items: &xlsxTabularSlicerCacheItems{
Count: 1, I: []xlsxTabularSlicerCacheItem{{S: true}},
},
},
}
}
if table != nil {
tableSlicerBytes, _ = xml.Marshal(&xlsxTableSlicerCache{
TableID: table.tID,
Column: colIdx + 1,
SortOrder: sortOrder,
})
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}},
URI: ExtURISlicerCacheDefinition, Content: string(tableSlicerBytes),
})
extLstBytes, _ = xml.Marshal(decodeExtLst)
slicerCache.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
extURI = ExtURISlicerCachesX15
}
slicerCacheXML := "xl/slicerCaches/slicerCache" + strconv.Itoa(slicerCacheID) + ".xml"
slicerCacheBytes, _ = xml.Marshal(slicerCache)
f.saveFileList(slicerCacheXML, slicerCacheBytes)
if err := f.addContentTypePart(slicerCacheID, "slicerCache"); err != nil {
return err
}
if err := f.addWorkbookSlicerCache(slicerCacheID, extURI); err != nil {
return err
}
return f.SetDefinedName(&DefinedName{Name: slicerCacheName, RefersTo: formulaErrorNA})
}
// addPivotCacheSlicer adds a new slicer cache by giving the pivot table options
// and returns pivot table cache ID.
func (f *File) addPivotCacheSlicer(opts *PivotTableOptions) (int, error) {
var (
pivotCacheID int
pivotCacheBytes, extLstBytes []byte
decodeExtLst = new(decodeExtLst)
decodeX14PivotCacheDefinition = new(decodeX14PivotCacheDefinition)
)
pc, err := f.pivotCacheReader(opts.pivotCacheXML)
if err != nil {
return pivotCacheID, err
}
if pc.ExtLst != nil {
_ = f.xmlNewDecoder(strings.NewReader("<extLst>" + pc.ExtLst.Ext + "</extLst>")).Decode(decodeExtLst)
for _, ext := range decodeExtLst.Ext {
if ext.URI == ExtURIPivotCacheDefinition {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeX14PivotCacheDefinition)
return decodeX14PivotCacheDefinition.PivotCacheID, err
}
}
}
pivotCacheID = f.genPivotCacheDefinitionID()
pivotCacheBytes, _ = xml.Marshal(&xlsxX14PivotCacheDefinition{PivotCacheID: pivotCacheID})
ext := &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: ExtURIPivotCacheDefinition, Content: string(pivotCacheBytes),
}
decodeExtLst.Ext = append(decodeExtLst.Ext, ext)
extLstBytes, _ = xml.Marshal(decodeExtLst)
pc.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
pivotCache, err := xml.Marshal(pc)
f.saveFileList(opts.pivotCacheXML, pivotCache)
return pivotCacheID, err
}
// addDrawingSlicer adds a slicer shape and fallback shape by giving the
// worksheet name, slicer name, and slicer options.
func (f *File) addDrawingSlicer(sheet, slicerName string, ns xml.Attr, opts *SlicerOptions) error {
drawingID := f.countDrawings() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
content, twoCellAnchor, cNvPrID, err := f.twoCellAnchorShape(sheet, drawingXML, opts.Cell, opts.Width, opts.Height, opts.Format)
if err != nil {
return err
}
graphicFrame := xlsxGraphicFrame{
NvGraphicFramePr: xlsxNvGraphicFramePr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
Name: slicerName,
},
},
Xfrm: xlsxXfrm{Off: xlsxOff{}, Ext: aExt{}},
Graphic: &xlsxGraphic{
GraphicData: &xlsxGraphicData{
URI: NameSpaceDrawingMLSlicer.Value,
Sle: &xlsxSle{XMLNS: NameSpaceDrawingMLSlicer.Value, Name: slicerName},
},
},
}
graphic, _ := xml.Marshal(graphicFrame)
sp := xdrSp{
Macro: opts.Macro,
NvSpPr: &xdrNvSpPr{
CNvPr: &xlsxCNvPr{
ID: cNvPrID,
},
CNvSpPr: &xdrCNvSpPr{
TxBox: true,
},
},
SpPr: &xlsxSpPr{
Xfrm: xlsxXfrm{Off: xlsxOff{X: 2914650, Y: 152400}, Ext: aExt{Cx: 1828800, Cy: 2238375}},
SolidFill: &xlsxInnerXML{Content: "<a:prstClr val=\"white\"/>"},
PrstGeom: xlsxPrstGeom{
Prst: "rect",
},
Ln: xlsxLineProperties{W: 1, SolidFill: &xlsxInnerXML{Content: "<a:prstClr val=\"black\"/>"}},
},
TxBody: &xdrTxBody{
BodyPr: &aBodyPr{VertOverflow: "clip", HorzOverflow: "clip"},
P: []*aP{
{R: &aR{T: "This shape represents a table slicer. Table slicers are not supported in this version of Excel."}},
{R: &aR{T: "If the shape was modified in an earlier version of Excel, or if the workbook was saved in Excel 2007 or earlier, the slicer can't be used."}},
},
},
}
shape, _ := xml.Marshal(sp)
twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: *opts.Format.Locked,
FPrintsWithSheet: *opts.Format.PrintObject,
}
choice := xlsxChoice{Requires: ns.Name.Local, Content: string(graphic)}
if ns.Value == NameSpaceDrawingMLA14.Value { // pivot table slicer
choice.XMLNSA14 = ns.Value
}
if ns.Value == NameSpaceDrawingMLSlicerX15.Value { // table slicer
choice.XMLNSSle15 = ns.Value
}
fallback := xlsxFallback{Content: string(shape)}
choiceBytes, _ := xml.Marshal(choice)
shapeBytes, _ := xml.Marshal(fallback)
twoCellAnchor.AlternateContent = append(twoCellAnchor.AlternateContent, &xlsxAlternateContent{
XMLNSMC: SourceRelationshipCompatibility.Value,
Content: string(choiceBytes) + string(shapeBytes),
})
content.TwoCellAnchor = append(content.TwoCellAnchor, twoCellAnchor)
f.Drawings.Store(drawingXML, content)
return f.addContentTypePart(drawingID, "drawings")
}
// addWorkbookSlicerCache add the association ID of the slicer cache in
// workbook.xml.
func (f *File) addWorkbookSlicerCache(slicerCacheID int, URI string) error {
var (
wb *xlsxWorkbook
err error
idx int
appendMode bool
decodeExtLst = new(decodeExtLst)
decodeSlicerCaches = new(decodeSlicerCaches)
x14SlicerCaches = new(xlsxX14SlicerCaches)
x15SlicerCaches = new(xlsxX15SlicerCaches)
ext *xlsxExt
slicerCacheBytes, slicerCachesBytes, extLstBytes []byte
)
if wb, err = f.workbookReader(); err != nil {
return err
}
rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipSlicerCache, fmt.Sprintf("/xl/slicerCaches/slicerCache%d.xml", slicerCacheID), "")
if wb.ExtLst != nil { // append mode ext
if err = f.xmlNewDecoder(strings.NewReader("<extLst>" + wb.ExtLst.Ext + "</extLst>")).
Decode(decodeExtLst); err != nil && err != io.EOF {
return err
}
for idx, ext = range decodeExtLst.Ext {
if ext.URI == URI {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeSlicerCaches)
slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)}
slicerCacheBytes, _ = xml.Marshal(slicerCache)
if URI == ExtURISlicerCachesX14 { // pivot table slicer
x14SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes)
x14SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x14SlicerCaches)
}
if URI == ExtURISlicerCachesX15 { // table slicer
x15SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes)
x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches)
}
decodeExtLst.Ext[idx].Content = string(slicerCachesBytes)
appendMode = true
}
}
}
if !appendMode {
slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)}
slicerCacheBytes, _ = xml.Marshal(slicerCache)
if URI == ExtURISlicerCachesX14 {
x14SlicerCaches.Content = string(slicerCacheBytes)
x14SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x14SlicerCaches)
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX14.Name.Local}, Value: NameSpaceSpreadSheetX14.Value}},
URI: ExtURISlicerCachesX14, Content: string(slicerCachesBytes),
})
}
if URI == ExtURISlicerCachesX15 {
x15SlicerCaches.Content = string(slicerCacheBytes)
x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value
slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches)
decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{
xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}},
URI: ExtURISlicerCachesX15, Content: string(slicerCachesBytes),
})
}
}
sort.Slice(decodeExtLst.Ext, func(i, j int) bool {
return inStrSlice(workbookExtURIPriority, decodeExtLst.Ext[i].URI, false) <
inStrSlice(workbookExtURIPriority, decodeExtLst.Ext[j].URI, false)
})
extLstBytes, err = xml.Marshal(decodeExtLst)
wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
return err
}