2023-09-16 12:21:11 +08:00
// Copyright 2016 - 2023 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.16 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.
//
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 {
Name string
Cell string
2023-09-27 00:05:59 +08:00
TableSheet string
TableName string
2023-09-16 12:21:11 +08:00
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
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
}
2023-09-27 00:05:59 +08:00
slicerCacheName , err := f . setSlicerCache ( sheet , 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-09-27 00:05:59 +08:00
order , _ := f . getTableFieldsOrder ( opts . TableSheet , 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 )
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 {
2023-09-27 00:05:59 +08:00
if ext . URI == extURI {
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-09-16 12:21:11 +08:00
// genSlicerNames 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.
2023-09-27 00:05:59 +08:00
func ( f * File ) setSlicerCache ( sheet string , 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" ) {
slicerCache := & xlsxSlicerCacheDefinition { }
if err := f . xmlNewDecoder ( bytes . NewReader ( namespaceStrictToTransitional ( v . ( [ ] byte ) ) ) ) .
Decode ( slicerCache ) ; err != nil && err != io . EOF {
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
}
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 {
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
}