2024-01-09 20:56:20 +08:00
// Copyright 2016 - 2024 The excelize Authors. All rights reserved. Use of
2023-09-16 12:21:11 +08:00
// 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
2024-01-18 15:31:43 +08:00
// data. This library needs Go version 1.18 or later.
2023-09-16 12:21:11 +08:00
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.
//
2023-09-27 00:05:59 +08:00
// 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.
//
2023-09-16 12:21:11 +08:00
// Caption specifies the caption of the slicer, this setting is optional.
//
// Macro used for set macro for the slicer, the workbook extension should be
2023-09-21 00:06:31 +08:00
// XLSM or XLTM.
2023-09-16 12:21:11 +08:00
//
// 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 {
2024-08-23 10:47:47 +08:00
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
2023-09-16 12:21:11 +08:00
}
// AddSlicer function inserts a slicer by giving the worksheet name and slicer
2023-09-27 00:05:59 +08:00
// settings.
2023-09-16 12:21:11 +08:00
//
// For example, insert a slicer on the Sheet1!E1 with field Column1 for the
// table named Table1:
//
// err := f.AddSlicer("Sheet1", &excelize.SlicerOptions{
2023-09-27 00:05:59 +08:00
// Name: "Column1",
// Cell: "E1",
// TableSheet: "Sheet1",
// TableName: "Table1",
// Caption: "Column1",
// Width: 200,
// Height: 200,
2023-09-16 12:21:11 +08:00
// })
func ( f * File ) AddSlicer ( sheet string , opts * SlicerOptions ) error {
opts , err := parseSlicerOptions ( opts )
if err != nil {
return err
}
2023-09-27 00:05:59 +08:00
table , pivotTable , colIdx , err := f . getSlicerSource ( opts )
2023-09-16 12:21:11 +08:00
if err != nil {
return err
}
2023-09-27 00:05:59 +08:00
extURI , ns := ExtURISlicerListX14 , NameSpaceDrawingMLA14
if table != nil {
extURI = ExtURISlicerListX15
ns = NameSpaceDrawingMLSlicerX15
}
slicerID , err := f . addSheetSlicer ( sheet , extURI )
2023-09-16 12:21:11 +08:00
if err != nil {
return err
}
2024-08-23 10:47:47 +08:00
slicerCacheName , err := f . setSlicerCache ( colIdx , opts , table , pivotTable )
2023-09-16 12:21:11 +08:00
if err != nil {
return err
}
2023-09-27 00:05:59 +08:00
slicerName := f . genSlicerName ( opts . Name )
if err := f . addDrawingSlicer ( sheet , slicerName , ns , opts ) ; err != nil {
2023-09-16 12:21:11 +08:00
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
}
2023-09-27 00:05:59 +08:00
if opts . Name == "" || opts . Cell == "" || opts . TableSheet == "" || opts . TableName == "" {
2023-09-16 12:21:11 +08:00
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.
2023-09-27 00:05:59 +08:00
func ( f * File ) getSlicerSource ( opts * SlicerOptions ) ( * Table , * PivotTableOptions , int , error ) {
2023-09-16 12:21:11 +08:00
var (
table * Table
2023-09-27 00:05:59 +08:00
pivotTable * PivotTableOptions
2023-09-16 12:21:11 +08:00
colIdx int
2023-09-27 00:05:59 +08:00
err error
dataRange string
tables [ ] Table
pivotTables [ ] PivotTableOptions
2023-09-16 12:21:11 +08:00
)
2023-09-27 00:05:59 +08:00
if tables , err = f . GetTables ( opts . TableSheet ) ; err != nil {
return table , pivotTable , colIdx , err
2023-09-16 12:21:11 +08:00
}
for _ , tbl := range tables {
2023-09-27 00:05:59 +08:00
if tbl . Name == opts . TableName {
2023-09-16 12:21:11 +08:00
table = & tbl
2023-09-27 00:05:59 +08:00
dataRange = fmt . Sprintf ( "%s!%s" , opts . TableSheet , tbl . Range )
2023-09-16 12:21:11 +08:00
break
}
}
if table == nil {
2023-09-27 00:05:59 +08:00
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 )
}
2023-09-16 12:21:11 +08:00
}
2023-10-05 00:08:31 +08:00
order , _ := f . getTableFieldsOrder ( & PivotTableOptions { DataRange : dataRange } )
2023-09-16 12:21:11 +08:00
if colIdx = inStrSlice ( order , opts . Name , true ) ; colIdx == - 1 {
2023-09-27 00:05:59 +08:00
return table , pivotTable , colIdx , newInvalidSlicerNameError ( opts . Name )
2023-09-16 12:21:11 +08:00
}
2023-09-27 00:05:59 +08:00
return table , pivotTable , colIdx , err
2023-09-16 12:21:11 +08:00
}
// addSheetSlicer adds a new slicer and updates the namespace and relationships
// parts of the worksheet by giving the worksheet name.
2023-09-27 00:05:59 +08:00
func ( f * File ) addSheetSlicer ( sheet , extURI string ) ( int , error ) {
2023-09-16 12:21:11 +08:00
var (
slicerID = f . countSlicers ( ) + 1
ws , err = f . workSheetReader ( sheet )
decodeExtLst = new ( decodeExtLst )
)
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 {
2023-09-27 00:05:59 +08:00
if ext . URI == extURI {
2024-08-23 10:47:47 +08:00
slicerList := new ( decodeSlicerList )
2023-09-16 12:21:11 +08:00
_ = 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 )
2023-09-27 00:05:59 +08:00
return slicerID , f . addSheetTableSlicer ( ws , rID , extURI )
2023-09-16 12:21:11 +08:00
}
// addSheetTableSlicer adds a new table slicer for the worksheet by giving the
2023-09-27 00:05:59 +08:00
// worksheet relationships ID and extension URI.
func ( f * File ) addSheetTableSlicer ( ws * xlsxWorksheet , rID int , extURI string ) error {
2023-09-16 12:21:11 +08:00
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 ) } } ,
} )
2023-09-27 00:05:59 +08:00
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 )
2023-09-16 12:21:11 +08:00
sort . Slice ( decodeExtLst . Ext , func ( i , j int ) bool {
2023-09-27 00:05:59 +08:00
return inStrSlice ( worksheetExtURIPriority , decodeExtLst . Ext [ i ] . URI , false ) <
inStrSlice ( worksheetExtURIPriority , decodeExtLst . Ext [ j ] . URI , false )
2023-09-16 12:21:11 +08:00
} )
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
}
2023-09-27 00:05:59 +08:00
// 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
}
2023-12-07 15:22:26 +08:00
// genSlicerCacheName generates a unique slicer cache name by giving the slicer name.
2023-09-16 12:21:11 +08:00
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.
2024-08-23 10:47:47 +08:00
func ( f * File ) setSlicerCache ( colIdx int , opts * SlicerOptions , table * Table , pivotTable * PivotTableOptions ) ( string , error ) {
2023-09-16 12:21:11 +08:00
var ok bool
var slicerCacheName string
f . Pkg . Range ( func ( k , v interface { } ) bool {
if strings . Contains ( k . ( string ) , "xl/slicerCaches/slicerCache" ) {
2024-08-23 10:47:47 +08:00
slicerCache , err := f . slicerCacheReader ( k . ( string ) )
if err != nil {
2023-09-16 12:21:11 +08:00
return true
}
2023-09-27 00:05:59 +08:00
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 {
2023-09-16 12:21:11 +08:00
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 )
2023-09-27 00:05:59 +08:00
return slicerCacheName , f . addSlicerCache ( slicerCacheName , colIdx , opts , table , pivotTable )
2023-09-16 12:21:11 +08:00
}
// 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
}
2024-08-23 10:47:47 +08:00
// 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
}
2023-09-27 00:05:59 +08:00
// 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
}
2023-09-16 12:21:11 +08:00
// addSlicerCache adds a new slicer cache by giving the slicer cache name,
2023-09-27 00:05:59 +08:00
// column index, slicer, and table or pivot table options.
func ( f * File ) addSlicerCache ( slicerCacheName string , colIdx int , opts * SlicerOptions , table * Table , pivotTable * PivotTableOptions ) error {
2023-09-16 12:21:11 +08:00
var (
2023-09-27 00:05:59 +08:00
sortOrder string
2023-09-16 12:21:11 +08:00
slicerCacheBytes , tableSlicerBytes , extLstBytes [ ] byte
2023-09-27 00:05:59 +08:00
extURI = ExtURISlicerCachesX14
2023-09-16 12:21:11 +08:00
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"
}
2023-09-27 00:05:59 +08:00
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
}
2023-09-16 12:21:11 +08:00
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
}
2023-09-27 00:05:59 +08:00
if err := f . addWorkbookSlicerCache ( slicerCacheID , extURI ) ; err != nil {
2023-09-16 12:21:11 +08:00
return err
}
return f . SetDefinedName ( & DefinedName { Name : slicerCacheName , RefersTo : formulaErrorNA } )
}
2023-09-27 00:05:59 +08:00
// 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
}
2023-09-16 12:21:11 +08:00
// addDrawingSlicer adds a slicer shape and fallback shape by giving the
2023-09-27 00:05:59 +08:00
// worksheet name, slicer name, and slicer options.
func ( f * File ) addDrawingSlicer ( sheet , slicerName string , ns xml . Attr , opts * SlicerOptions ) error {
2023-09-16 12:21:11 +08:00
drawingID := f . countDrawings ( ) + 1
drawingXML := "xl/drawings/drawing" + strconv . Itoa ( drawingID ) + ".xml"
ws , err := f . workSheetReader ( sheet )
if err != nil {
2023-09-27 00:05:59 +08:00
return err
2023-09-16 12:21:11 +08:00
}
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 {
2023-09-27 00:05:59 +08:00
return err
2023-09-16 12:21:11 +08:00
}
graphicFrame := xlsxGraphicFrame {
2024-08-23 10:47:47 +08:00
Macro : opts . Macro ,
2023-09-16 12:21:11 +08:00
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 ,
}
2023-09-27 00:05:59 +08:00
choice := xlsxChoice { Requires : ns . Name . Local , Content : string ( graphic ) }
if ns . Value == NameSpaceDrawingMLA14 . Value { // pivot table slicer
choice . XMLNSA14 = ns . Value
2023-09-16 12:21:11 +08:00
}
2023-09-27 00:05:59 +08:00
if ns . Value == NameSpaceDrawingMLSlicerX15 . Value { // table slicer
choice . XMLNSSle15 = ns . Value
2023-09-16 12:21:11 +08:00
}
2023-09-27 00:05:59 +08:00
fallback := xlsxFallback { Content : string ( shape ) }
2023-09-16 12:21:11 +08:00
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 )
2023-09-27 00:05:59 +08:00
return f . addContentTypePart ( drawingID , "drawings" )
2023-09-16 12:21:11 +08:00
}
// 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 )
2023-09-27 00:05:59 +08:00
decodeSlicerCaches = new ( decodeSlicerCaches )
x14SlicerCaches = new ( xlsxX14SlicerCaches )
2023-09-16 12:21:11 +08:00
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 {
2023-09-27 00:05:59 +08:00
_ = 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
2023-09-16 12:21:11 +08:00
x15SlicerCaches . Content = decodeSlicerCaches . Content + string ( slicerCacheBytes )
x15SlicerCaches . XMLNS = NameSpaceSpreadSheetX14 . Value
slicerCachesBytes , _ = xml . Marshal ( x15SlicerCaches )
}
2023-09-27 00:05:59 +08:00
decodeExtLst . Ext [ idx ] . Content = string ( slicerCachesBytes )
appendMode = true
2023-09-16 12:21:11 +08:00
}
}
}
if ! appendMode {
2023-09-27 00:05:59 +08:00
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 ) ,
} )
}
2023-09-16 12:21:11 +08:00
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 ) ,
} )
}
}
2023-09-27 00:05:59 +08:00
sort . Slice ( decodeExtLst . Ext , func ( i , j int ) bool {
return inStrSlice ( workbookExtURIPriority , decodeExtLst . Ext [ i ] . URI , false ) <
inStrSlice ( workbookExtURIPriority , decodeExtLst . Ext [ j ] . URI , false )
} )
2023-09-16 12:21:11 +08:00
extLstBytes , err = xml . Marshal ( decodeExtLst )
wb . ExtLst = & xlsxExtLst { Ext : strings . TrimSuffix ( strings . TrimPrefix ( string ( extLstBytes ) , "<extLst>" ) , "</extLst>" ) }
return err
}
2024-08-23 10:47:47 +08:00
// 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 )
}