diff --git a/README.md b/README.md index 008efc5..b261206 100644 --- a/README.md +++ b/README.md @@ -121,41 +121,40 @@ import ( ) func main() { - categories := map[string]string{ - "A2": "Small", "A3": "Normal", "A4": "Large", - "B1": "Apple", "C1": "Orange", "D1": "Pear"} - values := map[string]int{ - "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} f := excelize.NewFile() - for k, v := range categories { - f.SetCellValue("Sheet1", k, v) - } - for k, v := range values { - f.SetCellValue("Sheet1", k, v) - } - if err := f.AddChart("Sheet1", "E1", `{ - "type": "col3DClustered", - "series": [ - { - "name": "Sheet1!$A$2", - "categories": "Sheet1!$B$1:$D$1", - "values": "Sheet1!$B$2:$D$2" - }, - { - "name": "Sheet1!$A$3", - "categories": "Sheet1!$B$1:$D$1", - "values": "Sheet1!$B$3:$D$3" - }, - { - "name": "Sheet1!$A$4", - "categories": "Sheet1!$B$1:$D$1", - "values": "Sheet1!$B$4:$D$4" - }], - "title": - { - "name": "Fruit 3D Clustered Column Chart" + for idx, row := range [][]interface{}{ + {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, + {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, + } { + cell, err := excelize.CoordinatesToCellName(1, idx+1) + if err != nil { + fmt.Println(err) + return } - }`); err != nil { + f.SetSheetRow("Sheet1", cell, &row) + } + if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ + Type: "col3DClustered", + Series: []excelize.ChartSeries{ + { + Name: "Sheet1!$A$2", + Categories: "Sheet1!$B$1:$D$1", + Values: "Sheet1!$B$2:$D$2", + }, + { + Name: "Sheet1!$A$3", + Categories: "Sheet1!$B$1:$D$1", + Values: "Sheet1!$B$3:$D$3", + }, + { + Name: "Sheet1!$A$4", + Categories: "Sheet1!$B$1:$D$1", + Values: "Sheet1!$B$4:$D$4", + }}, + Title: excelize.ChartTitle{ + Name: "Fruit 3D Clustered Column Chart", + }, + }); err != nil { fmt.Println(err) return } @@ -193,22 +192,24 @@ func main() { } }() // Insert a picture. - if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil { + if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil { fmt.Println(err) } // Insert a picture to worksheet with scaling. + enable, disable, scale := true, false, 0.5 if err := f.AddPicture("Sheet1", "D2", "image.jpg", - `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil { + &excelize.PictureOptions{XScale: &scale, YScale: &scale}); err != nil { fmt.Println(err) } // Insert a picture offset in the cell with printing support. - if err := f.AddPicture("Sheet1", "H2", "image.gif", `{ - "x_offset": 15, - "y_offset": 10, - "print_obj": true, - "lock_aspect_ratio": false, - "locked": false - }`); err != nil { + if err := f.AddPicture("Sheet1", "H2", "image.gif", + &excelize.PictureOptions{ + PrintObject: &enable, + LockAspectRatio: false, + OffsetX: 15, + OffsetY: 10, + Locked: &disable, + }); err != nil { fmt.Println(err) } // Save the spreadsheet with the origin path. diff --git a/README_zh.md b/README_zh.md index 212bf79..ddd892e 100644 --- a/README_zh.md +++ b/README_zh.md @@ -121,41 +121,40 @@ import ( ) func main() { - categories := map[string]string{ - "A2": "Small", "A3": "Normal", "A4": "Large", - "B1": "Apple", "C1": "Orange", "D1": "Pear"} - values := map[string]int{ - "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} f := excelize.NewFile() - for k, v := range categories { - f.SetCellValue("Sheet1", k, v) - } - for k, v := range values { - f.SetCellValue("Sheet1", k, v) - } - if err := f.AddChart("Sheet1", "E1", `{ - "type": "col3DClustered", - "series": [ - { - "name": "Sheet1!$A$2", - "categories": "Sheet1!$B$1:$D$1", - "values": "Sheet1!$B$2:$D$2" - }, - { - "name": "Sheet1!$A$3", - "categories": "Sheet1!$B$1:$D$1", - "values": "Sheet1!$B$3:$D$3" - }, - { - "name": "Sheet1!$A$4", - "categories": "Sheet1!$B$1:$D$1", - "values": "Sheet1!$B$4:$D$4" - }], - "title": - { - "name": "Fruit 3D Clustered Column Chart" + for idx, row := range [][]interface{}{ + {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, + {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, + } { + cell, err := excelize.CoordinatesToCellName(1, idx+1) + if err != nil { + fmt.Println(err) + return } - }`); err != nil { + f.SetSheetRow("Sheet1", cell, &row) + } + if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ + Type: "col3DClustered", + Series: []excelize.ChartSeries{ + { + Name: "Sheet1!$A$2", + Categories: "Sheet1!$B$1:$D$1", + Values: "Sheet1!$B$2:$D$2", + }, + { + Name: "Sheet1!$A$3", + Categories: "Sheet1!$B$1:$D$1", + Values: "Sheet1!$B$3:$D$3", + }, + { + Name: "Sheet1!$A$4", + Categories: "Sheet1!$B$1:$D$1", + Values: "Sheet1!$B$4:$D$4", + }}, + Title: excelize.ChartTitle{ + Name: "Fruit 3D Clustered Column Chart", + }, + }); err != nil { fmt.Println(err) return } @@ -193,22 +192,24 @@ func main() { } }() // 插入图片 - if err := f.AddPicture("Sheet1", "A2", "image.png", ""); err != nil { + if err := f.AddPicture("Sheet1", "A2", "image.png", nil); err != nil { fmt.Println(err) } // 在工作表中插入图片,并设置图片的缩放比例 + enable, disable, scale := true, false, 0.5 if err := f.AddPicture("Sheet1", "D2", "image.jpg", - `{"x_scale": 0.5, "y_scale": 0.5}`); err != nil { + &excelize.PictureOptions{XScale: &scale, YScale: &scale}); err != nil { fmt.Println(err) } // 在工作表中插入图片,并设置图片的打印属性 - if err := f.AddPicture("Sheet1", "H2", "image.gif", `{ - "x_offset": 15, - "y_offset": 10, - "print_obj": true, - "lock_aspect_ratio": false, - "locked": false - }`); err != nil { + if err := f.AddPicture("Sheet1", "H2", "image.gif", + &excelize.PictureOptions{ + PrintObject: &enable, + LockAspectRatio: false, + OffsetX: 15, + OffsetY: 10, + Locked: &disable, + }); err != nil { fmt.Println(err) } // 保存工作簿 diff --git a/adjust_test.go b/adjust_test.go index 3ce1796..7b99241 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -10,7 +10,7 @@ import ( func TestAdjustMergeCells(t *testing.T) { f := NewFile() - // Test adjustAutoFilter with illegal cell reference. + // Test adjustAutoFilter with illegal cell reference assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{ MergeCells: &xlsxMergeCells{ Cells: []*xlsxMergeCell{ @@ -57,7 +57,7 @@ func TestAdjustMergeCells(t *testing.T) { }, }, columns, 1, -1)) - // Test adjustMergeCells. + // Test adjust merge cells var cases []struct { label string ws *xlsxWorksheet @@ -68,7 +68,7 @@ func TestAdjustMergeCells(t *testing.T) { expectRect []int } - // Test insert. + // Test adjust merged cell when insert rows and columns cases = []struct { label string ws *xlsxWorksheet @@ -139,7 +139,7 @@ func TestAdjustMergeCells(t *testing.T) { assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label) } - // Test delete, + // Test adjust merged cells when delete rows and columns cases = []struct { label string ws *xlsxWorksheet @@ -292,7 +292,7 @@ func TestAdjustAutoFilter(t *testing.T) { Ref: "A1:A3", }, }, rows, 1, -1)) - // Test adjustAutoFilter with illegal cell reference. + // Test adjustAutoFilter with illegal cell reference assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{ AutoFilter: &xlsxAutoFilter{ Ref: "A:B1", @@ -307,15 +307,15 @@ func TestAdjustAutoFilter(t *testing.T) { func TestAdjustTable(t *testing.T) { f, sheetName := NewFile(), "Sheet1" - for idx, tableRange := range [][]string{{"B2", "C3"}, {"E3", "F5"}, {"H5", "H8"}, {"J5", "K9"}} { - assert.NoError(t, f.AddTable(sheetName, tableRange[0], tableRange[1], fmt.Sprintf(`{ - "table_name": "table%d", - "table_style": "TableStyleMedium2", - "show_first_column": true, - "show_last_column": true, - "show_row_stripes": false, - "show_column_stripes": true - }`, idx))) + for idx, reference := range []string{"B2:C3", "E3:F5", "H5:H8", "J5:K9"} { + assert.NoError(t, f.AddTable(sheetName, reference, &TableOptions{ + Name: fmt.Sprintf("table%d", idx), + StyleName: "TableStyleMedium2", + ShowFirstColumn: true, + ShowLastColumn: true, + ShowRowStripes: boolPtr(false), + ShowColumnStripes: true, + })) } assert.NoError(t, f.RemoveRow(sheetName, 2)) assert.NoError(t, f.RemoveRow(sheetName, 3)) @@ -323,31 +323,32 @@ func TestAdjustTable(t *testing.T) { assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx"))) f = NewFile() - assert.NoError(t, f.AddTable(sheetName, "A1", "D5", "")) - // Test adjust table with non-table part. + assert.NoError(t, f.AddTable(sheetName, "A1:D5", nil)) + // Test adjust table with non-table part f.Pkg.Delete("xl/tables/table1.xml") assert.NoError(t, f.RemoveRow(sheetName, 1)) - // Test adjust table with unsupported charset. + // Test adjust table with unsupported charset f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) assert.NoError(t, f.RemoveRow(sheetName, 1)) - // Test adjust table with invalid table range reference. + // Test adjust table with invalid table range reference f.Pkg.Store("xl/tables/table1.xml", []byte(``)) assert.NoError(t, f.RemoveRow(sheetName, 1)) } func TestAdjustHelper(t *testing.T) { f := NewFile() - f.NewSheet("Sheet2") + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}, }) f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{ AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}, }) - // Test adjustHelper with illegal cell reference. + // Test adjustHelper with illegal cell reference assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) - // Test adjustHelper on not exists worksheet. + // Test adjustHelper on not exists worksheet assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN does not exist") } diff --git a/calc.go b/calc.go index 9ab5eeb..aeb00fe 100644 --- a/calc.go +++ b/calc.go @@ -48,7 +48,7 @@ const ( formulaErrorSPILL = "#SPILL!" formulaErrorCALC = "#CALC!" formulaErrorGETTINGDATA = "#GETTING_DATA" - // formula criteria condition enumeration. + // Formula criteria condition enumeration _ byte = iota criteriaEq criteriaLe @@ -100,7 +100,7 @@ const ( ) var ( - // tokenPriority defined basic arithmetic operator priority. + // tokenPriority defined basic arithmetic operator priority tokenPriority = map[string]int{ "^": 5, "*": 4, diff --git a/calc_test.go b/calc_test.go index 1c1f4d5..9ebfef8 100644 --- a/calc_test.go +++ b/calc_test.go @@ -5478,7 +5478,8 @@ func TestCalcSLOP(t *testing.T) { func TestCalcSHEET(t *testing.T) { f := NewFile() - f.NewSheet("Sheet2") + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) formulaList := map[string]string{ "=SHEET(\"Sheet2\")": "2", "=SHEET(Sheet2!A1)": "2", @@ -5494,7 +5495,8 @@ func TestCalcSHEET(t *testing.T) { func TestCalcSHEETS(t *testing.T) { f := NewFile() - f.NewSheet("Sheet2") + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) formulaList := map[string]string{ "=SHEETS(Sheet1!A1:B1)": "1", "=SHEETS(Sheet1!A1:Sheet1!A1)": "1", diff --git a/calcchain_test.go b/calcchain_test.go index 9eec804..9256d17 100644 --- a/calcchain_test.go +++ b/calcchain_test.go @@ -8,7 +8,7 @@ import ( func TestCalcChainReader(t *testing.T) { f := NewFile() - // Test read calculation chain with unsupported charset. + // Test read calculation chain with unsupported charset f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) _, err := f.calcChainReader() @@ -34,12 +34,12 @@ func TestDeleteCalcChain(t *testing.T) { formulaType, ref := STCellFormulaTypeShared, "C1:C5" assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) - // Test delete calculation chain with unsupported charset calculation chain. + // Test delete calculation chain with unsupported charset calculation chain f.CalcChain = nil f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8") - // Test delete calculation chain with unsupported charset content types. + // Test delete calculation chain with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8") diff --git a/cell.go b/cell.go index fe393ec..de08ffa 100644 --- a/cell.go +++ b/cell.go @@ -655,9 +655,9 @@ type FormulaOpts struct { // // Example 5, set range array formula "A1:A2" for the cell "A3" on "Sheet1": // -// formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3" -// err := f.SetCellFormula("Sheet1", "A3", "=A1:A2", -// excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) +// formulaType, ref := excelize.STCellFormulaTypeArray, "A3:A3" +// err := f.SetCellFormula("Sheet1", "A3", "=A1:A2", +// excelize.FormulaOpts{Ref: &ref, Type: &formulaType}) // // Example 6, set shared formula "=A1+B1" for the cell "C1:C5" // on "Sheet1", "C1" is the master cell: @@ -681,12 +681,13 @@ type FormulaOpts struct { // f := excelize.NewFile() // for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { // if err := f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row); err != nil { -// fmt.Println(err) -// return +// fmt.Println(err) +// return // } // } -// if err := f.AddTable("Sheet1", "A1", "C2", -// `{"table_name":"Table1","table_style":"TableStyleMedium2"}`); err != nil { +// if err := f.AddTable("Sheet1", "A1:C2", &excelize.TableOptions{ +// Name: "Table1", StyleName: "TableStyleMedium2", +// }); err != nil { // fmt.Println(err) // return // } diff --git a/cell_test.go b/cell_test.go index 2a9357a..e387793 100644 --- a/cell_test.go +++ b/cell_test.go @@ -37,13 +37,20 @@ func TestConcurrency(t *testing.T) { uint64(1<<32 - 1), true, complex64(5 + 10i), })) // Concurrency create style - style, err := f.NewStyle(`{"font":{"color":"#1265BE","underline":"single"}}`) + style, err := f.NewStyle(&Style{Font: &Font{Color: "#1265BE", Underline: "single"}}) assert.NoError(t, err) // Concurrency set cell style assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style)) // Concurrency add picture assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), - `{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`)) + &PictureOptions{ + OffsetX: 10, + OffsetY: 10, + Hyperlink: "https://github.com/xuri/excelize", + HyperlinkType: "External", + Positioning: "oneCell", + }, + )) // Concurrency get cell picture name, raw, err := f.GetPicture("Sheet1", "A1") assert.Equal(t, "", name) @@ -556,7 +563,7 @@ func TestSetCellFormula(t *testing.T) { for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { assert.NoError(t, f.SetSheetRow("Sheet1", fmt.Sprintf("A%d", idx+1), &row)) } - assert.NoError(t, f.AddTable("Sheet1", "A1", "C2", `{"table_name":"Table1","table_style":"TableStyleMedium2"}`)) + assert.NoError(t, f.AddTable("Sheet1", "A1:C2", &TableOptions{Name: "Table1", StyleName: "TableStyleMedium2"})) formulaType = STCellFormulaTypeDataTable assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula6.xlsx"))) @@ -874,7 +881,7 @@ func TestSharedStringsError(t *testing.T) { assert.Equal(t, "1", f.getFromStringItem(1)) // Cleanup undelete temporary files assert.NoError(t, os.Remove(tempFile.(string))) - // Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows. + // Test reload the file error on set cell value and rich text. The error message was different between macOS and Windows err = f.SetCellValue("Sheet1", "A19", "A19") assert.Error(t, err) diff --git a/chart.go b/chart.go index d4ea8d9..a9d96a0 100644 --- a/chart.go +++ b/chart.go @@ -12,7 +12,6 @@ package excelize import ( - "encoding/json" "encoding/xml" "fmt" "strconv" @@ -480,28 +479,41 @@ var ( // parseChartOptions provides a function to parse the format settings of the // chart with default value. -func parseChartOptions(opts string) (*chartOptions, error) { - options := chartOptions{ - Dimension: chartDimensionOptions{ - Width: 480, - Height: 290, - }, - Format: pictureOptions{ - FPrintsWithSheet: true, - XScale: 1, - YScale: 1, - }, - Legend: chartLegendOptions{ - Position: "bottom", - }, - Title: chartTitleOptions{ - Name: " ", - }, - VaryColors: true, - ShowBlanksAs: "gap", +func parseChartOptions(opts *Chart) (*Chart, error) { + if opts == nil { + return nil, ErrParameterInvalid } - err := json.Unmarshal([]byte(opts), &options) - return &options, err + if opts.Dimension.Width == nil { + opts.Dimension.Width = intPtr(defaultChartDimensionWidth) + } + if opts.Dimension.Height == nil { + opts.Dimension.Height = intPtr(defaultChartDimensionHeight) + } + if opts.Format.PrintObject == nil { + opts.Format.PrintObject = boolPtr(true) + } + if opts.Format.Locked == nil { + opts.Format.Locked = boolPtr(false) + } + if opts.Format.XScale == nil { + opts.Format.XScale = float64Ptr(defaultPictureScale) + } + if opts.Format.YScale == nil { + opts.Format.YScale = float64Ptr(defaultPictureScale) + } + if opts.Legend.Position == nil { + opts.Legend.Position = stringPtr(defaultChartLegendPosition) + } + if opts.Title.Name == "" { + opts.Title.Name = " " + } + if opts.VaryColors == nil { + opts.VaryColors = boolPtr(true) + } + if opts.ShowBlanksAs == "" { + opts.ShowBlanksAs = defaultChartShowBlanksAs + } + return opts, nil } // AddChart provides the method to add chart in a sheet by given chart format @@ -518,66 +530,53 @@ func parseChartOptions(opts string) (*chartOptions, error) { // ) // // func main() { -// categories := map[string]string{ -// "A2": "Small", "A3": "Normal", "A4": "Large", -// "B1": "Apple", "C1": "Orange", "D1": "Pear"} -// values := map[string]int{ -// "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} // f := excelize.NewFile() -// for k, v := range categories { -// f.SetCellValue("Sheet1", k, v) -// } -// for k, v := range values { -// f.SetCellValue("Sheet1", k, v) -// } -// if err := f.AddChart("Sheet1", "E1", `{ -// "type": "col3DClustered", -// "series": [ -// { -// "name": "Sheet1!$A$2", -// "categories": "Sheet1!$B$1:$D$1", -// "values": "Sheet1!$B$2:$D$2" -// }, -// { -// "name": "Sheet1!$A$3", -// "categories": "Sheet1!$B$1:$D$1", -// "values": "Sheet1!$B$3:$D$3" -// }, -// { -// "name": "Sheet1!$A$4", -// "categories": "Sheet1!$B$1:$D$1", -// "values": "Sheet1!$B$4:$D$4" -// }], -// "title": -// { -// "name": "Fruit 3D Clustered Column Chart" -// }, -// "legend": -// { -// "none": false, -// "position": "bottom", -// "show_legend_key": false -// }, -// "plotarea": -// { -// "show_bubble_size": true, -// "show_cat_name": false, -// "show_leader_lines": false, -// "show_percent": true, -// "show_series_name": true, -// "show_val": true -// }, -// "show_blanks_as": "zero", -// "x_axis": -// { -// "reverse_order": true -// }, -// "y_axis": -// { -// "maximum": 7.5, -// "minimum": 0.5 +// for idx, row := range [][]interface{}{ +// {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, +// {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, +// } { +// cell, err := excelize.CoordinatesToCellName(1, idx+1) +// if err != nil { +// fmt.Println(err) +// return // } -// }`); err != nil { +// f.SetSheetRow("Sheet1", cell, &row) +// } +// positionBottom := "bottom" +// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ +// Type: "col3DClustered", +// Series: []excelize.ChartSeries{ +// { +// Name: "Sheet1!$A$2", +// Categories: "Sheet1!$B$1:$D$1", +// Values: "Sheet1!$B$2:$D$2", +// }, +// { +// Name: "Sheet1!$A$3", +// Categories: "Sheet1!$B$1:$D$1", +// Values: "Sheet1!$B$3:$D$3", +// }, +// { +// Name: "Sheet1!$A$4", +// Categories: "Sheet1!$B$1:$D$1", +// Values: "Sheet1!$B$4:$D$4", +// }, +// }, +// Title: excelize.ChartTitle{ +// Name: "Fruit 3D Clustered Column Chart", +// }, +// Legend: excelize.ChartLegend{ +// None: false, Position: &positionBottom, ShowLegendKey: false, +// }, +// PlotArea: excelize.ChartPlotArea{ +// ShowBubbleSize: true, +// ShowCatName: false, +// ShowLeaderLines: false, +// ShowPercent: true, +// ShowSerName: true, +// ShowVal: true, +// }, +// }); err != nil { // fmt.Println(err) // return // } @@ -651,21 +650,21 @@ func parseChartOptions(opts string) (*chartOptions, error) { // // The series options that can be set are: // -// name -// categories -// values -// line -// marker +// Name +// Categories +// Values +// Line +// Marker // -// name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The name property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1 +// Name: Set the name for the series. The name is displayed in the chart legend and in the formula bar. The 'Name' property is optional and if it isn't supplied it will default to Series 1..n. The name can also be a formula such as Sheet1!$A$1 // -// categories: This sets the chart category labels. The category is more or less the same as the X axis. In most chart types the categories property is optional and the chart will just assume a sequential series from 1..n. +// Categories: This sets the chart category labels. The category is more or less the same as the X axis. In most chart types the 'Categories' property is optional and the chart will just assume a sequential series from 1..n. // -// values: This is the most important property of a series and is the only mandatory option for every chart object. This option links the chart with the worksheet data that it displays. +// Values: This is the most important property of a series and is the only mandatory option for every chart object. This option links the chart with the worksheet data that it displays. // -// line: This sets the line format of the line chart. The line property is optional and if it isn't supplied it will default style. The options that can be set are width and color. The range of width is 0.25pt - 999pt. If the value of width is outside the range, the default width of the line is 2pt. The value for color should be represented in hex format (e.g., #000000 - #FFFFFF) +// Line: This sets the line format of the line chart. The 'Line' property is optional and if it isn't supplied it will default style. The options that can be set are width and color. The range of width is 0.25pt - 999pt. If the value of width is outside the range, the default width of the line is 2pt. The value for color should be represented in hex format (e.g., #000000 - #FFFFFF) // -// marker: This sets the marker of the line chart and scatter chart. The range of optional field 'size' is 2-72 (default value is 5). The enumeration value of optional field 'symbol' are (default value is 'auto'): +// Marker: This sets the marker of the line chart and scatter chart. The range of optional field 'size' is 2-72 (default value is 5). The enumeration value of optional field 'Symbol' are (default value is 'auto'): // // circle // dash @@ -682,13 +681,13 @@ func parseChartOptions(opts string) (*chartOptions, error) { // // Set properties of the chart legend. The options that can be set are: // -// none -// position -// show_legend_key +// None +// Position +// ShowLegendKey // -// none: Specified if show the legend without overlapping the chart. The default value is 'false'. +// None: Specified if show the legend without overlapping the chart. The default value is 'false'. // -// position: Set the position of the chart legend. The default legend position is right. This parameter only takes effect when 'none' is false. The available positions are: +// Position: Set the position of the chart legend. The default legend position is right. This parameter only takes effect when 'none' is false. The available positions are: // // top // bottom @@ -696,15 +695,15 @@ func parseChartOptions(opts string) (*chartOptions, error) { // right // top_right // -// show_legend_key: Set the legend keys shall be shown in data labels. The default value is false. +// ShowLegendKey: Set the legend keys shall be shown in data labels. The default value is false. // // Set properties of the chart title. The properties that can be set are: // -// title +// Title // -// name: Set the name (title) for the chart. The name is displayed above the chart. The name can also be a formula such as Sheet1!$A$1 or a list with a sheet name. The name property is optional. The default is to have no chart title. +// Name: Set the name (title) for the chart. The name is displayed above the chart. The name can also be a formula such as Sheet1!$A$1 or a list with a sheet name. The name property is optional. The default is to have no chart title. // -// Specifies how blank cells are plotted on the chart by show_blanks_as. The default value is gap. The options that can be set are: +// Specifies how blank cells are plotted on the chart by ShowBlanksAs. The default value is gap. The options that can be set are: // // gap // span @@ -716,80 +715,80 @@ func parseChartOptions(opts string) (*chartOptions, error) { // // zero: Specifies that blank values shall be treated as zero. // -// Specifies that each data marker in the series has a different color by vary_colors. The default value is true. +// Specifies that each data marker in the series has a different color by VaryColors. The default value is true. // // Set chart offset, scale, aspect ratio setting and print settings by format, same as function AddPicture. // -// Set the position of the chart plot area by plotarea. The properties that can be set are: +// Set the position of the chart plot area by PlotArea. The properties that can be set are: // -// show_bubble_size -// show_cat_name -// show_leader_lines -// show_percent -// show_series_name -// show_val +// ShowBubbleSize +// ShowCatName +// ShowLeaderLines +// ShowPercent +// ShowSerName +// ShowVal // -// show_bubble_size: Specifies the bubble size shall be shown in a data label. The show_bubble_size property is optional. The default value is false. +// ShowBubbleSize: Specifies the bubble size shall be shown in a data label. The ShowBubbleSize property is optional. The default value is false. // -// show_cat_name: Specifies that the category name shall be shown in the data label. The show_cat_name property is optional. The default value is true. +// ShowCatName: Specifies that the category name shall be shown in the data label. The ShowCatName property is optional. The default value is true. // -// show_leader_lines: Specifies leader lines shall be shown for data labels. The show_leader_lines property is optional. The default value is false. +// ShowLeaderLines: Specifies leader lines shall be shown for data labels. The ShowLeaderLines property is optional. The default value is false. // -// show_percent: Specifies that the percentage shall be shown in a data label. The show_percent property is optional. The default value is false. +// ShowPercent: Specifies that the percentage shall be shown in a data label. The ShowPercent property is optional. The default value is false. // -// show_series_name: Specifies that the series name shall be shown in a data label. The show_series_name property is optional. The default value is false. +// ShowSerName: Specifies that the series name shall be shown in a data label. The ShowSerName property is optional. The default value is false. // -// show_val: Specifies that the value shall be shown in a data label. The show_val property is optional. The default value is false. +// ShowVal: Specifies that the value shall be shown in a data label. The ShowVal property is optional. The default value is false. // -// Set the primary horizontal and vertical axis options by x_axis and y_axis. The properties of x_axis that can be set are: +// Set the primary horizontal and vertical axis options by XAxis and YAxis. The properties of XAxis that can be set are: // -// none -// major_grid_lines -// minor_grid_lines -// tick_label_skip -// reverse_order -// maximum -// minimum -// font +// None +// MajorGridLines +// MinorGridLines +// TickLabelSkip +// ReverseOrder +// Maximum +// Minimum +// Font // -// The properties of y_axis that can be set are: +// The properties of YAxis that can be set are: // -// none -// major_grid_lines -// minor_grid_lines -// major_unit -// tick_label_skip -// reverse_order -// maximum -// minimum -// font +// None +// MajorGridLines +// MinorGridLines +// MajorUnit +// TickLabelSkip +// ReverseOrder +// Maximum +// Minimum +// Font // // none: Disable axes. // -// major_grid_lines: Specifies major grid lines. +// MajorGridLines: Specifies major grid lines. // -// minor_grid_lines: Specifies minor grid lines. +// MinorGridLines: Specifies minor grid lines. // -// major_unit: Specifies the distance between major ticks. Shall contain a positive floating-point number. The major_unit property is optional. The default value is auto. +// MajorUnit: Specifies the distance between major ticks. Shall contain a positive floating-point number. The MajorUnit property is optional. The default value is auto. // -// tick_label_skip: Specifies how many tick labels to skip between label that is drawn. The tick_label_skip property is optional. The default value is auto. +// TickLabelSkip: Specifies how many tick labels to skip between label that is drawn. The TickLabelSkip property is optional. The default value is auto. // -// reverse_order: Specifies that the categories or values on reverse order (orientation of the chart). The reverse_order property is optional. The default value is false. +// ReverseOrder: Specifies that the categories or values on reverse order (orientation of the chart). The ReverseOrder property is optional. The default value is false. // -// maximum: Specifies that the fixed maximum, 0 is auto. The maximum property is optional. The default value is auto. +// Maximum: Specifies that the fixed maximum, 0 is auto. The Maximum property is optional. The default value is auto. // -// minimum: Specifies that the fixed minimum, 0 is auto. The minimum property is optional. The default value is auto. +// Minimum: Specifies that the fixed minimum, 0 is auto. The Minimum property is optional. The default value is auto. // -// font: Specifies that the font of the horizontal and vertical axis. The properties of font that can be set are: +// Font: Specifies that the font of the horizontal and vertical axis. The properties of font that can be set are: // -// bold -// italic -// underline -// family -// size -// strike -// color -// vertAlign +// Bold +// Italic +// Underline +// Family +// Size +// Strike +// Color +// VertAlign // // Set chart size by dimension property. The dimension property is optional. The default width is 480, and height is 290. // @@ -806,112 +805,100 @@ func parseChartOptions(opts string) (*chartOptions, error) { // ) // // func main() { -// categories := map[string]string{ -// "A2": "Small", "A3": "Normal", "A4": "Large", -// "B1": "Apple", "C1": "Orange", "D1": "Pear"} -// values := map[string]int{ -// "B2": 2, "C2": 3, "D2": 3, "B3": 5, "C3": 2, "D3": 4, "B4": 6, "C4": 7, "D4": 8} // f := excelize.NewFile() -// for k, v := range categories { -// f.SetCellValue("Sheet1", k, v) -// } -// for k, v := range values { -// f.SetCellValue("Sheet1", k, v) -// } -// if err := f.AddChart("Sheet1", "E1", `{ -// "type": "col", -// "series": [ -// { -// "name": "Sheet1!$A$2", -// "categories": "", -// "values": "Sheet1!$B$2:$D$2" -// }, -// { -// "name": "Sheet1!$A$3", -// "categories": "Sheet1!$B$1:$D$1", -// "values": "Sheet1!$B$3:$D$3" -// }], -// "format": -// { -// "x_scale": 1.0, -// "y_scale": 1.0, -// "x_offset": 15, -// "y_offset": 10, -// "print_obj": true, -// "lock_aspect_ratio": false, -// "locked": false -// }, -// "title": -// { -// "name": "Clustered Column - Line Chart" -// }, -// "legend": -// { -// "position": "left", -// "show_legend_key": false -// }, -// "plotarea": -// { -// "show_bubble_size": true, -// "show_cat_name": false, -// "show_leader_lines": false, -// "show_percent": true, -// "show_series_name": true, -// "show_val": true +// for idx, row := range [][]interface{}{ +// {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3}, +// {"Normal", 5, 2, 4}, {"Large", 6, 7, 8}, +// } { +// cell, err := excelize.CoordinatesToCellName(1, idx+1) +// if err != nil { +// fmt.Println(err) +// return // } -// }`, `{ -// "type": "line", -// "series": [ -// { -// "name": "Sheet1!$A$4", -// "categories": "Sheet1!$B$1:$D$1", -// "values": "Sheet1!$B$4:$D$4", -// "marker": +// f.SetSheetRow("Sheet1", cell, &row) +// } +// enable, disable, scale := true, false, 1.0 +// positionLeft, positionRight := "left", "right" +// if err := f.AddChart("Sheet1", "E1", &excelize.Chart{ +// Type: "col", +// Series: []excelize.ChartSeries{ // { -// "symbol": "none", -// "size": 10 -// } -// }], -// "format": -// { -// "x_scale": 1, -// "y_scale": 1, -// "x_offset": 15, -// "y_offset": 10, -// "print_obj": true, -// "lock_aspect_ratio": false, -// "locked": false +// Name: "Sheet1!$A$2", +// Categories: "Sheet1!$B$1:$D$1", +// Values: "Sheet1!$B$2:$D$2", +// }, // }, -// "legend": -// { -// "position": "right", -// "show_legend_key": false +// Format: excelize.Picture{ +// XScale: &scale, +// YScale: &scale, +// OffsetX: 15, +// OffsetY: 10, +// PrintObject: &enable, +// LockAspectRatio: false, +// Locked: &disable, // }, -// "plotarea": -// { -// "show_bubble_size": true, -// "show_cat_name": false, -// "show_leader_lines": false, -// "show_percent": true, -// "show_series_name": true, -// "show_val": true -// } -// }`); err != nil { +// Title: excelize.ChartTitle{ +// Name: "Clustered Column - Line Chart", +// }, +// Legend: excelize.ChartLegend{ +// Position: &positionLeft, ShowLegendKey: false, +// }, +// PlotArea: excelize.ChartPlotArea{ +// ShowBubbleSize: true, +// ShowCatName: false, +// ShowLeaderLines: false, +// ShowPercent: true, +// ShowSerName: true, +// ShowVal: true, +// }, +// }, &excelize.Chart{ +// Type: "line", +// Series: []excelize.ChartSeries{ +// { +// Name: "Sheet1!$A$4", +// Categories: "Sheet1!$B$1:$D$1", +// Values: "Sheet1!$B$4:$D$4", +// Marker: excelize.ChartMarker{ +// Symbol: "none", Size: 10, +// }, +// }, +// }, +// Format: excelize.Picture{ +// XScale: &scale, +// YScale: &scale, +// OffsetX: 15, +// OffsetY: 10, +// PrintObject: &enable, +// LockAspectRatio: false, +// Locked: &disable, +// }, +// Legend: excelize.ChartLegend{ +// Position: &positionRight, ShowLegendKey: false, +// }, +// PlotArea: excelize.ChartPlotArea{ +// ShowBubbleSize: true, +// ShowCatName: false, +// ShowLeaderLines: false, +// ShowPercent: true, +// ShowSerName: true, +// ShowVal: true, +// }, +// }); err != nil { // fmt.Println(err) // return // } -// // Save spreadsheet file by the given path. +// // Save spreadsheet by the given path. // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } -func (f *File) AddChart(sheet, cell, opts string, combo ...string) error { - // Read sheet data. +func (f *File) AddChart(sheet, cell string, chart *Chart, combo ...*Chart) error { + // Read worksheet data ws, err := f.workSheetReader(sheet) if err != nil { return err } - options, comboCharts, err := f.getChartOptions(opts, combo) + opts, comboCharts, err := f.getChartOptions(chart, combo) if err != nil { return err } @@ -922,11 +909,11 @@ func (f *File) AddChart(sheet, cell, opts string, combo ...string) error { drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML) drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") - err = f.addDrawingChart(sheet, drawingXML, cell, options.Dimension.Width, options.Dimension.Height, drawingRID, &options.Format) + err = f.addDrawingChart(sheet, drawingXML, cell, *opts.Dimension.Width, *opts.Dimension.Height, drawingRID, &opts.Format) if err != nil { return err } - f.addChart(options, comboCharts) + f.addChart(opts, comboCharts) if err = f.addContentTypePart(chartID, "chart"); err != nil { return err } @@ -939,7 +926,7 @@ func (f *File) AddChart(sheet, cell, opts string, combo ...string) error { // format set (such as offset, scale, aspect ratio setting and print settings) // and properties set. In Excel a chartsheet is a worksheet that only contains // a chart. -func (f *File) AddChartSheet(sheet, opts string, combo ...string) error { +func (f *File) AddChartSheet(sheet string, chart *Chart, combo ...*Chart) error { // Check if the worksheet already exists idx, err := f.GetSheetIndex(sheet) if err != nil { @@ -948,7 +935,7 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error { if idx != -1 { return ErrExistsSheet } - options, comboCharts, err := f.getChartOptions(opts, combo) + opts, comboCharts, err := f.getChartOptions(chart, combo) if err != nil { return err } @@ -975,10 +962,10 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error { f.prepareChartSheetDrawing(&cs, drawingID, sheet) drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") - if err = f.addSheetDrawingChart(drawingXML, drawingRID, &options.Format); err != nil { + if err = f.addSheetDrawingChart(drawingXML, drawingRID, &opts.Format); err != nil { return err } - f.addChart(options, comboCharts) + f.addChart(opts, comboCharts) if err = f.addContentTypePart(chartID, "chart"); err != nil { return err } @@ -996,8 +983,8 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error { // getChartOptions provides a function to check format set of the chart and // create chart format. -func (f *File) getChartOptions(opts string, combo []string) (*chartOptions, []*chartOptions, error) { - var comboCharts []*chartOptions +func (f *File) getChartOptions(opts *Chart, combo []*Chart) (*Chart, []*Chart, error) { + var comboCharts []*Chart options, err := parseChartOptions(opts) if err != nil { return options, comboCharts, err diff --git a/chart_test.go b/chart_test.go index a61f1d7..deed7dd 100644 --- a/chart_test.go +++ b/chart_test.go @@ -41,11 +41,20 @@ func TestChartSize(t *testing.T) { assert.NoError(t, f.SetCellValue(sheet1, cell, v)) } - assert.NoError(t, f.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+ - `"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+ - `{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+ - `{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+ - `"title":{"name":"3D Clustered Column Chart"}}`)) + width, height := 640, 480 + assert.NoError(t, f.AddChart("Sheet1", "E4", &Chart{ + Type: "col3DClustered", + Dimension: ChartDimension{ + Width: &width, + Height: &height, + }, + Series: []ChartSeries{ + {Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2"}, + {Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3"}, + {Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4"}, + }, + Title: ChartTitle{Name: "3D Clustered Column Chart"}, + })) var buffer bytes.Buffer @@ -98,14 +107,14 @@ func TestAddDrawingChart(t *testing.T) { path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &PictureOptions{PrintObject: boolPtr(true), Locked: boolPtr(false), XScale: float64Ptr(defaultPictureScale), YScale: float64Ptr(defaultPictureScale)}), "XML syntax error on line 1: invalid UTF-8") } func TestAddSheetDrawingChart(t *testing.T) { f := NewFile() path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - assert.EqualError(t, f.addSheetDrawingChart(path, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.addSheetDrawingChart(path, 0, &PictureOptions{PrintObject: boolPtr(true), Locked: boolPtr(false), XScale: float64Ptr(defaultPictureScale), YScale: float64Ptr(defaultPictureScale)}), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteDrawing(t *testing.T) { @@ -129,75 +138,124 @@ func TestAddChart(t *testing.T) { for k, v := range values { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } - assert.EqualError(t, f.AddChart("Sheet1", "P1", ""), "unexpected end of JSON input") + assert.EqualError(t, f.AddChart("Sheet1", "P1", nil), ErrParameterInvalid.Error()) - // Test add chart on not exists worksheet. - assert.EqualError(t, f.AddChart("SheetN", "P1", "{}"), "sheet SheetN does not exist") - - assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"none":true,"show_legend_key":true},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"font":{"bold":true,"italic":true,"underline":"dbl","color":"#000000"}},"y_axis":{"font":{"bold":false,"italic":false,"underline":"sng","color":"#777777"}}}`)) - assert.NoError(t, f.AddChart("Sheet1", "X1", `{"type":"colStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "P16", `{"type":"colPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "X16", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Clustered Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "P30", `{"type":"col3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "X30", `{"type":"col3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "X45", `{"type":"radar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top_right","show_legend_key":false},"title":{"name":"Radar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"span"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AF1", `{"type":"col3DConeStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AF16", `{"type":"col3DConeClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AF30", `{"type":"col3DConePercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AF45", `{"type":"col3DCone","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cone Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AN1", `{"type":"col3DPyramidStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AN16", `{"type":"col3DPyramidClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AN30", `{"type":"col3DPyramidPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AN45", `{"type":"col3DPyramid","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Pyramid Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AV1", `{"type":"col3DCylinderStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AV16", `{"type":"col3DCylinderClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AV30", `{"type":"col3DCylinderPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "AV45", `{"type":"col3DCylinder","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Cylinder Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet1", "P45", `{"type":"col3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "P1", `{"type":"line3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30","marker":{"symbol":"none","size":10}, "line":{"color":"#000000"}},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"3D Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`)) - assert.NoError(t, f.AddChart("Sheet2", "X1", `{"type":"scatter","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Scatter Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "P16", `{"type":"doughnut","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"right","show_legend_key":false},"title":{"name":"Doughnut Chart"},"plotarea":{"show_bubble_size":false,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero","hole_size":30}`)) - assert.NoError(t, f.AddChart("Sheet2", "X16", `{"type":"line","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30","marker":{"symbol":"none","size":10}, "line":{"color":"#000000"}},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37","line":{"width":0.25}}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"top","show_legend_key":false},"title":{"name":"Line Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true,"minor_grid_lines":true,"tick_label_skip":1},"y_axis":{"major_grid_lines":true,"minor_grid_lines":true,"major_unit":1}}`)) - assert.NoError(t, f.AddChart("Sheet2", "P32", `{"type":"pie3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"3D Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "X32", `{"type":"pie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"bottom","show_legend_key":false},"title":{"name":"Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":false,"show_val":false},"show_blanks_as":"gap"}`)) - assert.NoError(t, f.AddChart("Sheet2", "P48", `{"type":"bar","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "X48", `{"type":"barStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "P64", `{"type":"barPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked 100% Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "X64", `{"type":"bar3DClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Clustered Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "P80", `{"type":"bar3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"maximum":7.5,"minimum":0.5}}`)) - assert.NoError(t, f.AddChart("Sheet2", "X80", `{"type":"bar3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Bar Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"reverse_order":true,"minimum":0},"y_axis":{"reverse_order":true,"maximum":0,"minimum":0}}`)) - // area series charts - assert.NoError(t, f.AddChart("Sheet2", "AF1", `{"type":"area","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AN1", `{"type":"areaStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AF16", `{"type":"areaPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D 100% Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AN16", `{"type":"area3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AF32", `{"type":"area3DStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AN32", `{"type":"area3DPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D 100% Stacked Area Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - // cylinder series chart - assert.NoError(t, f.AddChart("Sheet2", "AF48", `{"type":"bar3DCylinderStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AF64", `{"type":"bar3DCylinderClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AF80", `{"type":"bar3DCylinderPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cylinder Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - // cone series chart - assert.NoError(t, f.AddChart("Sheet2", "AN48", `{"type":"bar3DConeStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AN64", `{"type":"bar3DConeClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AN80", `{"type":"bar3DConePercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Cone Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AV48", `{"type":"bar3DPyramidStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AV64", `{"type":"bar3DPyramidClustered","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Clustered Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "AV80", `{"type":"bar3DPyramidPercentStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Bar Pyramid Percent Stacked Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - // surface series chart - assert.NoError(t, f.AddChart("Sheet2", "AV1", `{"type":"surface3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Surface Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"major_grid_lines":true}}`)) - assert.NoError(t, f.AddChart("Sheet2", "AV16", `{"type":"wireframeSurface3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"3D Wireframe Surface Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","y_axis":{"major_grid_lines":true}}`)) - assert.NoError(t, f.AddChart("Sheet2", "AV32", `{"type":"contour","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Contour Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "BD1", `{"type":"wireframeContour","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Wireframe Contour Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - // bubble chart - assert.NoError(t, f.AddChart("Sheet2", "BD16", `{"type":"bubble","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) - assert.NoError(t, f.AddChart("Sheet2", "BD32", `{"type":"bubble3D","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble 3D Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`)) - // pie of pie chart - assert.NoError(t, f.AddChart("Sheet2", "BD48", `{"type":"pieOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Pie of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`)) - // bar of pie chart - assert.NoError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`)) + // Test add chart on not exists worksheet + assert.EqualError(t, f.AddChart("SheetN", "P1", nil), "sheet SheetN does not exist") + positionLeft, positionBottom, positionRight, positionTop, positionTopRight := "left", "bottom", "right", "top", "top_right" + maximum, minimum, zero := 7.5, 0.5, .0 + series := []ChartSeries{ + {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}, + {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"}, + {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"}, + {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"}, + {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"}, + {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"}, + {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"}, + {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"}, + } + series2 := []ChartSeries{ + {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30", Marker: ChartMarker{Symbol: "none", Size: 10}, Line: ChartLine{Color: "#000000"}}, + {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"}, + {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"}, + {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"}, + {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"}, + {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"}, + {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"}, + {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Line: ChartLine{Width: 0.25}}, + } + series3 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}} + format := PictureOptions{ + XScale: float64Ptr(defaultPictureScale), + YScale: float64Ptr(defaultPictureScale), + OffsetX: 15, + OffsetY: 10, + PrintObject: boolPtr(true), + LockAspectRatio: false, + Locked: boolPtr(false), + } + legend := ChartLegend{Position: &positionLeft, ShowLegendKey: false} + plotArea := ChartPlotArea{ + ShowBubbleSize: true, + ShowCatName: true, + ShowLeaderLines: false, + ShowPercent: true, + ShowSerName: true, + ShowVal: true, + } + for _, c := range []struct { + sheetName, cell string + opts *Chart + }{ + {sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: "col", Series: series, Format: format, Legend: ChartLegend{None: true, ShowLegendKey: true}, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Color: "#000000"}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "#777777"}}}}, + {sheetName: "Sheet1", cell: "X1", opts: &Chart{Type: "colStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: "colPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "100% Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: "col3DClustered", Series: series, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "3D Clustered Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: "col3DStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "X30", opts: &Chart{Type: "col3DPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "X45", opts: &Chart{Type: "radar", Series: series, Format: format, Legend: ChartLegend{Position: &positionTopRight, ShowLegendKey: false}, Title: ChartTitle{Name: "Radar Chart"}, PlotArea: plotArea, ShowBlanksAs: "span"}}, + {sheetName: "Sheet1", cell: "AF1", opts: &Chart{Type: "col3DConeStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AF16", opts: &Chart{Type: "col3DConeClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AF30", opts: &Chart{Type: "col3DConePercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AF45", opts: &Chart{Type: "col3DCone", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cone Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AN1", opts: &Chart{Type: "col3DPyramidStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AN16", opts: &Chart{Type: "col3DPyramidClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AN30", opts: &Chart{Type: "col3DPyramidPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AN45", opts: &Chart{Type: "col3DPyramid", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Pyramid Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV1", opts: &Chart{Type: "col3DCylinderStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV16", opts: &Chart{Type: "col3DCylinderClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV30", opts: &Chart{Type: "col3DCylinderPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV45", opts: &Chart{Type: "col3DCylinder", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Cylinder Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: "col3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P1", opts: &Chart{Type: "line3D", Series: series2, Format: format, Legend: ChartLegend{Position: &positionTop, ShowLegendKey: false}, Title: ChartTitle{Name: "3D Line Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}}, + {sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: "scatter", Series: series, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "Scatter Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P16", opts: &Chart{Type: "doughnut", Series: series3, Format: format, Legend: ChartLegend{Position: &positionRight, ShowLegendKey: false}, Title: ChartTitle{Name: "Doughnut Chart"}, PlotArea: ChartPlotArea{ShowBubbleSize: false, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "zero", HoleSize: 30}}, + {sheetName: "Sheet2", cell: "X16", opts: &Chart{Type: "line", Series: series2, Format: format, Legend: ChartLegend{Position: &positionTop, ShowLegendKey: false}, Title: ChartTitle{Name: "Line Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1}}}, + {sheetName: "Sheet2", cell: "P32", opts: &Chart{Type: "pie3D", Series: series3, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "3D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "X32", opts: &Chart{Type: "pie", Series: series3, Format: format, Legend: ChartLegend{Position: &positionBottom, ShowLegendKey: false}, Title: ChartTitle{Name: "Pie Chart"}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false}, ShowBlanksAs: "gap"}}, + // bar series chart + {sheetName: "Sheet2", cell: "P48", opts: &Chart{Type: "bar", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Clustered Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "X48", opts: &Chart{Type: "barStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P64", opts: &Chart{Type: "barPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked 100% Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "X64", opts: &Chart{Type: "bar3DClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Clustered Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P80", opts: &Chart{Type: "bar3DStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{Maximum: &maximum, Minimum: &minimum}}}, + {sheetName: "Sheet2", cell: "X80", opts: &Chart{Type: "bar3DPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Bar Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}}, + // area series chart + {sheetName: "Sheet2", cell: "AF1", opts: &Chart{Type: "area", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN1", opts: &Chart{Type: "areaStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF16", opts: &Chart{Type: "areaPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D 100% Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN16", opts: &Chart{Type: "area3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF32", opts: &Chart{Type: "area3DStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN32", opts: &Chart{Type: "area3DPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D 100% Stacked Area Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + // cylinder series chart + {sheetName: "Sheet2", cell: "AF48", opts: &Chart{Type: "bar3DCylinderStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF64", opts: &Chart{Type: "bar3DCylinderClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF80", opts: &Chart{Type: "bar3DCylinderPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + // cone series chart + {sheetName: "Sheet2", cell: "AN48", opts: &Chart{Type: "bar3DConeStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN64", opts: &Chart{Type: "bar3DConeClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN80", opts: &Chart{Type: "bar3DConePercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV48", opts: &Chart{Type: "bar3DPyramidStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV64", opts: &Chart{Type: "bar3DPyramidClustered", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV80", opts: &Chart{Type: "bar3DPyramidPercentStacked", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + // surface series chart + {sheetName: "Sheet2", cell: "AV1", opts: &Chart{Type: "surface3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Surface Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "AV16", opts: &Chart{Type: "wireframeSurface3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Wireframe Surface Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: "contour", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: "wireframeContour", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Wireframe Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + // bubble chart + {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: "bubble", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: "bubble3D", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble 3D Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, + // pie of pie chart + {sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: "pieOfPie", Series: series3, Format: format, Legend: legend, Title: ChartTitle{Name: "Pie of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, + // bar of pie chart + {sheetName: "Sheet2", cell: "BD64", opts: &Chart{Type: "barOfPie", Series: series3, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, + } { + assert.NoError(t, f.AddChart(c.sheetName, c.cell, c.opts)) + } // combo chart - f.NewSheet("Combo Charts") + _, err = f.NewSheet("Combo Charts") + assert.NoError(t, err) clusteredColumnCombo := [][]string{ {"A1", "line", "Clustered Column - Line Chart"}, {"I1", "bubble", "Clustered Column - Bubble Chart"}, @@ -205,7 +263,7 @@ func TestAddChart(t *testing.T) { {"Y1", "doughnut", "Clustered Column - Doughnut Chart"}, } for _, props := range clusteredColumnCombo { - assert.NoError(t, f.AddChart("Combo Charts", props[0], fmt.Sprintf(`{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[2]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1]))) + assert.NoError(t, f.AddChart("Combo Charts", props[0], &Chart{Type: "col", Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[2]}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1], Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}})) } stackedAreaCombo := map[string][]string{ "A16": {"line", "Stacked Area - Line Chart"}, @@ -214,25 +272,25 @@ func TestAddChart(t *testing.T) { "Y16": {"doughnut", "Stacked Area - Doughnut Chart"}, } for axis, props := range stackedAreaCombo { - assert.NoError(t, f.AddChart("Combo Charts", axis, fmt.Sprintf(`{"type":"areaStacked","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"%s"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[1]), fmt.Sprintf(`{"type":"%s","series":[{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true}}`, props[0]))) + assert.NoError(t, f.AddChart("Combo Charts", axis, &Chart{Type: "areaStacked", Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[1]}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[0], Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}})) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx"))) // Test with invalid sheet name - assert.EqualError(t, f.AddChart("Sheet:1", "A1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}]}`), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddChart("Sheet:1", "A1", &Chart{Type: "col", Series: series[:1]}), ErrSheetNameInvalid.Error()) // Test with illegal cell reference - assert.EqualError(t, f.AddChart("Sheet2", "A", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.AddChart("Sheet2", "A", &Chart{Type: "col", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test with unsupported chart type - assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bubble 3D Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`), "unsupported chart type unknown") + assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: "unknown", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble 3D Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}), "unsupported chart type unknown") // Test add combo chart with invalid format set - assert.EqualError(t, f.AddChart("Sheet2", "BD32", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`, ""), "unexpected end of JSON input") + assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: "col", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}, nil), ErrParameterInvalid.Error()) // Test add combo chart with unsupported chart type - assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown") + assert.EqualError(t, f.AddChart("Sheet2", "BD64", &Chart{Type: "barOfPie", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}, &Chart{Type: "unknown", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}), "unsupported chart type unknown") assert.NoError(t, f.Close()) // Test add chart with unsupported charset content types. f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.AddChart("Sheet1", "P1", &Chart{Type: "col", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: ChartTitle{Name: "2D Column Chart"}}), "XML syntax error on line 1: invalid UTF-8") } func TestAddChartSheet(t *testing.T) { @@ -245,7 +303,12 @@ func TestAddChartSheet(t *testing.T) { for k, v := range values { assert.NoError(t, f.SetCellValue("Sheet1", k, v)) } - assert.NoError(t, f.AddChartSheet("Chart1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`)) + series := []ChartSeries{ + {Name: "Sheet1!$A$2", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$2:$D$2"}, + {Name: "Sheet1!$A$3", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$3:$D$3"}, + {Name: "Sheet1!$A$4", Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4"}, + } + assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: "col3DClustered", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}})) // Test set the chartsheet as active sheet var sheetIdx int for idx, sheetName := range f.GetSheetList() { @@ -259,11 +322,12 @@ func TestAddChartSheet(t *testing.T) { // Test cell value on chartsheet assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet") // Test add chartsheet on already existing name sheet - assert.EqualError(t, f.AddChartSheet("Sheet1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), ErrExistsSheet.Error()) + + assert.EqualError(t, f.AddChartSheet("Sheet1", &Chart{Type: "col3DClustered", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), ErrExistsSheet.Error()) // Test add chartsheet with invalid sheet name - assert.EqualError(t, f.AddChartSheet("Sheet:1", "A1", `{"type":"col3DClustered","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddChartSheet("Sheet:1", nil, &Chart{Type: "col3DClustered", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), ErrSheetNameInvalid.Error()) // Test with unsupported chart type - assert.EqualError(t, f.AddChartSheet("Chart2", `{"type":"unknown","series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],"title":{"name":"Fruit 3D Clustered Column Chart"}}`), "unsupported chart type unknown") + assert.EqualError(t, f.AddChartSheet("Chart2", &Chart{Type: "unknown", Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), "unsupported chart type unknown") assert.NoError(t, f.UpdateLinkedValue()) @@ -272,14 +336,43 @@ func TestAddChartSheet(t *testing.T) { f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, f.AddChartSheet("Chart4", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.AddChartSheet("Chart4", &Chart{Type: "col", Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}}, Title: ChartTitle{Name: "2D Column Chart"}}), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteChart(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.DeleteChart("Sheet1", "A1")) - assert.NoError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"},{"name":"Sheet1!$A$31","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$31:$D$31"},{"name":"Sheet1!$A$32","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$32:$D$32"},{"name":"Sheet1!$A$33","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$33:$D$33"},{"name":"Sheet1!$A$34","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$34:$D$34"},{"name":"Sheet1!$A$35","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$35:$D$35"},{"name":"Sheet1!$A$36","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$36:$D$36"},{"name":"Sheet1!$A$37","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$37:$D$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"2D Column Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero"}`)) + positionLeft := "left" + series := []ChartSeries{ + {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"}, + {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"}, + {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"}, + {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"}, + {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"}, + {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"}, + {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"}, + {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"}, + } + format := PictureOptions{ + XScale: float64Ptr(defaultPictureScale), + YScale: float64Ptr(defaultPictureScale), + OffsetX: 15, + OffsetY: 10, + PrintObject: boolPtr(true), + LockAspectRatio: false, + Locked: boolPtr(false), + } + legend := ChartLegend{Position: &positionLeft, ShowLegendKey: false} + plotArea := ChartPlotArea{ + ShowBubbleSize: true, + ShowCatName: true, + ShowLeaderLines: false, + ShowPercent: true, + ShowSerName: true, + ShowVal: true, + } + assert.NoError(t, f.AddChart("Sheet1", "P1", &Chart{Type: "col", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"})) assert.NoError(t, f.DeleteChart("Sheet1", "P1")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx"))) // Test delete chart with invalid sheet name @@ -322,37 +415,22 @@ func TestChartWithLogarithmicBase(t *testing.T) { for cell, v := range categories { assert.NoError(t, f.SetCellValue(sheet1, cell, v)) } - - // Add two chart, one without and one with log scaling - assert.NoError(t, f.AddChart(sheet1, "C1", - `{"type":"line","dimension":{"width":640, "height":480},`+ - `"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+ - `"title":{"name":"Line chart without log scaling"}}`)) - assert.NoError(t, f.AddChart(sheet1, "M1", - `{"type":"line","dimension":{"width":640, "height":480},`+ - `"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+ - `"y_axis":{"logbase":10.5},`+ - `"title":{"name":"Line chart with log 10 scaling"}}`)) - assert.NoError(t, f.AddChart(sheet1, "A25", - `{"type":"line","dimension":{"width":320, "height":240},`+ - `"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+ - `"y_axis":{"logbase":1.9},`+ - `"title":{"name":"Line chart with log 1.9 scaling"}}`)) - assert.NoError(t, f.AddChart(sheet1, "F25", - `{"type":"line","dimension":{"width":320, "height":240},`+ - `"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+ - `"y_axis":{"logbase":2},`+ - `"title":{"name":"Line chart with log 2 scaling"}}`)) - assert.NoError(t, f.AddChart(sheet1, "K25", - `{"type":"line","dimension":{"width":320, "height":240},`+ - `"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+ - `"y_axis":{"logbase":1000.1},`+ - `"title":{"name":"Line chart with log 1000.1 scaling"}}`)) - assert.NoError(t, f.AddChart(sheet1, "P25", - `{"type":"line","dimension":{"width":320, "height":240},`+ - `"series":[{"name":"value","categories":"Sheet1!$A$1:$A$19","values":"Sheet1!$B$1:$B$10"}],`+ - `"y_axis":{"logbase":1000},`+ - `"title":{"name":"Line chart with log 1000 scaling"}}`)) + series := []ChartSeries{{Name: "value", Categories: "Sheet1!$A$1:$A$19", Values: "Sheet1!$B$1:$B$10"}} + dimension := []int{640, 480, 320, 240} + for _, c := range []struct { + cell string + opts *Chart + }{ + {cell: "C1", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[0], Height: &dimension[1]}, Series: series, Title: ChartTitle{Name: "Line chart without log scaling"}}}, + {cell: "M1", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[0], Height: &dimension[1]}, Series: series, Title: ChartTitle{Name: "Line chart with log 10.5 scaling"}, YAxis: ChartAxis{LogBase: 10.5}}}, + {cell: "A25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 1.9 scaling"}, YAxis: ChartAxis{LogBase: 1.9}}}, + {cell: "F25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 2 scaling"}, YAxis: ChartAxis{LogBase: 2}}}, + {cell: "K25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 1000.1 scaling"}, YAxis: ChartAxis{LogBase: 1000.1}}}, + {cell: "P25", opts: &Chart{Type: "line", Dimension: ChartDimension{Width: &dimension[2], Height: &dimension[3]}, Series: series, Title: ChartTitle{Name: "Line chart with log 1000 scaling"}, YAxis: ChartAxis{LogBase: 1000}}}, + } { + // Add two chart, one without and one with log scaling + assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts)) + } // Export XLSX file for human confirmation assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx"))) diff --git a/col_test.go b/col_test.go index 4c5961f..4debab0 100644 --- a/col_test.go +++ b/col_test.go @@ -151,7 +151,6 @@ func TestGetColsError(t *testing.T) { func TestColsRows(t *testing.T) { f := NewFile() - f.NewSheet("Sheet1") _, err := f.Cols("Sheet1") assert.NoError(t, err) @@ -231,7 +230,8 @@ func TestColumnVisibility(t *testing.T) { // Test set column visible with invalid sheet name assert.EqualError(t, f.SetColVisible("Sheet:1", "A", false), ErrSheetNameInvalid.Error()) - f.NewSheet("Sheet3") + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) assert.NoError(t, f.SetColVisible("Sheet3", "E", false)) assert.EqualError(t, f.SetColVisible("Sheet1", "A:-1", true), newInvalidColumnNameError("-1").Error()) assert.EqualError(t, f.SetColVisible("SheetN", "E", false), "sheet SheetN does not exist") @@ -253,7 +253,8 @@ func TestOutlineLevel(t *testing.T) { assert.Equal(t, uint8(0), level) assert.NoError(t, err) - f.NewSheet("Sheet2") + _, err = f.NewSheet("Sheet2") + assert.NoError(t, err) assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4)) level, err = f.GetColOutlineLevel("Sheet1", "D") @@ -318,7 +319,8 @@ func TestOutlineLevel(t *testing.T) { func TestSetColStyle(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello")) - styleID, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#94d3a2"],"pattern":1}}`) + + styleID, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#94d3a2"}, Pattern: 1}}) assert.NoError(t, err) // Test set column style on not exists worksheet assert.EqualError(t, f.SetColStyle("SheetN", "E", styleID), "sheet SheetN does not exist") @@ -410,7 +412,7 @@ func TestInsertCols(t *testing.T) { assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External")) assert.NoError(t, f.MergeCell(sheet1, "A1", "C3")) - assert.NoError(t, f.AutoFilter(sheet1, "A2", "B2", `{"column":"B","expression":"x != blanks"}`)) + assert.NoError(t, f.AutoFilter(sheet1, "A2:B2", &AutoFilterOptions{Column: "B", Expression: "x != blanks"})) assert.NoError(t, f.InsertCols(sheet1, "A", 1)) // Test insert column with illegal cell reference diff --git a/datavalidation_test.go b/datavalidation_test.go index c307d20..7260b1a 100644 --- a/datavalidation_test.go +++ b/datavalidation_test.go @@ -51,7 +51,8 @@ func TestDataValidation(t *testing.T) { assert.NoError(t, f.SaveAs(resultFile)) - f.NewSheet("Sheet2") + _, err = f.NewSheet("Sheet2") + assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet2", "A2", &[]interface{}{"B2", 1})) assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3})) dvRange = NewDataValidation(true) diff --git a/docProps.go b/docProps.go index ebe929b..0531d4c 100644 --- a/docProps.go +++ b/docProps.go @@ -147,6 +147,13 @@ func (f *File) GetAppProps() (ret *AppProperties, err error) { // Category | A categorization of the content of this package. // | // Version | The version number. This value is set by the user or by the application. +// | +// Modified | The created time of the content of the resource which +// | represent in ISO 8601 UTC format, for example "2019-06-04T22:00:10Z". +// | +// Modified | The modified time of the content of the resource which +// | represent in ISO 8601 UTC format, for example "2019-06-04T22:00:10Z". +// | // // For example: // diff --git a/docProps_test.go b/docProps_test.go index 64b690c..da16a04 100644 --- a/docProps_test.go +++ b/docProps_test.go @@ -58,7 +58,7 @@ func TestGetAppProps(t *testing.T) { assert.NoError(t, err) assert.NoError(t, f.Close()) - // Test get application properties with unsupported charset. + // Test get application properties with unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) _, err = f.GetAppProps() @@ -110,7 +110,7 @@ func TestGetDocProps(t *testing.T) { assert.NoError(t, err) assert.NoError(t, f.Close()) - // Test get workbook properties with unsupported charset. + // Test get workbook properties with unsupported charset f = NewFile() f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) _, err = f.GetDocProps() diff --git a/drawing.go b/drawing.go index 08b7aac..de5a1d1 100644 --- a/drawing.go +++ b/drawing.go @@ -55,7 +55,7 @@ func (f *File) prepareChartSheetDrawing(cs *xlsxChartsheet, drawingID int, sheet // addChart provides a function to create chart as xl/charts/chart%d.xml by // given format sets. -func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) { +func (f *File) addChart(opts *Chart, comboCharts []*Chart) { count := f.countCharts() xlsxChartSpace := xlsxChartSpace{ XMLNSa: NameSpaceDrawingML.Value, @@ -139,7 +139,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) { }, PlotArea: &cPlotArea{}, Legend: &cLegend{ - LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[opts.Legend.Position])}, + LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[*opts.Legend.Position])}, Overlay: &attrValBool{Val: boolPtr(false)}, }, @@ -180,7 +180,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) { }, }, } - plotAreaFunc := map[string]func(*chartOptions) *cPlotArea{ + plotAreaFunc := map[string]func(*Chart) *cPlotArea{ Area: f.drawBaseChart, AreaStacked: f.drawBaseChart, AreaPercentStacked: f.drawBaseChart, @@ -264,7 +264,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) { // drawBaseChart provides a function to draw the c:plotArea element for bar, // and column series charts by given format sets. -func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea { +func (f *File) drawBaseChart(opts *Chart) *cPlotArea { c := cCharts{ BarDir: &attrValString{ Val: stringPtr("col"), @@ -273,7 +273,7 @@ func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea { Val: stringPtr("clustered"), }, VaryColors: &attrValBool{ - Val: boolPtr(opts.VaryColors), + Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), Shape: f.drawChartShape(opts), @@ -513,7 +513,7 @@ func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea { // drawDoughnutChart provides a function to draw the c:plotArea element for // doughnut chart by given format sets. -func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea { +func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea { holeSize := 75 if opts.HoleSize > 0 && opts.HoleSize <= 90 { holeSize = opts.HoleSize @@ -522,7 +522,7 @@ func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea { return &cPlotArea{ DoughnutChart: &cCharts{ VaryColors: &attrValBool{ - Val: boolPtr(opts.VaryColors), + Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), HoleSize: &attrValInt{Val: intPtr(holeSize)}, @@ -532,7 +532,7 @@ func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea { // drawLineChart provides a function to draw the c:plotArea element for line // chart by given format sets. -func (f *File) drawLineChart(opts *chartOptions) *cPlotArea { +func (f *File) drawLineChart(opts *Chart) *cPlotArea { return &cPlotArea{ LineChart: &cCharts{ Grouping: &attrValString{ @@ -555,7 +555,7 @@ func (f *File) drawLineChart(opts *chartOptions) *cPlotArea { // drawLine3DChart provides a function to draw the c:plotArea element for line // chart by given format sets. -func (f *File) drawLine3DChart(opts *chartOptions) *cPlotArea { +func (f *File) drawLine3DChart(opts *Chart) *cPlotArea { return &cPlotArea{ Line3DChart: &cCharts{ Grouping: &attrValString{ @@ -578,11 +578,11 @@ func (f *File) drawLine3DChart(opts *chartOptions) *cPlotArea { // drawPieChart provides a function to draw the c:plotArea element for pie // chart by given format sets. -func (f *File) drawPieChart(opts *chartOptions) *cPlotArea { +func (f *File) drawPieChart(opts *Chart) *cPlotArea { return &cPlotArea{ PieChart: &cCharts{ VaryColors: &attrValBool{ - Val: boolPtr(opts.VaryColors), + Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), }, @@ -591,11 +591,11 @@ func (f *File) drawPieChart(opts *chartOptions) *cPlotArea { // drawPie3DChart provides a function to draw the c:plotArea element for 3D // pie chart by given format sets. -func (f *File) drawPie3DChart(opts *chartOptions) *cPlotArea { +func (f *File) drawPie3DChart(opts *Chart) *cPlotArea { return &cPlotArea{ Pie3DChart: &cCharts{ VaryColors: &attrValBool{ - Val: boolPtr(opts.VaryColors), + Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), }, @@ -604,14 +604,14 @@ func (f *File) drawPie3DChart(opts *chartOptions) *cPlotArea { // drawPieOfPieChart provides a function to draw the c:plotArea element for // pie chart by given format sets. -func (f *File) drawPieOfPieChart(opts *chartOptions) *cPlotArea { +func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea { return &cPlotArea{ OfPieChart: &cCharts{ OfPieType: &attrValString{ Val: stringPtr("pie"), }, VaryColors: &attrValBool{ - Val: boolPtr(opts.VaryColors), + Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), SerLines: &attrValString{}, @@ -621,14 +621,14 @@ func (f *File) drawPieOfPieChart(opts *chartOptions) *cPlotArea { // drawBarOfPieChart provides a function to draw the c:plotArea element for // pie chart by given format sets. -func (f *File) drawBarOfPieChart(opts *chartOptions) *cPlotArea { +func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea { return &cPlotArea{ OfPieChart: &cCharts{ OfPieType: &attrValString{ Val: stringPtr("bar"), }, VaryColors: &attrValBool{ - Val: boolPtr(opts.VaryColors), + Val: opts.VaryColors, }, Ser: f.drawChartSeries(opts), SerLines: &attrValString{}, @@ -638,7 +638,7 @@ func (f *File) drawBarOfPieChart(opts *chartOptions) *cPlotArea { // drawRadarChart provides a function to draw the c:plotArea element for radar // chart by given format sets. -func (f *File) drawRadarChart(opts *chartOptions) *cPlotArea { +func (f *File) drawRadarChart(opts *Chart) *cPlotArea { return &cPlotArea{ RadarChart: &cCharts{ RadarStyle: &attrValString{ @@ -661,7 +661,7 @@ func (f *File) drawRadarChart(opts *chartOptions) *cPlotArea { // drawScatterChart provides a function to draw the c:plotArea element for // scatter chart by given format sets. -func (f *File) drawScatterChart(opts *chartOptions) *cPlotArea { +func (f *File) drawScatterChart(opts *Chart) *cPlotArea { return &cPlotArea{ ScatterChart: &cCharts{ ScatterStyle: &attrValString{ @@ -684,7 +684,7 @@ func (f *File) drawScatterChart(opts *chartOptions) *cPlotArea { // drawSurface3DChart provides a function to draw the c:surface3DChart element by // given format sets. -func (f *File) drawSurface3DChart(opts *chartOptions) *cPlotArea { +func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea { plotArea := &cPlotArea{ Surface3DChart: &cCharts{ Ser: f.drawChartSeries(opts), @@ -706,7 +706,7 @@ func (f *File) drawSurface3DChart(opts *chartOptions) *cPlotArea { // drawSurfaceChart provides a function to draw the c:surfaceChart element by // given format sets. -func (f *File) drawSurfaceChart(opts *chartOptions) *cPlotArea { +func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea { plotArea := &cPlotArea{ SurfaceChart: &cCharts{ Ser: f.drawChartSeries(opts), @@ -728,7 +728,7 @@ func (f *File) drawSurfaceChart(opts *chartOptions) *cPlotArea { // drawChartShape provides a function to draw the c:shape element by given // format sets. -func (f *File) drawChartShape(opts *chartOptions) *attrValString { +func (f *File) drawChartShape(opts *Chart) *attrValString { shapes := map[string]string{ Bar3DConeClustered: "cone", Bar3DConeStacked: "cone", @@ -760,7 +760,7 @@ func (f *File) drawChartShape(opts *chartOptions) *attrValString { // drawChartSeries provides a function to draw the c:ser element by given // format sets. -func (f *File) drawChartSeries(opts *chartOptions) *[]cSer { +func (f *File) drawChartSeries(opts *Chart) *[]cSer { var ser []cSer for k := range opts.Series { ser = append(ser, cSer{ @@ -790,7 +790,7 @@ func (f *File) drawChartSeries(opts *chartOptions) *[]cSer { // drawChartSeriesSpPr provides a function to draw the c:spPr element by given // format sets. -func (f *File) drawChartSeriesSpPr(i int, opts *chartOptions) *cSpPr { +func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr { var srgbClr *attrValString var schemeClr *aSchemeClr @@ -823,7 +823,7 @@ func (f *File) drawChartSeriesSpPr(i int, opts *chartOptions) *cSpPr { // drawChartSeriesDPt provides a function to draw the c:dPt element by given // data index and format sets. -func (f *File) drawChartSeriesDPt(i int, opts *chartOptions) []*cDPt { +func (f *File) drawChartSeriesDPt(i int, opts *Chart) []*cDPt { dpt := []*cDPt{{ IDx: &attrValInt{Val: intPtr(i)}, Bubble3D: &attrValBool{Val: boolPtr(false)}, @@ -852,7 +852,7 @@ func (f *File) drawChartSeriesDPt(i int, opts *chartOptions) []*cDPt { // drawChartSeriesCat provides a function to draw the c:cat element by given // chart series and format sets. -func (f *File) drawChartSeriesCat(v chartSeriesOptions, opts *chartOptions) *cCat { +func (f *File) drawChartSeriesCat(v ChartSeries, opts *Chart) *cCat { cat := &cCat{ StrRef: &cStrRef{ F: v.Categories, @@ -867,7 +867,7 @@ func (f *File) drawChartSeriesCat(v chartSeriesOptions, opts *chartOptions) *cCa // drawChartSeriesVal provides a function to draw the c:val element by given // chart series and format sets. -func (f *File) drawChartSeriesVal(v chartSeriesOptions, opts *chartOptions) *cVal { +func (f *File) drawChartSeriesVal(v ChartSeries, opts *Chart) *cVal { val := &cVal{ NumRef: &cNumRef{ F: v.Values, @@ -882,7 +882,7 @@ func (f *File) drawChartSeriesVal(v chartSeriesOptions, opts *chartOptions) *cVa // drawChartSeriesMarker provides a function to draw the c:marker element by // given data index and format sets. -func (f *File) drawChartSeriesMarker(i int, opts *chartOptions) *cMarker { +func (f *File) drawChartSeriesMarker(i int, opts *Chart) *cMarker { defaultSymbol := map[string]*attrValString{Scatter: {Val: stringPtr("circle")}} marker := &cMarker{ Symbol: defaultSymbol[opts.Type], @@ -917,7 +917,7 @@ func (f *File) drawChartSeriesMarker(i int, opts *chartOptions) *cMarker { // drawChartSeriesXVal provides a function to draw the c:xVal element by given // chart series and format sets. -func (f *File) drawChartSeriesXVal(v chartSeriesOptions, opts *chartOptions) *cCat { +func (f *File) drawChartSeriesXVal(v ChartSeries, opts *Chart) *cCat { cat := &cCat{ StrRef: &cStrRef{ F: v.Categories, @@ -929,7 +929,7 @@ func (f *File) drawChartSeriesXVal(v chartSeriesOptions, opts *chartOptions) *cC // drawChartSeriesYVal provides a function to draw the c:yVal element by given // chart series and format sets. -func (f *File) drawChartSeriesYVal(v chartSeriesOptions, opts *chartOptions) *cVal { +func (f *File) drawChartSeriesYVal(v ChartSeries, opts *Chart) *cVal { val := &cVal{ NumRef: &cNumRef{ F: v.Values, @@ -941,7 +941,7 @@ func (f *File) drawChartSeriesYVal(v chartSeriesOptions, opts *chartOptions) *cV // drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize // element by given chart series and format sets. -func (f *File) drawCharSeriesBubbleSize(v chartSeriesOptions, opts *chartOptions) *cVal { +func (f *File) drawCharSeriesBubbleSize(v ChartSeries, opts *Chart) *cVal { if _, ok := map[string]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok { return nil } @@ -954,7 +954,7 @@ func (f *File) drawCharSeriesBubbleSize(v chartSeriesOptions, opts *chartOptions // drawCharSeriesBubble3D provides a function to draw the c:bubble3D element // by given format sets. -func (f *File) drawCharSeriesBubble3D(opts *chartOptions) *attrValBool { +func (f *File) drawCharSeriesBubble3D(opts *Chart) *attrValBool { if _, ok := map[string]bool{Bubble3D: true}[opts.Type]; !ok { return nil } @@ -963,21 +963,21 @@ func (f *File) drawCharSeriesBubble3D(opts *chartOptions) *attrValBool { // drawChartDLbls provides a function to draw the c:dLbls element by given // format sets. -func (f *File) drawChartDLbls(opts *chartOptions) *cDLbls { +func (f *File) drawChartDLbls(opts *Chart) *cDLbls { return &cDLbls{ ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)}, - ShowVal: &attrValBool{Val: boolPtr(opts.Plotarea.ShowVal)}, - ShowCatName: &attrValBool{Val: boolPtr(opts.Plotarea.ShowCatName)}, - ShowSerName: &attrValBool{Val: boolPtr(opts.Plotarea.ShowSerName)}, - ShowBubbleSize: &attrValBool{Val: boolPtr(opts.Plotarea.ShowBubbleSize)}, - ShowPercent: &attrValBool{Val: boolPtr(opts.Plotarea.ShowPercent)}, - ShowLeaderLines: &attrValBool{Val: boolPtr(opts.Plotarea.ShowLeaderLines)}, + ShowVal: &attrValBool{Val: boolPtr(opts.PlotArea.ShowVal)}, + ShowCatName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowCatName)}, + ShowSerName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowSerName)}, + ShowBubbleSize: &attrValBool{Val: boolPtr(opts.PlotArea.ShowBubbleSize)}, + ShowPercent: &attrValBool{Val: boolPtr(opts.PlotArea.ShowPercent)}, + ShowLeaderLines: &attrValBool{Val: boolPtr(opts.PlotArea.ShowLeaderLines)}, } } // drawChartSeriesDLbls provides a function to draw the c:dLbls element by // given format sets. -func (f *File) drawChartSeriesDLbls(opts *chartOptions) *cDLbls { +func (f *File) drawChartSeriesDLbls(opts *Chart) *cDLbls { dLbls := f.drawChartDLbls(opts) chartSeriesDLbls := map[string]*cDLbls{ Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil, @@ -989,7 +989,7 @@ func (f *File) drawChartSeriesDLbls(opts *chartOptions) *cDLbls { } // drawPlotAreaCatAx provides a function to draw the c:catAx element. -func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs { +func (f *File) drawPlotAreaCatAx(opts *Chart) []*cAxs { max := &attrValFloat{Val: opts.XAxis.Maximum} min := &attrValFloat{Val: opts.XAxis.Minimum} if opts.XAxis.Maximum == nil { @@ -1025,10 +1025,10 @@ func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs { NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, }, } - if opts.XAxis.MajorGridlines { + if opts.XAxis.MajorGridLines { axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } - if opts.XAxis.MinorGridlines { + if opts.XAxis.MinorGridLines { axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if opts.XAxis.TickLabelSkip != 0 { @@ -1038,7 +1038,7 @@ func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs { } // drawPlotAreaValAx provides a function to draw the c:valAx element. -func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs { +func (f *File) drawPlotAreaValAx(opts *Chart) []*cAxs { max := &attrValFloat{Val: opts.YAxis.Maximum} min := &attrValFloat{Val: opts.YAxis.Minimum} if opts.YAxis.Maximum == nil { @@ -1076,10 +1076,10 @@ func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs { CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, }, } - if opts.YAxis.MajorGridlines { + if opts.YAxis.MajorGridLines { axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } - if opts.YAxis.MinorGridlines { + if opts.YAxis.MinorGridLines { axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} } if pos, ok := valTickLblPos[opts.Type]; ok { @@ -1092,7 +1092,7 @@ func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs { } // drawPlotAreaSerAx provides a function to draw the c:serAx element. -func (f *File) drawPlotAreaSerAx(opts *chartOptions) []*cAxs { +func (f *File) drawPlotAreaSerAx(opts *Chart) []*cAxs { max := &attrValFloat{Val: opts.YAxis.Maximum} min := &attrValFloat{Val: opts.YAxis.Minimum} if opts.YAxis.Maximum == nil { @@ -1139,7 +1139,7 @@ func (f *File) drawPlotAreaSpPr() *cSpPr { } // drawPlotAreaTxPr provides a function to draw the c:txPr element. -func (f *File) drawPlotAreaTxPr(opts *chartAxisOptions) *cTxPr { +func (f *File) drawPlotAreaTxPr(opts *ChartAxis) *cTxPr { cTxPr := &cTxPr{ BodyPr: aBodyPr{ Rot: -60000000, @@ -1242,7 +1242,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) { // addDrawingChart provides a function to add chart graphic frame by given // sheet, drawingXML, cell, width, height, relationship index and format sets. -func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *pictureOptions) error { +func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rID int, opts *PictureOptions) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err @@ -1250,8 +1250,8 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI colIdx := col - 1 rowIdx := row - 1 - width = int(float64(width) * opts.XScale) - height = int(float64(height) * opts.YScale) + width = int(float64(width) * *opts.XScale) + height = int(float64(height) * *opts.YScale) colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.OffsetX, opts.OffsetY, width, height) content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { @@ -1293,8 +1293,8 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI graphic, _ := xml.Marshal(graphicFrame) twoCellAnchor.GraphicFrame = string(graphic) twoCellAnchor.ClientData = &xdrClientData{ - FLocksWithSheet: opts.FLocksWithSheet, - FPrintsWithSheet: opts.FPrintsWithSheet, + FLocksWithSheet: *opts.Locked, + FPrintsWithSheet: *opts.PrintObject, } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) f.Drawings.Store(drawingXML, content) @@ -1304,7 +1304,7 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI // addSheetDrawingChart provides a function to add chart graphic frame for // chartsheet by given sheet, drawingXML, width, height, relationship index // and format sets. -func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOptions) error { +func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *PictureOptions) error { content, cNvPrID, err := f.drawingParser(drawingXML) if err != nil { return err @@ -1336,8 +1336,8 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOpt graphic, _ := xml.Marshal(graphicFrame) absoluteAnchor.GraphicFrame = string(graphic) absoluteAnchor.ClientData = &xdrClientData{ - FLocksWithSheet: opts.FLocksWithSheet, - FPrintsWithSheet: opts.FPrintsWithSheet, + FLocksWithSheet: *opts.Locked, + FPrintsWithSheet: *opts.PrintObject, } content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor) f.Drawings.Store(drawingXML, content) diff --git a/drawing_test.go b/drawing_test.go index 5c090eb..f0580dd 100644 --- a/drawing_test.go +++ b/drawing_test.go @@ -26,13 +26,13 @@ func TestDrawingParser(t *testing.T) { } f.Pkg.Store("charset", MacintoshCyrillicCharset) f.Pkg.Store("wsDr", []byte(xml.Header+``)) - // Test with one cell anchor. + // Test with one cell anchor _, _, err := f.drawingParser("wsDr") assert.NoError(t, err) - // Test with unsupported charset. + // Test with unsupported charset _, _, err = f.drawingParser("charset") assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") - // Test with alternate content. + // Test with alternate content f.Drawings = sync.Map{} f.Pkg.Store("wsDr", []byte(xml.Header+``)) _, _, err = f.drawingParser("wsDr") diff --git a/excelize_test.go b/excelize_test.go index 673664c..b9e1403 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -71,9 +71,10 @@ func TestOpenFile(t *testing.T) { assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns")) // Test max characters in a cell assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", TotalCellChars+2))) - f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.") + _, err = f.NewSheet(":\\/?*[]Maximum 31 characters allowed in sheet title.") + assert.EqualError(t, err, ErrSheetNameLength.Error()) // Test set worksheet name with illegal name - f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title.") + assert.EqualError(t, f.SetSheetName("Maximum 31 characters allowed i", "[Rename]:\\/?* Maximum 31 characters allowed in sheet title."), ErrSheetNameLength.Error()) assert.EqualError(t, f.SetCellInt("Sheet3", "A23", 10), "sheet Sheet3 does not exist") assert.EqualError(t, f.SetCellStr("Sheet3", "b230", "10"), "sheet Sheet3 does not exist") assert.EqualError(t, f.SetCellStr("Sheet10", "b230", "10"), "sheet Sheet10 does not exist") @@ -102,13 +103,13 @@ func TestOpenFile(t *testing.T) { assert.NoError(t, err) getSharedFormula(&xlsxWorksheet{}, 0, "") - // Test read cell value with given illegal rows number. + // Test read cell value with given illegal rows number _, err = f.GetCellValue("Sheet2", "a-1") assert.EqualError(t, err, newCellNameToCoordinatesError("A-1", newInvalidCellNameError("A-1")).Error()) _, err = f.GetCellValue("Sheet2", "A") assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - // Test read cell value with given lowercase column number. + // Test read cell value with given lowercase column number _, err = f.GetCellValue("Sheet2", "a5") assert.NoError(t, err) _, err = f.GetCellValue("Sheet2", "C11") @@ -145,7 +146,7 @@ func TestOpenFile(t *testing.T) { assert.EqualError(t, f.SetCellHyperLink("SheetN", "A1", "Sheet1!A40", "Location"), "sheet SheetN does not exist") // Test boolean write - booltest := []struct { + boolTest := []struct { value bool raw bool expected string @@ -155,7 +156,7 @@ func TestOpenFile(t *testing.T) { {false, false, "FALSE"}, {true, false, "TRUE"}, } - for _, test := range booltest { + for _, test := range boolTest { assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value)) val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw}) assert.NoError(t, err) @@ -175,10 +176,12 @@ func TestOpenFile(t *testing.T) { // Test read cell value with given cell reference large than exists row _, err = f.GetCellValue("Sheet2", "E231") assert.NoError(t, err) - // Test get active worksheet of spreadsheet and get worksheet name of spreadsheet by given worksheet index + // Test get active worksheet of spreadsheet and get worksheet name of + // spreadsheet by given worksheet index f.GetSheetName(f.GetActiveSheetIndex()) // Test get worksheet index of spreadsheet by given worksheet name - f.GetSheetIndex("Sheet1") + _, err = f.GetSheetIndex("Sheet1") + assert.NoError(t, err) // Test get worksheet name of spreadsheet by given invalid worksheet index f.GetSheetName(4) // Test get worksheet map of workbook @@ -339,31 +342,23 @@ func TestBrokenFile(t *testing.T) { func TestNewFile(t *testing.T) { // Test create a spreadsheet file f := NewFile() - f.NewSheet("Sheet1") - f.NewSheet("XLSXSheet2") - f.NewSheet("XLSXSheet3") + _, err := f.NewSheet("Sheet1") + assert.NoError(t, err) + _, err = f.NewSheet("XLSXSheet2") + assert.NoError(t, err) + _, err = f.NewSheet("XLSXSheet3") + assert.NoError(t, err) assert.NoError(t, f.SetCellInt("XLSXSheet2", "A23", 56)) assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42")) f.SetActiveSheet(0) // Test add picture to sheet with scaling and positioning - err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), - `{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`) - if !assert.NoError(t, err) { - t.FailNow() - } + scale := 0.5 + assert.NoError(t, f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), + &PictureOptions{XScale: &scale, YScale: &scale, Positioning: "absolute"})) // Test add picture to worksheet without options - err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), "") - if !assert.NoError(t, err) { - t.FailNow() - } - - // Test add picture to worksheet with invalid options - err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), `{`) - if !assert.Error(t, err) { - t.FailNow() - } + assert.NoError(t, f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewFile.xlsx"))) assert.NoError(t, f.Save()) @@ -385,7 +380,7 @@ func TestSetCellHyperLink(t *testing.T) { assert.NoError(t, f.SetCellHyperLink("Sheet1", "B19", "https://github.com/xuri/excelize", "External")) // Test add first hyperlink in a work sheet assert.NoError(t, f.SetCellHyperLink("Sheet2", "C1", "https://github.com/xuri/excelize", "External")) - // Test add Location hyperlink in a work sheet. + // Test add Location hyperlink in a work sheet assert.NoError(t, f.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location")) // Test add Location hyperlink with display & tooltip in a work sheet display, tooltip := "Display value", "Hover text" @@ -429,9 +424,7 @@ func TestSetCellHyperLink(t *testing.T) { func TestGetCellHyperLink(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) _, _, err = f.GetCellHyperLink("Sheet1", "") assert.EqualError(t, err, `invalid cell name ""`) @@ -513,9 +506,9 @@ func TestSetSheetBackgroundErrors(t *testing.T) { assert.EqualError(t, f.SetSheetBackground("Sheet1", filepath.Join("test", "images", "background.jpg")), "XML syntax error on line 1: invalid UTF-8") } -// TestWriteArrayFormula tests the extended options of SetCellFormula by writing an array function -// to a workbook. In the resulting file, the lines 2 and 3 as well as 4 and 5 should have matching -// contents. +// TestWriteArrayFormula tests the extended options of SetCellFormula by writing +// an array function to a workbook. In the resulting file, the lines 2 and 3 as +// well as 4 and 5 should have matching contents func TestWriteArrayFormula(t *testing.T) { cell := func(col, row int) string { c, err := CoordinatesToCellName(col, row) @@ -620,15 +613,11 @@ func TestWriteArrayFormula(t *testing.T) { func TestSetCellStyleAlignment(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) var style int - style, err = f.NewStyle(`{"alignment":{"horizontal":"center","ident":1,"justify_last_line":true,"reading_order":0,"relative_indent":1,"shrink_to_fit":true,"text_rotation":45,"vertical":"top","wrap_text":true}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Alignment: &Alignment{Horizontal: "center", Indent: 1, JustifyLastLine: true, ReadingOrder: 0, RelativeIndent: 1, ShrinkToFit: true, TextRotation: 45, Vertical: "top", WrapText: true}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A22", "A22", style)) @@ -651,13 +640,11 @@ func TestSetCellStyleAlignment(t *testing.T) { func TestSetCellStyleBorder(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) var style int - // Test set border on overlapping range with vertical variants shading styles gradient fill. + // Test set border on overlapping range with vertical variants shading styles gradient fill style, err = f.NewStyle(&Style{ Border: []Border{ {Type: "left", Color: "0000FF", Style: 3}, @@ -668,24 +655,18 @@ func TestSetCellStyleBorder(t *testing.T) { {Type: "diagonalUp", Color: "A020F0", Style: 8}, }, }) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "J21", "L25", style)) - style, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}],"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":1}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 1}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) - style, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":2},{"type":"top","color":"00FF00","style":3},{"type":"bottom","color":"FFFF00","style":4},{"type":"right","color":"FF0000","style":5},{"type":"diagonalDown","color":"A020F0","style":6},{"type":"diagonalUp","color":"A020F0","style":7}],"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":4}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: 2}, {Type: "top", Color: "00FF00", Style: 3}, {Type: "bottom", Color: "FFFF00", Style: 4}, {Type: "right", Color: "FF0000", Style: 5}, {Type: "diagonalDown", Color: "A020F0", Style: 6}, {Type: "diagonalUp", Color: "A020F0", Style: 7}}, Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 4}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) - // Test set border and solid style pattern fill for a single cell. + // Test set border and solid style pattern fill for a single cell style, err = f.NewStyle(&Style{ Border: []Border{ { @@ -725,9 +706,7 @@ func TestSetCellStyleBorder(t *testing.T) { Pattern: 1, }, }) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O22", "O22", style)) @@ -736,30 +715,18 @@ func TestSetCellStyleBorder(t *testing.T) { func TestSetCellStyleBorderErrors(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) - // Set border with invalid style parameter. - _, err = f.NewStyle("") - if !assert.EqualError(t, err, "unexpected end of JSON input") { - t.FailNow() - } - - // Set border with invalid style index number. - _, err = f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":-1},{"type":"top","color":"00FF00","style":14},{"type":"bottom","color":"FFFF00","style":5},{"type":"right","color":"FF0000","style":6},{"type":"diagonalDown","color":"A020F0","style":9},{"type":"diagonalUp","color":"A020F0","style":8}]}`) - if !assert.NoError(t, err) { - t.FailNow() - } + // Set border with invalid style index number + _, err = f.NewStyle(&Style{Border: []Border{{Type: "left", Color: "0000FF", Style: -1}, {Type: "top", Color: "00FF00", Style: 14}, {Type: "bottom", Color: "FFFF00", Style: 5}, {Type: "right", Color: "FF0000", Style: 6}, {Type: "diagonalDown", Color: "A020F0", Style: 9}, {Type: "diagonalUp", Color: "A020F0", Style: 8}}}) + assert.NoError(t, err) } func TestSetCellStyleNumberFormat(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) - // Test only set fill and number format for a cell. + // Test only set fill and number format for a cell col := []string{"L", "M", "N", "O", "P"} data := []int{0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49} value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"} @@ -781,7 +748,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) { } else { assert.NoError(t, f.SetCellValue("Sheet2", c, val)) } - style, err := f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":5},"number_format": ` + strconv.Itoa(d) + `}`) + style, err := f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 5}, NumFmt: d}) if !assert.NoError(t, err) { t.FailNow() } @@ -792,10 +759,8 @@ func TestSetCellStyleNumberFormat(t *testing.T) { } } var style int - style, err = f.NewStyle(`{"number_format":-1}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{NumFmt: -1}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "L33", "L33", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx"))) @@ -804,23 +769,17 @@ func TestSetCellStyleNumberFormat(t *testing.T) { func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { t.Run("TestBook3", func(t *testing.T) { f, err := prepareTestBook3() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 56)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", -32.3)) var style int - style, err = f.NewStyle(`{"number_format": 188, "decimal_places": -1}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: -1}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) - style, err = f.NewStyle(`{"number_format": 188, "decimal_places": 31, "negred": true}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: 31, NegRed: true}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) @@ -829,34 +788,24 @@ func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { t.Run("TestBook4", func(t *testing.T) { f, err := prepareTestBook4() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5)) - _, err = f.NewStyle(`{"number_format": 26, "lang": "zh-tw"}`) - if !assert.NoError(t, err) { - t.FailNow() - } + _, err = f.NewStyle(&Style{NumFmt: 26, Lang: "zh-tw"}) + assert.NoError(t, err) - style, err := f.NewStyle(`{"number_format": 27}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err := f.NewStyle(&Style{NumFmt: 27}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) - style, err = f.NewStyle(`{"number_format": 31, "lang": "ko-kr"}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{NumFmt: 31, Lang: "ko-kr"}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) - style, err = f.NewStyle(`{"number_format": 71, "lang": "th-th"}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{NumFmt: 71, Lang: "th-th"}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx"))) @@ -867,42 +816,40 @@ func TestSetCellStyleCustomNumberFormat(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5)) - style, err := f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@"}`) + customNumFmt := "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@" + style, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) - style, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yyyy;@","font":{"color":"#9A0511"}}`) + style, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt, Font: &Font{Color: "#9A0511"}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) - _, err = f.NewStyle(`{"custom_number_format": "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@"}`) + customNumFmt = "[$-380A]dddd\\,\\ dd\" de \"mmmm\" de \"yy;@" + _, err = f.NewStyle(&Style{CustomNumFmt: &customNumFmt}) assert.NoError(t, err) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx"))) } func TestSetCellStyleFill(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) var style int - // Test set fill for cell with invalid parameter. - style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":6}}`) + // Test set fill for cell with invalid parameter + style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 6}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) - style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF"],"shading":1}}`) + style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF"}, Shading: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) - style, err = f.NewStyle(`{"fill":{"type":"pattern","color":[],"pattern":1}}`) + style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{}, Shading: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) - style, err = f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":19}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 19}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx"))) @@ -910,43 +857,31 @@ func TestSetCellStyleFill(t *testing.T) { func TestSetCellStyleFont(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) var style int - style, err = f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "#777777", Underline: "single"}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A1", "A1", style)) - style, err = f.NewStyle(`{"font":{"italic":true,"underline":"double"}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Font: &Font{Italic: true, Underline: "double"}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A2", "A2", style)) - style, err = f.NewStyle(`{"font":{"bold":true}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Font: &Font{Bold: true}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A3", "A3", style)) - style, err = f.NewStyle(`{"font":{"bold":true,"family":"","size":0,"color":"","underline":""}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Family: "", Size: 0, Color: "", Underline: ""}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A4", "A4", style)) - style, err = f.NewStyle(`{"font":{"color":"#777777","strike":true}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Font: &Font{Color: "#777777", Strike: true}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A5", "A5", style)) @@ -955,40 +890,30 @@ func TestSetCellStyleFont(t *testing.T) { func TestSetCellStyleProtection(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) var style int - style, err = f.NewStyle(`{"protection":{"hidden":true, "locked":true}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + style, err = f.NewStyle(&Style{Protection: &Protection{Hidden: true, Locked: true}}) + assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet2", "A6", "A6", style)) err = f.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) } func TestSetDeleteSheet(t *testing.T) { t.Run("TestBook3", func(t *testing.T) { f, err := prepareTestBook3() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) - f.DeleteSheet("XLSXSheet3") + assert.NoError(t, f.DeleteSheet("XLSXSheet3")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx"))) }) t.Run("TestBook4", func(t *testing.T) { f, err := prepareTestBook4() - if !assert.NoError(t, err) { - t.FailNow() - } - f.DeleteSheet("Sheet1") + assert.NoError(t, err) + assert.NoError(t, f.DeleteSheet("Sheet1")) assert.NoError(t, f.AddComment("Sheet1", Comment{Cell: "A1", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook4.xlsx"))) }) @@ -996,11 +921,10 @@ func TestSetDeleteSheet(t *testing.T) { func TestSheetVisibility(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetSheetVisible("Sheet2", false)) + assert.NoError(t, f.SetSheetVisible("Sheet2", false, true)) assert.NoError(t, f.SetSheetVisible("Sheet1", false)) assert.NoError(t, f.SetSheetVisible("Sheet1", true)) visible, err := f.GetSheetVisible("Sheet1") @@ -1058,123 +982,231 @@ func TestConditionalFormat(t *testing.T) { var format1, format2, format3, format4 int var err error - // Rose format for bad conditional. - format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + // Rose format for bad conditional + format1, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}}) + assert.NoError(t, err) - // Light yellow format for neutral conditional. - format2, err = f.NewConditionalStyle(`{"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + // Light yellow format for neutral conditional + format2, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#FEEAA0"}, Pattern: 1}}) + assert.NoError(t, err) - // Light green format for good conditional. - format3, err = f.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + // Light green format for good conditional + format3, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#09600B"}, Fill: Fill{Type: "pattern", Color: []string{"#C7EECF"}, Pattern: 1}}) + assert.NoError(t, err) - // conditional style with align and left border. - format4, err = f.NewConditionalStyle(`{"alignment":{"wrap_text":true},"border":[{"type":"left","color":"#000000","style":1}]}`) - if !assert.NoError(t, err) { - t.FailNow() - } + // conditional style with align and left border + format4, err = f.NewConditionalStyle(&Style{Alignment: &Alignment{WrapText: true}, Border: []Border{{Type: "left", Color: "#000000", Style: 1}}}) + assert.NoError(t, err) // Color scales: 2 color - assert.NoError(t, f.SetConditionalFormat(sheet1, "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`)) + assert.NoError(t, f.SetConditionalFormat(sheet1, "A1:A10", + []ConditionalFormatOptions{ + { + Type: "2_color_scale", + Criteria: "=", + MinType: "min", + MaxType: "max", + MinColor: "#F8696B", + MaxColor: "#63BE7B", + }, + }, + )) // Color scales: 3 color - assert.NoError(t, f.SetConditionalFormat(sheet1, "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`)) + assert.NoError(t, f.SetConditionalFormat(sheet1, "B1:B10", + []ConditionalFormatOptions{ + { + Type: "3_color_scale", + Criteria: "=", + MinType: "min", + MidType: "percentile", + MaxType: "max", + MinColor: "#F8696B", + MidColor: "#FFEB84", + MaxColor: "#63BE7B", + }, + }, + )) // Highlight cells rules: between... - assert.NoError(t, f.SetConditionalFormat(sheet1, "C1:C10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format1))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "C1:C10", + []ConditionalFormatOptions{ + { + Type: "cell", + Criteria: "between", + Format: format1, + Minimum: "6", + Maximum: "8", + }, + }, + )) // Highlight cells rules: Greater Than... - assert.NoError(t, f.SetConditionalFormat(sheet1, "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format3))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "D1:D10", + []ConditionalFormatOptions{ + { + Type: "cell", + Criteria: ">", + Format: format3, + Value: "6", + }, + }, + )) // Highlight cells rules: Equal To... - assert.NoError(t, f.SetConditionalFormat(sheet1, "E1:E10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d}]`, format3))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "E1:E10", + []ConditionalFormatOptions{ + { + Type: "top", + Criteria: "=", + Format: format3, + }, + }, + )) // Highlight cells rules: Not Equal To... - assert.NoError(t, f.SetConditionalFormat(sheet1, "F1:F10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format2))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "F1:F10", + []ConditionalFormatOptions{ + { + Type: "unique", + Criteria: "=", + Format: format2, + }, + }, + )) // Highlight cells rules: Duplicate Values... - assert.NoError(t, f.SetConditionalFormat(sheet1, "G1:G10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format2))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "G1:G10", + []ConditionalFormatOptions{ + { + Type: "duplicate", + Criteria: "=", + Format: format2, + }, + }, + )) // Top/Bottom rules: Top 10%. - assert.NoError(t, f.SetConditionalFormat(sheet1, "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format1))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "H1:H10", + []ConditionalFormatOptions{ + { + Type: "top", + Criteria: "=", + Format: format1, + Value: "6", + Percent: true, + }, + }, + )) // Top/Bottom rules: Above Average... - assert.NoError(t, f.SetConditionalFormat(sheet1, "I1:I10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format3))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "I1:I10", + []ConditionalFormatOptions{ + { + Type: "average", + Criteria: "=", + Format: format3, + AboveAverage: true, + }, + }, + )) // Top/Bottom rules: Below Average... - assert.NoError(t, f.SetConditionalFormat(sheet1, "J1:J10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format1))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "J1:J10", + []ConditionalFormatOptions{ + { + Type: "average", + Criteria: "=", + Format: format1, + AboveAverage: false, + }, + }, + )) // Data Bars: Gradient Fill - assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)) + assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", + []ConditionalFormatOptions{ + { + Type: "data_bar", + Criteria: "=", + MinType: "min", + MaxType: "max", + BarColor: "#638EC6", + }, + }, + )) // Use a formula to determine which cells to format - assert.NoError(t, f.SetConditionalFormat(sheet1, "L1:L10", fmt.Sprintf(`[{"type":"formula", "criteria":"L2<3", "format":%d}]`, format1))) + assert.NoError(t, f.SetConditionalFormat(sheet1, "L1:L10", + []ConditionalFormatOptions{ + { + Type: "formula", + Criteria: "L2<3", + Format: format1, + }, + }, + )) // Alignment/Border cells rules - assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"0"}]`, format4))) - - // Test set invalid format set in conditional format - assert.EqualError(t, f.SetConditionalFormat(sheet1, "L1:L10", ""), "unexpected end of JSON input") + assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10", + []ConditionalFormatOptions{ + { + Type: "cell", + Criteria: ">", + Format: format4, + Value: "0", + }, + }, + )) // Test set conditional format on not exists worksheet - assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", "[]"), "sheet SheetN does not exist") + assert.EqualError(t, f.SetConditionalFormat("SheetN", "L1:L10", nil), "sheet SheetN does not exist") // Test set conditional format with invalid sheet name - assert.EqualError(t, f.SetConditionalFormat("Sheet:1", "L1:L10", "[]"), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.SetConditionalFormat("Sheet:1", "L1:L10", nil), ErrSheetNameInvalid.Error()) err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) // Set conditional format with illegal valid type - assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)) + assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", + []ConditionalFormatOptions{ + { + Type: "", + Criteria: "=", + MinType: "min", + MaxType: "max", + BarColor: "#638EC6", + }, + }, + )) // Set conditional format with illegal criteria type - assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", `[{"type":"data_bar", "criteria":"", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`)) + assert.NoError(t, f.SetConditionalFormat(sheet1, "K1:K10", + []ConditionalFormatOptions{ + { + Type: "data_bar", + Criteria: "", + MinType: "min", + MaxType: "max", + BarColor: "#638EC6", + }, + }, + )) + // Test create conditional format with invalid custom number format + var exp string + _, err = f.NewConditionalStyle(&Style{CustomNumFmt: &exp}) + assert.EqualError(t, err, ErrCustomNumFmt.Error()) // Set conditional format with file without dxfs element should not return error f, err = OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) - _, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) - if !assert.NoError(t, err) { - t.FailNow() - } + _, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "", Color: []string{"#FEC7CE"}, Pattern: 1}}) + assert.NoError(t, err) assert.NoError(t, f.Close()) } -func TestConditionalFormatError(t *testing.T) { - f := NewFile() - sheet1 := f.GetSheetName(0) - - fillCells(f, sheet1, 10, 15) - - // Set conditional format with illegal JSON string should return error. - _, err := f.NewConditionalStyle("") - if !assert.EqualError(t, err, "unexpected end of JSON input") { - t.FailNow() - } -} - func TestSharedStrings(t *testing.T) { f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) rows, err := f.GetRows("Sheet1") - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.Equal(t, "A", rows[0][0]) rows, err = f.GetRows("Sheet2") - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.Equal(t, "Test Weight (Kgs)", rows[0][0]) assert.NoError(t, f.Close()) } func TestSetSheetCol(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetSheetCol("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})) @@ -1190,9 +1222,7 @@ func TestSetSheetCol(t *testing.T) { func TestSetSheetRow(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})) @@ -1300,10 +1330,8 @@ func TestProtectSheet(t *testing.T) { func TestUnprotectSheet(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } - // Test remove protection on not exists worksheet. + assert.NoError(t, err) + // Test remove protection on not exists worksheet assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN does not exist") assert.NoError(t, f.UnprotectSheet("Sheet1")) @@ -1343,7 +1371,7 @@ func TestProtectWorkbook(t *testing.T) { assert.Equal(t, 24, len(wb.WorkbookProtection.WorkbookSaltValue)) assert.Equal(t, 88, len(wb.WorkbookProtection.WorkbookHashValue)) assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount) - assert.NoError(t, f.SaveAs(filepath.Join("test", "TestProtectWorkbook.xlsx"))) + // Test protect workbook with password exceeds the limit length assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{ AlgorithmName: "MD4", @@ -1354,13 +1382,15 @@ func TestProtectWorkbook(t *testing.T) { AlgorithmName: "RIPEMD-160", Password: "password", }), ErrUnsupportedHashAlgorithm.Error()) + // Test protect workbook with unsupported charset workbook + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.ProtectWorkbook(nil), "XML syntax error on line 1: invalid UTF-8") } func TestUnprotectWorkbook(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) - if !assert.NoError(t, err) { - t.FailNow() - } + assert.NoError(t, err) assert.NoError(t, f.UnprotectWorkbook()) assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error()) @@ -1382,6 +1412,10 @@ func TestUnprotectWorkbook(t *testing.T) { assert.NoError(t, err) wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA=====" assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), "illegal base64 data at input byte 8") + // Test remove workbook protection with unsupported charset workbook + f.WorkBook = nil + f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) + assert.EqualError(t, f.UnprotectWorkbook(), "XML syntax error on line 1: invalid UTF-8") } func TestSetDefaultTimeStyle(t *testing.T) { @@ -1409,7 +1443,7 @@ func TestAddVBAProject(t *testing.T) { } func TestContentTypesReader(t *testing.T) { - // Test unsupported charset. + // Test unsupported charset f := NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) @@ -1418,7 +1452,7 @@ func TestContentTypesReader(t *testing.T) { } func TestWorkbookReader(t *testing.T) { - // Test unsupported charset. + // Test unsupported charset f := NewFile() f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) @@ -1427,7 +1461,7 @@ func TestWorkbookReader(t *testing.T) { } func TestWorkSheetReader(t *testing.T) { - // Test unsupported charset. + // Test unsupported charset f := NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) @@ -1435,7 +1469,7 @@ func TestWorkSheetReader(t *testing.T) { assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") - // Test on no checked worksheet. + // Test on no checked worksheet f = NewFile() f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) @@ -1445,7 +1479,7 @@ func TestWorkSheetReader(t *testing.T) { } func TestRelsReader(t *testing.T) { - // Test unsupported charset. + // Test unsupported charset f := NewFile() rels := defaultXMLPathWorkbookRels f.Relationships.Store(rels, nil) @@ -1463,7 +1497,7 @@ func TestDeleteSheetFromWorkbookRels(t *testing.T) { func TestUpdateLinkedValue(t *testing.T) { f := NewFile() - // Test update lined value with unsupported charset workbook. + // Test update lined value with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") @@ -1482,16 +1516,21 @@ func prepareTestBook1() (*File, error) { return nil, err } - err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), - `{"x_offset": 140, "y_offset": 120, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`) - if err != nil { + if err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), + &PictureOptions{OffsetX: 140, OffsetY: 120, Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"}); err != nil { return nil, err } - // Test add picture to worksheet with offset, external hyperlink and positioning. - err = f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"), - `{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`) - if err != nil { + // Test add picture to worksheet with offset, external hyperlink and positioning + if err := f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"), + &PictureOptions{ + OffsetX: 10, + OffsetY: 10, + Hyperlink: "https://github.com/xuri/excelize", + HyperlinkType: "External", + Positioning: "oneCell", + }, + ); err != nil { return nil, err } @@ -1500,7 +1539,7 @@ func prepareTestBook1() (*File, error) { return nil, err } - err = f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".jpg", file) + err = f.AddPictureFromBytes("Sheet1", "Q1", "Excel Logo", ".jpg", file, nil) if err != nil { return nil, err } @@ -1510,9 +1549,12 @@ func prepareTestBook1() (*File, error) { func prepareTestBook3() (*File, error) { f := NewFile() - f.NewSheet("Sheet1") - f.NewSheet("XLSXSheet2") - f.NewSheet("XLSXSheet3") + if _, err := f.NewSheet("XLSXSheet2"); err != nil { + return nil, err + } + if _, err := f.NewSheet("XLSXSheet3"); err != nil { + return nil, err + } if err := f.SetCellInt("XLSXSheet2", "A23", 56); err != nil { return nil, err } @@ -1520,18 +1562,14 @@ func prepareTestBook3() (*File, error) { return nil, err } f.SetActiveSheet(0) - - err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), - `{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`) - if err != nil { + scale := 0.5 + if err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), + &PictureOptions{XScale: &scale, YScale: &scale, Positioning: "absolute"}); err != nil { return nil, err } - - err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), "") - if err != nil { + if err := f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil); err != nil { return nil, err } - return f, nil } diff --git a/lib.go b/lib.go index 27b5ab7..d62b789 100644 --- a/lib.go +++ b/lib.go @@ -313,15 +313,15 @@ func sortCoordinates(coordinates []int) error { // coordinatesToRangeRef provides a function to convert a pair of coordinates // to range reference. -func (f *File) coordinatesToRangeRef(coordinates []int) (string, error) { +func (f *File) coordinatesToRangeRef(coordinates []int, abs ...bool) (string, error) { if len(coordinates) != 4 { return "", ErrCoordinates } - firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1]) + firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1], abs...) if err != nil { return "", err } - lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3]) + lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3], abs...) if err != nil { return "", err } @@ -493,15 +493,6 @@ func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err return nil } -// fallbackOptions provides a method to convert format string to []byte and -// handle empty string. -func fallbackOptions(opts string) []byte { - if opts != "" { - return []byte(opts) - } - return []byte("{}") -} - // namespaceStrictToTransitional provides a method to convert Strict and // Transitional namespaces. func namespaceStrictToTransitional(content []byte) []byte { diff --git a/merge_test.go b/merge_test.go index 40055c9..9bef612 100644 --- a/merge_test.go +++ b/merge_test.go @@ -37,7 +37,8 @@ func TestMergeCell(t *testing.T) { assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value) assert.NoError(t, err) - f.NewSheet("Sheet3") + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) assert.NoError(t, f.MergeCell("Sheet3", "D11", "F13")) assert.NoError(t, f.MergeCell("Sheet3", "G10", "K12")) diff --git a/picture.go b/picture.go index 045e2af..72d3f7d 100644 --- a/picture.go +++ b/picture.go @@ -13,7 +13,6 @@ package excelize import ( "bytes" - "encoding/json" "encoding/xml" "image" "io" @@ -26,14 +25,28 @@ import ( // parsePictureOptions provides a function to parse the format settings of // the picture with default value. -func parsePictureOptions(opts string) (*pictureOptions, error) { - format := pictureOptions{ - FPrintsWithSheet: true, - XScale: 1, - YScale: 1, +func parsePictureOptions(opts *PictureOptions) *PictureOptions { + if opts == nil { + return &PictureOptions{ + PrintObject: boolPtr(true), + Locked: boolPtr(false), + XScale: float64Ptr(defaultPictureScale), + YScale: float64Ptr(defaultPictureScale), + } } - err := json.Unmarshal(fallbackOptions(opts), &format) - return &format, err + if opts.PrintObject == nil { + opts.PrintObject = boolPtr(true) + } + if opts.Locked == nil { + opts.Locked = boolPtr(false) + } + if opts.XScale == nil { + opts.XScale = float64Ptr(defaultPictureScale) + } + if opts.YScale == nil { + opts.YScale = float64Ptr(defaultPictureScale) + } + return opts } // AddPicture provides the method to add picture in a sheet by given picture @@ -44,6 +57,7 @@ func parsePictureOptions(opts string) (*pictureOptions, error) { // package main // // import ( +// "fmt" // _ "image/gif" // _ "image/jpeg" // _ "image/png" @@ -54,15 +68,33 @@ func parsePictureOptions(opts string) (*pictureOptions, error) { // func main() { // f := excelize.NewFile() // // Insert a picture. -// if err := f.AddPicture("Sheet1", "A2", "image.jpg", ""); err != nil { +// if err := f.AddPicture("Sheet1", "A2", "image.jpg", nil); err != nil { // fmt.Println(err) // } // // Insert a picture scaling in the cell with location hyperlink. -// if err := f.AddPicture("Sheet1", "D2", "image.png", `{"x_scale": 0.5, "y_scale": 0.5, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`); err != nil { +// enable, scale := true, 0.5 +// if err := f.AddPicture("Sheet1", "D2", "image.png", +// &excelize.PictureOptions{ +// XScale: &scale, +// YScale: &scale, +// Hyperlink: "#Sheet2!D8", +// HyperlinkType: "Location", +// }, +// ); err != nil { // fmt.Println(err) // } // // Insert a picture offset in the cell with external hyperlink, printing and positioning support. -// if err := f.AddPicture("Sheet1", "H2", "image.gif", `{"x_offset": 15, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "print_obj": true, "lock_aspect_ratio": false, "locked": false, "positioning": "oneCell"}`); err != nil { +// if err := f.AddPicture("Sheet1", "H2", "image.gif", +// &excelize.PictureOptions{ +// PrintObject: &enable, +// LockAspectRatio: false, +// OffsetX: 15, +// OffsetY: 10, +// Hyperlink: "https://github.com/xuri/excelize", +// HyperlinkType: "External", +// Positioning: "oneCell", +// }, +// ); err != nil { // fmt.Println(err) // } // if err := f.SaveAs("Book1.xlsx"); err != nil { @@ -70,42 +102,42 @@ func parsePictureOptions(opts string) (*pictureOptions, error) { // } // } // -// The optional parameter "autofit" specifies if you make image size auto-fits the +// The optional parameter "Autofit" specifies if you make image size auto-fits the // cell, the default value of that is 'false'. // -// The optional parameter "hyperlink" specifies the hyperlink of the image. +// The optional parameter "Hyperlink" specifies the hyperlink of the image. // -// The optional parameter "hyperlink_type" defines two types of +// The optional parameter "HyperlinkType" defines two types of // hyperlink "External" for website or "Location" for moving to one of the // cells in this workbook. When the "hyperlink_type" is "Location", // coordinates need to start with "#". // -// The optional parameter "positioning" defines two types of the position of an +// The optional parameter "Positioning" defines two types of the position of an // image in an Excel spreadsheet, "oneCell" (Move but don't size with // cells) or "absolute" (Don't move or size with cells). If you don't set this // parameter, the default positioning is move and size with cells. // -// The optional parameter "print_obj" indicates whether the image is printed +// The optional parameter "PrintObject" indicates whether the image is printed // when the worksheet is printed, the default value of that is 'true'. // -// The optional parameter "lock_aspect_ratio" indicates whether lock aspect +// The optional parameter "LockAspectRatio" indicates whether lock aspect // ratio for the image, the default value of that is 'false'. // -// The optional parameter "locked" indicates whether lock the image. Locking +// The optional parameter "Locked" indicates whether lock the image. Locking // an object has no effect unless the sheet is protected. // -// The optional parameter "x_offset" specifies the horizontal offset of the +// The optional parameter "OffsetX" specifies the horizontal offset of the // image with the cell, the default value of that is 0. // -// The optional parameter "x_scale" specifies the horizontal scale of images, +// The optional parameter "XScale" specifies the horizontal scale of images, // the default value of that is 1.0 which presents 100%. // -// The optional parameter "y_offset" specifies the vertical offset of the +// The optional parameter "OffsetY" specifies the vertical offset of the // image with the cell, the default value of that is 0. // -// The optional parameter "y_scale" specifies the vertical scale of images, +// The optional parameter "YScale" specifies the vertical scale of images, // the default value of that is 1.0 which presents 100%. -func (f *File) AddPicture(sheet, cell, picture, format string) error { +func (f *File) AddPicture(sheet, cell, picture string, opts *PictureOptions) error { var err error // Check picture exists first. if _, err = os.Stat(picture); os.IsNotExist(err) { @@ -117,7 +149,7 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error { } file, _ := os.ReadFile(filepath.Clean(picture)) _, name := filepath.Split(picture) - return f.AddPictureFromBytes(sheet, cell, format, name, ext, file) + return f.AddPictureFromBytes(sheet, cell, name, ext, file, opts) } // AddPictureFromBytes provides the method to add picture in a sheet by given @@ -143,24 +175,21 @@ func (f *File) AddPicture(sheet, cell, picture, format string) error { // if err != nil { // fmt.Println(err) // } -// if err := f.AddPictureFromBytes("Sheet1", "A2", "", "Excel Logo", ".jpg", file); err != nil { +// if err := f.AddPictureFromBytes("Sheet1", "A2", "Excel Logo", ".jpg", file, nil); err != nil { // fmt.Println(err) // } // if err := f.SaveAs("Book1.xlsx"); err != nil { // fmt.Println(err) // } // } -func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, file []byte) error { +func (f *File) AddPictureFromBytes(sheet, cell, name, extension string, file []byte, opts *PictureOptions) error { var drawingHyperlinkRID int var hyperlinkType string ext, ok := supportedImageTypes[extension] if !ok { return ErrImgExt } - options, err := parsePictureOptions(opts) - if err != nil { - return err - } + options := parsePictureOptions(opts) img, _, err := image.DecodeConfig(bytes.NewReader(file)) if err != nil { return err @@ -276,20 +305,20 @@ func (f *File) countDrawings() int { // addDrawingPicture provides a function to add picture by given sheet, // drawingXML, cell, file name, width, height relationship index and format // sets. -func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, hyperlinkRID int, img image.Config, opts *pictureOptions) error { +func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, hyperlinkRID int, img image.Config, opts *PictureOptions) error { col, row, err := CellNameToCoordinates(cell) if err != nil { return err } width, height := img.Width, img.Height - if opts.Autofit { + if opts.AutoFit { width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts) if err != nil { return err } } else { - width = int(float64(width) * opts.XScale) - height = int(float64(height) * opts.YScale) + width = int(float64(width) * *opts.XScale) + height = int(float64(height) * *opts.YScale) } col-- row-- @@ -313,7 +342,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, twoCellAnchor.From = &from twoCellAnchor.To = &to pic := xlsxPic{} - pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.NoChangeAspect + pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio pic.NvPicPr.CNvPr.ID = cNvPrID pic.NvPicPr.CNvPr.Descr = file pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID) @@ -342,8 +371,8 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID, twoCellAnchor.Pic = &pic twoCellAnchor.ClientData = &xdrClientData{ - FLocksWithSheet: opts.FLocksWithSheet, - FPrintsWithSheet: opts.FPrintsWithSheet, + FLocksWithSheet: *opts.Locked, + FPrintsWithSheet: *opts.PrintObject, } content.Lock() defer content.Unlock() @@ -682,7 +711,7 @@ func (f *File) drawingsWriter() { } // drawingResize calculate the height and width after resizing. -func (f *File) drawingResize(sheet, cell string, width, height float64, opts *pictureOptions) (w, h, c, r int, err error) { +func (f *File) drawingResize(sheet, cell string, width, height float64, opts *PictureOptions) (w, h, c, r int, err error) { var mergeCells []MergeCell mergeCells, err = f.GetMergeCells(sheet) if err != nil { @@ -725,6 +754,6 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *pi height, width = float64(cellHeight), width*asp } width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY) - w, h = int(width*opts.XScale), int(height*opts.YScale) + w, h = int(width**opts.XScale), int(height**opts.YScale) return } diff --git a/picture_test.go b/picture_test.go index 2392314..11243b7 100644 --- a/picture_test.go +++ b/picture_test.go @@ -25,7 +25,7 @@ func BenchmarkAddPictureFromBytes(b *testing.B) { } b.ResetTimer() for i := 1; i <= b.N; i++ { - if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "", "excel", ".png", imgFile); err != nil { + if err := f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", i), "excel", ".png", imgFile, nil); err != nil { b.Error(err) } } @@ -37,32 +37,33 @@ func TestAddPicture(t *testing.T) { // Test add picture to worksheet with offset and location hyperlink assert.NoError(t, f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), - `{"x_offset": 140, "y_offset": 120, "hyperlink": "#Sheet2!D8", "hyperlink_type": "Location"}`)) + &PictureOptions{OffsetX: 140, OffsetY: 120, Hyperlink: "#Sheet2!D8", HyperlinkType: "Location"})) // Test add picture to worksheet with offset, external hyperlink and positioning assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), - `{"x_offset": 10, "y_offset": 10, "hyperlink": "https://github.com/xuri/excelize", "hyperlink_type": "External", "positioning": "oneCell"}`)) + &PictureOptions{OffsetX: 10, OffsetY: 10, Hyperlink: "https://github.com/xuri/excelize", HyperlinkType: "External", Positioning: "oneCell"})) file, err := os.ReadFile(filepath.Join("test", "images", "excel.png")) assert.NoError(t, err) // Test add picture to worksheet with autofit - assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`)) - assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), `{"x_offset": 10, "y_offset": 10, "autofit": true}`)) - f.NewSheet("AddPicture") + assert.NoError(t, f.AddPicture("Sheet1", "A30", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true})) + assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{OffsetX: 10, OffsetY: 10, AutoFit: true})) + _, err = f.NewSheet("AddPicture") + assert.NoError(t, err) assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30)) assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9")) assert.NoError(t, f.MergeCell("AddPicture", "B1", "D1")) - assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`)) - assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`)) + assert.NoError(t, f.AddPicture("AddPicture", "C6", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true})) + assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true})) // Test add picture to worksheet from bytes - assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file)) + assert.NoError(t, f.AddPictureFromBytes("Sheet1", "Q1", "Excel Logo", ".png", file, nil)) // Test add picture to worksheet from bytes with illegal cell reference - assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", "", "Excel Logo", ".png", file), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "A", "Excel Logo", ".png", file, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - assert.NoError(t, f.AddPicture("Sheet1", "Q8", filepath.Join("test", "images", "excel.gif"), "")) - assert.NoError(t, f.AddPicture("Sheet1", "Q15", filepath.Join("test", "images", "excel.jpg"), "")) - assert.NoError(t, f.AddPicture("Sheet1", "Q22", filepath.Join("test", "images", "excel.tif"), "")) + assert.NoError(t, f.AddPicture("Sheet1", "Q8", filepath.Join("test", "images", "excel.gif"), nil)) + assert.NoError(t, f.AddPicture("Sheet1", "Q15", filepath.Join("test", "images", "excel.jpg"), nil)) + assert.NoError(t, f.AddPicture("Sheet1", "Q22", filepath.Join("test", "images", "excel.tif"), nil)) // Test write file to given path assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx"))) @@ -72,10 +73,10 @@ func TestAddPicture(t *testing.T) { f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", "Excel Logo", ".png", file, nil), "XML syntax error on line 1: invalid UTF-8") // Test add picture with invalid sheet name - assert.EqualError(t, f.AddPicture("Sheet:1", "A1", filepath.Join("test", "images", "excel.jpg"), ""), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddPicture("Sheet:1", "A1", filepath.Join("test", "images", "excel.jpg"), nil), ErrSheetNameInvalid.Error()) } func TestAddPictureErrors(t *testing.T) { @@ -83,14 +84,14 @@ func TestAddPictureErrors(t *testing.T) { assert.NoError(t, err) // Test add picture to worksheet with invalid file path - assert.Error(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), "")) + assert.Error(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "not_exists_dir", "not_exists.icon"), nil)) // Test add picture to worksheet with unsupported file type - assert.EqualError(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), ""), ErrImgExt.Error()) - assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1)), ErrImgExt.Error()) + assert.EqualError(t, f.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), nil), ErrImgExt.Error()) + assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "Excel Logo", "jpg", make([]byte, 1), nil), ErrImgExt.Error()) // Test add picture to worksheet with invalid file data - assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", ".jpg", make([]byte, 1)), image.ErrFormat.Error()) + assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "Excel Logo", ".jpg", make([]byte, 1), nil), image.ErrFormat.Error()) // Test add picture with custom image decoder and encoder decode := func(r io.Reader) (image.Image, error) { return nil, nil } @@ -100,18 +101,19 @@ func TestAddPictureErrors(t *testing.T) { image.RegisterFormat("emz", "", decode, decodeConfig) image.RegisterFormat("wmz", "", decode, decodeConfig) image.RegisterFormat("svg", "", decode, decodeConfig) - assert.NoError(t, f.AddPicture("Sheet1", "Q1", filepath.Join("test", "images", "excel.emf"), "")) - assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), "")) - assert.NoError(t, f.AddPicture("Sheet1", "Q13", filepath.Join("test", "images", "excel.emz"), "")) - assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), "")) - assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", `{"x_scale": 2.1}`)) + assert.NoError(t, f.AddPicture("Sheet1", "Q1", filepath.Join("test", "images", "excel.emf"), nil)) + assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), nil)) + assert.NoError(t, f.AddPicture("Sheet1", "Q13", filepath.Join("test", "images", "excel.emz"), nil)) + assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), nil)) + xScale := 2.1 + assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", &PictureOptions{XScale: &xScale})) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx"))) assert.NoError(t, f.Close()) } func TestGetPicture(t *testing.T) { f := NewFile() - assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), "")) + assert.NoError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.png"), nil)) name, content, err := f.GetPicture("Sheet1", "A1") assert.NoError(t, err) assert.Equal(t, 13233, len(content)) @@ -196,19 +198,20 @@ func TestGetPicture(t *testing.T) { func TestAddDrawingPicture(t *testing.T) { // Test addDrawingPicture with illegal cell reference f := NewFile() - assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + opts := &PictureOptions{PrintObject: boolPtr(true), Locked: boolPtr(false), XScale: float64Ptr(defaultPictureScale), YScale: float64Ptr(defaultPictureScale)} + assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, opts), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", "", 0, 0, image.Config{}, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", "", 0, 0, image.Config{}, opts), "XML syntax error on line 1: invalid UTF-8") } func TestAddPictureFromBytes(t *testing.T) { f := NewFile() imgFile, err := os.ReadFile("logo.png") assert.NoError(t, err, "Unable to load logo for test") - assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile)) - assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile)) + assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "logo", ".png", imgFile, nil)) + assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "logo", ".png", imgFile, nil)) imageCount := 0 f.Pkg.Range(func(fileName, v interface{}) bool { if strings.Contains(fileName.(string), "media/image") { @@ -217,16 +220,16 @@ func TestAddPictureFromBytes(t *testing.T) { return true }) assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.") - assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), "sheet SheetN does not exist") + assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "logo", ".png", imgFile, nil), "sheet SheetN does not exist") // Test add picture from bytes with invalid sheet name - assert.EqualError(t, f.AddPictureFromBytes("Sheet:1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddPictureFromBytes("Sheet:1", fmt.Sprint("A", 1), "logo", ".png", imgFile, nil), ErrSheetNameInvalid.Error()) } func TestDeletePicture(t *testing.T) { f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) assert.NoError(t, err) assert.NoError(t, f.DeletePicture("Sheet1", "A1")) - assert.NoError(t, f.AddPicture("Sheet1", "P1", filepath.Join("test", "images", "excel.jpg"), "")) + assert.NoError(t, f.AddPicture("Sheet1", "P1", filepath.Join("test", "images", "excel.jpg"), nil)) assert.NoError(t, f.DeletePicture("Sheet1", "P1")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx"))) // Test delete picture on not exists worksheet @@ -251,7 +254,7 @@ func TestDrawingResize(t *testing.T) { ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") assert.True(t, ok) ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} - assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) } func TestSetContentTypePartImageExtensions(t *testing.T) { diff --git a/pivotTable.go b/pivotTable.go index 7b4b553..381938e 100644 --- a/pivotTable.go +++ b/pivotTable.go @@ -27,26 +27,26 @@ import ( // PivotStyleDark1 - PivotStyleDark28 type PivotTableOptions struct { pivotTableSheetName string - DataRange string `json:"data_range"` - PivotTableRange string `json:"pivot_table_range"` - Rows []PivotTableField `json:"rows"` - Columns []PivotTableField `json:"columns"` - Data []PivotTableField `json:"data"` - Filter []PivotTableField `json:"filter"` - RowGrandTotals bool `json:"row_grand_totals"` - ColGrandTotals bool `json:"col_grand_totals"` - ShowDrill bool `json:"show_drill"` - UseAutoFormatting bool `json:"use_auto_formatting"` - PageOverThenDown bool `json:"page_over_then_down"` - MergeItem bool `json:"merge_item"` - CompactData bool `json:"compact_data"` - ShowError bool `json:"show_error"` - ShowRowHeaders bool `json:"show_row_headers"` - ShowColHeaders bool `json:"show_col_headers"` - ShowRowStripes bool `json:"show_row_stripes"` - ShowColStripes bool `json:"show_col_stripes"` - ShowLastColumn bool `json:"show_last_column"` - PivotTableStyleName string `json:"pivot_table_style_name"` + DataRange string + PivotTableRange string + Rows []PivotTableField + Columns []PivotTableField + Data []PivotTableField + Filter []PivotTableField + RowGrandTotals bool + ColGrandTotals bool + ShowDrill bool + UseAutoFormatting bool + PageOverThenDown bool + MergeItem bool + CompactData bool + ShowError bool + ShowRowHeaders bool + ShowColHeaders bool + ShowRowStripes bool + ShowColStripes bool + ShowLastColumn bool + PivotTableStyleName string } // PivotTableField directly maps the field settings of the pivot table. @@ -69,12 +69,12 @@ type PivotTableOptions struct { // Name specifies the name of the data field. Maximum 255 characters // are allowed in data field name, excess characters will be truncated. type PivotTableField struct { - Compact bool `json:"compact"` - Data string `json:"data"` - Name string `json:"name"` - Outline bool `json:"outline"` - Subtotal string `json:"subtotal"` - DefaultSubtotal bool `json:"default_subtotal"` + Compact bool + Data string + Name string + Outline bool + Subtotal string + DefaultSubtotal bool } // AddPivotTable provides the method to add pivot table by given pivot table diff --git a/pivotTable_test.go b/pivotTable_test.go index 206388c..fbf60b3 100644 --- a/pivotTable_test.go +++ b/pivotTable_test.go @@ -109,7 +109,8 @@ func TestAddPivotTable(t *testing.T) { ShowLastColumn: true, PivotTableStyleName: "PivotStyleLight19", })) - f.NewSheet("Sheet2") + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ DataRange: "Sheet1!$A$1:$E$31", PivotTableRange: "Sheet2!$A$1:$AR$15", @@ -232,7 +233,7 @@ func TestAddPivotTable(t *testing.T) { Rows: []PivotTableField{{Data: "Year"}}, }), ErrSheetNameInvalid.Error()) // Test adjust range with invalid range - _, _, err := f.adjustRange("") + _, _, err = f.adjustRange("") assert.EqualError(t, err, ErrParameterRequired.Error()) // Test adjust range with incorrect range _, _, err = f.adjustRange("sheet1!") diff --git a/rows_test.go b/rows_test.go index 70ad48b..20b7a89 100644 --- a/rows_test.go +++ b/rows_test.go @@ -189,7 +189,8 @@ func TestRowHeight(t *testing.T) { // Test set row height with custom default row height with prepare XML assert.NoError(t, f.SetCellValue(sheet1, "A10", "A10")) - f.NewSheet("Sheet2") + _, err = f.NewSheet("Sheet2") + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet2", "A2", true)) height, err = f.GetRowHeight("Sheet2", 1) assert.NoError(t, err) @@ -258,10 +259,9 @@ func TestSharedStringsReader(t *testing.T) { func TestRowVisibility(t *testing.T) { f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } - f.NewSheet("Sheet3") + assert.NoError(t, err) + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) assert.NoError(t, f.SetRowVisible("Sheet3", 2, false)) assert.NoError(t, f.SetRowVisible("Sheet3", 2, true)) visible, err := f.GetRowVisible("Sheet3", 2) @@ -320,7 +320,7 @@ func TestRemoveRow(t *testing.T) { t.FailNow() } - err = f.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`) + err = f.AutoFilter(sheet1, "A2:A2", &AutoFilterOptions{Column: "A", Expression: "x != blanks"}) if !assert.NoError(t, err) { t.FailNow() } @@ -990,9 +990,9 @@ func TestCheckRow(t *testing.T) { func TestSetRowStyle(t *testing.T) { f := NewFile() - style1, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#63BE7B"],"pattern":1}}`) + style1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#63BE7B"}, Pattern: 1}}) assert.NoError(t, err) - style2, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`) + style2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#E0EBF5"}, Pattern: 1}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1)) assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error()) diff --git a/shape.go b/shape.go index 2022ee6..3e2db80 100644 --- a/shape.go +++ b/shape.go @@ -12,26 +12,38 @@ package excelize import ( - "encoding/json" "strconv" "strings" ) // parseShapeOptions provides a function to parse the format settings of the // shape with default value. -func parseShapeOptions(opts string) (*shapeOptions, error) { - options := shapeOptions{ - Width: 160, - Height: 160, - Format: pictureOptions{ - FPrintsWithSheet: true, - XScale: 1, - YScale: 1, - }, - Line: lineOptions{Width: 1}, +func parseShapeOptions(opts *Shape) (*Shape, error) { + if opts == nil { + return nil, ErrParameterInvalid } - err := json.Unmarshal([]byte(opts), &options) - return &options, err + if opts.Width == nil { + opts.Width = intPtr(defaultShapeSize) + } + if opts.Height == nil { + opts.Height = intPtr(defaultShapeSize) + } + if opts.Format.PrintObject == nil { + opts.Format.PrintObject = boolPtr(true) + } + if opts.Format.Locked == nil { + opts.Format.Locked = boolPtr(false) + } + if opts.Format.XScale == nil { + opts.Format.XScale = float64Ptr(defaultPictureScale) + } + if opts.Format.YScale == nil { + opts.Format.YScale = float64Ptr(defaultPictureScale) + } + if opts.Line.Width == nil { + opts.Line.Width = float64Ptr(defaultShapeLineWidth) + } + return opts, nil } // AddShape provides the method to add shape in a sheet by given worksheet @@ -39,33 +51,29 @@ func parseShapeOptions(opts string) (*shapeOptions, error) { // print settings) and properties set. For example, add text box (rect shape) // in Sheet1: // -// err := f.AddShape("Sheet1", "G6", `{ -// "type": "rect", -// "color": -// { -// "line": "#4286F4", -// "fill": "#8eb9ff" +// width, height, lineWidth := 180, 90, 1.2 +// err := f.AddShape("Sheet1", "G6", +// &excelize.Shape{ +// Type: "rect", +// Color: excelize.ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"}, +// Paragraph: []excelize.ShapeParagraph{ +// { +// Text: "Rectangle Shape", +// Font: excelize.Font{ +// Bold: true, +// Italic: true, +// Family: "Times New Roman", +// Size: 36, +// Color: "#777777", +// Underline: "sng", +// }, +// }, +// }, +// Width: &width, +// Height: &height, +// Line: excelize.ShapeLine{Width: &lineWidth}, // }, -// "paragraph": [ -// { -// "text": "Rectangle Shape", -// "font": -// { -// "bold": true, -// "italic": true, -// "family": "Times New Roman", -// "size": 36, -// "color": "#777777", -// "underline": "sng" -// } -// }], -// "width": 180, -// "height": 90, -// "line": -// { -// "width": 1.2 -// } -// }`) +// ) // // The following shows the type of shape supported by excelize: // @@ -277,7 +285,7 @@ func parseShapeOptions(opts string) (*shapeOptions, error) { // wavy // wavyHeavy // wavyDbl -func (f *File) AddShape(sheet, cell, opts string) error { +func (f *File) AddShape(sheet, cell string, opts *Shape) error { options, err := parseShapeOptions(opts) if err != nil { return err @@ -313,7 +321,7 @@ func (f *File) AddShape(sheet, cell, opts string) error { // addDrawingShape provides a function to add preset geometry by given sheet, // drawingXMLand format sets. -func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOptions) error { +func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) error { fromCol, fromRow, err := CellNameToCoordinates(cell) if err != nil { return err @@ -321,8 +329,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption colIdx := fromCol - 1 rowIdx := fromRow - 1 - width := int(float64(opts.Width) * opts.Format.XScale) - height := int(float64(opts.Height) * opts.Format.YScale) + width := int(float64(*opts.Width) * *opts.Format.XScale) + height := int(float64(*opts.Height) * *opts.Format.YScale) colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY, width, height) @@ -381,9 +389,9 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption }, }, } - if opts.Line.Width != 1 { + if *opts.Line.Width != 1 { shape.SpPr.Ln = xlsxLineProperties{ - W: f.ptToEMUs(opts.Line.Width), + W: f.ptToEMUs(*opts.Line.Width), } } defaultFont, err := f.GetDefaultFont() @@ -391,7 +399,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption return err } if len(opts.Paragraph) < 1 { - opts.Paragraph = []shapeParagraphOptions{ + opts.Paragraph = []ShapeParagraph{ { Font: Font{ Bold: false, @@ -443,8 +451,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption } twoCellAnchor.Sp = &shape twoCellAnchor.ClientData = &xdrClientData{ - FLocksWithSheet: opts.Format.FLocksWithSheet, - FPrintsWithSheet: opts.Format.FPrintsWithSheet, + FLocksWithSheet: *opts.Format.Locked, + FPrintsWithSheet: *opts.Format.PrintObject, } content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) f.Drawings.Store(drawingXML, content) diff --git a/shape_test.go b/shape_test.go index 4c47d58..bddc8d2 100644 --- a/shape_test.go +++ b/shape_test.go @@ -12,97 +12,88 @@ func TestAddShape(t *testing.T) { if !assert.NoError(t, err) { t.FailNow() } - - assert.NoError(t, f.AddShape("Sheet1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`)) - assert.NoError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`)) - assert.NoError(t, f.AddShape("Sheet1", "C30", `{"type":"rect","paragraph":[]}`)) - assert.EqualError(t, f.AddShape("Sheet3", "H1", `{ - "type": "ellipseRibbon", - "color": - { - "line": "#4286f4", - "fill": "#8eb9ff" + shape := &Shape{ + Type: "rect", + Paragraph: []ShapeParagraph{ + {Text: "Rectangle", Font: Font{Color: "CD5C5C"}}, + {Text: "Shape", Font: Font{Bold: true, Color: "2980B9"}}, }, - "paragraph": [ - { - "font": - { - "bold": true, - "italic": true, - "family": "Times New Roman", - "size": 36, - "color": "#777777", - "underline": "single" - } - }], - "height": 90 - }`), "sheet Sheet3 does not exist") - assert.EqualError(t, f.AddShape("Sheet3", "H1", ""), "unexpected end of JSON input") - assert.EqualError(t, f.AddShape("Sheet1", "A", `{ - "type": "rect", - "paragraph": [ - { - "text": "Rectangle", - "font": - { - "color": "CD5C5C" - } + } + assert.NoError(t, f.AddShape("Sheet1", "A30", shape)) + assert.NoError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}})) + assert.NoError(t, f.AddShape("Sheet1", "C30", &Shape{Type: "rect"})) + assert.EqualError(t, f.AddShape("Sheet3", "H1", + &Shape{ + Type: "ellipseRibbon", + Color: ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"}, + Paragraph: []ShapeParagraph{ + { + Font: Font{ + Bold: true, + Italic: true, + Family: "Times New Roman", + Size: 36, + Color: "#777777", + Underline: "single", + }, + }, + }, }, - { - "text": "Shape", - "font": - { - "bold": true, - "color": "2980B9" - } - }] - }`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + ), "sheet Sheet3 does not exist") + assert.EqualError(t, f.AddShape("Sheet3", "H1", nil), ErrParameterInvalid.Error()) + assert.EqualError(t, f.AddShape("Sheet1", "A", shape), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx"))) // Test add first shape for given sheet f = NewFile() - assert.NoError(t, f.AddShape("Sheet1", "A1", `{ - "type": "ellipseRibbon", - "color": - { - "line": "#4286f4", - "fill": "#8eb9ff" - }, - "paragraph": [ - { - "font": - { - "bold": true, - "italic": true, - "family": "Times New Roman", - "size": 36, - "color": "#777777", - "underline": "single" - } - }], - "height": 90, - "line": - { - "width": 1.2 - } - }`)) + width, height := 1.2, 90 + assert.NoError(t, f.AddShape("Sheet1", "A1", + &Shape{ + Type: "ellipseRibbon", + Color: ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"}, + Paragraph: []ShapeParagraph{ + { + Font: Font{ + Bold: true, + Italic: true, + Family: "Times New Roman", + Size: 36, + Color: "#777777", + Underline: "single", + }, + }, + }, + Height: &height, + Line: ShapeLine{Width: &width}, + })) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) // Test add shape with invalid sheet name - assert.EqualError(t, f.AddShape("Sheet:1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddShape("Sheet:1", "A30", shape), ErrSheetNameInvalid.Error()) // Test add shape with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) - assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8") // Test add shape with unsupported charset content types f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8") } func TestAddDrawingShape(t *testing.T) { f := NewFile() path := "xl/drawings/drawing1.xml" f.Pkg.Store(path, MacintoshCyrillicCharset) - assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1", &shapeOptions{}), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1", + &Shape{ + Width: intPtr(defaultShapeSize), + Height: intPtr(defaultShapeSize), + Format: PictureOptions{ + PrintObject: boolPtr(true), + Locked: boolPtr(false), + XScale: float64Ptr(defaultPictureScale), + YScale: float64Ptr(defaultPictureScale), + }, + }, + ), "XML syntax error on line 1: invalid UTF-8") } diff --git a/sheet.go b/sheet.go index 16c4c16..cbafdd2 100644 --- a/sheet.go +++ b/sheet.go @@ -13,7 +13,6 @@ package excelize import ( "bytes" - "encoding/json" "encoding/xml" "fmt" "io" @@ -45,7 +44,7 @@ func (f *File) NewSheet(sheet string) (int, error) { if index != -1 { return index, err } - f.DeleteSheet(sheet) + _ = f.DeleteSheet(sheet) f.SheetCount++ wb, _ := f.workbookReader() sheetID := 0 @@ -682,19 +681,24 @@ func (f *File) copySheet(from, to int) error { return err } -// SetSheetVisible provides a function to set worksheet visible by given worksheet -// name. A workbook must contain at least one visible worksheet. If the given -// worksheet has been activated, this setting will be invalidated. Sheet state -// values as defined by https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetstatevalues -// -// visible -// hidden -// veryHidden +// getSheetState returns sheet visible enumeration by given hidden status. +func getSheetState(visible bool, veryHidden []bool) string { + state := "hidden" + if !visible && len(veryHidden) > 0 && veryHidden[0] { + state = "veryHidden" + } + return state +} + +// SetSheetVisible provides a function to set worksheet visible by given +// worksheet name. A workbook must contain at least one visible worksheet. If +// the given worksheet has been activated, this setting will be invalidated. +// The third optional veryHidden parameter only works when visible was false. // // For example, hide Sheet1: // // err := f.SetSheetVisible("Sheet1", false) -func (f *File) SetSheetVisible(sheet string, visible bool) error { +func (f *File) SetSheetVisible(sheet string, visible bool, veryHidden ...bool) error { if err := checkSheetName(sheet); err != nil { return err } @@ -710,9 +714,9 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error { } return err } - count := 0 + count, state := 0, getSheetState(visible, veryHidden) for _, v := range wb.Sheets.Sheet { - if v.State != "hidden" { + if v.State != state { count++ } } @@ -726,45 +730,37 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error { tabSelected = ws.SheetViews.SheetView[0].TabSelected } if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected { - wb.Sheets.Sheet[k].State = "hidden" + wb.Sheets.Sheet[k].State = state } } return err } -// parsePanesOptions provides a function to parse the panes settings. -func parsePanesOptions(opts string) (*panesOptions, error) { - format := panesOptions{} - err := json.Unmarshal([]byte(opts), &format) - return &format, err -} - // setPanes set create freeze panes and split panes by given options. -func (ws *xlsxWorksheet) setPanes(panes string) error { - opts, err := parsePanesOptions(panes) - if err != nil { - return err +func (ws *xlsxWorksheet) setPanes(panes *Panes) error { + if panes == nil { + return ErrParameterInvalid } p := &xlsxPane{ - ActivePane: opts.ActivePane, - TopLeftCell: opts.TopLeftCell, - XSplit: float64(opts.XSplit), - YSplit: float64(opts.YSplit), + ActivePane: panes.ActivePane, + TopLeftCell: panes.TopLeftCell, + XSplit: float64(panes.XSplit), + YSplit: float64(panes.YSplit), } - if opts.Freeze { + if panes.Freeze { p.State = "frozen" } if ws.SheetViews == nil { ws.SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}} } ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p - if !(opts.Freeze) && !(opts.Split) { + if !(panes.Freeze) && !(panes.Split) { if len(ws.SheetViews.SheetView) > 0 { ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil } } var s []*xlsxSelection - for _, p := range opts.Panes { + for _, p := range panes.Panes { s = append(s, &xlsxSelection{ ActiveCell: p.ActiveCell, Pane: p.Pane, @@ -772,94 +768,128 @@ func (ws *xlsxWorksheet) setPanes(panes string) error { }) } ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s - return err + return nil } // SetPanes provides a function to create and remove freeze panes and split panes // by given worksheet name and panes options. // -// activePane defines the pane that is active. The possible values for this +// ActivePane defines the pane that is active. The possible values for this // attribute are defined in the following table: // -// Enumeration Value | Description -// --------------------------------+------------------------------------------------------------- -// bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal -// | splits are applied. -// | -// | This value is also used when only a horizontal split has -// | been applied, dividing the pane into upper and lower -// | regions. In that case, this value specifies the bottom -// | pane. -// | -// bottomRight (Bottom Right Pane) | Bottom right pane, when both vertical and horizontal -// | splits are applied. -// | -// topLeft (Top Left Pane) | Top left pane, when both vertical and horizontal splits -// | are applied. -// | -// | This value is also used when only a horizontal split has -// | been applied, dividing the pane into upper and lower -// | regions. In that case, this value specifies the top pane. -// | -// | This value is also used when only a vertical split has -// | been applied, dividing the pane into right and left -// | regions. In that case, this value specifies the left pane -// | -// topRight (Top Right Pane) | Top right pane, when both vertical and horizontal -// | splits are applied. -// | -// | This value is also used when only a vertical split has -// | been applied, dividing the pane into right and left -// | regions. In that case, this value specifies the right -// | pane. +// Enumeration Value | Description +// ---------------------------------+------------------------------------------------------------- +// bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal +// | splits are applied. +// | +// | This value is also used when only a horizontal split has +// | been applied, dividing the pane into upper and lower +// | regions. In that case, this value specifies the bottom +// | pane. +// | +// bottomRight (Bottom Right Pane) | Bottom right pane, when both vertical and horizontal +// | splits are applied. +// | +// topLeft (Top Left Pane) | Top left pane, when both vertical and horizontal splits +// | are applied. +// | +// | This value is also used when only a horizontal split has +// | been applied, dividing the pane into upper and lower +// | regions. In that case, this value specifies the top pane. +// | +// | This value is also used when only a vertical split has +// | been applied, dividing the pane into right and left +// | regions. In that case, this value specifies the left pane +// | +// topRight (Top Right Pane) | Top right pane, when both vertical and horizontal +// | splits are applied. +// | +// | This value is also used when only a vertical split has +// | been applied, dividing the pane into right and left +// | regions. In that case, this value specifies the right +// | pane. // // Pane state type is restricted to the values supported currently listed in the following table: // -// Enumeration Value | Description -// --------------------------------+------------------------------------------------------------- -// frozen (Frozen) | Panes are frozen, but were not split being frozen. In -// | this state, when the panes are unfrozen again, a single -// | pane results, with no split. -// | -// | In this state, the split bars are not adjustable. -// | -// split (Split) | Panes are split, but not frozen. In this state, the split -// | bars are adjustable by the user. +// Enumeration Value | Description +// ---------------------------------+------------------------------------------------------------- +// frozen (Frozen) | Panes are frozen, but were not split being frozen. In +// | this state, when the panes are unfrozen again, a single +// | pane results, with no split. +// | +// | In this state, the split bars are not adjustable. +// | +// split (Split) | Panes are split, but not frozen. In this state, the split +// | bars are adjustable by the user. // -// x_split (Horizontal Split Position): Horizontal position of the split, in +// XSplit (Horizontal Split Position): Horizontal position of the split, in // 1/20th of a point; 0 (zero) if none. If the pane is frozen, this value // indicates the number of columns visible in the top pane. // -// y_split (Vertical Split Position): Vertical position of the split, in 1/20th +// YSplit (Vertical Split Position): Vertical position of the split, in 1/20th // of a point; 0 (zero) if none. If the pane is frozen, this value indicates the // number of rows visible in the left pane. The possible values for this // attribute are defined by the W3C XML Schema double datatype. // -// top_left_cell: Location of the top left visible cell in the bottom right pane +// TopLeftCell: Location of the top left visible cell in the bottom right pane // (when in Left-To-Right mode). // -// sqref (Sequence of References): Range of the selection. Can be non-contiguous +// SQRef (Sequence of References): Range of the selection. Can be non-contiguous // set of ranges. // // An example of how to freeze column A in the Sheet1 and set the active cell on // Sheet1!K16: // -// f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`) +// err := f.SetPanes("Sheet1", &excelize.Panes{ +// Freeze: true, +// Split: false, +// XSplit: 1, +// YSplit: 0, +// TopLeftCell: "B1", +// ActivePane: "topRight", +// Panes: []excelize.PaneOptions{ +// {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, +// }, +// }) // // An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell // ranges on Sheet1!A11:XFD11: // -// f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`) +// err := f.SetPanes("Sheet1", &excelize.Panes{ +// Freeze: true, +// Split: false, +// XSplit: 0, +// YSplit: 9, +// TopLeftCell: "A34", +// ActivePane: "bottomLeft", +// Panes: []excelize.PaneOptions{ +// {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"}, +// }, +// }) // // An example of how to create split panes in the Sheet1 and set the active cell // on Sheet1!J60: // -// f.SetPanes("Sheet1", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`) +// err := f.SetPanes("Sheet1", &excelize.Panes{ +// Freeze: false, +// Split: true, +// XSplit: 3270, +// YSplit: 1800, +// TopLeftCell: "N57", +// ActivePane: "bottomLeft", +// Panes: []excelize.PaneOptions{ +// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"}, +// {SQRef: "I36", ActiveCell: "I36"}, +// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"}, +// {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"}, +// {SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"}, +// }, +// }) // // An example of how to unfreeze and remove all panes on Sheet1: // -// f.SetPanes("Sheet1", `{"freeze":false,"split":false}`) -func (f *File) SetPanes(sheet, panes string) error { +// err := f.SetPanes("Sheet1", &excelize.Panes{Freeze: false, Split: false}) +func (f *File) SetPanes(sheet string, panes *Panes) error { ws, err := f.workSheetReader(sheet) if err != nil { return err @@ -870,7 +900,7 @@ func (f *File) SetPanes(sheet, panes string) error { // GetSheetVisible provides a function to get worksheet visible by given worksheet // name. For example, get visible state of Sheet1: // -// f.GetSheetVisible("Sheet1") +// visible, err := f.GetSheetVisible("Sheet1") func (f *File) GetSheetVisible(sheet string) (bool, error) { var visible bool if err := checkSheetName(sheet); err != nil { @@ -1509,6 +1539,9 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) { // Scope: "Sheet2", // }) func (f *File) SetDefinedName(definedName *DefinedName) error { + if definedName.Name == "" || definedName.RefersTo == "" { + return ErrParameterInvalid + } wb, err := f.workbookReader() if err != nil { return err diff --git a/sheet_test.go b/sheet_test.go index ed85c26..09b6155 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -15,14 +15,15 @@ import ( func TestNewSheet(t *testing.T) { f := NewFile() - f.NewSheet("Sheet2") + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) sheetID, err := f.NewSheet("sheet2") assert.NoError(t, err) f.SetActiveSheet(sheetID) // Test delete original sheet idx, err := f.GetSheetIndex("Sheet1") assert.NoError(t, err) - f.DeleteSheet(f.GetSheetName(idx)) + assert.NoError(t, f.DeleteSheet(f.GetSheetName(idx))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx"))) // Test create new worksheet with already exists name sheetID, err = f.NewSheet("Sheet2") @@ -38,24 +39,79 @@ func TestNewSheet(t *testing.T) { func TestSetPanes(t *testing.T) { f := NewFile() - assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`)) - f.NewSheet("Panes 2") - assert.NoError(t, f.SetPanes("Panes 2", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`)) - f.NewSheet("Panes 3") - assert.NoError(t, f.SetPanes("Panes 3", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`)) - f.NewSheet("Panes 4") - assert.NoError(t, f.SetPanes("Panes 4", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`)) - assert.EqualError(t, f.SetPanes("Panes 4", ""), "unexpected end of JSON input") - assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN does not exist") + + assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false})) + _, err := f.NewSheet("Panes 2") + assert.NoError(t, err) + assert.NoError(t, f.SetPanes("Panes 2", + &Panes{ + Freeze: true, + Split: false, + XSplit: 1, + YSplit: 0, + TopLeftCell: "B1", + ActivePane: "topRight", + Panes: []PaneOptions{ + {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, + }, + }, + )) + _, err = f.NewSheet("Panes 3") + assert.NoError(t, err) + assert.NoError(t, f.SetPanes("Panes 3", + &Panes{ + Freeze: false, + Split: true, + XSplit: 3270, + YSplit: 1800, + TopLeftCell: "N57", + ActivePane: "bottomLeft", + Panes: []PaneOptions{ + {SQRef: "I36", ActiveCell: "I36"}, + {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"}, + {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"}, + {SQRef: "O60", ActiveCell: "O60", Pane: "bottomRight"}, + }, + }, + )) + _, err = f.NewSheet("Panes 4") + assert.NoError(t, err) + assert.NoError(t, f.SetPanes("Panes 4", + &Panes{ + Freeze: true, + Split: false, + XSplit: 0, + YSplit: 9, + TopLeftCell: "A34", + ActivePane: "bottomLeft", + Panes: []PaneOptions{ + {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"}, + }, + }, + )) + assert.EqualError(t, f.SetPanes("Panes 4", nil), ErrParameterInvalid.Error()) + assert.EqualError(t, f.SetPanes("SheetN", nil), "sheet SheetN does not exist") // Test set panes with invalid sheet name - assert.EqualError(t, f.SetPanes("Sheet:1", `{"freeze":false,"split":false}`), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.SetPanes("Sheet:1", &Panes{Freeze: false, Split: false}), ErrSheetNameInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx"))) // Test add pane on empty sheet views worksheet f = NewFile() f.checked = nil f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) - assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`)) + assert.NoError(t, f.SetPanes("Sheet1", + &Panes{ + Freeze: true, + Split: false, + XSplit: 1, + YSplit: 0, + TopLeftCell: "B1", + ActivePane: "topRight", + Panes: []PaneOptions{ + {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, + }, + }, + )) } func TestSearchSheet(t *testing.T) { @@ -108,7 +164,7 @@ func TestSearchSheet(t *testing.T) { assert.EqualError(t, err, "invalid cell reference [1, 0]") assert.Equal(t, []string(nil), result) - // Test search sheet with unsupported charset shared strings table. + // Test search sheet with unsupported charset shared strings table f.SharedStrings = nil f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) _, err = f.SearchSheet("Sheet1", "A") @@ -204,6 +260,14 @@ func TestDefinedName(t *testing.T) { assert.EqualError(t, f.DeleteDefinedName(&DefinedName{ Name: "No Exist Defined Name", }), ErrDefinedNameScope.Error()) + // Test set defined name without name + assert.EqualError(t, f.SetDefinedName(&DefinedName{ + RefersTo: "Sheet1!$A$2:$D$5", + }), ErrParameterInvalid.Error()) + // Test set defined name without reference + assert.EqualError(t, f.SetDefinedName(&DefinedName{ + Name: "Amount", + }), ErrParameterInvalid.Error()) assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo) assert.NoError(t, f.DeleteDefinedName(&DefinedName{ Name: "Amount", @@ -228,7 +292,8 @@ func TestGroupSheets(t *testing.T) { f := NewFile() sheets := []string{"Sheet2", "Sheet3"} for _, sheet := range sheets { - f.NewSheet(sheet) + _, err := f.NewSheet(sheet) + assert.NoError(t, err) } assert.EqualError(t, f.GroupSheets([]string{"Sheet1", "SheetN"}), "sheet SheetN does not exist") assert.EqualError(t, f.GroupSheets([]string{"Sheet2", "Sheet3"}), "group worksheet must contain an active worksheet") @@ -242,7 +307,8 @@ func TestUngroupSheets(t *testing.T) { f := NewFile() sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"} for _, sheet := range sheets { - f.NewSheet(sheet) + _, err := f.NewSheet(sheet) + assert.NoError(t, err) } assert.NoError(t, f.UngroupSheets()) } @@ -276,7 +342,8 @@ func TestRemovePageBreak(t *testing.T) { assert.NoError(t, f.RemovePageBreak("Sheet1", "B3")) assert.NoError(t, f.RemovePageBreak("Sheet1", "A3")) - f.NewSheet("Sheet2") + _, err := f.NewSheet("Sheet2") + assert.NoError(t, err) assert.NoError(t, f.InsertPageBreak("Sheet2", "B2")) assert.NoError(t, f.InsertPageBreak("Sheet2", "C2")) assert.NoError(t, f.RemovePageBreak("Sheet2", "B2")) @@ -381,20 +448,23 @@ func TestDeleteSheet(t *testing.T) { idx, err := f.NewSheet("Sheet2") assert.NoError(t, err) f.SetActiveSheet(idx) - f.NewSheet("Sheet3") - f.DeleteSheet("Sheet1") + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) + assert.NoError(t, f.DeleteSheet("Sheet1")) assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex())) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx"))) // Test with auto filter defined names f = NewFile() - f.NewSheet("Sheet2") - f.NewSheet("Sheet3") + _, err = f.NewSheet("Sheet2") + assert.NoError(t, err) + _, err = f.NewSheet("Sheet3") + assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A")) assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A")) assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A")) - assert.NoError(t, f.AutoFilter("Sheet1", "A1", "A1", "")) - assert.NoError(t, f.AutoFilter("Sheet2", "A1", "A1", "")) - assert.NoError(t, f.AutoFilter("Sheet3", "A1", "A1", "")) + assert.NoError(t, f.AutoFilter("Sheet1", "A1:A1", nil)) + assert.NoError(t, f.AutoFilter("Sheet2", "A1:A1", nil)) + assert.NoError(t, f.AutoFilter("Sheet3", "A1:A1", nil)) assert.NoError(t, f.DeleteSheet("Sheet2")) assert.NoError(t, f.DeleteSheet("Sheet1")) // Test delete sheet with invalid sheet name @@ -408,9 +478,10 @@ func TestDeleteAndAdjustDefinedNames(t *testing.T) { } func TestGetSheetID(t *testing.T) { - file := NewFile() - file.NewSheet("Sheet1") - id := file.getSheetID("sheet1") + f := NewFile() + _, err := f.NewSheet("Sheet1") + assert.NoError(t, err) + id := f.getSheetID("sheet1") assert.NotEqual(t, -1, id) } @@ -444,7 +515,7 @@ func TestGetSheetIndex(t *testing.T) { func TestSetContentTypes(t *testing.T) { f := NewFile() - // Test set content type with unsupported charset content types. + // Test set content type with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8") @@ -452,7 +523,7 @@ func TestSetContentTypes(t *testing.T) { func TestDeleteSheetFromContentTypes(t *testing.T) { f := NewFile() - // Test delete sheet from content types with unsupported charset content types. + // Test delete sheet from content types with unsupported charset content types f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) assert.EqualError(t, f.deleteSheetFromContentTypes("/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8") @@ -468,9 +539,8 @@ func BenchmarkNewSheet(b *testing.B) { func newSheetWithSet() { file := NewFile() - file.NewSheet("sheet1") for i := 0; i < 1000; i++ { - _ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i) + _ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), i) } file = nil } @@ -485,9 +555,8 @@ func BenchmarkFile_SaveAs(b *testing.B) { func newSheetWithSave() { file := NewFile() - file.NewSheet("sheet1") for i := 0; i < 1000; i++ { - _ = file.SetCellInt("sheet1", "A"+strconv.Itoa(i+1), i) + _ = file.SetCellInt("Sheet1", "A"+strconv.Itoa(i+1), i) } _ = file.Save() } @@ -520,12 +589,13 @@ func TestAttrValToFloat(t *testing.T) { func TestSetSheetBackgroundFromBytes(t *testing.T) { f := NewFile() - f.SetSheetName("Sheet1", ".svg") + assert.NoError(t, f.SetSheetName("Sheet1", ".svg")) for i, imageTypes := range []string{".svg", ".emf", ".emz", ".gif", ".jpg", ".png", ".tif", ".wmf", ".wmz"} { file := fmt.Sprintf("excelize%s", imageTypes) if i > 0 { file = filepath.Join("test", "images", fmt.Sprintf("excel%s", imageTypes)) - f.NewSheet(imageTypes) + _, err := f.NewSheet(imageTypes) + assert.NoError(t, err) } img, err := os.Open(file) assert.NoError(t, err) diff --git a/sheetview_test.go b/sheetview_test.go index 8d022a2..b734777 100644 --- a/sheetview_test.go +++ b/sheetview_test.go @@ -28,10 +28,10 @@ func TestSetView(t *testing.T) { opts, err := f.GetSheetView("Sheet1", 0) assert.NoError(t, err) assert.Equal(t, expected, opts) - // Test set sheet view options with invalid view index. + // Test set sheet view options with invalid view index assert.EqualError(t, f.SetSheetView("Sheet1", 1, nil), "view index 1 out of range") assert.EqualError(t, f.SetSheetView("Sheet1", -2, nil), "view index -2 out of range") - // Test set sheet view options on not exists worksheet. + // Test set sheet view options on not exists worksheet assert.EqualError(t, f.SetSheetView("SheetN", 0, nil), "sheet SheetN does not exist") } @@ -39,12 +39,12 @@ func TestGetView(t *testing.T) { f := NewFile() _, err := f.getSheetView("SheetN", 0) assert.EqualError(t, err, "sheet SheetN does not exist") - // Test get sheet view options with invalid view index. + // Test get sheet view options with invalid view index _, err = f.GetSheetView("Sheet1", 1) assert.EqualError(t, err, "view index 1 out of range") _, err = f.GetSheetView("Sheet1", -2) assert.EqualError(t, err, "view index -2 out of range") - // Test get sheet view options on not exists worksheet. + // Test get sheet view options on not exists worksheet _, err = f.GetSheetView("SheetN", 0) assert.EqualError(t, err, "sheet SheetN does not exist") } diff --git a/sparkline_test.go b/sparkline_test.go index c2c1c41..e6bca25 100644 --- a/sparkline_test.go +++ b/sparkline_test.go @@ -9,10 +9,11 @@ import ( ) func TestAddSparkline(t *testing.T) { - f := prepareSparklineDataset() + f, err := prepareSparklineDataset() + assert.NoError(t, err) // Set the columns widths to make the output clearer - style, err := f.NewStyle(`{"font":{"bold":true}}`) + style, err := f.NewStyle(&Style{Font: &Font{Bold: true}}) assert.NoError(t, err) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style)) viewOpts, err := f.GetSheetView("Sheet1", 0) @@ -291,7 +292,7 @@ func TestAppendSparkline(t *testing.T) { assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8") } -func prepareSparklineDataset() *File { +func prepareSparklineDataset() (*File, error) { f := NewFile() sheet2 := [][]int{ {-2, 2, 3, -1, 0}, @@ -307,8 +308,12 @@ func prepareSparklineDataset() *File { {3, -1, 0, -2, 3, 2, 1, 0, 2, 1}, {0, -2, 3, 2, 1, 0, 1, 2, 3, 1}, } - f.NewSheet("Sheet2") - f.NewSheet("Sheet3") + if _, err := f.NewSheet("Sheet2"); err != nil { + return f, err + } + if _, err := f.NewSheet("Sheet3"); err != nil { + return f, err + } for row, data := range sheet2 { if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil { fmt.Println(err) @@ -319,5 +324,5 @@ func prepareSparklineDataset() *File { fmt.Println(err) } } - return f + return f, nil } diff --git a/stream.go b/stream.go index 0209e22..7a17484 100644 --- a/stream.go +++ b/stream.go @@ -134,18 +134,19 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) { // AddTable creates an Excel table for the StreamWriter using the given // cell range and format set. For example, create a table of A1:D5: // -// err := sw.AddTable("A1", "D5", "") +// err := sw.AddTable("A1:D5", nil) // // Create a table of F2:H6 with format set: // -// err := sw.AddTable("F2", "H6", `{ -// "table_name": "table", -// "table_style": "TableStyleMedium2", -// "show_first_column": true, -// "show_last_column": true, -// "show_row_stripes": false, -// "show_column_stripes": true -// }`) +// disable := false +// err := sw.AddTable("F2:H6", &excelize.TableOptions{ +// Name: "table", +// StyleName: "TableStyleMedium2", +// ShowFirstColumn: true, +// ShowLastColumn: true, +// ShowRowStripes: &disable, +// ShowColumnStripes: true, +// }) // // Note that the table must be at least two lines including the header. The // header cells must contain strings and must be unique. @@ -154,13 +155,9 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) { // called after the rows are written but before Flush. // // See File.AddTable for details on the table format. -func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error { - options, err := parseTableOptions(opts) - if err != nil { - return err - } - - coordinates, err := cellRefsToCoordinates(hCell, vCell) +func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error { + options := parseTableOptions(opts) + coordinates, err := rangeRefToCoordinates(reference) if err != nil { return err } @@ -192,7 +189,7 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error { tableID := sw.file.countTables() + 1 - name := options.TableName + name := options.Name if name == "" { name = "Table" + strconv.Itoa(tableID) } @@ -211,10 +208,10 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error { TableColumn: tableColumn, }, TableStyleInfo: &xlsxTableStyleInfo{ - Name: options.TableStyle, + Name: options.StyleName, ShowFirstColumn: options.ShowFirstColumn, ShowLastColumn: options.ShowLastColumn, - ShowRowStripes: options.ShowRowStripes, + ShowRowStripes: *options.ShowRowStripes, ShowColumnStripes: options.ShowColumnStripes, }, } @@ -462,7 +459,7 @@ func (sw *StreamWriter) InsertPageBreak(cell string) error { // SetPanes provides a function to create and remove freeze panes and split // panes by giving panes options for the StreamWriter. Note that you must call // the 'SetPanes' function before the 'SetRow' function. -func (sw *StreamWriter) SetPanes(panes string) error { +func (sw *StreamWriter) SetPanes(panes *Panes) error { if sw.sheetWritten { return ErrStreamSetPanes } diff --git a/stream_test.go b/stream_test.go index 1a63e35..195bdf0 100644 --- a/stream_test.go +++ b/stream_test.go @@ -41,12 +41,12 @@ func TestStreamWriter(t *testing.T) { streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) - // Test max characters in a cell. + // Test max characters in a cell row := make([]interface{}, 1) row[0] = strings.Repeat("c", TotalCellChars+2) assert.NoError(t, streamWriter.SetRow("A1", row)) - // Test leading and ending space(s) character characters in a cell. + // Test leading and ending space(s) character characters in a cell row = make([]interface{}, 1) row[0] = " characters" assert.NoError(t, streamWriter.SetRow("A2", row)) @@ -55,7 +55,7 @@ func TestStreamWriter(t *testing.T) { row[0] = []byte("Word") assert.NoError(t, streamWriter.SetRow("A3", row)) - // Test set cell with style and rich text. + // Test set cell with style and rich text styleID, err := file.NewStyle(&Style{Font: &Font{Color: "#777777"}}) assert.NoError(t, err) assert.NoError(t, streamWriter.SetRow("A4", []interface{}{ @@ -85,14 +85,14 @@ func TestStreamWriter(t *testing.T) { } assert.NoError(t, streamWriter.Flush()) - // Save spreadsheet by the given path. + // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx"))) - // Test set cell column overflow. + // Test set cell column overflow assert.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber) assert.NoError(t, file.Close()) - // Test close temporary file error. + // Test close temporary file error file = NewFile() streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) @@ -114,7 +114,7 @@ func TestStreamWriter(t *testing.T) { assert.NoError(t, streamWriter.rawData.tmp.Close()) assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name())) - // Test create stream writer with unsupported charset. + // Test create stream writer with unsupported charset file = NewFile() file.Sheet.Delete("xl/worksheets/sheet1.xml") file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) @@ -122,7 +122,7 @@ func TestStreamWriter(t *testing.T) { assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") assert.NoError(t, file.Close()) - // Test read cell. + // Test read cell file = NewFile() streamWriter, err = file.NewStreamWriter("Sheet1") assert.NoError(t, err) @@ -132,7 +132,7 @@ func TestStreamWriter(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Data", cellValue) - // Test stream reader for a worksheet with huge amounts of data. + // Test stream reader for a worksheet with huge amounts of data file, err = OpenFile(filepath.Join("test", "TestStreamWriter.xlsx")) assert.NoError(t, err) rows, err := file.Rows("Sheet1") @@ -166,14 +166,24 @@ func TestStreamSetColWidth(t *testing.T) { } func TestStreamSetPanes(t *testing.T) { - file, paneOpts := NewFile(), `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}` + file, paneOpts := NewFile(), &Panes{ + Freeze: true, + Split: false, + XSplit: 1, + YSplit: 0, + TopLeftCell: "B1", + ActivePane: "topRight", + Panes: []PaneOptions{ + {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"}, + }, + } defer func() { assert.NoError(t, file.Close()) }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.SetPanes(paneOpts)) - assert.EqualError(t, streamWriter.SetPanes(""), "unexpected end of JSON input") + assert.EqualError(t, streamWriter.SetPanes(nil), ErrParameterInvalid.Error()) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) assert.ErrorIs(t, streamWriter.SetPanes(paneOpts), ErrStreamSetPanes) } @@ -185,19 +195,20 @@ func TestStreamTable(t *testing.T) { }() streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) - - // Write some rows. We want enough rows to force a temp file (>16MB). + // Test add table without table header + assert.EqualError(t, streamWriter.AddTable("A1:C2", nil), "XML syntax error on line 2: unexpected EOF") + // Write some rows. We want enough rows to force a temp file (>16MB) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"})) row := []interface{}{1, 2, 3} for r := 2; r < 10000; r++ { assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row)) } - // Write a table. - assert.NoError(t, streamWriter.AddTable("A1", "C2", "")) + // Write a table + assert.NoError(t, streamWriter.AddTable("A1:C2", nil)) assert.NoError(t, streamWriter.Flush()) - // Verify the table has names. + // Verify the table has names var table xlsxTable val, ok := file.Pkg.Load("xl/tables/table1.xml") assert.True(t, ok) @@ -206,17 +217,15 @@ func TestStreamTable(t *testing.T) { assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name) assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name) - assert.NoError(t, streamWriter.AddTable("A1", "C1", "")) + assert.NoError(t, streamWriter.AddTable("A1:C1", nil)) - // Test add table with illegal options. - assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string") - // Test add table with illegal cell reference. - assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) - // Test add table with unsupported charset content types. + // Test add table with illegal cell reference + assert.EqualError(t, streamWriter.AddTable("A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, streamWriter.AddTable("A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) + // Test add table with unsupported charset content types file.ContentTypes = nil file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, streamWriter.AddTable("A1", "C2", ""), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, streamWriter.AddTable("A1:C2", nil), "XML syntax error on line 1: invalid UTF-8") } func TestStreamMergeCells(t *testing.T) { @@ -227,10 +236,10 @@ func TestStreamMergeCells(t *testing.T) { streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.NoError(t, streamWriter.MergeCell("A1", "D1")) - // Test merge cells with illegal cell reference. + // Test merge cells with illegal cell reference assert.EqualError(t, streamWriter.MergeCell("A", "D1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.NoError(t, streamWriter.Flush()) - // Save spreadsheet by the given path. + // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamMergeCells.xlsx"))) } @@ -243,7 +252,7 @@ func TestStreamInsertPageBreak(t *testing.T) { assert.NoError(t, err) assert.NoError(t, streamWriter.InsertPageBreak("A1")) assert.NoError(t, streamWriter.Flush()) - // Save spreadsheet by the given path. + // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamInsertPageBreak.xlsx"))) } @@ -270,7 +279,7 @@ func TestStreamMarshalAttrs(t *testing.T) { } func TestStreamSetRow(t *testing.T) { - // Test error exceptions. + // Test error exceptions file := NewFile() defer func() { assert.NoError(t, file.Close()) @@ -278,10 +287,10 @@ func TestStreamSetRow(t *testing.T) { streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - // Test set row with non-ascending row number. + // Test set row with non-ascending row number assert.NoError(t, streamWriter.SetRow("A1", []interface{}{})) assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error()) - // Test set row with unsupported charset workbook. + // Test set row with unsupported charset workbook file.WorkBook = nil file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8") @@ -367,13 +376,13 @@ func TestStreamWriterOutlineLevel(t *testing.T) { streamWriter, err := file.NewStreamWriter("Sheet1") assert.NoError(t, err) - // Test set outlineLevel in row. + // Test set outlineLevel in row assert.NoError(t, streamWriter.SetRow("A1", nil, RowOpts{OutlineLevel: 1})) assert.NoError(t, streamWriter.SetRow("A2", nil, RowOpts{OutlineLevel: 7})) assert.ErrorIs(t, ErrOutlineLevel, streamWriter.SetRow("A3", nil, RowOpts{OutlineLevel: 8})) assert.NoError(t, streamWriter.Flush()) - // Save spreadsheet by the given path. + // Save spreadsheet by the given path assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx"))) file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx")) diff --git a/styles.go b/styles.go index 0f0b560..6eb86e1 100644 --- a/styles.go +++ b/styles.go @@ -13,7 +13,6 @@ package excelize import ( "bytes" - "encoding/json" "encoding/xml" "fmt" "io" @@ -1076,35 +1075,25 @@ func (f *File) sharedStringsWriter() { // parseFormatStyleSet provides a function to parse the format settings of the // cells and conditional formats. -func parseFormatStyleSet(style interface{}) (*Style, error) { - fs := Style{} +func parseFormatStyleSet(style *Style) (*Style, error) { var err error - switch v := style.(type) { - case string: - err = json.Unmarshal([]byte(v), &fs) - case *Style: - fs = *v - default: - err = ErrParameterInvalid - } - if fs.Font != nil { - if len(fs.Font.Family) > MaxFontFamilyLength { - return &fs, ErrFontLength + if style.Font != nil { + if len(style.Font.Family) > MaxFontFamilyLength { + return style, ErrFontLength } - if fs.Font.Size > MaxFontSize { - return &fs, ErrFontSize + if style.Font.Size > MaxFontSize { + return style, ErrFontSize } } - if fs.CustomNumFmt != nil && len(*fs.CustomNumFmt) == 0 { + if style.CustomNumFmt != nil && len(*style.CustomNumFmt) == 0 { err = ErrCustomNumFmt } - return &fs, err + return style, err } -// NewStyle provides a function to create the style for cells by given structure -// pointer or JSON. This function is concurrency safe. Note that -// the 'Font.Color' field uses an RGB color represented in 'RRGGBB' hexadecimal -// notation. +// NewStyle provides a function to create the style for cells by given style +// options. This function is concurrency safe. Note that the 'Font.Color' field +// uses an RGB color represented in 'RRGGBB' hexadecimal notation. // // The following table shows the border types used in 'Border.Type' supported by // excelize: @@ -1983,13 +1972,16 @@ func parseFormatStyleSet(style interface{}) (*Style, error) { // err = f.SetCellStyle("Sheet1", "A6", "A6", style) // // Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 -func (f *File) NewStyle(style interface{}) (int, error) { +func (f *File) NewStyle(style *Style) (int, error) { var ( fs *Style font *xlsxFont err error cellXfsID, fontID, borderID, fillID int ) + if style == nil { + return cellXfsID, err + } fs, err = parseFormatStyleSet(style) if err != nil { return cellXfsID, err @@ -2123,9 +2115,8 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) { // NewConditionalStyle provides a function to create style for conditional // format by given style format. The parameters are the same with the NewStyle -// function. Note that the color field uses RGB color code and only support to -// set font, fills, alignment and borders currently. -func (f *File) NewConditionalStyle(style string) (int, error) { +// function. +func (f *File) NewConditionalStyle(style *Style) (int, error) { s, err := f.stylesReader() if err != nil { return 0, err @@ -2836,51 +2827,51 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // // Type | Parameters // ---------------+------------------------------------ -// cell | criteria -// | value -// | minimum -// | maximum -// date | criteria -// | value -// | minimum -// | maximum -// time_period | criteria -// text | criteria -// | value -// average | criteria +// cell | Criteria +// | Value +// | Minimum +// | Maximum +// date | Criteria +// | Value +// | Minimum +// | Maximum +// time_period | Criteria +// text | Criteria +// | Value +// average | Criteria // duplicate | (none) // unique | (none) -// top | criteria -// | value -// bottom | criteria -// | value +// top | Criteria +// | Value +// bottom | Criteria +// | Value // blanks | (none) // no_blanks | (none) // errors | (none) // no_errors | (none) -// 2_color_scale | min_type -// | max_type -// | min_value -// | max_value -// | min_color -// | max_color -// 3_color_scale | min_type -// | mid_type -// | max_type -// | min_value -// | mid_value -// | max_value -// | min_color -// | mid_color -// | max_color -// data_bar | min_type -// | max_type -// | min_value -// | max_value -// | bar_color -// formula | criteria +// 2_color_scale | MinType +// | MaxType +// | MinValue +// | MaxValue +// | MinColor +// | MaxColor +// 3_color_scale | MinType +// | MidType +// | MaxType +// | MinValue +// | MidValue +// | MaxValue +// | MinColor +// | MidColor +// | MaxColor +// data_bar | MinType +// | MaxType +// | MinValue +// | MaxValue +// | BarColor +// formula | Criteria // -// The criteria parameter is used to set the criteria by which the cell data +// The 'Criteria' parameter is used to set the criteria by which the cell data // will be evaluated. It has no default value. The most common criteria as // applied to {"type":"cell"} are: // @@ -2902,22 +2893,51 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // value: The value is generally used along with the criteria parameter to set // the rule by which the cell data will be evaluated: // -// f.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "D1:D10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "cell", +// Criteria: ">", +// Format: format, +// Value: "6", +// }, +// }, +// ) // // The value property can also be an cell reference: // -// f.SetConditionalFormat("Sheet1", "D1:D10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"$C$1"}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "D1:D10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "cell", +// Criteria: ">", +// Format: format, +// Value: "$C$1", +// }, +// }, +// ) // // type: format - The format parameter is used to specify the format that will // be applied to the cell when the conditional formatting criterion is met. The -// format is created using the NewConditionalStyle() method in the same way as +// format is created using the NewConditionalStyle function in the same way as // cell formats: // -// format, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) +// format, err := f.NewConditionalStyle( +// &excelize.Style{ +// Font: &excelize.Font{Color: "#9A0511"}, +// Fill: excelize.Fill{ +// Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1, +// }, +// }, +// ) // if err != nil { // fmt.Println(err) // } -// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format)) +// err = f.SetConditionalFormat("Sheet1", "D1:D10", +// []excelize.ConditionalFormatOptions{ +// {Type: "cell", Criteria: ">", Format: format, Value: "6"}, +// }, +// ) // // Note: In Excel, a conditional format is superimposed over the existing cell // format and not all cell format properties can be modified. Properties that @@ -2929,19 +2949,50 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // These can be replicated using the following excelize formats: // // // Rose format for bad conditional. -// format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) +// format1, err := f.NewConditionalStyle( +// &excelize.Style{ +// Font: &excelize.Font{Color: "#9A0511"}, +// Fill: excelize.Fill{ +// Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1, +// }, +// }, +// ) // // // Light yellow format for neutral conditional. -// format2, err = f.NewConditionalStyle(`{"font":{"color":"#9B5713"},"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`) +// format2, err := f.NewConditionalStyle( +// &excelize.Style{ +// Font: &excelize.Font{Color: "#9B5713"}, +// Fill: excelize.Fill{ +// Type: "pattern", Color: []string{"#FEEAA0"}, Pattern: 1, +// }, +// }, +// ) // // // Light green format for good conditional. -// format3, err = f.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`) +// format3, err := f.NewConditionalStyle( +// &excelize.Style{ +// Font: &excelize.Font{Color: "#09600B"}, +// Fill: excelize.Fill{ +// Type: "pattern", Color: []string{"#C7EECF"}, Pattern: 1, +// }, +// }, +// ) // // type: minimum - The minimum parameter is used to set the lower limiting value // when the criteria is either "between" or "not between". // // // Highlight cells rules: between... -// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":"between","format":%d,"minimum":"6","maximum":"8"}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "cell", +// Criteria: "between", +// Format: format, +// Minimum: "6", +// Maximum: "8", +// }, +// }, +// ) // // type: maximum - The maximum parameter is used to set the upper limiting value // when the criteria is either "between" or "not between". See the previous @@ -2951,98 +3002,184 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error { // conditional format: // // // Top/Bottom rules: Above Average... -// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": true}]`, format1)) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "average", +// Criteria: "=", +// Format: format1, +// AboveAverage: true, +// }, +// }, +// ) // // // Top/Bottom rules: Below Average... -// f.SetConditionalFormat("Sheet1", "B1:B10", fmt.Sprintf(`[{"type":"average","criteria":"=","format":%d, "above_average": false}]`, format2)) +// err := f.SetConditionalFormat("Sheet1", "B1:B10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "average", +// Criteria: "=", +// Format: format2, +// AboveAverage: false, +// }, +// }, +// ) // // type: duplicate - The duplicate type is used to highlight duplicate cells in a range: // // // Highlight cells rules: Duplicate Values... -// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"duplicate","criteria":"=","format":%d}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// {Type: "duplicate", Criteria: "=", Format: format}, +// }, +// ) // // type: unique - The unique type is used to highlight unique cells in a range: // // // Highlight cells rules: Not Equal To... -// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"unique","criteria":"=","format":%d}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// {Type: "unique", Criteria: "=", Format: format}, +// }, +// ) // // type: top - The top type is used to specify the top n values by number or percentage in a range: // // // Top/Bottom rules: Top 10. -// f.SetConditionalFormat("Sheet1", "H1:H10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6"}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "H1:H10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "top", +// Criteria: "=", +// Format: format, +// Value: "6", +// }, +// }, +// ) // // The criteria can be used to indicate that a percentage condition is required: // -// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"top","criteria":"=","format":%d,"value":"6","percent":true}]`, format)) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "top", +// Criteria: "=", +// Format: format, +// Value: "6", +// Percent: true, +// }, +// }, +// ) // // type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2 // Color Scale" style conditional format: // // // Color scales: 2 color. -// f.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"2_color_scale","criteria":"=","min_type":"min","max_type":"max","min_color":"#F8696B","max_color":"#63BE7B"}]`) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "2_color_scale", +// Criteria: "=", +// MinType: "min", +// MaxType: "max", +// MinColor: "#F8696B", +// MaxColor: "#63BE7B", +// }, +// }, +// ) // -// This conditional type can be modified with min_type, max_type, min_value, -// max_value, min_color and max_color, see below. +// This conditional type can be modified with MinType, MaxType, MinValue, +// MaxValue, MinColor and MaxColor, see below. // // type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3 // Color Scale" style conditional format: // // // Color scales: 3 color. -// f.SetConditionalFormat("Sheet1", "A1:A10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`) +// err := f.SetConditionalFormat("Sheet1", "A1:A10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "3_color_scale", +// Criteria: "=", +// MinType: "min", +// MidType: "percentile", +// MaxType: "max", +// MinColor: "#F8696B", +// MidColor: "#FFEB84", +// MaxColor: "#63BE7B", +// }, +// }, +// ) // -// This conditional type can be modified with min_type, mid_type, max_type, -// min_value, mid_value, max_value, min_color, mid_color and max_color, see +// This conditional type can be modified with MinType, MidType, MaxType, +// MinValue, MidValue, MaxValue, MinColor, MidColor and MaxColor, see // below. // // type: data_bar - The data_bar type is used to specify Excel's "Data Bar" // style conditional format. // -// min_type - The min_type and max_type properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The mid_type is available for 3_color_scale. The properties are used as follows: +// MinType - The MinType and MaxType properties are available when the conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The MidType is available for 3_color_scale. The properties are used as follows: // // // Data Bars: Gradient Fill. -// f.SetConditionalFormat("Sheet1", "K1:K10", `[{"type":"data_bar", "criteria":"=", "min_type":"min","max_type":"max","bar_color":"#638EC6"}]`) +// err := f.SetConditionalFormat("Sheet1", "K1:K10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "data_bar", +// Criteria: "=", +// MinType: "min", +// MaxType: "max", +// BarColor: "#638EC6", +// }, +// }, +// ) // // The available min/mid/max types are: // -// min (for min_type only) +// min (for MinType only) // num // percent // percentile // formula -// max (for max_type only) +// max (for MaxType only) // -// mid_type - Used for 3_color_scale. Same as min_type, see above. +// MidType - Used for 3_color_scale. Same as MinType, see above. // -// max_type - Same as min_type, see above. +// MaxType - Same as MinType, see above. // -// min_value - The min_value and max_value properties are available when the -// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. The -// mid_value is available for 3_color_scale. -// -// mid_value - Used for 3_color_scale. Same as min_value, see above. -// -// max_value - Same as min_value, see above. -// -// min_color - The min_color and max_color properties are available when the +// MinValue - The MinValue and MaxValue properties are available when the // conditional formatting type is 2_color_scale, 3_color_scale or data_bar. -// The mid_color is available for 3_color_scale. The properties are used as -// follows: +// +// MidValue - The MidValue is available for 3_color_scale. Same as MinValue, +// see above. +// +// MaxValue - Same as MinValue, see above. +// +// MinColor - The MinColor and MaxColor properties are available when the +// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. +// +// MidColor - The MidColor is available for 3_color_scale. The properties +// are used as follows: // // // Color scales: 3 color. -// f.SetConditionalFormat("Sheet1", "B1:B10", `[{"type":"3_color_scale","criteria":"=","min_type":"min","mid_type":"percentile","max_type":"max","min_color":"#F8696B","mid_color":"#FFEB84","max_color":"#63BE7B"}]`) +// err := f.SetConditionalFormat("Sheet1", "B1:B10", +// []excelize.ConditionalFormatOptions{ +// { +// Type: "3_color_scale", +// Criteria: "=", +// MinType: "min", +// MidType: "percentile", +// MaxType: "max", +// MinColor: "#F8696B", +// MidColor: "#FFEB84", +// MaxColor: "#63BE7B", +// }, +// }, +// ) // -// mid_color - Used for 3_color_scale. Same as min_color, see above. +// MaxColor - Same as MinColor, see above. // -// max_color - Same as min_color, see above. -// -// bar_color - Used for data_bar. Same as min_color, see above. -func (f *File) SetConditionalFormat(sheet, reference, opts string) error { - var format []*conditionalOptions - err := json.Unmarshal([]byte(opts), &format) - if err != nil { - return err - } - drawContFmtFunc := map[string]func(p int, ct string, fmtCond *conditionalOptions) *xlsxCfRule{ +// BarColor - Used for data_bar. Same as MinColor, see above. +func (f *File) SetConditionalFormat(sheet, reference string, opts []ConditionalFormatOptions) error { + drawContFmtFunc := map[string]func(p int, ct string, fmtCond *ConditionalFormatOptions) *xlsxCfRule{ "cellIs": drawCondFmtCellIs, "top10": drawCondFmtTop10, "aboveAverage": drawCondFmtAboveAverage, @@ -3059,7 +3196,7 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error { return err } var cfRule []*xlsxCfRule - for p, v := range format { + for p, v := range opts { var vt, ct string var ok bool // "type" is a required parameter, check for valid validation types. @@ -3070,7 +3207,7 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error { if ok || vt == "expression" { drawFunc, ok := drawContFmtFunc[vt] if ok { - cfRule = append(cfRule, drawFunc(p, ct, v)) + cfRule = append(cfRule, drawFunc(p, ct, &v)) } } } @@ -3086,21 +3223,21 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error { // extractCondFmtCellIs provides a function to extract conditional format // settings for cell value (include between, not between, equal, not equal, // greater than and less than) by given conditional formatting rule. -func extractCondFmtCellIs(c *xlsxCfRule) *conditionalOptions { - format := conditionalOptions{Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID} +func extractCondFmtCellIs(c *xlsxCfRule) ConditionalFormatOptions { + format := ConditionalFormatOptions{Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID} if len(c.Formula) == 2 { format.Minimum, format.Maximum = c.Formula[0], c.Formula[1] - return &format + return format } format.Value = c.Formula[0] - return &format + return format } // extractCondFmtTop10 provides a function to extract conditional format // settings for top N (default is top 10) by given conditional formatting // rule. -func extractCondFmtTop10(c *xlsxCfRule) *conditionalOptions { - format := conditionalOptions{ +func extractCondFmtTop10(c *xlsxCfRule) ConditionalFormatOptions { + format := ConditionalFormatOptions{ Type: "top", Criteria: "=", Format: *c.DxfID, @@ -3110,14 +3247,14 @@ func extractCondFmtTop10(c *xlsxCfRule) *conditionalOptions { if c.Bottom { format.Type = "bottom" } - return &format + return format } // extractCondFmtAboveAverage provides a function to extract conditional format // settings for above average and below average by given conditional formatting // rule. -func extractCondFmtAboveAverage(c *xlsxCfRule) *conditionalOptions { - return &conditionalOptions{ +func extractCondFmtAboveAverage(c *xlsxCfRule) ConditionalFormatOptions { + return ConditionalFormatOptions{ Type: "average", Criteria: "=", Format: *c.DxfID, @@ -3128,8 +3265,8 @@ func extractCondFmtAboveAverage(c *xlsxCfRule) *conditionalOptions { // extractCondFmtDuplicateUniqueValues provides a function to extract // conditional format settings for duplicate and unique values by given // conditional formatting rule. -func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) *conditionalOptions { - return &conditionalOptions{ +func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) ConditionalFormatOptions { + return ConditionalFormatOptions{ Type: map[string]string{ "duplicateValues": "duplicate", "uniqueValues": "unique", @@ -3142,8 +3279,8 @@ func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) *conditionalOptions { // extractCondFmtColorScale provides a function to extract conditional format // settings for color scale (include 2 color scale and 3 color scale) by given // conditional formatting rule. -func extractCondFmtColorScale(c *xlsxCfRule) *conditionalOptions { - var format conditionalOptions +func extractCondFmtColorScale(c *xlsxCfRule) ConditionalFormatOptions { + var format ConditionalFormatOptions format.Type, format.Criteria = "2_color_scale", "=" values := len(c.ColorScale.Cfvo) colors := len(c.ColorScale.Color) @@ -3172,35 +3309,35 @@ func extractCondFmtColorScale(c *xlsxCfRule) *conditionalOptions { } format.MaxColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[2].RGB), "FF") } - return &format + return format } // extractCondFmtDataBar provides a function to extract conditional format // settings for data bar by given conditional formatting rule. -func extractCondFmtDataBar(c *xlsxCfRule) *conditionalOptions { - format := conditionalOptions{Type: "data_bar", Criteria: "="} +func extractCondFmtDataBar(c *xlsxCfRule) ConditionalFormatOptions { + format := ConditionalFormatOptions{Type: "data_bar", Criteria: "="} if c.DataBar != nil { format.MinType = c.DataBar.Cfvo[0].Type format.MaxType = c.DataBar.Cfvo[1].Type format.BarColor = "#" + strings.TrimPrefix(strings.ToUpper(c.DataBar.Color[0].RGB), "FF") } - return &format + return format } // extractCondFmtExp provides a function to extract conditional format settings // for expression by given conditional formatting rule. -func extractCondFmtExp(c *xlsxCfRule) *conditionalOptions { - format := conditionalOptions{Type: "formula", Format: *c.DxfID} +func extractCondFmtExp(c *xlsxCfRule) ConditionalFormatOptions { + format := ConditionalFormatOptions{Type: "formula", Format: *c.DxfID} if len(c.Formula) > 0 { format.Criteria = c.Formula[0] } - return &format + return format } // GetConditionalFormats returns conditional format settings by given worksheet // name. -func (f *File) GetConditionalFormats(sheet string) (map[string]string, error) { - extractContFmtFunc := map[string]func(c *xlsxCfRule) *conditionalOptions{ +func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) { + extractContFmtFunc := map[string]func(c *xlsxCfRule) ConditionalFormatOptions{ "cellIs": extractCondFmtCellIs, "top10": extractCondFmtTop10, "aboveAverage": extractCondFmtAboveAverage, @@ -3211,20 +3348,19 @@ func (f *File) GetConditionalFormats(sheet string) (map[string]string, error) { "expression": extractCondFmtExp, } - conditionalFormats := make(map[string]string) + conditionalFormats := make(map[string][]ConditionalFormatOptions) ws, err := f.workSheetReader(sheet) if err != nil { return conditionalFormats, err } for _, cf := range ws.ConditionalFormatting { - var opts []*conditionalOptions + var opts []ConditionalFormatOptions for _, cr := range cf.CfRule { if extractFunc, ok := extractContFmtFunc[cr.Type]; ok { opts = append(opts, extractFunc(cr)) } } - options, _ := json.Marshal(opts) - conditionalFormats[cf.SQRef] = string(options) + conditionalFormats[cf.SQRef] = opts } return conditionalFormats, err } @@ -3248,7 +3384,7 @@ func (f *File) UnsetConditionalFormat(sheet, reference string) error { // drawCondFmtCellIs provides a function to create conditional formatting rule // for cell value (include between, not between, equal, not equal, greater // than and less than) by given priority, criteria type and format settings. -func drawCondFmtCellIs(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtCellIs(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { c := &xlsxCfRule{ Priority: p + 1, Type: validType[format.Type], @@ -3268,7 +3404,7 @@ func drawCondFmtCellIs(p int, ct string, format *conditionalOptions) *xlsxCfRule // drawCondFmtTop10 provides a function to create conditional formatting rule // for top N (default is top 10) by given priority, criteria type and format // settings. -func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtTop10(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { c := &xlsxCfRule{ Priority: p + 1, Bottom: format.Type == "bottom", @@ -3286,7 +3422,7 @@ func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule // drawCondFmtAboveAverage provides a function to create conditional // formatting rule for above average and below average by given priority, // criteria type and format settings. -func drawCondFmtAboveAverage(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtAboveAverage(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { return &xlsxCfRule{ Priority: p + 1, Type: validType[format.Type], @@ -3298,7 +3434,7 @@ func drawCondFmtAboveAverage(p int, ct string, format *conditionalOptions) *xlsx // drawCondFmtDuplicateUniqueValues provides a function to create conditional // formatting rule for duplicate and unique values by given priority, criteria // type and format settings. -func drawCondFmtDuplicateUniqueValues(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtDuplicateUniqueValues(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { return &xlsxCfRule{ Priority: p + 1, Type: validType[format.Type], @@ -3309,7 +3445,7 @@ func drawCondFmtDuplicateUniqueValues(p int, ct string, format *conditionalOptio // drawCondFmtColorScale provides a function to create conditional formatting // rule for color scale (include 2 color scale and 3 color scale) by given // priority, criteria type and format settings. -func drawCondFmtColorScale(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtColorScale(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { minValue := format.MinValue if minValue == "" { minValue = "0" @@ -3346,7 +3482,7 @@ func drawCondFmtColorScale(p int, ct string, format *conditionalOptions) *xlsxCf // drawCondFmtDataBar provides a function to create conditional formatting // rule for data bar by given priority, criteria type and format settings. -func drawCondFmtDataBar(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtDataBar(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { return &xlsxCfRule{ Priority: p + 1, Type: validType[format.Type], @@ -3359,7 +3495,7 @@ func drawCondFmtDataBar(p int, ct string, format *conditionalOptions) *xlsxCfRul // drawCondFmtExp provides a function to create conditional formatting rule // for expression by given priority, criteria type and format settings. -func drawCondFmtExp(p int, ct string, format *conditionalOptions) *xlsxCfRule { +func drawCondFmtExp(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule { return &xlsxCfRule{ Priority: p + 1, Type: validType[format.Type], diff --git a/styles_test.go b/styles_test.go index 0d216b0..44ba535 100644 --- a/styles_test.go +++ b/styles_test.go @@ -1,7 +1,6 @@ package excelize import ( - "fmt" "math" "path/filepath" "strings" @@ -13,15 +12,15 @@ import ( func TestStyleFill(t *testing.T) { cases := []struct { label string - format string + format *Style expectFill bool }{{ label: "no_fill", - format: `{"alignment":{"wrap_text":true}}`, + format: &Style{Alignment: &Alignment{WrapText: true}}, expectFill: false, }, { label: "fill", - format: `{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`, + format: &Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}}, expectFill: true, }} @@ -40,9 +39,9 @@ func TestStyleFill(t *testing.T) { } } f := NewFile() - styleID1, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`) + styleID1, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}}) assert.NoError(t, err) - styleID2, err := f.NewStyle(`{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`) + styleID2, err := f.NewStyle(&Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}}) assert.NoError(t, err) assert.Equal(t, styleID1, styleID2) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx"))) @@ -51,23 +50,23 @@ func TestStyleFill(t *testing.T) { func TestSetConditionalFormat(t *testing.T) { cases := []struct { label string - format string + format []ConditionalFormatOptions rules []*xlsxCfRule }{{ label: "3_color_scale", - format: `[{ - "type":"3_color_scale", - "criteria":"=", - "min_type":"num", - "mid_type":"num", - "max_type":"num", - "min_value": "-10", - "mid_value": "0", - "max_value": "10", - "min_color":"ff0000", - "mid_color":"00ff00", - "max_color":"0000ff" - }]`, + format: []ConditionalFormatOptions{{ + Type: "3_color_scale", + Criteria: "=", + MinType: "num", + MidType: "num", + MaxType: "num", + MinValue: "-10", + MidValue: "0", + MaxValue: "10", + MinColor: "ff0000", + MidColor: "00ff00", + MaxColor: "0000ff", + }}, rules: []*xlsxCfRule{{ Priority: 1, Type: "colorScale", @@ -93,16 +92,16 @@ func TestSetConditionalFormat(t *testing.T) { }}, }, { label: "3_color_scale default min/mid/max", - format: `[{ - "type":"3_color_scale", - "criteria":"=", - "min_type":"num", - "mid_type":"num", - "max_type":"num", - "min_color":"ff0000", - "mid_color":"00ff00", - "max_color":"0000ff" - }]`, + format: []ConditionalFormatOptions{{ + Type: "3_color_scale", + Criteria: "=", + MinType: "num", + MidType: "num", + MaxType: "num", + MinColor: "ff0000", + MidColor: "00ff00", + MaxColor: "0000ff", + }}, rules: []*xlsxCfRule{{ Priority: 1, Type: "colorScale", @@ -128,14 +127,14 @@ func TestSetConditionalFormat(t *testing.T) { }}, }, { label: "2_color_scale default min/max", - format: `[{ - "type":"2_color_scale", - "criteria":"=", - "min_type":"num", - "max_type":"num", - "min_color":"ff0000", - "max_color":"0000ff" - }]`, + format: []ConditionalFormatOptions{{ + Type: "2_color_scale", + Criteria: "=", + MinType: "num", + MaxType: "num", + MinColor: "ff0000", + MaxColor: "0000ff", + }}, rules: []*xlsxCfRule{{ Priority: 1, Type: "colorScale", @@ -177,18 +176,18 @@ func TestSetConditionalFormat(t *testing.T) { } func TestGetConditionalFormats(t *testing.T) { - for _, format := range []string{ - `[{"type":"cell","format":1,"criteria":"greater than","value":"6"}]`, - `[{"type":"cell","format":1,"criteria":"between","minimum":"6","maximum":"8"}]`, - `[{"type":"top","format":1,"criteria":"=","value":"6"}]`, - `[{"type":"bottom","format":1,"criteria":"=","value":"6"}]`, - `[{"type":"average","above_average":true,"format":1,"criteria":"="}]`, - `[{"type":"duplicate","format":1,"criteria":"="}]`, - `[{"type":"unique","format":1,"criteria":"="}]`, - `[{"type":"3_color_scale","criteria":"=","min_type":"num","mid_type":"num","max_type":"num","min_value":"-10","mid_value":"50","max_value":"10","min_color":"#FF0000","mid_color":"#00FF00","max_color":"#0000FF"}]`, - `[{"type":"2_color_scale","criteria":"=","min_type":"num","max_type":"num","min_color":"#FF0000","max_color":"#0000FF"}]`, - `[{"type":"data_bar","criteria":"=","min_type":"min","max_type":"max","bar_color":"#638EC6"}]`, - `[{"type":"formula","format":1,"criteria":"="}]`, + for _, format := range [][]ConditionalFormatOptions{ + {{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}}, + {{Type: "cell", Format: 1, Criteria: "between", Minimum: "6", Maximum: "8"}}, + {{Type: "top", Format: 1, Criteria: "=", Value: "6"}}, + {{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}}, + {{Type: "average", AboveAverage: true, Format: 1, Criteria: "="}}, + {{Type: "duplicate", Format: 1, Criteria: "="}}, + {{Type: "unique", Format: 1, Criteria: "="}}, + {{Type: "3_color_scale", Criteria: "=", MinType: "num", MidType: "num", MaxType: "num", MinValue: "-10", MidValue: "50", MaxValue: "10", MinColor: "#FF0000", MidColor: "#00FF00", MaxColor: "#0000FF"}}, + {{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}}, + {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6"}}, + {{Type: "formula", Format: 1, Criteria: "="}}, } { f := NewFile() err := f.SetConditionalFormat("Sheet1", "A1:A2", format) @@ -210,9 +209,9 @@ func TestUnsetConditionalFormat(t *testing.T) { f := NewFile() assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7)) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10")) - format, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) + format, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}}) assert.NoError(t, err) - assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format))) + assert.NoError(t, f.SetConditionalFormat("Sheet1", "A1:A10", []ConditionalFormatOptions{{Type: "cell", Criteria: ">", Format: format, Value: "6"}})) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10")) // Test unset conditional format on not exists worksheet assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist") @@ -224,7 +223,7 @@ func TestUnsetConditionalFormat(t *testing.T) { func TestNewStyle(t *testing.T) { f := NewFile() - styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`) + styleID, err := f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "#777777"}}) assert.NoError(t, err) styles, err := f.stylesReader() assert.NoError(t, err) @@ -234,8 +233,8 @@ func TestNewStyle(t *testing.T) { assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles") _, err = f.NewStyle(&Style{}) assert.NoError(t, err) - _, err = f.NewStyle(Style{}) - assert.EqualError(t, err, ErrParameterInvalid.Error()) + _, err = f.NewStyle(nil) + assert.NoError(t, err) var exp string _, err = f.NewStyle(&Style{CustomNumFmt: &exp}) @@ -326,7 +325,7 @@ func TestNewConditionalStyle(t *testing.T) { // Test create conditional style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) - _, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) + _, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}}) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } @@ -378,13 +377,13 @@ func TestThemeReader(t *testing.T) { func TestSetCellStyle(t *testing.T) { f := NewFile() - // Test set cell style on not exists worksheet. + // Test set cell style on not exists worksheet assert.EqualError(t, f.SetCellStyle("SheetN", "A1", "A2", 1), "sheet SheetN does not exist") - // Test set cell style with invalid style ID. + // Test set cell style with invalid style ID assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", -1), newInvalidStyleID(-1).Error()) - // Test set cell style with not exists style ID. + // Test set cell style with not exists style ID assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 10), newInvalidStyleID(10).Error()) - // Test set cell style with unsupported charset style sheet. + // Test set cell style with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8") @@ -395,7 +394,7 @@ func TestGetStyleID(t *testing.T) { styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil) assert.NoError(t, err) assert.Equal(t, -1, styleID) - // Test get style ID with unsupported charset style sheet. + // Test get style ID with unsupported charset style sheet f.Styles = nil f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.getStyleID(&xlsxStyleSheet{ @@ -429,11 +428,11 @@ func TestThemeColor(t *testing.T) { func TestGetNumFmtID(t *testing.T) { f := NewFile() - fs1, err := parseFormatStyleSet(`{"protection":{"hidden":false,"locked":false},"number_format":10}`) + fs1, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 10}) assert.NoError(t, err) id1 := getNumFmtID(&xlsxStyleSheet{}, fs1) - fs2, err := parseFormatStyleSet(`{"protection":{"hidden":false,"locked":false},"number_format":0}`) + fs2, err := parseFormatStyleSet(&Style{Protection: &Protection{Hidden: false, Locked: false}, NumFmt: 0}) assert.NoError(t, err) id2 := getNumFmtID(&xlsxStyleSheet{}, fs2) diff --git a/table.go b/table.go index 90cc97f..42aa35a 100644 --- a/table.go +++ b/table.go @@ -12,7 +12,6 @@ package excelize import ( - "encoding/json" "encoding/xml" "fmt" "regexp" @@ -22,64 +21,54 @@ import ( // parseTableOptions provides a function to parse the format settings of the // table with default value. -func parseTableOptions(opts string) (*tableOptions, error) { - options := tableOptions{ShowRowStripes: true} - err := json.Unmarshal(fallbackOptions(opts), &options) - return &options, err +func parseTableOptions(opts *TableOptions) *TableOptions { + if opts == nil { + return &TableOptions{ShowRowStripes: boolPtr(true)} + } + if opts.ShowRowStripes == nil { + opts.ShowRowStripes = boolPtr(true) + } + return opts } // AddTable provides the method to add table in a worksheet by given worksheet // name, range reference and format set. For example, create a table of A1:D5 // on Sheet1: // -// err := f.AddTable("Sheet1", "A1", "D5", "") +// err := f.AddTable("Sheet1", "A1:D5", nil) // // Create a table of F2:H6 on Sheet2 with format set: // -// err := f.AddTable("Sheet2", "F2", "H6", `{ -// "table_name": "table", -// "table_style": "TableStyleMedium2", -// "show_first_column": true, -// "show_last_column": true, -// "show_row_stripes": false, -// "show_column_stripes": true -// }`) +// err := f.AddTable("Sheet2", "F2:H6", &excelize.TableOptions{ +// Name: "table", +// StyleName: "TableStyleMedium2", +// ShowFirstColumn: true, +// ShowLastColumn: true, +// ShowRowStripes: &disable, +// ShowColumnStripes: true, +// }) // // Note that the table must be at least two lines including the header. The // header cells must contain strings and must be unique, and must set the // header row data of the table before calling the AddTable function. Multiple // tables range reference that can't have an intersection. // -// table_name: The name of the table, in the same worksheet name of the table should be unique +// Name: The name of the table, in the same worksheet name of the table should be unique // -// table_style: The built-in table style names +// StyleName: The built-in table style names // // TableStyleLight1 - TableStyleLight21 // TableStyleMedium1 - TableStyleMedium28 // TableStyleDark1 - TableStyleDark11 -func (f *File) AddTable(sheet, hCell, vCell, opts string) error { - options, err := parseTableOptions(opts) - if err != nil { - return err - } +func (f *File) AddTable(sheet, reference string, opts *TableOptions) error { + options := parseTableOptions(opts) // Coordinate conversion, convert C1:B3 to 2,0,1,2. - hCol, hRow, err := CellNameToCoordinates(hCell) + coordinates, err := rangeRefToCoordinates(reference) if err != nil { return err } - vCol, vRow, err := CellNameToCoordinates(vCell) - if err != nil { - return err - } - - if vCol < hCol { - vCol, hCol = hCol, vCol - } - - if vRow < hRow { - vRow, hRow = hRow, vRow - } - + // Correct table reference range, such correct C1:B3 to B1:C3. + _ = sortCoordinates(coordinates) tableID := f.countTables() + 1 sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml" tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl") @@ -91,7 +80,7 @@ func (f *File) AddTable(sheet, hCell, vCell, opts string) error { return err } f.addSheetNameSpace(sheet, SourceRelationship) - if err = f.addTable(sheet, tableXML, hCol, hRow, vCol, vRow, tableID, options); err != nil { + if err = f.addTable(sheet, tableXML, coordinates[0], coordinates[1], coordinates[2], coordinates[3], tableID, options); err != nil { return err } return f.addContentTypePart(tableID, "table") @@ -159,7 +148,7 @@ func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn, // addTable provides a function to add table by given worksheet name, // range reference and format set. -func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tableOptions) error { +func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *TableOptions) error { // Correct the minimum number of rows, the table at least two lines. if y1 == y2 { y2++ @@ -171,7 +160,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab return err } tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2) - name := opts.TableName + name := opts.Name if name == "" { name = "Table" + strconv.Itoa(i) } @@ -189,10 +178,10 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab TableColumn: tableColumns, }, TableStyleInfo: &xlsxTableStyleInfo{ - Name: opts.TableStyle, + Name: opts.StyleName, ShowFirstColumn: opts.ShowFirstColumn, ShowLastColumn: opts.ShowLastColumn, - ShowRowStripes: opts.ShowRowStripes, + ShowRowStripes: *opts.ShowRowStripes, ShowColumnStripes: opts.ShowColumnStripes, }, } @@ -201,36 +190,30 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab return nil } -// parseAutoFilterOptions provides a function to parse the settings of the auto -// filter. -func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) { - options := autoFilterOptions{} - err := json.Unmarshal([]byte(opts), &options) - return &options, err -} - // AutoFilter provides the method to add auto filter in a worksheet by given // worksheet name, range reference and settings. An auto filter in Excel is a // way of filtering a 2D range of data based on some simple criteria. For // example applying an auto filter to a cell range A1:D4 in the Sheet1: // -// err := f.AutoFilter("Sheet1", "A1", "D4", "") +// err := f.AutoFilter("Sheet1", "A1:D4", nil) // // Filter data in an auto filter: // -// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`) +// err := f.AutoFilter("Sheet1", "A1:D4", &excelize.AutoFilterOptions{ +// Column: "B", Expression: "x != blanks", +// }) // -// column defines the filter columns in an auto filter range based on simple +// Column defines the filter columns in an auto filter range based on simple // criteria // // It isn't sufficient to just specify the filter condition. You must also // hide any rows that don't match the filter condition. Rows are hidden using -// the SetRowVisible() method. Excelize can't filter rows automatically since +// the SetRowVisible function. Excelize can't filter rows automatically since // this isn't part of the file format. // // Setting a filter criteria for a column: // -// expression defines the conditions, the following operators are available +// Expression defines the conditions, the following operators are available // for setting the filter criteria: // // == @@ -278,28 +261,15 @@ func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) { // x < 2000 // col < 2000 // Price < 2000 -func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error { - hCol, hRow, err := CellNameToCoordinates(hCell) +func (f *File) AutoFilter(sheet, reference string, opts *AutoFilterOptions) error { + coordinates, err := rangeRefToCoordinates(reference) if err != nil { return err } - vCol, vRow, err := CellNameToCoordinates(vCell) - if err != nil { - return err - } - - if vCol < hCol { - vCol, hCol = hCol, vCol - } - - if vRow < hRow { - vRow, hRow = hRow, vRow - } - - options, _ := parseAutoFilterOptions(opts) - cellStart, _ := CoordinatesToCellName(hCol, hRow, true) - cellEnd, _ := CoordinatesToCellName(vCol, vRow, true) - ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase" + _ = sortCoordinates(coordinates) + // Correct reference range, such correct C1:B3 to B1:C3. + ref, _ := f.coordinatesToRangeRef(coordinates, true) + filterDB := "_xlnm._FilterDatabase" wb, err := f.workbookReader() if err != nil { return err @@ -332,13 +302,13 @@ func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error { wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) } } - refRange := vCol - hCol - return f.autoFilter(sheet, ref, refRange, hCol, options) + refRange := coordinates[2] - coordinates[0] + return f.autoFilter(sheet, ref, refRange, coordinates[0], opts) } // autoFilter provides a function to extract the tokens from the filter // expression. The tokens are mainly non-whitespace groups. -func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *autoFilterOptions) error { +func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *AutoFilterOptions) error { ws, err := f.workSheetReader(sheet) if err != nil { return err @@ -351,7 +321,7 @@ func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *autoFilter Ref: ref, } ws.AutoFilter = filter - if opts.Column == "" || opts.Expression == "" { + if opts == nil || opts.Column == "" || opts.Expression == "" { return nil } diff --git a/table_test.go b/table_test.go index d26d20c..1e1afae 100644 --- a/table_test.go +++ b/table_test.go @@ -11,22 +11,28 @@ import ( func TestAddTable(t *testing.T) { f, err := prepareTestBook1() assert.NoError(t, err) - assert.NoError(t, f.AddTable("Sheet1", "B26", "A21", `{}`)) - assert.NoError(t, f.AddTable("Sheet2", "A2", "B5", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)) - assert.NoError(t, f.AddTable("Sheet2", "F1", "F1", `{"table_style":"TableStyleMedium8"}`)) + assert.NoError(t, f.AddTable("Sheet1", "B26:A21", nil)) + assert.NoError(t, f.AddTable("Sheet2", "A2:B5", &TableOptions{ + Name: "table", + StyleName: "TableStyleMedium2", + ShowFirstColumn: true, + ShowLastColumn: true, + ShowRowStripes: boolPtr(true), + ShowColumnStripes: true, + }, + )) + assert.NoError(t, f.AddTable("Sheet2", "F1:F1", &TableOptions{StyleName: "TableStyleMedium8"})) // Test add table in not exist worksheet - assert.EqualError(t, f.AddTable("SheetN", "B26", "A21", `{}`), "sheet SheetN does not exist") - // Test add table with illegal options - assert.EqualError(t, f.AddTable("Sheet1", "B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string") + assert.EqualError(t, f.AddTable("SheetN", "B26:A21", nil), "sheet SheetN does not exist") // Test add table with illegal cell reference - assert.EqualError(t, f.AddTable("Sheet1", "A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - assert.EqualError(t, f.AddTable("Sheet1", "A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) + assert.EqualError(t, f.AddTable("Sheet1", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.AddTable("Sheet1", "A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx"))) // Test add table with invalid sheet name - assert.EqualError(t, f.AddTable("Sheet:1", "B26", "A21", `{}`), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddTable("Sheet:1", "B26:A21", nil), ErrSheetNameInvalid.Error()) // Test addTable with illegal cell reference f = NewFile() assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell reference [0, 0]") @@ -43,73 +49,66 @@ func TestAutoFilter(t *testing.T) { outFile := filepath.Join("test", "TestAutoFilter%d.xlsx") f, err := prepareTestBook1() assert.NoError(t, err) - formats := []string{ - ``, - `{"column":"B","expression":"x != blanks"}`, - `{"column":"B","expression":"x == blanks"}`, - `{"column":"B","expression":"x != nonblanks"}`, - `{"column":"B","expression":"x == nonblanks"}`, - `{"column":"B","expression":"x <= 1 and x >= 2"}`, - `{"column":"B","expression":"x == 1 or x == 2"}`, - `{"column":"B","expression":"x == 1 or x == 2*"}`, - } - for i, format := range formats { + for i, opts := range []*AutoFilterOptions{ + nil, + {Column: "B", Expression: ""}, + {Column: "B", Expression: "x != blanks"}, + {Column: "B", Expression: "x == blanks"}, + {Column: "B", Expression: "x != nonblanks"}, + {Column: "B", Expression: "x == nonblanks"}, + {Column: "B", Expression: "x <= 1 and x >= 2"}, + {Column: "B", Expression: "x == 1 or x == 2"}, + {Column: "B", Expression: "x == 1 or x == 2*"}, + } { t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) { - err = f.AutoFilter("Sheet1", "D4", "B1", format) - assert.NoError(t, err) + assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts)) assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1))) }) } // Test add auto filter with invalid sheet name - assert.EqualError(t, f.AutoFilter("Sheet:1", "A1", "B1", ""), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AutoFilter("Sheet:1", "A1:B1", nil), ErrSheetNameInvalid.Error()) // Test add auto filter with illegal cell reference - assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) - assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) + assert.EqualError(t, f.AutoFilter("Sheet1", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) + assert.EqualError(t, f.AutoFilter("Sheet1", "A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) // Test add auto filter with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) - assert.EqualError(t, f.AutoFilter("Sheet1", "D4", "B1", formats[0]), "XML syntax error on line 1: invalid UTF-8") + assert.EqualError(t, f.AutoFilter("Sheet1", "D4:B1", nil), "XML syntax error on line 1: invalid UTF-8") } func TestAutoFilterError(t *testing.T) { outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx") - f, err := prepareTestBook1() - if !assert.NoError(t, err) { - t.FailNow() - } - - formats := []string{ - `{"column":"B","expression":"x <= 1 and x >= blanks"}`, - `{"column":"B","expression":"x -- y or x == *2*"}`, - `{"column":"B","expression":"x != y or x ? *2"}`, - `{"column":"B","expression":"x -- y o r x == *2"}`, - `{"column":"B","expression":"x -- y"}`, - `{"column":"A","expression":"x -- y"}`, - } - for i, format := range formats { + assert.NoError(t, err) + for i, opts := range []*AutoFilterOptions{ + {Column: "B", Expression: "x <= 1 and x >= blanks"}, + {Column: "B", Expression: "x -- y or x == *2*"}, + {Column: "B", Expression: "x != y or x ? *2"}, + {Column: "B", Expression: "x -- y o r x == *2"}, + {Column: "B", Expression: "x -- y"}, + {Column: "A", Expression: "x -- y"}, + } { t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) { - err = f.AutoFilter("Sheet2", "D4", "B1", format) - if assert.Error(t, err) { + if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) { assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1))) } }) } - assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, &autoFilterOptions{ + assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, &AutoFilterOptions{ Column: "A", Expression: "", }), "sheet SheetN does not exist") - assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &autoFilterOptions{ + assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &AutoFilterOptions{ Column: "-", Expression: "-", }), newInvalidColumnNameError("-").Error()) - assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &autoFilterOptions{ + assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &AutoFilterOptions{ Column: "A", Expression: "-", }), `incorrect index of column 'A'`) - assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &autoFilterOptions{ + assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &AutoFilterOptions{ Column: "A", Expression: "-", }), `incorrect number of tokens in criteria '-'`) diff --git a/workbook.go b/workbook.go index 1367eac..b3ee7ff 100644 --- a/workbook.go +++ b/workbook.go @@ -59,8 +59,18 @@ func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) { return opts, err } -// ProtectWorkbook provides a function to prevent other users from accidentally or -// deliberately changing, moving, or deleting data in a workbook. +// ProtectWorkbook provides a function to prevent other users from viewing +// hidden worksheets, adding, moving, deleting, or hiding worksheets, and +// renaming worksheets in a workbook. The optional field AlgorithmName +// specified hash algorithm, support XOR, MD4, MD5, SHA-1, SHA2-56, SHA-384, +// and SHA-512 currently, if no hash algorithm specified, will be using the XOR +// algorithm as default. The generated workbook only works on Microsoft Office +// 2007 and later. For example, protect workbook with protection settings: +// +// err := f.ProtectWorkbook(&excelize.WorkbookProtectionOptions{ +// Password: "password", +// LockStructure: true, +// }) func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error { wb, err := f.workbookReader() if err != nil { @@ -93,8 +103,8 @@ func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error { } // UnprotectWorkbook provides a function to remove protection for workbook, -// specified the second optional password parameter to remove workbook -// protection with password verification. +// specified the optional password parameter to remove workbook protection with +// password verification. func (f *File) UnprotectWorkbook(password ...string) error { wb, err := f.workbookReader() if err != nil { diff --git a/workbook_test.go b/workbook_test.go index a3b2b52..67cf5c8 100644 --- a/workbook_test.go +++ b/workbook_test.go @@ -21,11 +21,11 @@ func TestWorkbookProps(t *testing.T) { opts, err := f.GetWorkbookProps() assert.NoError(t, err) assert.Equal(t, expected, opts) - // Test set workbook properties with unsupported charset workbook. + // Test set workbook properties with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8") - // Test get workbook properties with unsupported charset workbook. + // Test get workbook properties with unsupported charset workbook f.WorkBook = nil f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) _, err = f.GetWorkbookProps() diff --git a/xmlApp.go b/xmlApp.go index f21e5f9..abfd82b 100644 --- a/xmlApp.go +++ b/xmlApp.go @@ -15,13 +15,13 @@ import "encoding/xml" // AppProperties directly maps the document application properties. type AppProperties struct { - Application string `json:"application"` - ScaleCrop bool `json:"scale_crop"` - DocSecurity int `json:"doc_security"` - Company string `json:"company"` - LinksUpToDate bool `json:"links_up_to_date"` - HyperlinksChanged bool `json:"hyperlinks_changed"` - AppVersion string `json:"app_version"` + Application string + ScaleCrop bool + DocSecurity int + Company string + LinksUpToDate bool + HyperlinksChanged bool + AppVersion string } // xlsxProperties specifies to an OOXML document properties such as the diff --git a/xmlChart.go b/xmlChart.go index 5165ea0..10e6c2e 100644 --- a/xmlChart.go +++ b/xmlChart.go @@ -518,136 +518,83 @@ type cPageMargins struct { T float64 `xml:"t,attr"` } -// chartAxisOptions directly maps the format settings of the chart axis. -type chartAxisOptions struct { - None bool `json:"none"` - Crossing string `json:"crossing"` - MajorGridlines bool `json:"major_grid_lines"` - MinorGridlines bool `json:"minor_grid_lines"` - MajorTickMark string `json:"major_tick_mark"` - MinorTickMark string `json:"minor_tick_mark"` - MinorUnitType string `json:"minor_unit_type"` - MajorUnit float64 `json:"major_unit"` - MajorUnitType string `json:"major_unit_type"` - TickLabelSkip int `json:"tick_label_skip"` - DisplayUnits string `json:"display_units"` - DisplayUnitsVisible bool `json:"display_units_visible"` - DateAxis bool `json:"date_axis"` - ReverseOrder bool `json:"reverse_order"` - Maximum *float64 `json:"maximum"` - Minimum *float64 `json:"minimum"` - NumFormat string `json:"number_format"` - Font Font `json:"font"` - LogBase float64 `json:"logbase"` - NameLayout layoutOptions `json:"name_layout"` +// ChartAxis directly maps the format settings of the chart axis. +type ChartAxis struct { + None bool + MajorGridLines bool + MinorGridLines bool + MajorUnit float64 + TickLabelSkip int + ReverseOrder bool + Maximum *float64 + Minimum *float64 + Font Font + LogBase float64 } -// chartDimensionOptions directly maps the dimension of the chart. -type chartDimensionOptions struct { - Width int `json:"width"` - Height int `json:"height"` +// ChartDimension directly maps the dimension of the chart. +type ChartDimension struct { + Width *int + Height *int } -// chartOptions directly maps the format settings of the chart. -type chartOptions struct { - Type string `json:"type"` - Series []chartSeriesOptions `json:"series"` - Format pictureOptions `json:"format"` - Dimension chartDimensionOptions `json:"dimension"` - Legend chartLegendOptions `json:"legend"` - Title chartTitleOptions `json:"title"` - VaryColors bool `json:"vary_colors"` - XAxis chartAxisOptions `json:"x_axis"` - YAxis chartAxisOptions `json:"y_axis"` - Chartarea struct { - Border struct { - None bool `json:"none"` - } `json:"border"` - Fill struct { - Color string `json:"color"` - } `json:"fill"` - Pattern struct { - Pattern string `json:"pattern"` - FgColor string `json:"fg_color"` - BgColor string `json:"bg_color"` - } `json:"pattern"` - } `json:"chartarea"` - Plotarea struct { - ShowBubbleSize bool `json:"show_bubble_size"` - ShowCatName bool `json:"show_cat_name"` - ShowLeaderLines bool `json:"show_leader_lines"` - ShowPercent bool `json:"show_percent"` - ShowSerName bool `json:"show_series_name"` - ShowVal bool `json:"show_val"` - Gradient struct { - Colors []string `json:"colors"` - } `json:"gradient"` - Border struct { - Color string `json:"color"` - Width int `json:"width"` - DashType string `json:"dash_type"` - } `json:"border"` - Fill struct { - Color string `json:"color"` - } `json:"fill"` - Layout layoutOptions `json:"layout"` - } `json:"plotarea"` - ShowBlanksAs string `json:"show_blanks_as"` - ShowHiddenData bool `json:"show_hidden_data"` - SetRotation int `json:"set_rotation"` - HoleSize int `json:"hole_size"` - order int +// ChartPlotArea directly maps the format settings of the plot area. +type ChartPlotArea struct { + ShowBubbleSize bool + ShowCatName bool + ShowLeaderLines bool + ShowPercent bool + ShowSerName bool + ShowVal bool } -// chartLegendOptions directly maps the format settings of the chart legend. -type chartLegendOptions struct { - None bool `json:"none"` - DeleteSeries []int `json:"delete_series"` - Font Font `json:"font"` - Layout layoutOptions `json:"layout"` - Position string `json:"position"` - ShowLegendEntry bool `json:"show_legend_entry"` - ShowLegendKey bool `json:"show_legend_key"` +// Chart directly maps the format settings of the chart. +type Chart struct { + Type string + Series []ChartSeries + Format PictureOptions + Dimension ChartDimension + Legend ChartLegend + Title ChartTitle + VaryColors *bool + XAxis ChartAxis + YAxis ChartAxis + PlotArea ChartPlotArea + ShowBlanksAs string + HoleSize int + order int } -// chartSeriesOptions directly maps the format settings of the chart series. -type chartSeriesOptions struct { - Name string `json:"name"` - Categories string `json:"categories"` - Values string `json:"values"` - Line struct { - None bool `json:"none"` - Color string `json:"color"` - Smooth bool `json:"smooth"` - Width float64 `json:"width"` - } `json:"line"` - Marker struct { - Symbol string `json:"symbol"` - Size int `json:"size"` - Width float64 `json:"width"` - Border struct { - Color string `json:"color"` - None bool `json:"none"` - } `json:"border"` - Fill struct { - Color string `json:"color"` - None bool `json:"none"` - } `json:"fill"` - } `json:"marker"` +// ChartLegend directly maps the format settings of the chart legend. +type ChartLegend struct { + None bool + Position *string + ShowLegendKey bool } -// chartTitleOptions directly maps the format settings of the chart title. -type chartTitleOptions struct { - None bool `json:"none"` - Name string `json:"name"` - Overlay bool `json:"overlay"` - Layout layoutOptions `json:"layout"` +// ChartMarker directly maps the format settings of the chart marker. +type ChartMarker struct { + Symbol string + Size int } -// layoutOptions directly maps the format settings of the element layout. -type layoutOptions struct { - X float64 `json:"x"` - Y float64 `json:"y"` - Width float64 `json:"width"` - Height float64 `json:"height"` +// ChartLine directly maps the format settings of the chart line. +type ChartLine struct { + Color string + Smooth bool + Width float64 +} + +// ChartSeries directly maps the format settings of the chart series. +type ChartSeries struct { + Name string + Categories string + Values string + Line ChartLine + Marker ChartMarker +} + +// ChartTitle directly maps the format settings of the chart title. +type ChartTitle struct { + Name string } diff --git a/xmlComments.go b/xmlComments.go index 13b727b..c559cc9 100644 --- a/xmlComments.go +++ b/xmlComments.go @@ -74,9 +74,9 @@ type xlsxPhoneticRun struct { // Comment directly maps the comment information. type Comment struct { - Author string `json:"author"` - AuthorID int `json:"author_id"` - Cell string `json:"cell"` - Text string `json:"text"` - Runs []RichTextRun `json:"runs"` + Author string + AuthorID int + Cell string + Text string + Runs []RichTextRun } diff --git a/xmlDrawing.go b/xmlDrawing.go index 9af6905..4df01b4 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -121,7 +121,14 @@ const ( // 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 + pivotTableVersion = 3 + defaultPictureScale = 1.0 + defaultChartDimensionWidth = 480 + defaultChartDimensionHeight = 290 + defaultChartLegendPosition = "bottom" + defaultChartShowBlanksAs = "gap" + defaultShapeSize = 160 + defaultShapeLineWidth = 1 ) // ColorMappingType is the type of color transformation. @@ -554,48 +561,48 @@ type xdrTxBody struct { P []*aP `xml:"a:p"` } -// pictureOptions directly maps the format settings of the picture. -type pictureOptions struct { - FPrintsWithSheet bool `json:"print_obj"` - FLocksWithSheet bool `json:"locked"` - NoChangeAspect bool `json:"lock_aspect_ratio"` - Autofit bool `json:"autofit"` - OffsetX int `json:"x_offset"` - OffsetY int `json:"y_offset"` - XScale float64 `json:"x_scale"` - YScale float64 `json:"y_scale"` - Hyperlink string `json:"hyperlink"` - HyperlinkType string `json:"hyperlink_type"` - Positioning string `json:"positioning"` +// PictureOptions directly maps the format settings of the picture. +type PictureOptions struct { + PrintObject *bool + Locked *bool + LockAspectRatio bool + AutoFit bool + OffsetX int + OffsetY int + XScale *float64 + YScale *float64 + Hyperlink string + HyperlinkType string + Positioning string } -// shapeOptions directly maps the format settings of the shape. -type shapeOptions struct { - Macro string `json:"macro"` - Type string `json:"type"` - Width int `json:"width"` - Height int `json:"height"` - Format pictureOptions `json:"format"` - Color shapeColorOptions `json:"color"` - Line lineOptions `json:"line"` - Paragraph []shapeParagraphOptions `json:"paragraph"` +// Shape directly maps the format settings of the shape. +type Shape struct { + Macro string + Type string + Width *int + Height *int + Format PictureOptions + Color ShapeColor + Line ShapeLine + Paragraph []ShapeParagraph } -// shapeParagraphOptions directly maps the format settings of the paragraph in +// ShapeParagraph directly maps the format settings of the paragraph in // the shape. -type shapeParagraphOptions struct { - Font Font `json:"font"` - Text string `json:"text"` +type ShapeParagraph struct { + Font Font + Text string } -// shapeColorOptions directly maps the color settings of the shape. -type shapeColorOptions struct { - Line string `json:"line"` - Fill string `json:"fill"` - Effect string `json:"effect"` +// ShapeColor directly maps the color settings of the shape. +type ShapeColor struct { + Line string + Fill string + Effect string } -// lineOptions directly maps the line settings of the shape. -type lineOptions struct { - Width float64 `json:"width"` +// ShapeLine directly maps the line settings of the shape. +type ShapeLine struct { + Width *float64 } diff --git a/xmlSharedStrings.go b/xmlSharedStrings.go index 7dac544..3249eca 100644 --- a/xmlSharedStrings.go +++ b/xmlSharedStrings.go @@ -83,6 +83,6 @@ type xlsxRPr struct { // RichTextRun directly maps the settings of the rich text run. type RichTextRun struct { - Font *Font `json:"font"` - Text string `json:"text"` + Font *Font + Text string } diff --git a/xmlStyles.go b/xmlStyles.go index c9e0761..2864c8b 100644 --- a/xmlStyles.go +++ b/xmlStyles.go @@ -314,63 +314,63 @@ type xlsxStyleColors struct { // Alignment directly maps the alignment settings of the cells. type Alignment struct { - Horizontal string `json:"horizontal"` - Indent int `json:"indent"` - JustifyLastLine bool `json:"justify_last_line"` - ReadingOrder uint64 `json:"reading_order"` - RelativeIndent int `json:"relative_indent"` - ShrinkToFit bool `json:"shrink_to_fit"` - TextRotation int `json:"text_rotation"` - Vertical string `json:"vertical"` - WrapText bool `json:"wrap_text"` + Horizontal string + Indent int + JustifyLastLine bool + ReadingOrder uint64 + RelativeIndent int + ShrinkToFit bool + TextRotation int + Vertical string + WrapText bool } // Border directly maps the border settings of the cells. type Border struct { - Type string `json:"type"` - Color string `json:"color"` - Style int `json:"style"` + Type string + Color string + Style int } // Font directly maps the font settings of the fonts. type Font struct { - Bold bool `json:"bold"` - Italic bool `json:"italic"` - Underline string `json:"underline"` - Family string `json:"family"` - Size float64 `json:"size"` - Strike bool `json:"strike"` - Color string `json:"color"` - ColorIndexed int `json:"color_indexed"` - ColorTheme *int `json:"color_theme"` - ColorTint float64 `json:"color_tint"` - VertAlign string `json:"vertAlign"` + Bold bool + Italic bool + Underline string + Family string + Size float64 + Strike bool + Color string + ColorIndexed int + ColorTheme *int + ColorTint float64 + VertAlign string } // Fill directly maps the fill settings of the cells. type Fill struct { - Type string `json:"type"` - Pattern int `json:"pattern"` - Color []string `json:"color"` - Shading int `json:"shading"` + Type string + Pattern int + Color []string + Shading int } // Protection directly maps the protection settings of the cells. type Protection struct { - Hidden bool `json:"hidden"` - Locked bool `json:"locked"` + Hidden bool + Locked bool } // Style directly maps the style settings of the cells. type Style struct { - Border []Border `json:"border"` - Fill Fill `json:"fill"` - Font *Font `json:"font"` - Alignment *Alignment `json:"alignment"` - Protection *Protection `json:"protection"` - NumFmt int `json:"number_format"` - DecimalPlaces int `json:"decimal_places"` - CustomNumFmt *string `json:"custom_number_format"` - Lang string `json:"lang"` - NegRed bool `json:"negred"` + Border []Border + Fill Fill + Font *Font + Alignment *Alignment + Protection *Protection + NumFmt int + DecimalPlaces int + CustomNumFmt *string + Lang string + NegRed bool } diff --git a/xmlTable.go b/xmlTable.go index 758e0ea..3a5ded6 100644 --- a/xmlTable.go +++ b/xmlTable.go @@ -196,22 +196,25 @@ type xlsxTableStyleInfo struct { ShowColumnStripes bool `xml:"showColumnStripes,attr"` } -// tableOptions directly maps the format settings of the table. -type tableOptions struct { - TableName string `json:"table_name"` - TableStyle string `json:"table_style"` - ShowFirstColumn bool `json:"show_first_column"` - ShowLastColumn bool `json:"show_last_column"` - ShowRowStripes bool `json:"show_row_stripes"` - ShowColumnStripes bool `json:"show_column_stripes"` +// TableOptions directly maps the format settings of the table. +type TableOptions struct { + Name string + StyleName string + ShowFirstColumn bool + ShowLastColumn bool + ShowRowStripes *bool + ShowColumnStripes bool } -// autoFilterOptions directly maps the auto filter settings. -type autoFilterOptions struct { - Column string `json:"column"` - Expression string `json:"expression"` - FilterList []struct { - Column string `json:"column"` - Value []int `json:"value"` - } `json:"filter_list"` +// AutoFilterListOptions directly maps the auto filter list settings. +type AutoFilterListOptions struct { + Column string + Value []int +} + +// AutoFilterOptions directly maps the auto filter settings. +type AutoFilterOptions struct { + Column string + Expression string + FilterList []AutoFilterListOptions } diff --git a/xmlWorkbook.go b/xmlWorkbook.go index 503eac1..0d88596 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -308,23 +308,23 @@ type xlsxCustomWorkbookView struct { // DefinedName directly maps the name for a cell or cell range on a // worksheet. type DefinedName struct { - Name string `json:"name,omitempty"` - Comment string `json:"comment,omitempty"` - RefersTo string `json:"refers_to,omitempty"` - Scope string `json:"scope,omitempty"` + Name string + Comment string + RefersTo string + Scope string } // WorkbookPropsOptions directly maps the settings of workbook proprieties. type WorkbookPropsOptions struct { - Date1904 *bool `json:"date_1994,omitempty"` - FilterPrivacy *bool `json:"filter_privacy,omitempty"` - CodeName *string `json:"code_name,omitempty"` + Date1904 *bool + FilterPrivacy *bool + CodeName *string } // WorkbookProtectionOptions directly maps the settings of workbook protection. type WorkbookProtectionOptions struct { - AlgorithmName string `json:"algorithmName,omitempty"` - Password string `json:"password,omitempty"` - LockStructure bool `json:"lockStructure,omitempty"` - LockWindows bool `json:"lockWindows,omitempty"` + AlgorithmName string + Password string + LockStructure bool + LockWindows bool } diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 263c2a3..be7a5c9 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -794,141 +794,143 @@ type xlsxX14Sparkline struct { // SparklineOptions directly maps the settings of the sparkline. type SparklineOptions struct { - Location []string `json:"location"` - Range []string `json:"range"` - Max int `json:"max"` - CustMax int `json:"cust_max"` - Min int `json:"min"` - CustMin int `json:"cust_min"` - Type string `json:"hype"` - Weight float64 `json:"weight"` - DateAxis bool `json:"date_axis"` - Markers bool `json:"markers"` - High bool `json:"high"` - Low bool `json:"low"` - First bool `json:"first"` - Last bool `json:"last"` - Negative bool `json:"negative"` - Axis bool `json:"axis"` - Hidden bool `json:"hidden"` - Reverse bool `json:"reverse"` - Style int `json:"style"` - SeriesColor string `json:"series_color"` - NegativeColor string `json:"negative_color"` - MarkersColor string `json:"markers_color"` - FirstColor string `json:"first_color"` - LastColor string `json:"last_color"` - HightColor string `json:"hight_color"` - LowColor string `json:"low_color"` - EmptyCells string `json:"empty_cells"` + Location []string + Range []string + Max int + CustMax int + Min int + CustMin int + Type string + Weight float64 + DateAxis bool + Markers bool + High bool + Low bool + First bool + Last bool + Negative bool + Axis bool + Hidden bool + Reverse bool + Style int + SeriesColor string + NegativeColor string + MarkersColor string + FirstColor string + LastColor string + HightColor string + LowColor string + EmptyCells string } -// panesOptions directly maps the settings of the panes. -type panesOptions struct { - Freeze bool `json:"freeze"` - Split bool `json:"split"` - XSplit int `json:"x_split"` - YSplit int `json:"y_split"` - TopLeftCell string `json:"top_left_cell"` - ActivePane string `json:"active_pane"` - Panes []struct { - SQRef string `json:"sqref"` - ActiveCell string `json:"active_cell"` - Pane string `json:"pane"` - } `json:"panes"` +// PaneOptions directly maps the settings of the pane. +type PaneOptions struct { + SQRef string + ActiveCell string + Pane string } -// conditionalOptions directly maps the conditional format settings of the cells. -type conditionalOptions struct { - Type string `json:"type"` - AboveAverage bool `json:"above_average,omitempty"` - Percent bool `json:"percent,omitempty"` - Format int `json:"format,omitempty"` - Criteria string `json:"criteria,omitempty"` - Value string `json:"value,omitempty"` - Minimum string `json:"minimum,omitempty"` - Maximum string `json:"maximum,omitempty"` - MinType string `json:"min_type,omitempty"` - MidType string `json:"mid_type,omitempty"` - MaxType string `json:"max_type,omitempty"` - MinValue string `json:"min_value,omitempty"` - MidValue string `json:"mid_value,omitempty"` - MaxValue string `json:"max_value,omitempty"` - MinColor string `json:"min_color,omitempty"` - MidColor string `json:"mid_color,omitempty"` - MaxColor string `json:"max_color,omitempty"` - MinLength string `json:"min_length,omitempty"` - MaxLength string `json:"max_length,omitempty"` - MultiRange string `json:"multi_range,omitempty"` - BarColor string `json:"bar_color,omitempty"` +// Panes directly maps the settings of the panes. +type Panes struct { + Freeze bool + Split bool + XSplit int + YSplit int + TopLeftCell string + ActivePane string + Panes []PaneOptions +} + +// ConditionalFormatOptions directly maps the conditional format settings of the cells. +type ConditionalFormatOptions struct { + Type string + AboveAverage bool + Percent bool + Format int + Criteria string + Value string + Minimum string + Maximum string + MinType string + MidType string + MaxType string + MinValue string + MidValue string + MaxValue string + MinColor string + MidColor string + MaxColor string + MinLength string + MaxLength string + BarColor string } // SheetProtectionOptions directly maps the settings of worksheet protection. type SheetProtectionOptions struct { - AlgorithmName string `json:"algorithm_name,omitempty"` - AutoFilter bool `json:"auto_filter,omitempty"` - DeleteColumns bool `json:"delete_columns,omitempty"` - DeleteRows bool `json:"delete_rows,omitempty"` - EditObjects bool `json:"edit_objects,omitempty"` - EditScenarios bool `json:"edit_scenarios,omitempty"` - FormatCells bool `json:"format_cells,omitempty"` - FormatColumns bool `json:"format_columns,omitempty"` - FormatRows bool `json:"format_rows,omitempty"` - InsertColumns bool `json:"insert_columns,omitempty"` - InsertHyperlinks bool `json:"insert_hyperlinks,omitempty"` - InsertRows bool `json:"insert_rows,omitempty"` - Password string `json:"password,omitempty"` - PivotTables bool `json:"pivot_tables,omitempty"` - SelectLockedCells bool `json:"select_locked_cells,omitempty"` - SelectUnlockedCells bool `json:"select_unlocked_cells,omitempty"` - Sort bool `json:"sort,omitempty"` + AlgorithmName string + AutoFilter bool + DeleteColumns bool + DeleteRows bool + EditObjects bool + EditScenarios bool + FormatCells bool + FormatColumns bool + FormatRows bool + InsertColumns bool + InsertHyperlinks bool + InsertRows bool + Password string + PivotTables bool + SelectLockedCells bool + SelectUnlockedCells bool + Sort bool } // HeaderFooterOptions directly maps the settings of header and footer. type HeaderFooterOptions struct { - AlignWithMargins bool `json:"align_with_margins,omitempty"` - DifferentFirst bool `json:"different_first,omitempty"` - DifferentOddEven bool `json:"different_odd_even,omitempty"` - ScaleWithDoc bool `json:"scale_with_doc,omitempty"` - OddHeader string `json:"odd_header,omitempty"` - OddFooter string `json:"odd_footer,omitempty"` - EvenHeader string `json:"even_header,omitempty"` - EvenFooter string `json:"even_footer,omitempty"` - FirstHeader string `json:"first_header,omitempty"` - FirstFooter string `json:"first_footer,omitempty"` + AlignWithMargins bool + DifferentFirst bool + DifferentOddEven bool + ScaleWithDoc bool + OddHeader string + OddFooter string + EvenHeader string + EvenFooter string + FirstHeader string + FirstFooter string } // PageLayoutMarginsOptions directly maps the settings of page layout margins. type PageLayoutMarginsOptions struct { - Bottom *float64 `json:"bottom,omitempty"` - Footer *float64 `json:"footer,omitempty"` - Header *float64 `json:"header,omitempty"` - Left *float64 `json:"left,omitempty"` - Right *float64 `json:"right,omitempty"` - Top *float64 `json:"top,omitempty"` - Horizontally *bool `json:"horizontally,omitempty"` - Vertically *bool `json:"vertically,omitempty"` + Bottom *float64 + Footer *float64 + Header *float64 + Left *float64 + Right *float64 + Top *float64 + Horizontally *bool + Vertically *bool } // PageLayoutOptions directly maps the settings of page layout. type PageLayoutOptions struct { // Size defines the paper size of the worksheet. - Size *int `json:"size,omitempty"` + Size *int // Orientation defines the orientation of page layout for a worksheet. - Orientation *string `json:"orientation,omitempty"` + Orientation *string // FirstPageNumber specified the first printed page number. If no value is // specified, then 'automatic' is assumed. - FirstPageNumber *uint `json:"first_page_number,omitempty"` + FirstPageNumber *uint // AdjustTo defines the print scaling. This attribute is restricted to // value ranging from 10 (10%) to 400 (400%). This setting is overridden // when fitToWidth and/or fitToHeight are in use. - AdjustTo *uint `json:"adjust_to,omitempty"` + AdjustTo *uint // FitToHeight specified the number of vertical pages to fit on. - FitToHeight *int `json:"fit_to_height,omitempty"` + FitToHeight *int // FitToWidth specified the number of horizontal pages to fit on. - FitToWidth *int `json:"fit_to_width,omitempty"` + FitToWidth *int // BlackAndWhite specified print black and white. - BlackAndWhite *bool `json:"black_and_white,omitempty"` + BlackAndWhite *bool } // ViewOptions directly maps the settings of sheet view. @@ -936,37 +938,37 @@ type ViewOptions struct { // DefaultGridColor indicating that the consuming application should use // the default grid lines color(system dependent). Overrides any color // specified in colorId. - DefaultGridColor *bool `json:"default_grid_color,omitempty"` + DefaultGridColor *bool // RightToLeft indicating whether the sheet is in 'right to left' display // mode. When in this mode, Column A is on the far right, Column B; is one // column left of Column A, and so on. Also, information in cells is // displayed in the Right to Left format. - RightToLeft *bool `json:"right_to_left,omitempty"` + RightToLeft *bool // ShowFormulas indicating whether this sheet should display formulas. - ShowFormulas *bool `json:"show_formulas,omitempty"` + ShowFormulas *bool // ShowGridLines indicating whether this sheet should display grid lines. - ShowGridLines *bool `json:"show_grid_lines,omitempty"` + ShowGridLines *bool // ShowRowColHeaders indicating whether the sheet should display row and // column headings. - ShowRowColHeaders *bool `json:"show_row_col_headers,omitempty"` + ShowRowColHeaders *bool // ShowRuler indicating this sheet should display ruler. - ShowRuler *bool `json:"show_ruler,omitempty"` + ShowRuler *bool // ShowZeros indicating whether to "show a zero in cells that have zero // value". When using a formula to reference another cell which is empty, // the referenced value becomes 0 when the flag is true. (Default setting // is true.) - ShowZeros *bool `json:"show_zeros,omitempty"` + ShowZeros *bool // TopLeftCell specifies a location of the top left visible cell Location // of the top left visible cell in the bottom right pane (when in // Left-to-Right mode). - TopLeftCell *string `json:"top_left_cell,omitempty"` + TopLeftCell *string // View indicating how sheet is displayed, by default it uses empty string // available options: normal, pageLayout, pageBreakPreview - View *string `json:"low_color,omitempty"` + View *string // ZoomScale specifies a window zoom magnification for current view // representing percent values. This attribute is restricted to values // ranging from 10 to 400. Horizontal & Vertical scale together. - ZoomScale *float64 `json:"zoom_scale,omitempty"` + ZoomScale *float64 } // SheetPropsOptions directly maps the settings of sheet view. @@ -974,55 +976,55 @@ type SheetPropsOptions struct { // Specifies a stable name of the sheet, which should not change over time, // and does not change from user input. This name should be used by code // to reference a particular sheet. - CodeName *string `json:"code_name,omitempty"` + CodeName *string // EnableFormatConditionsCalculation indicating whether the conditional // formatting calculations shall be evaluated. If set to false, then the // min/max values of color scales or data bars or threshold values in Top N // rules shall not be updated. Essentially the conditional // formatting "calc" is off. - EnableFormatConditionsCalculation *bool `json:"enable_format_conditions_calculation,omitempty"` + EnableFormatConditionsCalculation *bool // Published indicating whether the worksheet is published. - Published *bool `json:"published,omitempty"` + Published *bool // AutoPageBreaks indicating whether the sheet displays Automatic Page // Breaks. - AutoPageBreaks *bool `json:"auto_page_breaks,omitempty"` + AutoPageBreaks *bool // FitToPage indicating whether the Fit to Page print option is enabled. - FitToPage *bool `json:"fit_to_page,omitempty"` + FitToPage *bool // TabColorIndexed represents the indexed color value. - TabColorIndexed *int `json:"tab_color_indexed,omitempty"` + TabColorIndexed *int // TabColorRGB represents the standard Alpha Red Green Blue color value. - TabColorRGB *string `json:"tab_color_rgb,omitempty"` + TabColorRGB *string // TabColorTheme represents the zero-based index into the collection, // referencing a particular value expressed in the Theme part. - TabColorTheme *int `json:"tab_color_theme,omitempty"` + TabColorTheme *int // TabColorTint specifies the tint value applied to the color. - TabColorTint *float64 `json:"tab_color_tint,omitempty"` + TabColorTint *float64 // OutlineSummaryBelow indicating whether summary rows appear below detail // in an outline, when applying an outline. - OutlineSummaryBelow *bool `json:"outline_summary_below,omitempty"` + OutlineSummaryBelow *bool // OutlineSummaryRight indicating whether summary columns appear to the // right of detail in an outline, when applying an outline. - OutlineSummaryRight *bool `json:"outline_summary_right,omitempty"` + OutlineSummaryRight *bool // BaseColWidth specifies the number of characters of the maximum digit // width of the normal style's font. This value does not include margin // padding or extra padding for grid lines. It is only the number of // characters. - BaseColWidth *uint8 `json:"base_col_width,omitempty"` + BaseColWidth *uint8 // DefaultColWidth specifies the default column width measured as the // number of characters of the maximum digit width of the normal style's // font. - DefaultColWidth *float64 `json:"default_col_width,omitempty"` + DefaultColWidth *float64 // DefaultRowHeight specifies the default row height measured in point // size. Optimization so we don't have to write the height on all rows. // This can be written out if most rows have custom height, to achieve the // optimization. - DefaultRowHeight *float64 `json:"default_row_height,omitempty"` + DefaultRowHeight *float64 // CustomHeight specifies the custom height. - CustomHeight *bool `json:"custom_height,omitempty"` + CustomHeight *bool // ZeroHeight specifies if rows are hidden. - ZeroHeight *bool `json:"zero_height,omitempty"` + ZeroHeight *bool // ThickTop specifies if rows have a thick top border by default. - ThickTop *bool `json:"thick_top,omitempty"` + ThickTop *bool // ThickBottom specifies if rows have a thick bottom border by default. - ThickBottom *bool `json:"thick_bottom,omitempty"` + ThickBottom *bool }