Skip to content

Add new functions DeleteSlicer and GetSlicers #1943

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7191e7c
Add drawing graphic structs
zhangyimingdatiancai Jul 4, 2024
904f070
Update structs for parsing slicer shapes
zhangyimingdatiancai Jul 8, 2024
78fbfc2
Support get tables and fix code review issue
zhangyimingdatiancai Jul 15, 2024
6d1125e
Add new function GetSlicers
zhangyimingdatiancai Aug 6, 2024
586f308
Optimize ColumnNumberToName function performance, reduce about 50% me…
zhayt Jul 5, 2024
3cfdf0a
This closes #1937, fix GetPivotTables returns incorrect data range (#…
ShowerBandV Jul 6, 2024
3e84ca6
This closes #1940, SetCellHyperLink function now support remove hyper…
xuri Jul 7, 2024
33593a0
This closes #1944, add new TickLabelPosition field in the ChartAxis d…
imink Jul 11, 2024
82047f0
This closes #1942, fix percent sign missing in formatted result for z…
samkeke Jul 12, 2024
13b714a
This closes #1945, an error will be return if column header cell is e…
xuri Jul 13, 2024
99cce9d
This fix missing horizontal axis in scatter chart with negative value…
pjh591029530 Jul 17, 2024
12694aa
This closes #1955, refs #119, support to set cell value with an IEEE …
xuri Jul 18, 2024
43b1d05
This closes #1957, fix missing shape macro missing after adjusted dra…
xuri Jul 19, 2024
8f6719b
This close #1963, prevent the GetStyle function panic when theme with…
xuri Jul 23, 2024
a415598
This closes #1968, closes #1969
xuri Jul 31, 2024
6df4a37
This closes #1957, fix missing shape macro missing after adjusted dra…
xuri Jul 19, 2024
755ff98
Merge branch 'qax-os:master' into slicers
zhangyimingdatiancai Aug 6, 2024
92609c8
Unit tests for the GetSlicers function
zhangyimingdatiancai Aug 9, 2024
98afbbf
Merge branch 'qax-os:master' into slicers
zhangyimingdatiancai Aug 18, 2024
a380981
Add new support for delete slicers by the DeleteSlicer function
zhangyimingdatiancai Aug 18, 2024
678c205
Lint code with gofmt
zhangyimingdatiancai Aug 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions pivotTable.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,12 +761,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
Expand Down Expand Up @@ -991,8 +990,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]++
}
Expand All @@ -1013,3 +1012,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
}
218 changes: 215 additions & 3 deletions slicer.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,8 @@ func (f *File) setSlicerCache(sheet string, colIdx int, opts *SlicerOptions, tab
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 {
Expand Down Expand Up @@ -449,6 +448,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) {
Expand Down Expand Up @@ -586,6 +599,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,
Expand Down Expand Up @@ -725,3 +739,201 @@ func (f *File) addWorkbookSlicerCache(slicerCacheID int, URI string) error {
wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>")}
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)
slicerList = new(decodeSlicerList)
)
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 {
_ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(slicerList)
for _, slicer := range slicerList.Slicer {
if slicer.RID != "" {
sheetRelationshipsDrawingXML := f.getSheetRelationshipsTargetByID(sheet, slicer.RID)
slicerXML := strings.ReplaceAll(sheetRelationshipsDrawingXML, "..", "xl")
opts, err := f.getSlicers(slicerXML, 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.
func (f *File) getSlicerCache(slicerCacheName string) *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 {
return false
}
}
return true
})
return slicerCache
}

// getSlicers provides a function to get slicers options by given slicer part
// path, drawing part path, table and pivot table options.
func (f *File) getSlicers(slicerXML, drawingXML string) ([]SlicerOptions, error) {
var opts []SlicerOptions
slicers, err := f.slicerReader(slicerXML)
if err != nil {
return opts, err
}
for _, slicer := range slicers.Slicer {
opt := SlicerOptions{
Name: slicer.Name,
Caption: slicer.Caption,
DisplayHeader: slicer.ShowCaption,
}
slicerCache := f.getSlicerCache(slicer.Cache)
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
}
24 changes: 19 additions & 5 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions xmlDecodeDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down Expand Up @@ -56,7 +56,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
Expand Down
7 changes: 4 additions & 3 deletions xmlSlicers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down