From e3b7dad69a2ece6acc4e3d6a9bf4f354ff523b57 Mon Sep 17 00:00:00 2001 From: xuri Date: Sat, 16 Sep 2023 12:21:11 +0800 Subject: [PATCH] Introduce the new exported function `AddSlicer` for adding table slicer - Fix a v2.8.0 regression bug, generate workbook corruption caused by incorrect MRU colors style parts - Fix corrupted workbooks generated when adding tables in some cases - Added several exported extension list child element URI constants - Move part of the internal constant and variables definition to the template source code file - Updated unit tests --- chart.go | 4 +- chart_test.go | 8 +- drawing.go | 2 +- errors.go | 6 + lib.go | 42 +++- lib_test.go | 9 + picture.go | 32 ++- picture_test.go | 11 + pivotTable.go | 26 +-- pivotTable_test.go | 8 +- rows.go | 4 +- shape.go | 41 ++-- slicer.go | 551 ++++++++++++++++++++++++++++++++++++++++++++ slicer_test.go | 247 ++++++++++++++++++++ sparkline.go | 6 +- styles.go | 10 +- table.go | 15 +- templates.go | 237 +++++++++++++++++++ xmlDecodeDrawing.go | 19 +- xmlDrawing.go | 263 +++------------------ xmlSlicers.go | 168 ++++++++++++++ xmlStyles.go | 4 +- xmlTable.go | 1 + xmlWorkbook.go | 60 ++++- xmlWorksheet.go | 30 +-- 25 files changed, 1464 insertions(+), 340 deletions(-) create mode 100644 slicer.go create mode 100644 slicer_test.go create mode 100644 xmlSlicers.go diff --git a/chart.go b/chart.go index ac13729bd7..4cfac96000 100644 --- a/chart.go +++ b/chart.go @@ -499,10 +499,10 @@ func parseChartOptions(opts *Chart) (*Chart, error) { opts.Format.Locked = boolPtr(false) } if opts.Format.ScaleX == 0 { - opts.Format.ScaleX = defaultPictureScale + opts.Format.ScaleX = defaultDrawingScale } if opts.Format.ScaleY == 0 { - opts.Format.ScaleY = defaultPictureScale + opts.Format.ScaleY = defaultDrawingScale } if opts.Legend.Position == "" { opts.Legend.Position = defaultChartLegendPosition diff --git a/chart_test.go b/chart_test.go index 0fa66580f4..a860fa55ef 100644 --- a/chart_test.go +++ b/chart_test.go @@ -184,8 +184,8 @@ func TestAddChart(t *testing.T) { {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Sizes: "Sheet1!$B$37:$D$37"}, } format := GraphicOptions{ - ScaleX: defaultPictureScale, - ScaleY: defaultPictureScale, + ScaleX: defaultDrawingScale, + ScaleY: defaultDrawingScale, OffsetX: 15, OffsetY: 10, PrintObject: boolPtr(true), @@ -369,8 +369,8 @@ func TestDeleteChart(t *testing.T) { {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"}, } format := GraphicOptions{ - ScaleX: defaultPictureScale, - ScaleY: defaultPictureScale, + ScaleX: defaultDrawingScale, + ScaleY: defaultDrawingScale, OffsetX: 15, OffsetY: 10, PrintObject: boolPtr(true), diff --git a/drawing.go b/drawing.go index 9f0f4b6b69..afe9d4d103 100644 --- a/drawing.go +++ b/drawing.go @@ -1356,7 +1356,7 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *GraphicOpt absoluteAnchor := xdrCellAnchor{ EditAs: opts.Positioning, Pos: &xlsxPoint2D{}, - Ext: &xlsxExt{}, + Ext: &aExt{}, } graphicFrame := xlsxGraphicFrame{ diff --git a/errors.go b/errors.go index a5d8814f53..59888007bc 100644 --- a/errors.go +++ b/errors.go @@ -34,6 +34,12 @@ func newInvalidCellNameError(cell string) error { return fmt.Errorf("invalid cell name %q", cell) } +// newInvalidSlicerNameError defined the error message on receiving the invalid +// slicer name. +func newInvalidSlicerNameError(name string) error { + return fmt.Errorf("invalid slicer name %q", name) +} + // newInvalidExcelDateError defined the error message on receiving the data // with negative values. func newInvalidExcelDateError(dateValue float64) error { diff --git a/lib.go b/lib.go index 25fdc3b16d..65d5ae04e3 100644 --- a/lib.go +++ b/lib.go @@ -329,7 +329,7 @@ func (f *File) coordinatesToRangeRef(coordinates []int, abs ...bool) (string, er } // getDefinedNameRefTo convert defined name to reference range. -func (f *File) getDefinedNameRefTo(definedNameName string, currentSheet string) (refTo string) { +func (f *File) getDefinedNameRefTo(definedNameName, currentSheet string) (refTo string) { var workbookRefTo, worksheetRefTo string for _, definedName := range f.GetDefinedName() { if definedName.Name == definedNameName { @@ -431,17 +431,17 @@ func float64Ptr(f float64) *float64 { return &f } func stringPtr(s string) *string { return &s } // Value extracts string data type text from a attribute value. -func (attr *attrValString) Value() string { - if attr != nil && attr.Val != nil { - return *attr.Val +func (avb *attrValString) Value() string { + if avb != nil && avb.Val != nil { + return *avb.Val } return "" } // Value extracts boolean data type value from a attribute value. -func (attr *attrValBool) Value() bool { - if attr != nil && attr.Val != nil { - return *attr.Val +func (avb *attrValBool) Value() bool { + if avb != nil && avb.Val != nil { + return *avb.Val } return false } @@ -517,6 +517,34 @@ func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err return nil } +// MarshalXML encodes ext element with specified namespace attributes on +// serialization. +func (ext xlsxExt) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + start.Attr = ext.xmlns + return e.EncodeElement(decodeExt{URI: ext.URI, Content: ext.Content}, start) +} + +// UnmarshalXML extracts ext element attributes namespace by giving XML decoder +// on deserialization. +func (ext *xlsxExt) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + for _, attr := range start.Attr { + if attr.Name.Local == "uri" { + continue + } + if attr.Name.Space == "xmlns" { + attr.Name.Space = "" + attr.Name.Local = "xmlns:" + attr.Name.Local + } + ext.xmlns = append(ext.xmlns, attr) + } + e := &decodeExt{} + if err := d.DecodeElement(&e, &start); err != nil { + return err + } + ext.URI, ext.Content = e.URI, e.Content + return nil +} + // namespaceStrictToTransitional provides a method to convert Strict and // Transitional namespaces. func namespaceStrictToTransitional(content []byte) []byte { diff --git a/lib_test.go b/lib_test.go index b41f5f1c48..5d54eaf9c1 100644 --- a/lib_test.go +++ b/lib_test.go @@ -274,6 +274,15 @@ func TestBoolValUnmarshalXML(t *testing.T) { assert.EqualError(t, attr.UnmarshalXML(xml.NewDecoder(strings.NewReader("")), xml.StartElement{}), io.EOF.Error()) } +func TestExtUnmarshalXML(t *testing.T) { + f, extLst := NewFile(), decodeExtLst{} + expected := fmt.Sprintf(``, + ExtURISlicerCachesX14, NameSpaceSpreadSheetX14.Value) + assert.NoError(t, f.xmlNewDecoder(strings.NewReader(expected)).Decode(&extLst)) + assert.Len(t, extLst.Ext, 1) + assert.Equal(t, extLst.Ext[0].URI, ExtURISlicerCachesX14) +} + func TestBytesReplace(t *testing.T) { s := []byte{0x01} assert.EqualValues(t, s, bytesReplace(s, []byte{}, []byte{}, 0)) diff --git a/picture.go b/picture.go index b8978ea950..4e646eeac5 100644 --- a/picture.go +++ b/picture.go @@ -30,8 +30,8 @@ func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions { return &GraphicOptions{ PrintObject: boolPtr(true), Locked: boolPtr(true), - ScaleX: defaultPictureScale, - ScaleY: defaultPictureScale, + ScaleX: defaultDrawingScale, + ScaleY: defaultDrawingScale, } } if opts.PrintObject == nil { @@ -41,10 +41,10 @@ func parseGraphicOptions(opts *GraphicOptions) *GraphicOptions { opts.Locked = boolPtr(true) } if opts.ScaleX == 0 { - opts.ScaleX = defaultPictureScale + opts.ScaleX = defaultDrawingScale } if opts.ScaleY == 0 { - opts.ScaleY = defaultPictureScale + opts.ScaleY = defaultDrawingScale } return opts } @@ -440,6 +440,28 @@ func (f *File) addMedia(file []byte, ext string) string { return media } +// setContentTypePartRelsExtensions provides a function to set the content +// type for relationship parts and the Main Document part. +func (f *File) setContentTypePartRelsExtensions() error { + var rels bool + content, err := f.contentTypesReader() + if err != nil { + return err + } + for _, v := range content.Defaults { + if v.Extension == "rels" { + rels = true + } + } + if !rels { + content.Defaults = append(content.Defaults, xlsxDefault{ + Extension: "rels", + ContentType: ContentTypeRelationships, + }) + } + return err +} + // setContentTypePartImageExtensions provides a function to set the content // type for relationship parts and the Main Document part. func (f *File) setContentTypePartImageExtensions() error { @@ -542,7 +564,7 @@ func (f *File) addContentTypePart(index int, contentType string) error { PartName: partNames[contentType], ContentType: contentTypes[contentType], }) - return err + return f.setContentTypePartRelsExtensions() } // getSheetRelationshipsTargetByID provides a function to get Target attribute diff --git a/picture_test.go b/picture_test.go index 4920634514..cd070d5ccf 100644 --- a/picture_test.go +++ b/picture_test.go @@ -269,6 +269,17 @@ func TestDrawingResize(t *testing.T) { assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &GraphicOptions{AutoFit: true}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } +func TestSetContentTypePartRelsExtensions(t *testing.T) { + f := NewFile() + f.ContentTypes = &xlsxTypes{} + assert.NoError(t, f.setContentTypePartRelsExtensions()) + + // Test set content type part relationships extensions with unsupported charset content types + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.setContentTypePartRelsExtensions(), "XML syntax error on line 1: invalid UTF-8") +} + func TestSetContentTypePartImageExtensions(t *testing.T) { f := NewFile() // Test set content type part image extensions with unsupported charset content types diff --git a/pivotTable.go b/pivotTable.go index f98e93b8de..2775af7f2b 100644 --- a/pivotTable.go +++ b/pivotTable.go @@ -89,9 +89,9 @@ type PivotTableField struct { // options. Note that the same fields can not in Columns, Rows and Filter // fields at the same time. // -// For example, create a pivot table on the range reference Sheet1!$G$2:$M$34 -// with the range reference Sheet1!$A$1:$E$31 as the data source, summarize by -// sum for sales: +// For example, create a pivot table on the range reference Sheet1!G2:M34 with +// the range reference Sheet1!A1:E31 as the data source, summarize by sum for +// sales: // // package main // @@ -242,15 +242,15 @@ func (f *File) adjustRange(rangeStr string) (string, []int, error) { return rng[0], []int{x1, y1, x2, y2}, nil } -// getPivotFieldsOrder provides a function to get order list of pivot table +// getTableFieldsOrder provides a function to get order list of pivot table // fields. -func (f *File) getPivotFieldsOrder(opts *PivotTableOptions) ([]string, error) { +func (f *File) getTableFieldsOrder(sheetName, dataRange string) ([]string, error) { var order []string - dataRange := f.getDefinedNameRefTo(opts.DataRange, opts.pivotTableSheetName) - if dataRange == "" { - dataRange = opts.DataRange + ref := f.getDefinedNameRefTo(dataRange, sheetName) + if ref == "" { + ref = dataRange } - dataSheet, coordinates, err := f.adjustRange(dataRange) + dataSheet, coordinates, err := f.adjustRange(ref) if err != nil { return order, fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error()) } @@ -279,7 +279,7 @@ func (f *File) addPivotCache(pivotCacheXML string, opts *PivotTableOptions) erro return fmt.Errorf("parameter 'DataRange' parsing error: %s", err.Error()) } // data range has been checked - order, _ := f.getPivotFieldsOrder(opts) + order, _ := f.getTableFieldsOrder(opts.pivotTableSheetName, opts.DataRange) hCell, _ := CoordinatesToCellName(coordinates[0], coordinates[1]) vCell, _ := CoordinatesToCellName(coordinates[2], coordinates[3]) pc := xlsxPivotCacheDefinition{ @@ -541,7 +541,7 @@ func (f *File) addPivotColFields(pt *xlsxPivotTableDefinition, opts *PivotTableO // addPivotFields create pivot fields based on the column order of the first // row in the data region by given pivot table definition and option. func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opts *PivotTableOptions) error { - order, err := f.getPivotFieldsOrder(opts) + order, err := f.getTableFieldsOrder(opts.pivotTableSheetName, opts.DataRange) if err != nil { return err } @@ -647,7 +647,7 @@ func (f *File) countPivotCache() int { // to a sequential index by given fields and pivot option. func (f *File) getPivotFieldsIndex(fields []PivotTableField, opts *PivotTableOptions) ([]int, error) { var pivotFieldsIndex []int - orders, err := f.getPivotFieldsOrder(opts) + orders, err := f.getTableFieldsOrder(opts.pivotTableSheetName, opts.DataRange) if err != nil { return pivotFieldsIndex, err } @@ -809,7 +809,7 @@ func (f *File) getPivotTable(sheet, pivotTableXML, pivotCacheRels string) (Pivot opts.ShowLastColumn = si.ShowLastColumn opts.PivotTableStyleName = si.Name } - order, _ := f.getPivotFieldsOrder(&PivotTableOptions{DataRange: dataRange, pivotTableSheetName: pt.Name}) + order, _ := f.getTableFieldsOrder(pt.Name, dataRange) f.extractPivotTableFields(order, pt, &opts) return opts, err } diff --git a/pivotTable_test.go b/pivotTable_test.go index 9c45d16e84..f6d0707524 100644 --- a/pivotTable_test.go +++ b/pivotTable_test.go @@ -257,8 +257,8 @@ func TestPivotTable(t *testing.T) { // Test adjust range with incorrect range _, _, err = f.adjustRange("sheet1!") assert.EqualError(t, err, "parameter is invalid") - // Test get pivot fields order with empty data range - _, err = f.getPivotFieldsOrder(&PivotTableOptions{}) + // Test get table fields order with empty data range + _, err = f.getTableFieldsOrder("", "") assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`) // Test add pivot cache with empty data range assert.EqualError(t, f.addPivotCache("", &PivotTableOptions{}), "parameter 'DataRange' parsing error: parameter is required") @@ -367,8 +367,8 @@ func TestAddPivotColFields(t *testing.T) { func TestGetPivotFieldsOrder(t *testing.T) { f := NewFile() - // Test get pivot fields order with not exist worksheet - _, err := f.getPivotFieldsOrder(&PivotTableOptions{DataRange: "SheetN!A1:E31"}) + // Test get table fields order with not exist worksheet + _, err := f.getTableFieldsOrder("", "SheetN!A1:E31") assert.EqualError(t, err, "sheet SheetN does not exist") } diff --git a/rows.go b/rows.go index 332bedda38..adb05a2bfd 100644 --- a/rows.go +++ b/rows.go @@ -708,7 +708,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in // checkRow provides a function to check and fill each column element for all // rows and make that is continuous in a worksheet of XML. For example: // -// +// // // // @@ -717,7 +717,7 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in // // in this case, we should to change it to // -// +// // // // diff --git a/shape.go b/shape.go index 53f1649011..6a48f794f7 100644 --- a/shape.go +++ b/shape.go @@ -38,10 +38,10 @@ func parseShapeOptions(opts *Shape) (*Shape, error) { opts.Format.Locked = boolPtr(false) } if opts.Format.ScaleX == 0 { - opts.Format.ScaleX = defaultPictureScale + opts.Format.ScaleX = defaultDrawingScale } if opts.Format.ScaleY == 0 { - opts.Format.ScaleY = defaultPictureScale + opts.Format.ScaleY = defaultDrawingScale } if opts.Line.Width == nil { opts.Line.Width = float64Ptr(defaultShapeLineWidth) @@ -322,29 +322,27 @@ func (f *File) AddShape(sheet string, opts *Shape) error { return f.addContentTypePart(drawingID, "drawings") } -// addDrawingShape provides a function to add preset geometry by given sheet, -// drawingXMLand format sets. -func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error { +// twoCellAnchorShape create a two cell anchor shape size placeholder for a +// group, a shape, or a drawing element. +func (f *File) twoCellAnchorShape(sheet, drawingXML, cell string, width, height uint, format GraphicOptions) (*xlsxWsDr, *xdrCellAnchor, int, error) { fromCol, fromRow, err := CellNameToCoordinates(cell) if err != nil { - return err + return nil, nil, 0, err } - width := int(float64(opts.Width) * opts.Format.ScaleX) - height := int(float64(opts.Height) * opts.Format.ScaleY) - - colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, opts.Format.OffsetX, opts.Format.OffsetY, - width, height) + w := int(float64(width) * format.ScaleX) + h := int(float64(height) * format.ScaleY) + colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, format.OffsetX, format.OffsetY, w, h) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { - return err + return content, nil, cNvPrID, err } twoCellAnchor := xdrCellAnchor{} - twoCellAnchor.EditAs = opts.Format.Positioning + twoCellAnchor.EditAs = format.Positioning from := xlsxFrom{} from.Col = colStart - from.ColOff = opts.Format.OffsetX * EMU + from.ColOff = format.OffsetX * EMU from.Row = rowStart - from.RowOff = opts.Format.OffsetY * EMU + from.RowOff = format.OffsetY * EMU to := xlsxTo{} to.Col = colEnd to.ColOff = x2 * EMU @@ -352,6 +350,17 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) erro to.RowOff = y2 * EMU twoCellAnchor.From = &from twoCellAnchor.To = &to + return content, &twoCellAnchor, cNvPrID, err +} + +// addDrawingShape provides a function to add preset geometry by given sheet, +// drawingXML and format sets. +func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error { + content, twoCellAnchor, cNvPrID, err := f.twoCellAnchorShape( + sheet, drawingXML, cell, opts.Width, opts.Height, opts.Format) + if err != nil { + return err + } var solidColor string if len(opts.Fill.Color) == 1 { solidColor = opts.Fill.Color[0] @@ -462,7 +471,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) erro FLocksWithSheet: *opts.Format.Locked, FPrintsWithSheet: *opts.Format.PrintObject, } - content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) + content.TwoCellAnchor = append(content.TwoCellAnchor, twoCellAnchor) f.Drawings.Store(drawingXML, content) return err } diff --git a/slicer.go b/slicer.go new file mode 100644 index 0000000000..435c0f96f2 --- /dev/null +++ b/slicer.go @@ -0,0 +1,551 @@ +// 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. +// +// Table specifies the name of the 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. +// +// 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 + Table string + Cell 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. The pivot table slicer is not supported currently. +// +// 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", +// Table: "Table1", +// Cell: "E1", +// 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, colIdx, err := f.getSlicerSource(sheet, opts) + if err != nil { + return err + } + slicerID, err := f.addSheetSlicer(sheet) + if err != nil { + return err + } + slicerCacheName, err := f.setSlicerCache(colIdx, opts, table) + if err != nil { + return err + } + slicerName, err := f.addDrawingSlicer(sheet, opts) + if 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.Table == "" || opts.Cell == "" { + 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(sheet string, opts *SlicerOptions) (*Table, int, error) { + var ( + table *Table + colIdx int + tables, err = f.GetTables(sheet) + ) + if err != nil { + return table, colIdx, err + } + for _, tbl := range tables { + if tbl.Name == opts.Table { + table = &tbl + break + } + } + if table == nil { + return table, colIdx, newNoExistTableError(opts.Table) + } + order, _ := f.getTableFieldsOrder(sheet, fmt.Sprintf("%s!%s", sheet, table.Range)) + if colIdx = inStrSlice(order, opts.Name, true); colIdx == -1 { + return table, colIdx, newInvalidSlicerNameError(opts.Name) + } + return table, 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 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("" + ws.ExtLst.Ext + "")). + Decode(decodeExtLst); err != nil && err != io.EOF { + return slicerID, err + } + for _, ext := range decodeExtLst.Ext { + if 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) + 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) +} + +// addSheetTableSlicer adds a new table slicer for the worksheet by giving the +// worksheet relationships ID. +func (f *File) addSheetTableSlicer(ws *xlsxWorksheet, rID int) error { + var ( + decodeExtLst = new(decodeExtLst) + err error + slicerListBytes, extLstBytes []byte + ) + if ws.ExtLst != nil { + if err = f.xmlNewDecoder(strings.NewReader("" + ws.ExtLst.Ext + "")). + Decode(decodeExtLst); err != nil && err != io.EOF { + return err + } + } + slicerListBytes, _ = xml.Marshal(&xlsxX14SlicerList{ + Slicer: []*xlsxX14Slicer{{RID: "rId" + strconv.Itoa(rID)}}, + }) + decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ + xmlns: []xml.Attr{{Name: xml.Name{Local: "xmlns:" + NameSpaceSpreadSheetX15.Name.Local}, Value: NameSpaceSpreadSheetX15.Value}}, + URI: ExtURISlicerListX15, Content: string(slicerListBytes), + }) + sort.Slice(decodeExtLst.Ext, func(i, j int) bool { + return inStrSlice(extensionURIPriority, decodeExtLst.Ext[i].URI, false) < + inStrSlice(extensionURIPriority, decodeExtLst.Ext[j].URI, false) + }) + extLstBytes, err = xml.Marshal(decodeExtLst) + ws.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} + 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 +} + +// 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. +func (f *File) setSlicerCache(colIdx int, opts *SlicerOptions, table *Table) (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 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) +} + +// 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 +} + +// addSlicerCache adds a new slicer cache by giving the slicer cache name, +// column index, slicer, and table options. +func (f *File) addSlicerCache(slicerCacheName string, colIdx int, opts *SlicerOptions, table *Table) error { + var ( + slicerCacheBytes, tableSlicerBytes, extLstBytes []byte + 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, + ExtLst: &xlsxExtLst{}, + } + ) + var sortOrder string + if opts.ItemDesc { + sortOrder = "descending" + } + 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), ""), "")} + 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, ExtURISlicerCachesX15); err != nil { + return err + } + return f.SetDefinedName(&DefinedName{Name: slicerCacheName, RefersTo: formulaErrorNA}) +} + +// addDrawingSlicer adds a slicer shape and fallback shape by giving the +// worksheet name, slicer options, and returns slicer name. +func (f *File) addDrawingSlicer(sheet string, opts *SlicerOptions) (string, error) { + var slicerName string + drawingID := f.countDrawings() + 1 + drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" + ws, err := f.workSheetReader(sheet) + if err != nil { + return slicerName, 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 slicerName, err + } + slicerName = fmt.Sprintf("%s %d", opts.Name, cNvPrID) + 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: ""}, + PrstGeom: xlsxPrstGeom{ + Prst: "rect", + }, + Ln: xlsxLineProperties{W: 1, SolidFill: &xlsxInnerXML{Content: ""}}, + }, + 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{ + XMLNSSle15: NameSpaceDrawingMLSlicerX15.Value, + Requires: NameSpaceDrawingMLSlicerX15.Name.Local, + Content: string(graphic), + } + 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 slicerName, 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 *decodeX15SlicerCaches + 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("" + wb.ExtLst.Ext + "")). + Decode(decodeExtLst); err != nil && err != io.EOF { + return err + } + for idx, ext = range decodeExtLst.Ext { + if ext.URI == URI { + if URI == ExtURISlicerCachesX15 { + decodeSlicerCaches = new(decodeX15SlicerCaches) + _ = f.xmlNewDecoder(strings.NewReader(ext.Content)).Decode(decodeSlicerCaches) + slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)} + slicerCacheBytes, _ = xml.Marshal(slicerCache) + x15SlicerCaches.Content = decodeSlicerCaches.Content + string(slicerCacheBytes) + x15SlicerCaches.XMLNS = NameSpaceSpreadSheetX14.Value + slicerCachesBytes, _ = xml.Marshal(x15SlicerCaches) + decodeExtLst.Ext[idx].Content = string(slicerCachesBytes) + appendMode = true + } + } + } + } + if !appendMode { + if URI == ExtURISlicerCachesX15 { + slicerCache := xlsxX14SlicerCache{RID: fmt.Sprintf("rId%d", rID)} + slicerCacheBytes, _ = xml.Marshal(slicerCache) + 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), + }) + } + } + extLstBytes, err = xml.Marshal(decodeExtLst) + wb.ExtLst = &xlsxExtLst{Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), ""), "")} + return err +} diff --git a/slicer_test.go b/slicer_test.go new file mode 100644 index 0000000000..663a4e1311 --- /dev/null +++ b/slicer_test.go @@ -0,0 +1,247 @@ +package excelize + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddSlicer(t *testing.T) { + f := NewFile() + disable, colName := false, "_!@#$%^&*()-+=|\\/<>" + assert.NoError(t, f.SetCellValue("Sheet1", "B1", colName)) + // Create table in a worksheet + assert.NoError(t, f.AddTable("Sheet1", &Table{ + Name: "Table1", + Range: "A1:D5", + })) + assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "E1", + Caption: "Column1", + })) + assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "I1", + Caption: "Column1", + })) + assert.NoError(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: colName, + Table: "Table1", + Cell: "M1", + Caption: colName, + Macro: "Button1_Click", + Width: 200, + Height: 200, + DisplayHeader: &disable, + ItemDesc: true, + })) + // Test add a table slicer with empty slicer options + assert.Equal(t, ErrParameterRequired, f.AddSlicer("Sheet1", nil)) + // Test add a table slicer with invalid slicer options + for _, opts := range []*SlicerOptions{ + {Table: "Table1", Cell: "Q1"}, + {Name: "Column", Cell: "Q1"}, + {Name: "Column", Table: "Table1"}, + } { + assert.Equal(t, ErrParameterInvalid, f.AddSlicer("Sheet1", opts)) + } + // Test add a table slicer with not exist worksheet + assert.EqualError(t, f.AddSlicer("SheetN", &SlicerOptions{ + Name: "Column2", + Table: "Table1", + Cell: "Q1", + }), "sheet SheetN does not exist") + // Test add a table slicer with not exist table name + assert.Equal(t, newNoExistTableError("Table2"), f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column2", + Table: "Table2", + Cell: "Q1", + })) + // Test add a table slicer with invalid slicer name + assert.Equal(t, newInvalidSlicerNameError("Column6"), f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column6", + Table: "Table1", + Cell: "Q1", + })) + file, err := os.ReadFile(filepath.Join("test", "vbaProject.bin")) + assert.NoError(t, err) + assert.NoError(t, f.AddVBAProject(file)) + assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddSlicer.xlsm"))) + assert.NoError(t, f.Close()) + + // Test add a table slicer with invalid worksheet extension list + f = NewFile() + assert.NoError(t, f.AddTable("Sheet1", &Table{ + Name: "Table1", + Range: "A1:D5", + })) + ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") + assert.True(t, ok) + ws.(*xlsxWorksheet).ExtLst = &xlsxExtLst{Ext: "<>"} + assert.Error(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "E1", + })) + assert.NoError(t, f.Close()) + + // Test add a table slicer with unsupported charset slicer + f = NewFile() + assert.NoError(t, f.AddTable("Sheet1", &Table{ + Name: "Table1", + Range: "A1:D5", + })) + f.Pkg.Store("xl/slicers/slicer2.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "E1", + }), "XML syntax error on line 1: invalid UTF-8") + assert.NoError(t, f.Close()) + + // Test add a table slicer with read workbook error + f = NewFile() + assert.NoError(t, f.AddTable("Sheet1", &Table{ + Name: "Table1", + Range: "A1:D5", + })) + f.WorkBook.ExtLst = &xlsxExtLst{Ext: "<>"} + assert.Error(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "E1", + })) + assert.NoError(t, f.Close()) + + // Test add a table slicer with unsupported charset content types + f = NewFile() + assert.NoError(t, f.AddTable("Sheet1", &Table{ + Name: "Table1", + Range: "A1:D5", + })) + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "E1", + }), "XML syntax error on line 1: invalid UTF-8") + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.addSlicer(0, xlsxSlicer{}), "XML syntax error on line 1: invalid UTF-8") + assert.NoError(t, f.Close()) + + f = NewFile() + // Create table in a worksheet + assert.NoError(t, f.AddTable("Sheet1", &Table{ + Name: "Table1", + Range: "A1:D5", + })) + f.Pkg.Store("xl/drawings/drawing2.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.AddSlicer("Sheet1", &SlicerOptions{ + Name: "Column1", + Table: "Table1", + Cell: "E1", + Caption: "Column1", + }), "XML syntax error on line 1: invalid UTF-8") + assert.NoError(t, f.Close()) +} + +func TestAddSheetSlicer(t *testing.T) { + f := NewFile() + // Test add sheet slicer with not exist worksheet name + _, err := f.addSheetSlicer("SheetN") + assert.EqualError(t, err, "sheet SheetN does not exist") + assert.NoError(t, f.Close()) +} + +func TestAddSheetTableSlicer(t *testing.T) { + f := NewFile() + // Test add sheet table slicer with invalid worksheet extension + assert.Error(t, f.addSheetTableSlicer(&xlsxWorksheet{ExtLst: &xlsxExtLst{Ext: "<>"}}, 0)) + // Test add sheet table slicer with existing worksheet extension + assert.NoError(t, f.addSheetTableSlicer(&xlsxWorksheet{ExtLst: &xlsxExtLst{Ext: fmt.Sprintf("", ExtURITimelineRefs)}}, 1)) + assert.NoError(t, f.Close()) +} + +func TestSetSlicerCache(t *testing.T) { + f := NewFile() + f.Pkg.Store("xl/slicerCaches/slicerCache1.xml", MacintoshCyrillicCharset) + _, err := f.setSlicerCache(1, &SlicerOptions{}, &Table{}) + assert.NoError(t, err) + assert.NoError(t, f.Close()) + + f = NewFile() + + f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) + _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}) + assert.NoError(t, err) + assert.NoError(t, f.Close()) + + f = NewFile() + f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) + _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{}) + assert.NoError(t, err) + assert.NoError(t, f.Close()) + + f = NewFile() + f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value, ExtURISlicerCacheDefinition))) + _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}) + assert.NoError(t, err) + assert.NoError(t, f.Close()) + + f = NewFile() + f.Pkg.Store("xl/slicerCaches/slicerCache2.xml", []byte(fmt.Sprintf(``, NameSpaceSpreadSheetX14.Value))) + _, err = f.setSlicerCache(1, &SlicerOptions{}, &Table{tID: 1}) + assert.NoError(t, err) + assert.NoError(t, f.Close()) +} + +func TestAddSlicerCache(t *testing.T) { + f := NewFile() + f.ContentTypes = nil + f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) + assert.EqualError(t, f.addSlicerCache("Slicer1", 0, &SlicerOptions{}, &Table{}), "XML syntax error on line 1: invalid UTF-8") + assert.NoError(t, f.Close()) +} + +func TestAddDrawingSlicer(t *testing.T) { + f := NewFile() + // Test add a drawing slicer with not exist worksheet + _, err := f.addDrawingSlicer("SheetN", &SlicerOptions{ + Name: "Column2", + Table: "Table1", + Cell: "Q1", + }) + assert.EqualError(t, err, "sheet SheetN does not exist") + // Test add a drawing slicer with invalid cell reference + _, err = f.addDrawingSlicer("Sheet1", &SlicerOptions{ + Name: "Column2", + Table: "Table1", + Cell: "A", + }) + assert.EqualError(t, err, "cannot convert cell \"A\" to coordinates: invalid cell name \"A\"") + assert.NoError(t, f.Close()) +} + +func TestAddWorkbookSlicerCache(t *testing.T) { + // Test add a workbook slicer cache with with unsupported charset workbook + f := NewFile() + f.WorkBook = nil + f.Pkg.Store("xl/workbook.xml", MacintoshCyrillicCharset) + assert.EqualError(t, f.addWorkbookSlicerCache(1, ExtURISlicerCachesX15), "XML syntax error on line 1: invalid UTF-8") + assert.NoError(t, f.Close()) +} + +func TestGenSlicerCacheName(t *testing.T) { + f := NewFile() + assert.NoError(t, f.SetDefinedName(&DefinedName{Name: "Slicer_Column_1", RefersTo: formulaErrorNA})) + assert.Equal(t, "Slicer_Column_11", f.genSlicerCacheName("Column 1")) + assert.NoError(t, f.Close()) +} diff --git a/sparkline.go b/sparkline.go index a208773844..7bb50f0f9e 100644 --- a/sparkline.go +++ b/sparkline.go @@ -489,9 +489,9 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, err error idx int appendMode bool - decodeExtLst = new(decodeWorksheetExt) + decodeExtLst = new(decodeExtLst) decodeSparklineGroups *decodeX14SparklineGroups - ext *xlsxWorksheetExt + ext *xlsxExt sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte ) sparklineGroupBytes, _ = xml.Marshal(group) @@ -523,7 +523,7 @@ func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, XMLNSXM: NameSpaceSpreadSheetExcel2006Main.Value, SparklineGroups: []*xlsxX14SparklineGroup{group}, }) - decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{ + decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ URI: ExtURISparklineGroups, Content: string(sparklineGroupsBytes), }) } diff --git a/styles.go b/styles.go index 13904679e1..7f0601ea14 100644 --- a/styles.go +++ b/styles.go @@ -2615,10 +2615,10 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error { err error idx int appendMode bool - decodeExtLst = new(decodeWorksheetExt) + decodeExtLst = new(decodeExtLst) condFmts *xlsxX14ConditionalFormattings decodeCondFmts *decodeX14ConditionalFormattings - ext *xlsxWorksheetExt + ext *xlsxExt condFmtBytes, condFmtsBytes, extLstBytes []byte ) condFmtBytes, _ = xml.Marshal([]*xlsxX14ConditionalFormatting{ @@ -2645,7 +2645,7 @@ func (f *File) appendCfRule(ws *xlsxWorksheet, rule *xlsxX14CfRule) error { } if !appendMode { condFmtsBytes, _ = xml.Marshal(&xlsxX14ConditionalFormattings{Content: string(condFmtBytes)}) - decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxWorksheetExt{ + decodeExtLst.Ext = append(decodeExtLst.Ext, &xlsxExt{ URI: ExtURIConditionalFormattings, Content: string(condFmtsBytes), }) } @@ -2781,7 +2781,7 @@ func extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatO } } } - extractExtLst := func(extLst *decodeWorksheetExt) { + extractExtLst := func(extLst *decodeExtLst) { for _, ext := range extLst.Ext { if ext.URI == ExtURIConditionalFormattings { decodeCondFmts := new(decodeX14ConditionalFormattings) @@ -2797,7 +2797,7 @@ func extractCondFmtDataBar(c *xlsxCfRule, extLst *xlsxExtLst) ConditionalFormatO if c.ExtLst != nil { ext := decodeX14ConditionalFormattingExt{} if err := xml.Unmarshal([]byte(c.ExtLst.Ext), &ext); err == nil && extLst != nil { - decodeExtLst := new(decodeWorksheetExt) + decodeExtLst := new(decodeExtLst) if err = xml.Unmarshal([]byte(""+extLst.Ext+""), decodeExtLst); err == nil { extractExtLst(decodeExtLst) } diff --git a/table.go b/table.go index 196be5ef86..f365a63e59 100644 --- a/table.go +++ b/table.go @@ -151,6 +151,7 @@ func (f *File) GetTables(sheet string) ([]Table, error) { } table := Table{ rID: tbl.RID, + tID: t.ID, Range: t.Ref, Name: t.Name, } @@ -216,7 +217,15 @@ func (f *File) countTables() int { count := 0 f.Pkg.Range(func(k, v interface{}) bool { if strings.Contains(k.(string), "xl/tables/table") { - count++ + var t xlsxTable + if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))). + Decode(&t); err != nil && err != io.EOF { + count++ + return true + } + if count < t.ID { + count = t.ID + } } return true }) @@ -343,9 +352,9 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab t.AutoFilter = nil t.HeaderRowCount = intPtr(0) } - table, _ := xml.Marshal(t) + table, err := xml.Marshal(t) f.saveFileList(tableXML, table) - return nil + return err } // AutoFilter provides the method to add auto filter in a worksheet by given diff --git a/templates.go b/templates.go index 91a7ed80aa..1c6f4ddb16 100644 --- a/templates.go +++ b/templates.go @@ -14,6 +14,190 @@ package excelize +import "encoding/xml" + +// Source relationship and namespace list, associated prefixes and schema in which it was +// introduced. +var ( + NameSpaceDocumentPropertiesVariantTypes = xml.Attr{Name: xml.Name{Local: "vt", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"} + NameSpaceDrawing2016SVG = xml.Attr{Name: xml.Name{Local: "asvg", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"} + NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"} + NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"} + NameSpaceDrawingMLSlicer = xml.Attr{Name: xml.Name{Local: "sle", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2010/slicer"} + NameSpaceDrawingMLSlicerX15 = xml.Attr{Name: xml.Name{Local: "sle15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2012/slicer"} + NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"} + NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"} + NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} + NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"} + NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"} + NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"} + NameSpaceSpreadSheetXR10 = xml.Attr{Name: xml.Name{Local: "xr10", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10"} + SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"} + SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"} + SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"} + SourceRelationshipChart201506 = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"} + SourceRelationshipCompatibility = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"} +) + +// Source relationship and namespace. +const ( + ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml" + ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml" + ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" + ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml" + ContentTypeRelationships = "application/vnd.openxmlformats-package.relationships+xml" + ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" + ContentTypeSlicer = "application/vnd.ms-excel.slicer+xml" + ContentTypeSlicerCache = "application/vnd.ms-excel.slicerCache+xml" + ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" + ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" + ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" + ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" + ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" + ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" + ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" + ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" + ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml" + ContentTypeVBA = "application/vnd.ms-office.vbaProject" + ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing" + NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main" + NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/" + NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/" + NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/" + NameSpaceExtendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" + NameSpaceXML = "http://www.w3.org/XML/1998/namespace" + NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance" + SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" + SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet" + SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" + SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet" + SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" + SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" + SourceRelationshipExtendProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" + SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" + SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" + SourceRelationshipOfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" + SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" + SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" + SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" + SourceRelationshipSlicer = "http://schemas.microsoft.com/office/2007/relationships/slicer" + SourceRelationshipSlicerCache = "http://schemas.microsoft.com/office/2007/relationships/slicerCache" + SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" + SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject" + SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" + StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes" + StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main" + StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties" + StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main" + StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships" + StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart" + StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments" + StrictSourceRelationshipExtendProperties = "http://purl.oclc.org/ooxml/officeDocument/relationships/extendedProperties" + StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image" + StrictSourceRelationshipOfficeDocument = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" + // The following constants defined the extLst child element + // ([ISO/IEC29500-1:2016] section 18.2.10) of the workbook and worksheet + // elements extended by the addition of new child ext elements. + ExtURICalcFeatures = "{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}" + ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}" + ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}" + ExtURIDataModel = "{FCE2AD5D-F65C-4FA6-A056-5C36A1767C68}" + ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}" + ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}" + ExtURIExternalLinkPr = "{FCE6A71B-6B00-49CD-AB44-F6B1AE7CDE65}" + ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}" + ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}" + ExtURIModelTimeGroupings = "{9835A34E-60A6-4A7C-AAB8-D5F71C897F49}" + ExtURIPivotCacheDefinition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}" + ExtURIPivotCachesX14 = "{876F7934-8845-4945-9796-88D515C7AA90}" + ExtURIPivotCachesX15 = "{841E416B-1EF1-43b6-AB56-02D37102CBD5}" + ExtURIPivotTableReferences = "{983426D0-5260-488c-9760-48F4B6AC55F4}" + ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}" + ExtURISlicerCacheDefinition = "{2F2917AC-EB37-4324-AD4E-5DD8C200BD13}" + ExtURISlicerCacheHideItemsWithNoData = "{470722E0-AACD-4C17-9CDC-17EF765DBC7E}" + ExtURISlicerCachesX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}" + ExtURISlicerCachesX15 = "{46BE6895-7355-4a93-B00E-2C351335B9C9}" + ExtURISlicerListX14 = "{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}" + ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}" + ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" + ExtURISVG = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" + ExtURITimelineCachePivotCaches = "{A2CB5862-8E78-49c6-8D9D-AF26E26ADB89}" + ExtURITimelineCacheRefs = "{D0CA8CA8-9F24-4464-BF8E-62219DCF47F9}" + ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}" + ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}" + ExtURIWorkbookPrX14 = "{79F54976-1DA5-4618-B147-ACDE4B953A38}" + ExtURIWorkbookPrX15 = "{140A7094-0E35-4892-8432-C4D2E57EDEB5}" +) + +// extensionURIPriority is the priority of URI in the extension lists. +var extensionURIPriority = []string{ + ExtURIConditionalFormattings, + ExtURIDataValidations, + ExtURISparklineGroups, + ExtURISlicerListX14, + ExtURIProtectedRanges, + ExtURIIgnoredErrors, + ExtURIWebExtensions, + ExtURISlicerListX15, + ExtURITimelineRefs, + ExtURIExternalLinkPr, +} + +// Excel specifications and limits +const ( + MaxCellStyles = 65430 + MaxColumns = 16384 + MaxColumnWidth = 255 + MaxFieldLength = 255 + MaxFilePathLength = 207 + MaxFormControlValue = 30000 + MaxFontFamilyLength = 31 + MaxFontSize = 409 + MaxRowHeight = 409 + MaxSheetNameLength = 31 + MinColumns = 1 + MinFontSize = 1 + StreamChunkSize = 1 << 24 + TotalCellChars = 32767 + TotalRows = 1048576 + TotalSheetHyperlinks = 65529 + UnzipSizeLimit = 1000 << 24 + // pivotTableVersion should be greater than 3. One or more of the + // PivotTables chosen are created in a version of Excel earlier than + // Excel 2007 or in compatibility mode. Slicer can only be used with + // PivotTables created in Excel 2007 or a newer version of Excel. + pivotTableVersion = 3 + defaultDrawingScale = 1.0 + defaultChartDimensionWidth = 480 + defaultChartDimensionHeight = 260 + defaultSlicerWidth = 200 + defaultSlicerHeight = 200 + defaultChartLegendPosition = "bottom" + defaultChartShowBlanksAs = "gap" + defaultShapeSize = 160 + defaultShapeLineWidth = 1 +) + +// ColorMappingType is the type of color transformation. +type ColorMappingType byte + +// Color transformation types enumeration. +const ( + ColorMappingTypeLight1 ColorMappingType = iota + ColorMappingTypeDark1 + ColorMappingTypeLight2 + ColorMappingTypeDark2 + ColorMappingTypeAccent1 + ColorMappingTypeAccent2 + ColorMappingTypeAccent3 + ColorMappingTypeAccent4 + ColorMappingTypeAccent5 + ColorMappingTypeAccent6 + ColorMappingTypeHyperlink + ColorMappingTypeFollowedHyperlink + ColorMappingTypeUnset int = -1 +) + const ( defaultXMLPathContentTypes = "[Content_Types].xml" defaultXMLPathDocPropsApp = "docProps/app.xml" @@ -27,6 +211,59 @@ const ( defaultTempFileSST = "sharedStrings" ) +// IndexedColorMapping is the table of default mappings from indexed color value +// to RGB value. Note that 0-7 are redundant of 8-15 to preserve backwards +// compatibility. A legacy indexing scheme for colors that is still required +// for some records, and for backwards compatibility with legacy formats. This +// element contains a sequence of RGB color values that correspond to color +// indexes (zero-based). When using the default indexed color palette, the +// values are not written out, but instead are implied. When the color palette +// has been modified from default, then the entire color palette is written +// out. +var IndexedColorMapping = []string{ + "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", + "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", + "800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080", + "9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF", + "000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF", + "00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99", + "3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696", + "003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333", + "000000", "FFFFFF", +} + +// supportedImageTypes defined supported image types. +var supportedImageTypes = map[string]string{ + ".bmp": ".bmp", ".emf": ".emf", ".emz": ".emz", ".gif": ".gif", + ".jpeg": ".jpeg", ".jpg": ".jpeg", ".png": ".png", ".svg": ".svg", + ".tif": ".tiff", ".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz", +} + +// supportedContentTypes defined supported file format types. +var supportedContentTypes = map[string]string{ + ".xlam": ContentTypeAddinMacro, + ".xlsm": ContentTypeMacro, + ".xlsx": ContentTypeSheetML, + ".xltm": ContentTypeTemplateMacro, + ".xltx": ContentTypeTemplate, +} + +// supportedUnderlineTypes defined supported underline types. +var supportedUnderlineTypes = []string{"none", "single", "double"} + +// supportedDrawingUnderlineTypes defined supported underline types in drawing +// markup language. +var supportedDrawingUnderlineTypes = []string{ + "none", "words", "sng", "dbl", "heavy", "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong", "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash", "dotDotDashHeavy", "wavy", "wavyHeavy", + "wavyDbl", +} + +// supportedPositioning defined supported positioning types. +var supportedPositioning = []string{"absolute", "oneCell", "twoCell"} + +// builtInDefinedNames defined built-in defined names are built with a _xlnm prefix. +var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_xlnm._FilterDatabase"} + const templateDocpropsApp = `0Go Excelize` const templateContentTypes = `` diff --git a/xmlDecodeDrawing.go b/xmlDecodeDrawing.go index a8b39d5c24..62251deadc 100644 --- a/xmlDecodeDrawing.go +++ b/xmlDecodeDrawing.go @@ -47,7 +47,7 @@ type decodeSp struct { // appearance of the shape to be stored. type decodeNvSpPr struct { CNvPr *decodeCNvPr `xml:"cNvPr"` - ExtLst *decodeExt `xml:"extLst"` + ExtLst *decodeAExt `xml:"extLst"` CNvSpPr *decodeCNvSpPr `xml:"cNvSpPr"` } @@ -78,10 +78,11 @@ type decodeWsDr struct { // information that does not affect the appearance of the picture to be // stored. type decodeCNvPr struct { - ID int `xml:"id,attr"` - Name string `xml:"name,attr"` - Descr string `xml:"descr,attr"` - Title string `xml:"title,attr,omitempty"` + XMLName xml.Name `xml:"cNvPr"` + ID int `xml:"id,attr"` + Name string `xml:"name,attr"` + Descr string `xml:"descr,attr"` + Title string `xml:"title,attr,omitempty"` } // decodePicLocks directly maps the picLocks (Picture Locks). This element @@ -124,8 +125,8 @@ type decodeOff struct { Y int `xml:"y,attr"` } -// decodeExt directly maps the ext element. -type decodeExt struct { +// decodeAExt directly maps the a:ext element. +type decodeAExt struct { Cx int `xml:"cx,attr"` Cy int `xml:"cy,attr"` } @@ -143,8 +144,8 @@ type decodePrstGeom struct { // frame. This transformation is applied to the graphic frame just as it would // be for a shape or group shape. type decodeXfrm struct { - Off decodeOff `xml:"off"` - Ext decodeExt `xml:"ext"` + Off decodeOff `xml:"off"` + Ext decodeAExt `xml:"ext"` } // decodeCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing diff --git a/xmlDrawing.go b/xmlDrawing.go index 688e601637..13ed87f7f3 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -16,220 +16,6 @@ import ( "sync" ) -// Source relationship and namespace list, associated prefixes and schema in which it was -// introduced. -var ( - NameSpaceDocumentPropertiesVariantTypes = xml.Attr{Name: xml.Name{Local: "vt", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"} - NameSpaceDrawing2016SVG = xml.Attr{Name: xml.Name{Local: "asvg", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2016/SVG/main"} - NameSpaceDrawingML = xml.Attr{Name: xml.Name{Local: "a", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"} - NameSpaceDrawingMLChart = xml.Attr{Name: xml.Name{Local: "c", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/chart"} - NameSpaceDrawingMLSpreadSheet = xml.Attr{Name: xml.Name{Local: "xdr", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"} - NameSpaceMacExcel2008Main = xml.Attr{Name: xml.Name{Local: "mx", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/mac/excel/2008/main"} - NameSpaceSpreadSheet = xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/spreadsheetml/2006/main"} - NameSpaceSpreadSheetExcel2006Main = xml.Attr{Name: xml.Name{Local: "xne", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/excel/2006/main"} - NameSpaceSpreadSheetX14 = xml.Attr{Name: xml.Name{Local: "x14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"} - NameSpaceSpreadSheetX15 = xml.Attr{Name: xml.Name{Local: "x15", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"} - NameSpaceSpreadSheetXR10 = xml.Attr{Name: xml.Name{Local: "xr10", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/spreadsheetml/2016/revision10"} - SourceRelationship = xml.Attr{Name: xml.Name{Local: "r", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"} - SourceRelationshipChart20070802 = xml.Attr{Name: xml.Name{Local: "c14", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2007/8/2/chart"} - SourceRelationshipChart2014 = xml.Attr{Name: xml.Name{Local: "c16", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2014/chart"} - SourceRelationshipChart201506 = xml.Attr{Name: xml.Name{Local: "c16r2", Space: "xmlns"}, Value: "http://schemas.microsoft.com/office/drawing/2015/06/chart"} - SourceRelationshipCompatibility = xml.Attr{Name: xml.Name{Local: "mc", Space: "xmlns"}, Value: "http://schemas.openxmlformats.org/markup-compatibility/2006"} -) - -// Source relationship and namespace. -const ( - ContentTypeAddinMacro = "application/vnd.ms-excel.addin.macroEnabled.main+xml" - ContentTypeDrawing = "application/vnd.openxmlformats-officedocument.drawing+xml" - ContentTypeDrawingML = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" - ContentTypeMacro = "application/vnd.ms-excel.sheet.macroEnabled.main+xml" - ContentTypeSheetML = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" - ContentTypeSlicer = "application/vnd.ms-excel.slicer+xml" - ContentTypeSlicerCache = "application/vnd.ms-excel.slicerCache+xml" - ContentTypeSpreadSheetMLChartsheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" - ContentTypeSpreadSheetMLComments = "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" - ContentTypeSpreadSheetMLPivotCacheDefinition = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" - ContentTypeSpreadSheetMLPivotTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" - ContentTypeSpreadSheetMLSharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" - ContentTypeSpreadSheetMLTable = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" - ContentTypeSpreadSheetMLWorksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" - ContentTypeTemplate = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" - ContentTypeTemplateMacro = "application/vnd.ms-excel.template.macroEnabled.main+xml" - ContentTypeVBA = "application/vnd.ms-office.vbaProject" - ContentTypeVML = "application/vnd.openxmlformats-officedocument.vmlDrawing" - NameSpaceDrawingMLMain = "http://schemas.openxmlformats.org/drawingml/2006/main" - NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/" - NameSpaceDublinCoreMetadataInitiative = "http://purl.org/dc/dcmitype/" - NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/" - NameSpaceExtendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" - NameSpaceXML = "http://www.w3.org/XML/1998/namespace" - NameSpaceXMLSchemaInstance = "http://www.w3.org/2001/XMLSchema-instance" - SourceRelationshipChart = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart" - SourceRelationshipChartsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chartsheet" - SourceRelationshipComments = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" - SourceRelationshipDialogsheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/dialogsheet" - SourceRelationshipDrawingML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" - SourceRelationshipDrawingVML = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing" - SourceRelationshipExtendProperties = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" - SourceRelationshipHyperLink = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" - SourceRelationshipImage = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" - SourceRelationshipOfficeDocument = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" - SourceRelationshipPivotCache = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition" - SourceRelationshipPivotTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable" - SourceRelationshipSharedStrings = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" - SourceRelationshipSlicer = "http://schemas.microsoft.com/office/2007/relationships/slicer" - SourceRelationshipTable = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table" - SourceRelationshipVBAProject = "http://schemas.microsoft.com/office/2006/relationships/vbaProject" - SourceRelationshipWorkSheet = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" - StrictNameSpaceDocumentPropertiesVariantTypes = "http://purl.oclc.org/ooxml/officeDocument/docPropsVTypes" - StrictNameSpaceDrawingMLMain = "http://purl.oclc.org/ooxml/drawingml/main" - StrictNameSpaceExtendedProperties = "http://purl.oclc.org/ooxml/officeDocument/extendedProperties" - StrictNameSpaceSpreadSheet = "http://purl.oclc.org/ooxml/spreadsheetml/main" - StrictSourceRelationship = "http://purl.oclc.org/ooxml/officeDocument/relationships" - StrictSourceRelationshipChart = "http://purl.oclc.org/ooxml/officeDocument/relationships/chart" - StrictSourceRelationshipComments = "http://purl.oclc.org/ooxml/officeDocument/relationships/comments" - StrictSourceRelationshipExtendProperties = "http://purl.oclc.org/ooxml/officeDocument/relationships/extendedProperties" - StrictSourceRelationshipImage = "http://purl.oclc.org/ooxml/officeDocument/relationships/image" - StrictSourceRelationshipOfficeDocument = "http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" - // ExtURIConditionalFormattings is the extLst child element - // ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet element - // ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by the addition of - // new child ext elements ([ISO/IEC29500-1:2016] section 18.2.7) - ExtURIConditionalFormattingRuleID = "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}" - ExtURIConditionalFormattings = "{78C0D931-6437-407d-A8EE-F0AAD7539E65}" - ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}" - ExtURIDrawingBlip = "{28A0092B-C50C-407E-A947-70E740481C1C}" - ExtURIIgnoredErrors = "{01252117-D84E-4E92-8308-4BE1C098FCBB}" - ExtURIMacExcelMX = "{64002731-A6B0-56B0-2670-7721B7C09600}" - ExtURIPivotCacheDefinition = "{725AE2AE-9491-48be-B2B4-4EB974FC3084}" - ExtURIProtectedRanges = "{FC87AEE6-9EDD-4A0A-B7FB-166176984837}" - ExtURISlicerCachesListX14 = "{BBE1A952-AA13-448e-AADC-164F8A28A991}" - ExtURISlicerListX14 = "{A8765BA9-456A-4DAB-B4F3-ACF838C121DE}" - ExtURISlicerListX15 = "{3A4CF648-6AED-40f4-86FF-DC5316D8AED3}" - ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}" - ExtURISVG = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}" - ExtURITimelineRefs = "{7E03D99C-DC04-49d9-9315-930204A7B6E9}" - ExtURIWebExtensions = "{F7C9EE02-42E1-4005-9D12-6889AFFD525C}" -) - -// extensionURIPriority is the priority of URI in the extension lists. -var extensionURIPriority = []string{ - ExtURIConditionalFormattings, - ExtURIDataValidations, - ExtURISparklineGroups, - ExtURISlicerListX14, - ExtURIProtectedRanges, - ExtURIIgnoredErrors, - ExtURIWebExtensions, - ExtURITimelineRefs, -} - -// Excel specifications and limits -const ( - MaxCellStyles = 65430 - MaxColumns = 16384 - MaxColumnWidth = 255 - MaxFieldLength = 255 - MaxFilePathLength = 207 - MaxFormControlValue = 30000 - MaxFontFamilyLength = 31 - MaxFontSize = 409 - MaxRowHeight = 409 - MaxSheetNameLength = 31 - MinColumns = 1 - MinFontSize = 1 - StreamChunkSize = 1 << 24 - TotalCellChars = 32767 - TotalRows = 1048576 - TotalSheetHyperlinks = 65529 - UnzipSizeLimit = 1000 << 24 - // pivotTableVersion should be greater than 3. One or more of the - // PivotTables chosen are created in a version of Excel earlier than - // Excel 2007 or in compatibility mode. Slicer can only be used with - // PivotTables created in Excel 2007 or a newer version of Excel. - pivotTableVersion = 3 - defaultPictureScale = 1.0 - defaultChartDimensionWidth = 480 - defaultChartDimensionHeight = 260 - defaultChartLegendPosition = "bottom" - defaultChartShowBlanksAs = "gap" - defaultShapeSize = 160 - defaultShapeLineWidth = 1 -) - -// ColorMappingType is the type of color transformation. -type ColorMappingType byte - -// Color transformation types enumeration. -const ( - ColorMappingTypeLight1 ColorMappingType = iota - ColorMappingTypeDark1 - ColorMappingTypeLight2 - ColorMappingTypeDark2 - ColorMappingTypeAccent1 - ColorMappingTypeAccent2 - ColorMappingTypeAccent3 - ColorMappingTypeAccent4 - ColorMappingTypeAccent5 - ColorMappingTypeAccent6 - ColorMappingTypeHyperlink - ColorMappingTypeFollowedHyperlink - ColorMappingTypeUnset int = -1 -) - -// IndexedColorMapping is the table of default mappings from indexed color value -// to RGB value. Note that 0-7 are redundant of 8-15 to preserve backwards -// compatibility. A legacy indexing scheme for colors that is still required -// for some records, and for backwards compatibility with legacy formats. This -// element contains a sequence of RGB color values that correspond to color -// indexes (zero-based). When using the default indexed color palette, the -// values are not written out, but instead are implied. When the color palette -// has been modified from default, then the entire color palette is written -// out. -var IndexedColorMapping = []string{ - "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", - "000000", "FFFFFF", "FF0000", "00FF00", "0000FF", "FFFF00", "FF00FF", "00FFFF", - "800000", "008000", "000080", "808000", "800080", "008080", "C0C0C0", "808080", - "9999FF", "993366", "FFFFCC", "CCFFFF", "660066", "FF8080", "0066CC", "CCCCFF", - "000080", "FF00FF", "FFFF00", "00FFFF", "800080", "800000", "008080", "0000FF", - "00CCFF", "CCFFFF", "CCFFCC", "FFFF99", "99CCFF", "FF99CC", "CC99FF", "FFCC99", - "3366FF", "33CCCC", "99CC00", "FFCC00", "FF9900", "FF6600", "666699", "969696", - "003366", "339966", "003300", "333300", "993300", "993366", "333399", "333333", - "000000", "FFFFFF", -} - -// supportedImageTypes defined supported image types. -var supportedImageTypes = map[string]string{ - ".bmp": ".bmp", ".emf": ".emf", ".emz": ".emz", ".gif": ".gif", - ".jpeg": ".jpeg", ".jpg": ".jpeg", ".png": ".png", ".svg": ".svg", - ".tif": ".tiff", ".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz", -} - -// supportedContentTypes defined supported file format types. -var supportedContentTypes = map[string]string{ - ".xlam": ContentTypeAddinMacro, - ".xlsm": ContentTypeMacro, - ".xlsx": ContentTypeSheetML, - ".xltm": ContentTypeTemplateMacro, - ".xltx": ContentTypeTemplate, -} - -// supportedUnderlineTypes defined supported underline types. -var supportedUnderlineTypes = []string{"none", "single", "double"} - -// supportedDrawingUnderlineTypes defined supported underline types in drawing -// markup language. -var supportedDrawingUnderlineTypes = []string{ - "none", "words", "sng", "dbl", "heavy", "dotted", "dottedHeavy", "dash", "dashHeavy", "dashLong", "dashLongHeavy", "dotDash", "dotDashHeavy", "dotDotDash", "dotDotDashHeavy", "wavy", "wavyHeavy", - "wavyDbl", -} - -// supportedPositioning defined supported positioning types. -var supportedPositioning = []string{"absolute", "oneCell", "twoCell"} - -// builtInDefinedNames defined built-in defined names are built with a _xlnm prefix. -var builtInDefinedNames = []string{"_xlnm.Print_Area", "_xlnm.Print_Titles", "_xlnm._FilterDatabase"} - // xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This // element specifies non-visual canvas properties. This allows for additional // information that does not affect the appearance of the picture to be stored. @@ -297,8 +83,8 @@ type xlsxOff struct { Y int `xml:"y,attr"` } -// xlsxExt directly maps the ext element. -type xlsxExt struct { +// aExt directly maps the a:ext element. +type aExt struct { Cx int `xml:"cx,attr"` Cy int `xml:"cy,attr"` } @@ -317,7 +103,7 @@ type xlsxPrstGeom struct { // be for a shape or group shape. type xlsxXfrm struct { Off xlsxOff `xml:"a:off"` - Ext xlsxExt `xml:"a:ext"` + Ext aExt `xml:"a:ext"` } // xlsxCNvPicPr directly maps the cNvPicPr (Non-Visual Picture Drawing @@ -375,7 +161,8 @@ type xlsxBlipFill struct { // has a minimum value of greater than or equal to 0. This simple type has a // maximum value of less than or equal to 20116800. type xlsxLineProperties struct { - W int `xml:"w,attr,omitempty"` + W int `xml:"w,attr,omitempty"` + SolidFill *xlsxInnerXML `xml:"a:solidFill"` } // xlsxSpPr directly maps the spPr (Shape Properties). This element specifies @@ -384,9 +171,10 @@ type xlsxLineProperties struct { // but are used here to describe the visual appearance of a picture within a // document. type xlsxSpPr struct { - Xfrm xlsxXfrm `xml:"a:xfrm"` - PrstGeom xlsxPrstGeom `xml:"a:prstGeom"` - Ln xlsxLineProperties `xml:"a:ln"` + Xfrm xlsxXfrm `xml:"a:xfrm"` + PrstGeom xlsxPrstGeom `xml:"a:prstGeom"` + SolidFill *xlsxInnerXML `xml:"a:solidFill"` + Ln xlsxLineProperties `xml:"a:ln"` } // xlsxPic elements encompass the definition of pictures within the DrawingML @@ -426,20 +214,20 @@ type xdrClientData struct { FPrintsWithSheet bool `xml:"fPrintsWithSheet,attr"` } -// xdrCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape Size) -// and twoCellAnchor (Two Cell Anchor Shape Size). This element specifies a two -// cell anchor placeholder for a group, a shape, or a drawing element. It moves -// with cells and its extents are in EMU units. +// xdrCellAnchor specifies a oneCellAnchor (One Cell Anchor Shape Size) and +// twoCellAnchor (Two Cell Anchor Shape Size) placeholder for a group, a shape, +// or a drawing element. It moves with cells and its extents are in EMU units. type xdrCellAnchor struct { - EditAs string `xml:"editAs,attr,omitempty"` - Pos *xlsxPoint2D `xml:"xdr:pos"` - From *xlsxFrom `xml:"xdr:from"` - To *xlsxTo `xml:"xdr:to"` - Ext *xlsxExt `xml:"xdr:ext"` - Sp *xdrSp `xml:"xdr:sp"` - Pic *xlsxPic `xml:"xdr:pic,omitempty"` - GraphicFrame string `xml:",innerxml"` - ClientData *xdrClientData `xml:"xdr:clientData"` + EditAs string `xml:"editAs,attr,omitempty"` + Pos *xlsxPoint2D `xml:"xdr:pos"` + From *xlsxFrom `xml:"xdr:from"` + To *xlsxTo `xml:"xdr:to"` + Ext *aExt `xml:"xdr:ext"` + Sp *xdrSp `xml:"xdr:sp"` + Pic *xlsxPic `xml:"xdr:pic,omitempty"` + GraphicFrame string `xml:",innerxml"` + AlternateContent []*xlsxAlternateContent `xml:"mc:AlternateContent"` + ClientData *xdrClientData `xml:"xdr:clientData"` } // xlsxPoint2D describes the position of a drawing element within a spreadsheet. @@ -503,6 +291,12 @@ type xlsxGraphic struct { type xlsxGraphicData struct { URI string `xml:"uri,attr"` Chart *xlsxChart `xml:"c:chart,omitempty"` + Sle *xlsxSle `xml:"sle:slicer"` +} + +type xlsxSle struct { + XMLNS string `xml:"xmlns:sle,attr"` + Name string `xml:"name,attr"` } // xlsxChart (Chart) directly maps the c:chart element. @@ -520,6 +314,7 @@ type xlsxChart struct { // This shape is specified along with all other shapes within either the shape // tree or group shape elements. type xdrSp struct { + XMLName xml.Name `xml:"xdr:sp"` Macro string `xml:"macro,attr"` Textlink string `xml:"textlink,attr"` NvSpPr *xdrNvSpPr `xml:"xdr:nvSpPr"` diff --git a/xmlSlicers.go b/xmlSlicers.go new file mode 100644 index 0000000000..e259de8990 --- /dev/null +++ b/xmlSlicers.go @@ -0,0 +1,168 @@ +// 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 "encoding/xml" + +// xlsxSlicers directly maps the slicers element that specifies a slicer view on +// the worksheet. +type xlsxSlicers struct { + XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicers"` + XMLNSXMC string `xml:"xmlns:mc,attr"` + XMLNSX string `xml:"xmlns:x,attr"` + XMLNSXR10 string `xml:"xmlns:xr10,attr"` + Slicer []xlsxSlicer `xml:"slicer"` +} + +// xlsxSlicer is a complex type that specifies a slicer view. +type xlsxSlicer struct { + Name string `xml:"name,attr"` + XR10UID string `xml:"xr10:uid,attr,omitempty"` + Cache string `xml:"cache,attr"` + Caption string `xml:"caption,attr,omitempty"` + StartItem *int `xml:"startItem,attr"` + ColumnCount *int `xml:"columnCount,attr"` + ShowCaption *bool `xml:"showCaption,attr"` + Level int `xml:"level,attr,omitempty"` + Style string `xml:"style,attr,omitempty"` + LockedPosition bool `xml:"lockedPosition,attr,omitempty"` + RowHeight int `xml:"rowHeight,attr"` +} + +// slicerCacheDefinition directly maps the slicerCacheDefinition element that +// specifies a slicer cache. +type xlsxSlicerCacheDefinition struct { + XMLName xml.Name `xml:"http://schemas.microsoft.com/office/spreadsheetml/2009/9/main slicerCacheDefinition"` + XMLNSXMC string `xml:"xmlns:mc,attr"` + XMLNSX string `xml:"xmlns:x,attr"` + XMLNSX15 string `xml:"xmlns:x15,attr,omitempty"` + XMLNSXR10 string `xml:"xmlns:xr10,attr"` + Name string `xml:"name,attr"` + XR10UID string `xml:"xr10:uid,attr,omitempty"` + SourceName string `xml:"sourceName,attr"` + PivotTables *xlsxSlicerCachePivotTables `xml:"pivotTables"` + Data *xlsxSlicerCacheData `xml:"data"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxSlicerCachePivotTables is a complex type that specifies a group of +// pivotTable elements that specify the PivotTable views that are filtered by +// the slicer cache. +type xlsxSlicerCachePivotTables struct { + PivotTable []xlsxSlicerCachePivotTable `xml:"pivotTable"` +} + +// xlsxSlicerCachePivotTable is a complex type that specifies a PivotTable view +// filtered by a slicer cache. +type xlsxSlicerCachePivotTable struct { + TabID int `xml:"tabId,attr"` + Name string `xml:"name,attr"` +} + +// xlsxSlicerCacheData is a complex type that specifies a data source for the +// slicer cache. +type xlsxSlicerCacheData struct { + OLAP *xlsxInnerXML `xml:"olap"` + Tabular *xlsxTabularSlicerCache `xml:"tabular"` +} + +// xlsxTabularSlicerCache is a complex type that specifies non-OLAP slicer items +// that are cached within this slicer cache and properties of the slicer cache +// specific to non-OLAP slicer items. +type xlsxTabularSlicerCache struct { + PivotCacheID int `xml:"pivotCacheId,attr"` + SortOrder string `xml:"sortOrder,attr,omitempty"` + CustomListSort *bool `xml:"customListSort,attr"` + ShowMissing *bool `xml:"showMissing,attr"` + CrossFilter string `xml:"crossFilter,attr,omitempty"` + Items *xlsxTabularSlicerCacheItems `xml:"items"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxTabularSlicerCacheItems is a complex type that specifies non-OLAP slicer +// items that are cached within this slicer cache. +type xlsxTabularSlicerCacheItems struct { + Count int `xml:"count,attr,omitempty"` + I []xlsxTabularSlicerCacheItem `xml:"i"` +} + +// xlsxTabularSlicerCacheItem is a complex type that specifies a non-OLAP slicer +// item that is cached within this slicer cache. +type xlsxTabularSlicerCacheItem struct { + X int `xml:"x,attr"` + S bool `xml:"s,attr,omitempty"` + ND bool `xml:"nd,attr,omitempty"` +} + +// xlsxTableSlicerCache specifies a table data source for the slicer cache. +type xlsxTableSlicerCache struct { + XMLName xml.Name `xml:"x15:tableSlicerCache"` + TableID int `xml:"tableId,attr"` + Column int `xml:"column,attr"` + SortOrder string `xml:"sortOrder,attr,omitempty"` + CustomListSort *bool `xml:"customListSort,attr"` + CrossFilter string `xml:"crossFilter,attr,omitempty"` + ExtLst *xlsxExtLst `xml:"extLst"` +} + +// xlsxX14SlicerList specifies a list of slicer. +type xlsxX14SlicerList struct { + XMLName xml.Name `xml:"x14:slicerList"` + Slicer []*xlsxX14Slicer `xml:"x14:slicer"` +} + +// xlsxX14Slicer specifies a slicer view, +type xlsxX14Slicer struct { + XMLName xml.Name `xml:"x14:slicer"` + RID string `xml:"r:id,attr"` +} + +// xlsxX15SlicerCaches directly maps the x14:slicerCache element. +type xlsxX14SlicerCache struct { + XMLName xml.Name `xml:"x14:slicerCache"` + RID string `xml:"r:id,attr"` +} + +// xlsxX15SlicerCaches directly maps the x15:slicerCaches element. +type xlsxX15SlicerCaches struct { + XMLName xml.Name `xml:"x15:slicerCaches"` + XMLNS string `xml:"xmlns:x14,attr"` + Content string `xml:",innerxml"` +} + +// 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"` +} + +// decodeSlicerList defines the structure used to parse the x14:slicerList +// element of a list of slicer. +type decodeSlicerList struct { + XMLName xml.Name `xml:"slicerList"` + Slicer []*decodeSlicer `xml:"slicer"` +} + +// decodeSlicer defines the structure used to parse the x14:slicer element of a +// slicer. +type decodeSlicer struct { + RID string `xml:"id,attr"` +} + +// decodeX15SlicerCaches defines the structure used to parse the +// x15:slicerCaches element of a slicer cache. +type decodeX15SlicerCaches struct { + XMLName xml.Name `xml:"slicerCaches"` + Content string `xml:",innerxml"` +} diff --git a/xmlStyles.go b/xmlStyles.go index e7de885629..3dd61c3307 100644 --- a/xmlStyles.go +++ b/xmlStyles.go @@ -257,7 +257,7 @@ type xlsxDxf struct { Alignment *xlsxAlignment `xml:"alignment"` Border *xlsxBorder `xml:"border"` Protection *xlsxProtection `xml:"protection"` - ExtLst *xlsxExt `xml:"extLst"` + ExtLst *aExt `xml:"extLst"` } // xlsxTableStyles directly maps the tableStyles element. This element @@ -312,7 +312,7 @@ type xlsxIndexedColors struct { // a custom color has been selected while using this workbook. type xlsxStyleColors struct { IndexedColors *xlsxIndexedColors `xml:"indexedColors"` - MruColors xlsxInnerXML `xml:"mruColors"` + MruColors *xlsxInnerXML `xml:"mruColors"` } // Alignment directly maps the alignment settings of the cells. diff --git a/xmlTable.go b/xmlTable.go index 00fa6748c9..16fd284cab 100644 --- a/xmlTable.go +++ b/xmlTable.go @@ -198,6 +198,7 @@ type xlsxTableStyleInfo struct { // Table directly maps the format settings of the table. type Table struct { + tID int rID string Range string Name string diff --git a/xmlWorkbook.go b/xmlWorkbook.go index c00637508c..0a3586f8e7 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -16,7 +16,8 @@ import ( "sync" ) -// xlsxRelationships describe references from parts to other internal resources in the package or to external resources. +// xlsxRelationships describe references from parts to other internal resources +// in the package or to external resources. type xlsxRelationships struct { mu sync.Mutex XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"` @@ -218,6 +219,63 @@ type xlsxExtLst struct { Ext string `xml:",innerxml"` } +// xlsxExt represents a the future feature data storage area. Each extension +// within an extension list shall be contained within an ext element. +// Extensions shall be versioned by namespace, using the uri attribute, and +// shall be allowed to appear in any order within the extension list. Any +// number of extensions shall be allowed within an extension list. +type xlsxExt struct { + XMLName xml.Name `xml:"ext"` + URI string `xml:"uri,attr"` + Content string `xml:",innerxml"` + xmlns []xml.Attr +} + +// xlsxAlternateContent is a container for a sequence of multiple +// representations of a given piece of content. The program reading the file +// should only process one of these, and the one chosen should be based on +// which conditions match. +type xlsxAlternateContent struct { + XMLNSMC string `xml:"xmlns:mc,attr,omitempty"` + Content string `xml:",innerxml"` +} + +// xlsxChoice element shall be an element in the Markup Compatibility namespace +// with local name "Choice". Parent elements of Choice elements shall be +// AlternateContent elements. +type xlsxChoice struct { + XMLName xml.Name `xml:"mc:Choice"` + XMLNSSle15 string `xml:"xmlns:sle15,attr,omitempty"` + Requires string `xml:"Requires,attr,omitempty"` + Content string `xml:",innerxml"` +} + +// xlsxFallback element shall be an element in the Markup Compatibility +// namespace with local name "Fallback". Parent elements of Fallback elements +// shall be AlternateContent elements. +type xlsxFallback struct { + XMLName xml.Name `xml:"mc:Fallback"` + Content string `xml:",innerxml"` +} + +// xlsxInnerXML holds parts of XML content currently not unmarshal. +type xlsxInnerXML struct { + Content string `xml:",innerxml"` +} + +// decodeExtLst defines the structure used to parse the extLst element +// of the future feature data storage area. +type decodeExtLst struct { + XMLName xml.Name `xml:"extLst"` + Ext []*xlsxExt `xml:"ext"` +} + +// decodeExt defines the structure used to parse the ext element. +type decodeExt struct { + URI string `xml:"uri,attr,omitempty"` + Content string `xml:",innerxml"` +} + // xlsxDefinedNames directly maps the definedNames element. This element defines // the collection of defined names for this workbook. Defined names are // descriptive names to represent cells, ranges of cells, formulas, or constant diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 76ffe52442..07085bbffb 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -699,33 +699,6 @@ type xlsxLegacyDrawingHF struct { RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"` } -// xlsxAlternateContent is a container for a sequence of multiple -// representations of a given piece of content. The program reading the file -// should only process one of these, and the one chosen should be based on -// which conditions match. -type xlsxAlternateContent struct { - XMLNSMC string `xml:"xmlns:mc,attr,omitempty"` - Content string `xml:",innerxml"` -} - -// xlsxInnerXML holds parts of XML content currently not unmarshal. -type xlsxInnerXML struct { - Content string `xml:",innerxml"` -} - -// xlsxWorksheetExt directly maps the ext element in the worksheet. -type xlsxWorksheetExt struct { - XMLName xml.Name `xml:"ext"` - URI string `xml:"uri,attr"` - Content string `xml:",innerxml"` -} - -// decodeWorksheetExt directly maps the ext element. -type decodeWorksheetExt struct { - XMLName xml.Name `xml:"extLst"` - Ext []*xlsxWorksheetExt `xml:"ext"` -} - // decodeX14SparklineGroups directly maps the sparklineGroups element. type decodeX14SparklineGroups struct { XMLName xml.Name `xml:"sparklineGroups"` @@ -733,8 +706,7 @@ type decodeX14SparklineGroups struct { Content string `xml:",innerxml"` } -// decodeX14ConditionalFormattingExt directly maps the ext -// element. +// decodeX14ConditionalFormattingExt directly maps the ext element. type decodeX14ConditionalFormattingExt struct { XMLName xml.Name `xml:"ext"` ID string `xml:"id"`