Breaking change: changed the function signature for 11 exported functions

* Change
    `func (f *File) NewConditionalStyle(style string) (int, error)`
    to
    `func (f *File) NewConditionalStyle(style *Style) (int, error)`
* Change
    `func (f *File) NewStyle(style interface{}) (int, error)`
     to
    `func (f *File) NewStyle(style *Style) (int, error)`
* Change
    `func (f *File) AddChart(sheet, cell, opts string, combo ...string) error`
     to
    `func (f *File) AddChart(sheet, cell string, chart *ChartOptions, combo ...*ChartOptions) error`
* Change
    `func (f *File) AddChartSheet(sheet, opts string, combo ...string) error`
     to
    `func (f *File) AddChartSheet(sheet string, chart *ChartOptions, combo ...*ChartOptions) error`
* Change
    `func (f *File) AddShape(sheet, cell, opts string) error`
     to
    `func (f *File) AddShape(sheet, cell string, opts *Shape) error`
* Change
    `func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, file []byte) error`
     to
    `func (f *File) AddPictureFromBytes(sheet, cell, name, extension string, file []byte, opts *PictureOptions) error`
* Change
    `func (f *File) AddTable(sheet, hCell, vCell, opts string) error`
     to
    `func (f *File) AddTable(sheet, reference string, opts *TableOptions) error`
* Change
    `func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error`
     to
    `func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error`
* Change
    `func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error`
     to
    `func (f *File) AutoFilter(sheet, reference string, opts *AutoFilterOptions) error`
* Change
    `func (f *File) SetPanes(sheet, panes string) error`
     to
    `func (f *File) SetPanes(sheet string, panes *Panes) error`
* Change
    `func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error`
     to
    `func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error`
* Change
    `func (f *File) SetConditionalFormat(sheet, reference, opts string) error`
     to
    `func (f *File) SetConditionalFormat(sheet, reference string, opts []ConditionalFormatOptions) error`
* Add exported types:
  * AutoFilterListOptions
  * AutoFilterOptions
  * Chart
  * ChartAxis
  * ChartDimension
  * ChartLegend
  * ChartLine
  * ChartMarker
  * ChartPlotArea
  * ChartSeries
  * ChartTitle
  * ConditionalFormatOptions
  * PaneOptions
  * Panes
  * PictureOptions
  * Shape
  * ShapeColor
  * ShapeLine
  * ShapeParagraph
  * TableOptions
* This added support for set sheet visible as very hidden
* Return error when missing required parameters for set defined name
* Update unit test and comments
This commit is contained in:
xuri 2022-12-30 00:50:08 +08:00
parent a57203a03a
commit f58dabd492
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
47 changed files with 2305 additions and 1968 deletions

View File

@ -121,41 +121,40 @@ import (
) )
func main() { 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() f := excelize.NewFile()
for k, v := range categories { for idx, row := range [][]interface{}{
f.SetCellValue("Sheet1", k, v) {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
} }
for k, v := range values { f.SetSheetRow("Sheet1", cell, &row)
f.SetCellValue("Sheet1", k, v)
} }
if err := f.AddChart("Sheet1", "E1", `{ if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
"type": "col3DClustered", Type: "col3DClustered",
"series": [ Series: []excelize.ChartSeries{
{ {
"name": "Sheet1!$A$2", Name: "Sheet1!$A$2",
"categories": "Sheet1!$B$1:$D$1", Categories: "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$2:$D$2" Values: "Sheet1!$B$2:$D$2",
}, },
{ {
"name": "Sheet1!$A$3", Name: "Sheet1!$A$3",
"categories": "Sheet1!$B$1:$D$1", Categories: "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$3:$D$3" Values: "Sheet1!$B$3:$D$3",
}, },
{ {
"name": "Sheet1!$A$4", Name: "Sheet1!$A$4",
"categories": "Sheet1!$B$1:$D$1", Categories: "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$4:$D$4" Values: "Sheet1!$B$4:$D$4",
}], }},
"title": Title: excelize.ChartTitle{
{ Name: "Fruit 3D Clustered Column Chart",
"name": "Fruit 3D Clustered Column Chart" },
} }); err != nil {
}`); err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
@ -193,22 +192,24 @@ func main() {
} }
}() }()
// Insert a picture. // 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) fmt.Println(err)
} }
// Insert a picture to worksheet with scaling. // Insert a picture to worksheet with scaling.
enable, disable, scale := true, false, 0.5
if err := f.AddPicture("Sheet1", "D2", "image.jpg", 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) fmt.Println(err)
} }
// Insert a picture offset in the cell with printing support. // Insert a picture offset in the cell with printing support.
if err := f.AddPicture("Sheet1", "H2", "image.gif", `{ if err := f.AddPicture("Sheet1", "H2", "image.gif",
"x_offset": 15, &excelize.PictureOptions{
"y_offset": 10, PrintObject: &enable,
"print_obj": true, LockAspectRatio: false,
"lock_aspect_ratio": false, OffsetX: 15,
"locked": false OffsetY: 10,
}`); err != nil { Locked: &disable,
}); err != nil {
fmt.Println(err) fmt.Println(err)
} }
// Save the spreadsheet with the origin path. // Save the spreadsheet with the origin path.

View File

@ -121,41 +121,40 @@ import (
) )
func main() { 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() f := excelize.NewFile()
for k, v := range categories { for idx, row := range [][]interface{}{
f.SetCellValue("Sheet1", k, v) {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
} }
for k, v := range values { f.SetSheetRow("Sheet1", cell, &row)
f.SetCellValue("Sheet1", k, v)
} }
if err := f.AddChart("Sheet1", "E1", `{ if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
"type": "col3DClustered", Type: "col3DClustered",
"series": [ Series: []excelize.ChartSeries{
{ {
"name": "Sheet1!$A$2", Name: "Sheet1!$A$2",
"categories": "Sheet1!$B$1:$D$1", Categories: "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$2:$D$2" Values: "Sheet1!$B$2:$D$2",
}, },
{ {
"name": "Sheet1!$A$3", Name: "Sheet1!$A$3",
"categories": "Sheet1!$B$1:$D$1", Categories: "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$3:$D$3" Values: "Sheet1!$B$3:$D$3",
}, },
{ {
"name": "Sheet1!$A$4", Name: "Sheet1!$A$4",
"categories": "Sheet1!$B$1:$D$1", Categories: "Sheet1!$B$1:$D$1",
"values": "Sheet1!$B$4:$D$4" Values: "Sheet1!$B$4:$D$4",
}], }},
"title": Title: excelize.ChartTitle{
{ Name: "Fruit 3D Clustered Column Chart",
"name": "Fruit 3D Clustered Column Chart" },
} }); err != nil {
}`); err != nil {
fmt.Println(err) fmt.Println(err)
return 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) fmt.Println(err)
} }
// 在工作表中插入图片,并设置图片的缩放比例 // 在工作表中插入图片,并设置图片的缩放比例
enable, disable, scale := true, false, 0.5
if err := f.AddPicture("Sheet1", "D2", "image.jpg", 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) fmt.Println(err)
} }
// 在工作表中插入图片,并设置图片的打印属性 // 在工作表中插入图片,并设置图片的打印属性
if err := f.AddPicture("Sheet1", "H2", "image.gif", `{ if err := f.AddPicture("Sheet1", "H2", "image.gif",
"x_offset": 15, &excelize.PictureOptions{
"y_offset": 10, PrintObject: &enable,
"print_obj": true, LockAspectRatio: false,
"lock_aspect_ratio": false, OffsetX: 15,
"locked": false OffsetY: 10,
}`); err != nil { Locked: &disable,
}); err != nil {
fmt.Println(err) fmt.Println(err)
} }
// 保存工作簿 // 保存工作簿

View File

@ -10,7 +10,7 @@ import (
func TestAdjustMergeCells(t *testing.T) { func TestAdjustMergeCells(t *testing.T) {
f := NewFile() f := NewFile()
// Test adjustAutoFilter with illegal cell reference. // Test adjustAutoFilter with illegal cell reference
assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{ assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{ MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{ Cells: []*xlsxMergeCell{
@ -57,7 +57,7 @@ func TestAdjustMergeCells(t *testing.T) {
}, },
}, columns, 1, -1)) }, columns, 1, -1))
// Test adjustMergeCells. // Test adjust merge cells
var cases []struct { var cases []struct {
label string label string
ws *xlsxWorksheet ws *xlsxWorksheet
@ -68,7 +68,7 @@ func TestAdjustMergeCells(t *testing.T) {
expectRect []int expectRect []int
} }
// Test insert. // Test adjust merged cell when insert rows and columns
cases = []struct { cases = []struct {
label string label string
ws *xlsxWorksheet ws *xlsxWorksheet
@ -139,7 +139,7 @@ func TestAdjustMergeCells(t *testing.T) {
assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label) 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 { cases = []struct {
label string label string
ws *xlsxWorksheet ws *xlsxWorksheet
@ -292,7 +292,7 @@ func TestAdjustAutoFilter(t *testing.T) {
Ref: "A1:A3", Ref: "A1:A3",
}, },
}, rows, 1, -1)) }, rows, 1, -1))
// Test adjustAutoFilter with illegal cell reference. // Test adjustAutoFilter with illegal cell reference
assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{ assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{ AutoFilter: &xlsxAutoFilter{
Ref: "A:B1", Ref: "A:B1",
@ -307,15 +307,15 @@ func TestAdjustAutoFilter(t *testing.T) {
func TestAdjustTable(t *testing.T) { func TestAdjustTable(t *testing.T) {
f, sheetName := NewFile(), "Sheet1" f, sheetName := NewFile(), "Sheet1"
for idx, tableRange := range [][]string{{"B2", "C3"}, {"E3", "F5"}, {"H5", "H8"}, {"J5", "K9"}} { for idx, reference := range []string{"B2:C3", "E3:F5", "H5:H8", "J5:K9"} {
assert.NoError(t, f.AddTable(sheetName, tableRange[0], tableRange[1], fmt.Sprintf(`{ assert.NoError(t, f.AddTable(sheetName, reference, &TableOptions{
"table_name": "table%d", Name: fmt.Sprintf("table%d", idx),
"table_style": "TableStyleMedium2", StyleName: "TableStyleMedium2",
"show_first_column": true, ShowFirstColumn: true,
"show_last_column": true, ShowLastColumn: true,
"show_row_stripes": false, ShowRowStripes: boolPtr(false),
"show_column_stripes": true ShowColumnStripes: true,
}`, idx))) }))
} }
assert.NoError(t, f.RemoveRow(sheetName, 2)) assert.NoError(t, f.RemoveRow(sheetName, 2))
assert.NoError(t, f.RemoveRow(sheetName, 3)) 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"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx")))
f = NewFile() f = NewFile()
assert.NoError(t, f.AddTable(sheetName, "A1", "D5", "")) assert.NoError(t, f.AddTable(sheetName, "A1:D5", nil))
// Test adjust table with non-table part. // Test adjust table with non-table part
f.Pkg.Delete("xl/tables/table1.xml") f.Pkg.Delete("xl/tables/table1.xml")
assert.NoError(t, f.RemoveRow(sheetName, 1)) 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) f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.RemoveRow(sheetName, 1)) 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(`<table ref="-" />`)) f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
assert.NoError(t, f.RemoveRow(sheetName, 1)) assert.NoError(t, f.RemoveRow(sheetName, 1))
} }
func TestAdjustHelper(t *testing.T) { func TestAdjustHelper(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") _, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}, MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}},
}) })
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{ f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}, 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("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).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") assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN does not exist")
} }

View File

@ -48,7 +48,7 @@ const (
formulaErrorSPILL = "#SPILL!" formulaErrorSPILL = "#SPILL!"
formulaErrorCALC = "#CALC!" formulaErrorCALC = "#CALC!"
formulaErrorGETTINGDATA = "#GETTING_DATA" formulaErrorGETTINGDATA = "#GETTING_DATA"
// formula criteria condition enumeration. // Formula criteria condition enumeration
_ byte = iota _ byte = iota
criteriaEq criteriaEq
criteriaLe criteriaLe
@ -100,7 +100,7 @@ const (
) )
var ( var (
// tokenPriority defined basic arithmetic operator priority. // tokenPriority defined basic arithmetic operator priority
tokenPriority = map[string]int{ tokenPriority = map[string]int{
"^": 5, "^": 5,
"*": 4, "*": 4,

View File

@ -5478,7 +5478,8 @@ func TestCalcSLOP(t *testing.T) {
func TestCalcSHEET(t *testing.T) { func TestCalcSHEET(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") _, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaList := map[string]string{ formulaList := map[string]string{
"=SHEET(\"Sheet2\")": "2", "=SHEET(\"Sheet2\")": "2",
"=SHEET(Sheet2!A1)": "2", "=SHEET(Sheet2!A1)": "2",
@ -5494,7 +5495,8 @@ func TestCalcSHEET(t *testing.T) {
func TestCalcSHEETS(t *testing.T) { func TestCalcSHEETS(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") _, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
formulaList := map[string]string{ formulaList := map[string]string{
"=SHEETS(Sheet1!A1:B1)": "1", "=SHEETS(Sheet1!A1:B1)": "1",
"=SHEETS(Sheet1!A1:Sheet1!A1)": "1", "=SHEETS(Sheet1!A1:Sheet1!A1)": "1",

View File

@ -8,7 +8,7 @@ import (
func TestCalcChainReader(t *testing.T) { func TestCalcChainReader(t *testing.T) {
f := NewFile() f := NewFile()
// Test read calculation chain with unsupported charset. // Test read calculation chain with unsupported charset
f.CalcChain = nil f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
_, err := f.calcChainReader() _, err := f.calcChainReader()
@ -34,12 +34,12 @@ func TestDeleteCalcChain(t *testing.T) {
formulaType, ref := STCellFormulaTypeShared, "C1:C5" formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) 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.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8") 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.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")

View File

@ -685,8 +685,9 @@ type FormulaOpts struct {
// return // return
// } // }
// } // }
// if err := f.AddTable("Sheet1", "A1", "C2", // if err := f.AddTable("Sheet1", "A1:C2", &excelize.TableOptions{
// `{"table_name":"Table1","table_style":"TableStyleMedium2"}`); err != nil { // Name: "Table1", StyleName: "TableStyleMedium2",
// }); err != nil {
// fmt.Println(err) // fmt.Println(err)
// return // return
// } // }

View File

@ -37,13 +37,20 @@ func TestConcurrency(t *testing.T) {
uint64(1<<32 - 1), true, complex64(5 + 10i), uint64(1<<32 - 1), true, complex64(5 + 10i),
})) }))
// Concurrency create style // 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) assert.NoError(t, err)
// Concurrency set cell style // Concurrency set cell style
assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A3", "A3", style))
// Concurrency add picture // Concurrency add picture
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), 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 // Concurrency get cell picture
name, raw, err := f.GetPicture("Sheet1", "A1") name, raw, err := f.GetPicture("Sheet1", "A1")
assert.Equal(t, "", name) assert.Equal(t, "", name)
@ -556,7 +563,7 @@ func TestSetCellFormula(t *testing.T) {
for idx, row := range [][]interface{}{{"A", "B", "C"}, {1, 2}} { 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.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 formulaType = STCellFormulaTypeDataTable
assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType})) assert.NoError(t, f.SetCellFormula("Sheet1", "C2", "=SUM(Table1[[A]:[B]])", FormulaOpts{Type: &formulaType}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellFormula6.xlsx"))) 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)) assert.Equal(t, "1", f.getFromStringItem(1))
// Cleanup undelete temporary files // Cleanup undelete temporary files
assert.NoError(t, os.Remove(tempFile.(string))) 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") err = f.SetCellValue("Sheet1", "A19", "A19")
assert.Error(t, err) assert.Error(t, err)

467
chart.go
View File

@ -12,7 +12,6 @@
package excelize package excelize
import ( import (
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"strconv" "strconv"
@ -480,28 +479,41 @@ var (
// parseChartOptions provides a function to parse the format settings of the // parseChartOptions provides a function to parse the format settings of the
// chart with default value. // chart with default value.
func parseChartOptions(opts string) (*chartOptions, error) { func parseChartOptions(opts *Chart) (*Chart, error) {
options := chartOptions{ if opts == nil {
Dimension: chartDimensionOptions{ return nil, ErrParameterInvalid
Width: 480,
Height: 290,
},
Format: pictureOptions{
FPrintsWithSheet: true,
XScale: 1,
YScale: 1,
},
Legend: chartLegendOptions{
Position: "bottom",
},
Title: chartTitleOptions{
Name: " ",
},
VaryColors: true,
ShowBlanksAs: "gap",
} }
err := json.Unmarshal([]byte(opts), &options) if opts.Dimension.Width == nil {
return &options, err 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 // 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() { // 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() // f := excelize.NewFile()
// for k, v := range categories { // for idx, row := range [][]interface{}{
// f.SetCellValue("Sheet1", k, v) // {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
// } // }
// for k, v := range values { // f.SetSheetRow("Sheet1", cell, &row)
// f.SetCellValue("Sheet1", k, v)
// } // }
// if err := f.AddChart("Sheet1", "E1", `{ // positionBottom := "bottom"
// "type": "col3DClustered", // if err := f.AddChart("Sheet1", "E1", &excelize.Chart{
// "series": [ // Type: "col3DClustered",
// Series: []excelize.ChartSeries{
// { // {
// "name": "Sheet1!$A$2", // Name: "Sheet1!$A$2",
// "categories": "Sheet1!$B$1:$D$1", // Categories: "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$2:$D$2" // Values: "Sheet1!$B$2:$D$2",
// }, // },
// { // {
// "name": "Sheet1!$A$3", // Name: "Sheet1!$A$3",
// "categories": "Sheet1!$B$1:$D$1", // Categories: "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$3:$D$3" // Values: "Sheet1!$B$3:$D$3",
// }, // },
// { // {
// "name": "Sheet1!$A$4", // Name: "Sheet1!$A$4",
// "categories": "Sheet1!$B$1:$D$1", // Categories: "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$4:$D$4" // Values: "Sheet1!$B$4:$D$4",
// }],
// "title":
// {
// "name": "Fruit 3D Clustered Column Chart"
// }, // },
// "legend":
// {
// "none": false,
// "position": "bottom",
// "show_legend_key": false
// }, // },
// "plotarea": // Title: excelize.ChartTitle{
// { // Name: "Fruit 3D Clustered Column Chart",
// "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", // Legend: excelize.ChartLegend{
// "x_axis": // None: false, Position: &positionBottom, ShowLegendKey: false,
// {
// "reverse_order": true
// }, // },
// "y_axis": // PlotArea: excelize.ChartPlotArea{
// { // ShowBubbleSize: true,
// "maximum": 7.5, // ShowCatName: false,
// "minimum": 0.5 // ShowLeaderLines: false,
// } // ShowPercent: true,
// }`); err != nil { // ShowSerName: true,
// ShowVal: true,
// },
// }); err != nil {
// fmt.Println(err) // fmt.Println(err)
// return // return
// } // }
@ -651,21 +650,21 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// //
// The series options that can be set are: // The series options that can be set are:
// //
// name // Name
// categories // Categories
// values // Values
// line // Line
// marker // 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 // circle
// dash // dash
@ -682,13 +681,13 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// //
// Set properties of the chart legend. The options that can be set are: // Set properties of the chart legend. The options that can be set are:
// //
// none // None
// position // Position
// show_legend_key // 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 // top
// bottom // bottom
@ -696,15 +695,15 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// right // right
// top_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: // 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 // gap
// span // span
@ -716,80 +715,80 @@ func parseChartOptions(opts string) (*chartOptions, error) {
// //
// zero: Specifies that blank values shall be treated as zero. // 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 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 // ShowBubbleSize
// show_cat_name // ShowCatName
// show_leader_lines // ShowLeaderLines
// show_percent // ShowPercent
// show_series_name // ShowSerName
// show_val // 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 // None
// major_grid_lines // MajorGridLines
// minor_grid_lines // MinorGridLines
// tick_label_skip // TickLabelSkip
// reverse_order // ReverseOrder
// maximum // Maximum
// minimum // Minimum
// font // Font
// //
// The properties of y_axis that can be set are: // The properties of YAxis that can be set are:
// //
// none // None
// major_grid_lines // MajorGridLines
// minor_grid_lines // MinorGridLines
// major_unit // MajorUnit
// tick_label_skip // TickLabelSkip
// reverse_order // ReverseOrder
// maximum // Maximum
// minimum // Minimum
// font // Font
// //
// none: Disable axes. // 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 // Bold
// italic // Italic
// underline // Underline
// family // Family
// size // Size
// strike // Strike
// color // Color
// vertAlign // VertAlign
// //
// Set chart size by dimension property. The dimension property is optional. The default width is 480, and height is 290. // 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() { // 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() // f := excelize.NewFile()
// for k, v := range categories { // for idx, row := range [][]interface{}{
// f.SetCellValue("Sheet1", k, v) // {nil, "Apple", "Orange", "Pear"}, {"Small", 2, 3, 3},
// } // {"Normal", 5, 2, 4}, {"Large", 6, 7, 8},
// for k, v := range values { // } {
// f.SetCellValue("Sheet1", k, v) // cell, err := excelize.CoordinatesToCellName(1, idx+1)
// } // if err != nil {
// 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
// }
// }`, `{
// "type": "line",
// "series": [
// {
// "name": "Sheet1!$A$4",
// "categories": "Sheet1!$B$1:$D$1",
// "values": "Sheet1!$B$4:$D$4",
// "marker":
// {
// "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
// },
// "legend":
// {
// "position": "right",
// "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
// }
// }`); err != nil {
// fmt.Println(err) // fmt.Println(err)
// return // return
// } // }
// // Save spreadsheet file by the given path. // 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{
// {
// Name: "Sheet1!$A$2",
// Categories: "Sheet1!$B$1:$D$1",
// Values: "Sheet1!$B$2:$D$2",
// },
// },
// Format: excelize.Picture{
// XScale: &scale,
// YScale: &scale,
// OffsetX: 15,
// OffsetY: 10,
// PrintObject: &enable,
// LockAspectRatio: false,
// Locked: &disable,
// },
// 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 by the given path.
// if err := f.SaveAs("Book1.xlsx"); err != nil { // if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err) // fmt.Println(err)
// } // }
// } // }
func (f *File) AddChart(sheet, cell, opts string, combo ...string) error { func (f *File) AddChart(sheet, cell string, chart *Chart, combo ...*Chart) error {
// Read sheet data. // Read worksheet data
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
return err return err
} }
options, comboCharts, err := f.getChartOptions(opts, combo) opts, comboCharts, err := f.getChartOptions(chart, combo)
if err != nil { if err != nil {
return err 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) drawingID, drawingXML = f.prepareDrawing(ws, drawingID, sheet, drawingXML)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") 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 { if err != nil {
return err return err
} }
f.addChart(options, comboCharts) f.addChart(opts, comboCharts)
if err = f.addContentTypePart(chartID, "chart"); err != nil { if err = f.addContentTypePart(chartID, "chart"); err != nil {
return err 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) // 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 // and properties set. In Excel a chartsheet is a worksheet that only contains
// a chart. // 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 // Check if the worksheet already exists
idx, err := f.GetSheetIndex(sheet) idx, err := f.GetSheetIndex(sheet)
if err != nil { if err != nil {
@ -948,7 +935,7 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
if idx != -1 { if idx != -1 {
return ErrExistsSheet return ErrExistsSheet
} }
options, comboCharts, err := f.getChartOptions(opts, combo) opts, comboCharts, err := f.getChartOptions(chart, combo)
if err != nil { if err != nil {
return err return err
} }
@ -975,10 +962,10 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
f.prepareChartSheetDrawing(&cs, drawingID, sheet) f.prepareChartSheetDrawing(&cs, drawingID, sheet)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") 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 return err
} }
f.addChart(options, comboCharts) f.addChart(opts, comboCharts)
if err = f.addContentTypePart(chartID, "chart"); err != nil { if err = f.addContentTypePart(chartID, "chart"); err != nil {
return err 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 // getChartOptions provides a function to check format set of the chart and
// create chart format. // create chart format.
func (f *File) getChartOptions(opts string, combo []string) (*chartOptions, []*chartOptions, error) { func (f *File) getChartOptions(opts *Chart, combo []*Chart) (*Chart, []*Chart, error) {
var comboCharts []*chartOptions var comboCharts []*Chart
options, err := parseChartOptions(opts) options, err := parseChartOptions(opts)
if err != nil { if err != nil {
return options, comboCharts, err return options, comboCharts, err

View File

@ -41,11 +41,20 @@ func TestChartSize(t *testing.T) {
assert.NoError(t, f.SetCellValue(sheet1, cell, v)) assert.NoError(t, f.SetCellValue(sheet1, cell, v))
} }
assert.NoError(t, f.AddChart("Sheet1", "E4", `{"type":"col3DClustered","dimension":{"width":640, "height":480},`+ width, height := 640, 480
`"series":[{"name":"Sheet1!$A$2","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$2:$D$2"},`+ assert.NoError(t, f.AddChart("Sheet1", "E4", &Chart{
`{"name":"Sheet1!$A$3","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$3:$D$3"},`+ Type: "col3DClustered",
`{"name":"Sheet1!$A$4","categories":"Sheet1!$B$1:$D$1","values":"Sheet1!$B$4:$D$4"}],`+ Dimension: ChartDimension{
`"title":{"name":"3D Clustered Column Chart"}}`)) 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 var buffer bytes.Buffer
@ -98,14 +107,14 @@ func TestAddDrawingChart(t *testing.T) {
path := "xl/drawings/drawing1.xml" path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset) 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) { func TestAddSheetDrawingChart(t *testing.T) {
f := NewFile() f := NewFile()
path := "xl/drawings/drawing1.xml" path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset) 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) { func TestDeleteDrawing(t *testing.T) {
@ -129,75 +138,124 @@ func TestAddChart(t *testing.T) {
for k, v := range values { for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v)) 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. // Test add chart on not exists worksheet
assert.EqualError(t, f.AddChart("SheetN", "P1", "{}"), "sheet SheetN does not exist") assert.EqualError(t, f.AddChart("SheetN", "P1", nil), "sheet SheetN does not exist")
positionLeft, positionBottom, positionRight, positionTop, positionTopRight := "left", "bottom", "right", "top", "top_right"
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"}}}`)) maximum, minimum, zero := 7.5, 0.5, .0
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"}`)) series := []ChartSeries{
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"}`)) {Name: "Sheet1!$A$30", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$30:$D$30"},
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"}`)) {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
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"}`)) {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
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"}`)) {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
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"}`)) {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
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"}`)) {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
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"}`)) {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
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"}`)) {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37"},
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"}`)) series2 := []ChartSeries{
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"}`)) {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"}},
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"}`)) {Name: "Sheet1!$A$31", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$31:$D$31"},
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"}`)) {Name: "Sheet1!$A$32", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$32:$D$32"},
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"}`)) {Name: "Sheet1!$A$33", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$33:$D$33"},
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"}`)) {Name: "Sheet1!$A$34", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$34:$D$34"},
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"}`)) {Name: "Sheet1!$A$35", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$35:$D$35"},
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"}`)) {Name: "Sheet1!$A$36", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$36:$D$36"},
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"}`)) {Name: "Sheet1!$A$37", Categories: "Sheet1!$B$29:$D$29", Values: "Sheet1!$B$37:$D$37", Line: ChartLine{Width: 0.25}},
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"}`)) series3 := []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}
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}`)) format := PictureOptions{
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}}`)) XScale: float64Ptr(defaultPictureScale),
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"}`)) YScale: float64Ptr(defaultPictureScale),
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"}`)) OffsetX: 15,
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"}`)) OffsetY: 10,
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"}`)) PrintObject: boolPtr(true),
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"}`)) LockAspectRatio: false,
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"}`)) Locked: boolPtr(false),
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}}`)) legend := ChartLegend{Position: &positionLeft, ShowLegendKey: false}
// area series charts plotArea := ChartPlotArea{
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"}`)) ShowBubbleSize: true,
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"}`)) ShowCatName: true,
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"}`)) ShowLeaderLines: false,
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"}`)) ShowPercent: true,
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"}`)) ShowSerName: true,
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"}`)) 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 // 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"}`)) {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"}},
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"}`)) {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"}},
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"}`)) {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 // 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"}`)) {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"}},
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"}`)) {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"}},
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"}`)) {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"}},
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"}`)) {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"}},
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"}`)) {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"}},
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"}`)) {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 // 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}}`)) {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}}},
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}}`)) {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}}},
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"}`)) {sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: "contour", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "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"}`)) {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 // 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"}`)) {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: "bubble", Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble Chart"}, PlotArea: plotArea, ShowBlanksAs: "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}}`)) {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 // 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}}`)) {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 // 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}}`)) {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 // combo chart
f.NewSheet("Combo Charts") _, err = f.NewSheet("Combo Charts")
assert.NoError(t, err)
clusteredColumnCombo := [][]string{ clusteredColumnCombo := [][]string{
{"A1", "line", "Clustered Column - Line Chart"}, {"A1", "line", "Clustered Column - Line Chart"},
{"I1", "bubble", "Clustered Column - Bubble Chart"}, {"I1", "bubble", "Clustered Column - Bubble Chart"},
@ -205,7 +263,7 @@ func TestAddChart(t *testing.T) {
{"Y1", "doughnut", "Clustered Column - Doughnut Chart"}, {"Y1", "doughnut", "Clustered Column - Doughnut Chart"},
} }
for _, props := range clusteredColumnCombo { 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{ stackedAreaCombo := map[string][]string{
"A16": {"line", "Stacked Area - Line Chart"}, "A16": {"line", "Stacked Area - Line Chart"},
@ -214,25 +272,25 @@ func TestAddChart(t *testing.T) {
"Y16": {"doughnut", "Stacked Area - Doughnut Chart"}, "Y16": {"doughnut", "Stacked Area - Doughnut Chart"},
} }
for axis, props := range stackedAreaCombo { 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"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx")))
// Test with invalid sheet name // 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 // 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 // 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 // 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 // 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()) assert.NoError(t, f.Close())
// Test add chart with unsupported charset content types. // Test add chart with unsupported charset content types.
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) 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) { func TestAddChartSheet(t *testing.T) {
@ -245,7 +303,12 @@ func TestAddChartSheet(t *testing.T) {
for k, v := range values { for k, v := range values {
assert.NoError(t, f.SetCellValue("Sheet1", k, v)) 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 // Test set the chartsheet as active sheet
var sheetIdx int var sheetIdx int
for idx, sheetName := range f.GetSheetList() { for idx, sheetName := range f.GetSheetList() {
@ -259,11 +322,12 @@ func TestAddChartSheet(t *testing.T) {
// Test cell value on chartsheet // Test cell value on chartsheet
assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet") assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet")
// Test add chartsheet on already existing name sheet // 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 // 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 // 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()) assert.NoError(t, f.UpdateLinkedValue())
@ -272,14 +336,43 @@ func TestAddChartSheet(t *testing.T) {
f = NewFile() f = NewFile()
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) 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) { func TestDeleteChart(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.DeleteChart("Sheet1", "A1")) 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.DeleteChart("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx")))
// Test delete chart with invalid sheet name // Test delete chart with invalid sheet name
@ -322,37 +415,22 @@ func TestChartWithLogarithmicBase(t *testing.T) {
for cell, v := range categories { for cell, v := range categories {
assert.NoError(t, f.SetCellValue(sheet1, cell, v)) assert.NoError(t, f.SetCellValue(sheet1, cell, v))
} }
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 // Add two chart, one without and one with log scaling
assert.NoError(t, f.AddChart(sheet1, "C1", assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts))
`{"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"}}`))
// Export XLSX file for human confirmation // Export XLSX file for human confirmation
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestChartWithLogarithmicBase10.xlsx")))

View File

@ -151,7 +151,6 @@ func TestGetColsError(t *testing.T) {
func TestColsRows(t *testing.T) { func TestColsRows(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet1")
_, err := f.Cols("Sheet1") _, err := f.Cols("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
@ -231,7 +230,8 @@ func TestColumnVisibility(t *testing.T) {
// Test set column visible with invalid sheet name // Test set column visible with invalid sheet name
assert.EqualError(t, f.SetColVisible("Sheet:1", "A", false), ErrSheetNameInvalid.Error()) 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.NoError(t, f.SetColVisible("Sheet3", "E", false))
assert.EqualError(t, f.SetColVisible("Sheet1", "A:-1", true), newInvalidColumnNameError("-1").Error()) 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") 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.Equal(t, uint8(0), level)
assert.NoError(t, err) assert.NoError(t, err)
f.NewSheet("Sheet2") _, err = f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4)) assert.NoError(t, f.SetColOutlineLevel("Sheet1", "D", 4))
level, err = f.GetColOutlineLevel("Sheet1", "D") level, err = f.GetColOutlineLevel("Sheet1", "D")
@ -318,7 +319,8 @@ func TestOutlineLevel(t *testing.T) {
func TestSetColStyle(t *testing.T) { func TestSetColStyle(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "B2", "Hello")) 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) assert.NoError(t, err)
// Test set column style on not exists worksheet // Test set column style on not exists worksheet
assert.EqualError(t, f.SetColStyle("SheetN", "E", styleID), "sheet SheetN does not exist") 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.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
assert.NoError(t, f.MergeCell(sheet1, "A1", "C3")) 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)) assert.NoError(t, f.InsertCols(sheet1, "A", 1))
// Test insert column with illegal cell reference // Test insert column with illegal cell reference

View File

@ -51,7 +51,8 @@ func TestDataValidation(t *testing.T) {
assert.NoError(t, f.SaveAs(resultFile)) 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", "A2", &[]interface{}{"B2", 1}))
assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3})) assert.NoError(t, f.SetSheetRow("Sheet2", "A3", &[]interface{}{"B3", 3}))
dvRange = NewDataValidation(true) dvRange = NewDataValidation(true)

View File

@ -147,6 +147,13 @@ func (f *File) GetAppProps() (ret *AppProperties, err error) {
// Category | A categorization of the content of this package. // Category | A categorization of the content of this package.
// | // |
// Version | The version number. This value is set by the user or by the application. // 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: // For example:
// //

View File

@ -58,7 +58,7 @@ func TestGetAppProps(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
// Test get application properties with unsupported charset. // Test get application properties with unsupported charset
f = NewFile() f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
_, err = f.GetAppProps() _, err = f.GetAppProps()
@ -110,7 +110,7 @@ func TestGetDocProps(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
// Test get workbook properties with unsupported charset. // Test get workbook properties with unsupported charset
f = NewFile() f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
_, err = f.GetDocProps() _, err = f.GetDocProps()

View File

@ -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 // addChart provides a function to create chart as xl/charts/chart%d.xml by
// given format sets. // given format sets.
func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) { func (f *File) addChart(opts *Chart, comboCharts []*Chart) {
count := f.countCharts() count := f.countCharts()
xlsxChartSpace := xlsxChartSpace{ xlsxChartSpace := xlsxChartSpace{
XMLNSa: NameSpaceDrawingML.Value, XMLNSa: NameSpaceDrawingML.Value,
@ -139,7 +139,7 @@ func (f *File) addChart(opts *chartOptions, comboCharts []*chartOptions) {
}, },
PlotArea: &cPlotArea{}, PlotArea: &cPlotArea{},
Legend: &cLegend{ Legend: &cLegend{
LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[opts.Legend.Position])}, LegendPos: &attrValString{Val: stringPtr(chartLegendPosition[*opts.Legend.Position])},
Overlay: &attrValBool{Val: boolPtr(false)}, 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, Area: f.drawBaseChart,
AreaStacked: f.drawBaseChart, AreaStacked: f.drawBaseChart,
AreaPercentStacked: 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, // drawBaseChart provides a function to draw the c:plotArea element for bar,
// and column series charts by given format sets. // and column series charts by given format sets.
func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea { func (f *File) drawBaseChart(opts *Chart) *cPlotArea {
c := cCharts{ c := cCharts{
BarDir: &attrValString{ BarDir: &attrValString{
Val: stringPtr("col"), Val: stringPtr("col"),
@ -273,7 +273,7 @@ func (f *File) drawBaseChart(opts *chartOptions) *cPlotArea {
Val: stringPtr("clustered"), Val: stringPtr("clustered"),
}, },
VaryColors: &attrValBool{ VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors), Val: opts.VaryColors,
}, },
Ser: f.drawChartSeries(opts), Ser: f.drawChartSeries(opts),
Shape: f.drawChartShape(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 // drawDoughnutChart provides a function to draw the c:plotArea element for
// doughnut chart by given format sets. // doughnut chart by given format sets.
func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea { func (f *File) drawDoughnutChart(opts *Chart) *cPlotArea {
holeSize := 75 holeSize := 75
if opts.HoleSize > 0 && opts.HoleSize <= 90 { if opts.HoleSize > 0 && opts.HoleSize <= 90 {
holeSize = opts.HoleSize holeSize = opts.HoleSize
@ -522,7 +522,7 @@ func (f *File) drawDoughnutChart(opts *chartOptions) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
DoughnutChart: &cCharts{ DoughnutChart: &cCharts{
VaryColors: &attrValBool{ VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors), Val: opts.VaryColors,
}, },
Ser: f.drawChartSeries(opts), Ser: f.drawChartSeries(opts),
HoleSize: &attrValInt{Val: intPtr(holeSize)}, 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 // drawLineChart provides a function to draw the c:plotArea element for line
// chart by given format sets. // chart by given format sets.
func (f *File) drawLineChart(opts *chartOptions) *cPlotArea { func (f *File) drawLineChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
LineChart: &cCharts{ LineChart: &cCharts{
Grouping: &attrValString{ 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 // drawLine3DChart provides a function to draw the c:plotArea element for line
// chart by given format sets. // chart by given format sets.
func (f *File) drawLine3DChart(opts *chartOptions) *cPlotArea { func (f *File) drawLine3DChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
Line3DChart: &cCharts{ Line3DChart: &cCharts{
Grouping: &attrValString{ 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 // drawPieChart provides a function to draw the c:plotArea element for pie
// chart by given format sets. // chart by given format sets.
func (f *File) drawPieChart(opts *chartOptions) *cPlotArea { func (f *File) drawPieChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
PieChart: &cCharts{ PieChart: &cCharts{
VaryColors: &attrValBool{ VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors), Val: opts.VaryColors,
}, },
Ser: f.drawChartSeries(opts), 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 // drawPie3DChart provides a function to draw the c:plotArea element for 3D
// pie chart by given format sets. // pie chart by given format sets.
func (f *File) drawPie3DChart(opts *chartOptions) *cPlotArea { func (f *File) drawPie3DChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
Pie3DChart: &cCharts{ Pie3DChart: &cCharts{
VaryColors: &attrValBool{ VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors), Val: opts.VaryColors,
}, },
Ser: f.drawChartSeries(opts), 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 // drawPieOfPieChart provides a function to draw the c:plotArea element for
// pie chart by given format sets. // pie chart by given format sets.
func (f *File) drawPieOfPieChart(opts *chartOptions) *cPlotArea { func (f *File) drawPieOfPieChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
OfPieChart: &cCharts{ OfPieChart: &cCharts{
OfPieType: &attrValString{ OfPieType: &attrValString{
Val: stringPtr("pie"), Val: stringPtr("pie"),
}, },
VaryColors: &attrValBool{ VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors), Val: opts.VaryColors,
}, },
Ser: f.drawChartSeries(opts), Ser: f.drawChartSeries(opts),
SerLines: &attrValString{}, SerLines: &attrValString{},
@ -621,14 +621,14 @@ func (f *File) drawPieOfPieChart(opts *chartOptions) *cPlotArea {
// drawBarOfPieChart provides a function to draw the c:plotArea element for // drawBarOfPieChart provides a function to draw the c:plotArea element for
// pie chart by given format sets. // pie chart by given format sets.
func (f *File) drawBarOfPieChart(opts *chartOptions) *cPlotArea { func (f *File) drawBarOfPieChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
OfPieChart: &cCharts{ OfPieChart: &cCharts{
OfPieType: &attrValString{ OfPieType: &attrValString{
Val: stringPtr("bar"), Val: stringPtr("bar"),
}, },
VaryColors: &attrValBool{ VaryColors: &attrValBool{
Val: boolPtr(opts.VaryColors), Val: opts.VaryColors,
}, },
Ser: f.drawChartSeries(opts), Ser: f.drawChartSeries(opts),
SerLines: &attrValString{}, 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 // drawRadarChart provides a function to draw the c:plotArea element for radar
// chart by given format sets. // chart by given format sets.
func (f *File) drawRadarChart(opts *chartOptions) *cPlotArea { func (f *File) drawRadarChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
RadarChart: &cCharts{ RadarChart: &cCharts{
RadarStyle: &attrValString{ RadarStyle: &attrValString{
@ -661,7 +661,7 @@ func (f *File) drawRadarChart(opts *chartOptions) *cPlotArea {
// drawScatterChart provides a function to draw the c:plotArea element for // drawScatterChart provides a function to draw the c:plotArea element for
// scatter chart by given format sets. // scatter chart by given format sets.
func (f *File) drawScatterChart(opts *chartOptions) *cPlotArea { func (f *File) drawScatterChart(opts *Chart) *cPlotArea {
return &cPlotArea{ return &cPlotArea{
ScatterChart: &cCharts{ ScatterChart: &cCharts{
ScatterStyle: &attrValString{ ScatterStyle: &attrValString{
@ -684,7 +684,7 @@ func (f *File) drawScatterChart(opts *chartOptions) *cPlotArea {
// drawSurface3DChart provides a function to draw the c:surface3DChart element by // drawSurface3DChart provides a function to draw the c:surface3DChart element by
// given format sets. // given format sets.
func (f *File) drawSurface3DChart(opts *chartOptions) *cPlotArea { func (f *File) drawSurface3DChart(opts *Chart) *cPlotArea {
plotArea := &cPlotArea{ plotArea := &cPlotArea{
Surface3DChart: &cCharts{ Surface3DChart: &cCharts{
Ser: f.drawChartSeries(opts), 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 // drawSurfaceChart provides a function to draw the c:surfaceChart element by
// given format sets. // given format sets.
func (f *File) drawSurfaceChart(opts *chartOptions) *cPlotArea { func (f *File) drawSurfaceChart(opts *Chart) *cPlotArea {
plotArea := &cPlotArea{ plotArea := &cPlotArea{
SurfaceChart: &cCharts{ SurfaceChart: &cCharts{
Ser: f.drawChartSeries(opts), 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 // drawChartShape provides a function to draw the c:shape element by given
// format sets. // format sets.
func (f *File) drawChartShape(opts *chartOptions) *attrValString { func (f *File) drawChartShape(opts *Chart) *attrValString {
shapes := map[string]string{ shapes := map[string]string{
Bar3DConeClustered: "cone", Bar3DConeClustered: "cone",
Bar3DConeStacked: "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 // drawChartSeries provides a function to draw the c:ser element by given
// format sets. // format sets.
func (f *File) drawChartSeries(opts *chartOptions) *[]cSer { func (f *File) drawChartSeries(opts *Chart) *[]cSer {
var ser []cSer var ser []cSer
for k := range opts.Series { for k := range opts.Series {
ser = append(ser, cSer{ 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 // drawChartSeriesSpPr provides a function to draw the c:spPr element by given
// format sets. // format sets.
func (f *File) drawChartSeriesSpPr(i int, opts *chartOptions) *cSpPr { func (f *File) drawChartSeriesSpPr(i int, opts *Chart) *cSpPr {
var srgbClr *attrValString var srgbClr *attrValString
var schemeClr *aSchemeClr 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 // drawChartSeriesDPt provides a function to draw the c:dPt element by given
// data index and format sets. // 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{{ dpt := []*cDPt{{
IDx: &attrValInt{Val: intPtr(i)}, IDx: &attrValInt{Val: intPtr(i)},
Bubble3D: &attrValBool{Val: boolPtr(false)}, 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 // drawChartSeriesCat provides a function to draw the c:cat element by given
// chart series and format sets. // 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{ cat := &cCat{
StrRef: &cStrRef{ StrRef: &cStrRef{
F: v.Categories, 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 // drawChartSeriesVal provides a function to draw the c:val element by given
// chart series and format sets. // 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{ val := &cVal{
NumRef: &cNumRef{ NumRef: &cNumRef{
F: v.Values, 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 // drawChartSeriesMarker provides a function to draw the c:marker element by
// given data index and format sets. // 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")}} defaultSymbol := map[string]*attrValString{Scatter: {Val: stringPtr("circle")}}
marker := &cMarker{ marker := &cMarker{
Symbol: defaultSymbol[opts.Type], 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 // drawChartSeriesXVal provides a function to draw the c:xVal element by given
// chart series and format sets. // 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{ cat := &cCat{
StrRef: &cStrRef{ StrRef: &cStrRef{
F: v.Categories, 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 // drawChartSeriesYVal provides a function to draw the c:yVal element by given
// chart series and format sets. // 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{ val := &cVal{
NumRef: &cNumRef{ NumRef: &cNumRef{
F: v.Values, 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 // drawCharSeriesBubbleSize provides a function to draw the c:bubbleSize
// element by given chart series and format sets. // 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 { if _, ok := map[string]bool{Bubble: true, Bubble3D: true}[opts.Type]; !ok {
return nil return nil
} }
@ -954,7 +954,7 @@ func (f *File) drawCharSeriesBubbleSize(v chartSeriesOptions, opts *chartOptions
// drawCharSeriesBubble3D provides a function to draw the c:bubble3D element // drawCharSeriesBubble3D provides a function to draw the c:bubble3D element
// by given format sets. // 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 { if _, ok := map[string]bool{Bubble3D: true}[opts.Type]; !ok {
return nil 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 // drawChartDLbls provides a function to draw the c:dLbls element by given
// format sets. // format sets.
func (f *File) drawChartDLbls(opts *chartOptions) *cDLbls { func (f *File) drawChartDLbls(opts *Chart) *cDLbls {
return &cDLbls{ return &cDLbls{
ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)}, ShowLegendKey: &attrValBool{Val: boolPtr(opts.Legend.ShowLegendKey)},
ShowVal: &attrValBool{Val: boolPtr(opts.Plotarea.ShowVal)}, ShowVal: &attrValBool{Val: boolPtr(opts.PlotArea.ShowVal)},
ShowCatName: &attrValBool{Val: boolPtr(opts.Plotarea.ShowCatName)}, ShowCatName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowCatName)},
ShowSerName: &attrValBool{Val: boolPtr(opts.Plotarea.ShowSerName)}, ShowSerName: &attrValBool{Val: boolPtr(opts.PlotArea.ShowSerName)},
ShowBubbleSize: &attrValBool{Val: boolPtr(opts.Plotarea.ShowBubbleSize)}, ShowBubbleSize: &attrValBool{Val: boolPtr(opts.PlotArea.ShowBubbleSize)},
ShowPercent: &attrValBool{Val: boolPtr(opts.Plotarea.ShowPercent)}, ShowPercent: &attrValBool{Val: boolPtr(opts.PlotArea.ShowPercent)},
ShowLeaderLines: &attrValBool{Val: boolPtr(opts.Plotarea.ShowLeaderLines)}, ShowLeaderLines: &attrValBool{Val: boolPtr(opts.PlotArea.ShowLeaderLines)},
} }
} }
// drawChartSeriesDLbls provides a function to draw the c:dLbls element by // drawChartSeriesDLbls provides a function to draw the c:dLbls element by
// given format sets. // given format sets.
func (f *File) drawChartSeriesDLbls(opts *chartOptions) *cDLbls { func (f *File) drawChartSeriesDLbls(opts *Chart) *cDLbls {
dLbls := f.drawChartDLbls(opts) dLbls := f.drawChartDLbls(opts)
chartSeriesDLbls := map[string]*cDLbls{ chartSeriesDLbls := map[string]*cDLbls{
Scatter: nil, Surface3D: nil, WireframeSurface3D: nil, Contour: nil, WireframeContour: nil, Bubble: nil, Bubble3D: nil, 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. // 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} max := &attrValFloat{Val: opts.XAxis.Maximum}
min := &attrValFloat{Val: opts.XAxis.Minimum} min := &attrValFloat{Val: opts.XAxis.Minimum}
if opts.XAxis.Maximum == nil { if opts.XAxis.Maximum == nil {
@ -1025,10 +1025,10 @@ func (f *File) drawPlotAreaCatAx(opts *chartOptions) []*cAxs {
NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)}, NoMultiLvlLbl: &attrValBool{Val: boolPtr(false)},
}, },
} }
if opts.XAxis.MajorGridlines { if opts.XAxis.MajorGridLines {
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
} }
if opts.XAxis.MinorGridlines { if opts.XAxis.MinorGridLines {
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
} }
if opts.XAxis.TickLabelSkip != 0 { 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. // 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} max := &attrValFloat{Val: opts.YAxis.Maximum}
min := &attrValFloat{Val: opts.YAxis.Minimum} min := &attrValFloat{Val: opts.YAxis.Minimum}
if opts.YAxis.Maximum == nil { if opts.YAxis.Maximum == nil {
@ -1076,10 +1076,10 @@ func (f *File) drawPlotAreaValAx(opts *chartOptions) []*cAxs {
CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])}, CrossBetween: &attrValString{Val: stringPtr(chartValAxCrossBetween[opts.Type])},
}, },
} }
if opts.YAxis.MajorGridlines { if opts.YAxis.MajorGridLines {
axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} axs[0].MajorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
} }
if opts.YAxis.MinorGridlines { if opts.YAxis.MinorGridLines {
axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()} axs[0].MinorGridlines = &cChartLines{SpPr: f.drawPlotAreaSpPr()}
} }
if pos, ok := valTickLblPos[opts.Type]; ok { 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. // 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} max := &attrValFloat{Val: opts.YAxis.Maximum}
min := &attrValFloat{Val: opts.YAxis.Minimum} min := &attrValFloat{Val: opts.YAxis.Minimum}
if opts.YAxis.Maximum == nil { if opts.YAxis.Maximum == nil {
@ -1139,7 +1139,7 @@ func (f *File) drawPlotAreaSpPr() *cSpPr {
} }
// drawPlotAreaTxPr provides a function to draw the c:txPr element. // 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{ cTxPr := &cTxPr{
BodyPr: aBodyPr{ BodyPr: aBodyPr{
Rot: -60000000, 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 // addDrawingChart provides a function to add chart graphic frame by given
// sheet, drawingXML, cell, width, height, relationship index and format sets. // 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) col, row, err := CellNameToCoordinates(cell)
if err != nil { if err != nil {
return err return err
@ -1250,8 +1250,8 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
colIdx := col - 1 colIdx := col - 1
rowIdx := row - 1 rowIdx := row - 1
width = int(float64(width) * opts.XScale) width = int(float64(width) * *opts.XScale)
height = int(float64(height) * opts.YScale) height = int(float64(height) * *opts.YScale)
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.OffsetX, opts.OffsetY, width, height) colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.OffsetX, opts.OffsetY, width, height)
content, cNvPrID, err := f.drawingParser(drawingXML) content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil { if err != nil {
@ -1293,8 +1293,8 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
graphic, _ := xml.Marshal(graphicFrame) graphic, _ := xml.Marshal(graphicFrame)
twoCellAnchor.GraphicFrame = string(graphic) twoCellAnchor.GraphicFrame = string(graphic)
twoCellAnchor.ClientData = &xdrClientData{ twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.FLocksWithSheet, FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: opts.FPrintsWithSheet, FPrintsWithSheet: *opts.PrintObject,
} }
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
f.Drawings.Store(drawingXML, content) 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 // addSheetDrawingChart provides a function to add chart graphic frame for
// chartsheet by given sheet, drawingXML, width, height, relationship index // chartsheet by given sheet, drawingXML, width, height, relationship index
// and format sets. // 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) content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil { if err != nil {
return err return err
@ -1336,8 +1336,8 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOpt
graphic, _ := xml.Marshal(graphicFrame) graphic, _ := xml.Marshal(graphicFrame)
absoluteAnchor.GraphicFrame = string(graphic) absoluteAnchor.GraphicFrame = string(graphic)
absoluteAnchor.ClientData = &xdrClientData{ absoluteAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.FLocksWithSheet, FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: opts.FPrintsWithSheet, FPrintsWithSheet: *opts.PrintObject,
} }
content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor) content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
f.Drawings.Store(drawingXML, content) f.Drawings.Store(drawingXML, content)

View File

@ -26,13 +26,13 @@ func TestDrawingParser(t *testing.T) {
} }
f.Pkg.Store("charset", MacintoshCyrillicCharset) f.Pkg.Store("charset", MacintoshCyrillicCharset)
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`)) f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`))
// Test with one cell anchor. // Test with one cell anchor
_, _, err := f.drawingParser("wsDr") _, _, err := f.drawingParser("wsDr")
assert.NoError(t, err) assert.NoError(t, err)
// Test with unsupported charset. // Test with unsupported charset
_, _, err = f.drawingParser("charset") _, _, err = f.drawingParser("charset")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") 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.Drawings = sync.Map{}
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" Requires="a14"><xdr:twoCellAnchor editAs="oneCell"></xdr:twoCellAnchor></mc:Choice><mc:Fallback/></mc:AlternateContent></xdr:wsDr>`)) f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" Requires="a14"><xdr:twoCellAnchor editAs="oneCell"></xdr:twoCellAnchor></mc:Choice><mc:Fallback/></mc:AlternateContent></xdr:wsDr>`))
_, _, err = f.drawingParser("wsDr") _, _, err = f.drawingParser("wsDr")

View File

@ -71,9 +71,10 @@ func TestOpenFile(t *testing.T) {
assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns")) assert.NoError(t, f.SetCellStr("Sheet2", "C11", "Knowns"))
// Test max characters in a cell // Test max characters in a cell
assert.NoError(t, f.SetCellStr("Sheet2", "D11", strings.Repeat("c", TotalCellChars+2))) 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 // 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.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("Sheet3", "b230", "10"), "sheet Sheet3 does not exist")
assert.EqualError(t, f.SetCellStr("Sheet10", "b230", "10"), "sheet Sheet10 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) assert.NoError(t, err)
getSharedFormula(&xlsxWorksheet{}, 0, "") 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") _, err = f.GetCellValue("Sheet2", "a-1")
assert.EqualError(t, err, newCellNameToCoordinatesError("A-1", newInvalidCellNameError("A-1")).Error()) assert.EqualError(t, err, newCellNameToCoordinatesError("A-1", newInvalidCellNameError("A-1")).Error())
_, err = f.GetCellValue("Sheet2", "A") _, err = f.GetCellValue("Sheet2", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) 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") _, err = f.GetCellValue("Sheet2", "a5")
assert.NoError(t, err) assert.NoError(t, err)
_, err = f.GetCellValue("Sheet2", "C11") _, 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") assert.EqualError(t, f.SetCellHyperLink("SheetN", "A1", "Sheet1!A40", "Location"), "sheet SheetN does not exist")
// Test boolean write // Test boolean write
booltest := []struct { boolTest := []struct {
value bool value bool
raw bool raw bool
expected string expected string
@ -155,7 +156,7 @@ func TestOpenFile(t *testing.T) {
{false, false, "FALSE"}, {false, false, "FALSE"},
{true, false, "TRUE"}, {true, false, "TRUE"},
} }
for _, test := range booltest { for _, test := range boolTest {
assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value)) assert.NoError(t, f.SetCellValue("Sheet2", "F16", test.value))
val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw}) val, err := f.GetCellValue("Sheet2", "F16", Options{RawCellValue: test.raw})
assert.NoError(t, err) 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 // Test read cell value with given cell reference large than exists row
_, err = f.GetCellValue("Sheet2", "E231") _, err = f.GetCellValue("Sheet2", "E231")
assert.NoError(t, err) 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()) f.GetSheetName(f.GetActiveSheetIndex())
// Test get worksheet index of spreadsheet by given worksheet name // 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 // Test get worksheet name of spreadsheet by given invalid worksheet index
f.GetSheetName(4) f.GetSheetName(4)
// Test get worksheet map of workbook // Test get worksheet map of workbook
@ -339,31 +342,23 @@ func TestBrokenFile(t *testing.T) {
func TestNewFile(t *testing.T) { func TestNewFile(t *testing.T) {
// Test create a spreadsheet file // Test create a spreadsheet file
f := NewFile() f := NewFile()
f.NewSheet("Sheet1") _, err := f.NewSheet("Sheet1")
f.NewSheet("XLSXSheet2") assert.NoError(t, err)
f.NewSheet("XLSXSheet3") _, 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.SetCellInt("XLSXSheet2", "A23", 56))
assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42")) assert.NoError(t, f.SetCellStr("Sheet1", "B20", "42"))
f.SetActiveSheet(0) f.SetActiveSheet(0)
// Test add picture to sheet with scaling and positioning // Test add picture to sheet with scaling and positioning
err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), scale := 0.5
`{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`) assert.NoError(t, f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
if !assert.NoError(t, err) { &PictureOptions{XScale: &scale, YScale: &scale, Positioning: "absolute"}))
t.FailNow()
}
// Test add picture to worksheet without options // Test add picture to worksheet without options
err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), "") assert.NoError(t, f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil))
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.SaveAs(filepath.Join("test", "TestNewFile.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewFile.xlsx")))
assert.NoError(t, f.Save()) 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")) assert.NoError(t, f.SetCellHyperLink("Sheet1", "B19", "https://github.com/xuri/excelize", "External"))
// Test add first hyperlink in a work sheet // Test add first hyperlink in a work sheet
assert.NoError(t, f.SetCellHyperLink("Sheet2", "C1", "https://github.com/xuri/excelize", "External")) 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")) assert.NoError(t, f.SetCellHyperLink("Sheet2", "D6", "Sheet1!D8", "Location"))
// Test add Location hyperlink with display & tooltip in a work sheet // Test add Location hyperlink with display & tooltip in a work sheet
display, tooltip := "Display value", "Hover text" display, tooltip := "Display value", "Hover text"
@ -429,9 +424,7 @@ func TestSetCellHyperLink(t *testing.T) {
func TestGetCellHyperLink(t *testing.T) { func TestGetCellHyperLink(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
_, _, err = f.GetCellHyperLink("Sheet1", "") _, _, err = f.GetCellHyperLink("Sheet1", "")
assert.EqualError(t, err, `invalid cell name ""`) 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") 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 // TestWriteArrayFormula tests the extended options of SetCellFormula by writing
// to a workbook. In the resulting file, the lines 2 and 3 as well as 4 and 5 should have matching // an array function to a workbook. In the resulting file, the lines 2 and 3 as
// contents. // well as 4 and 5 should have matching contents
func TestWriteArrayFormula(t *testing.T) { func TestWriteArrayFormula(t *testing.T) {
cell := func(col, row int) string { cell := func(col, row int) string {
c, err := CoordinatesToCellName(col, row) c, err := CoordinatesToCellName(col, row)
@ -620,15 +613,11 @@ func TestWriteArrayFormula(t *testing.T) {
func TestSetCellStyleAlignment(t *testing.T) { func TestSetCellStyleAlignment(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
var style int 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}}`) 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}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "A22", "A22", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A22", "A22", style))
@ -651,13 +640,11 @@ func TestSetCellStyleAlignment(t *testing.T) {
func TestSetCellStyleBorder(t *testing.T) { func TestSetCellStyleBorder(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
var style int 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{ style, err = f.NewStyle(&Style{
Border: []Border{ Border: []Border{
{Type: "left", Color: "0000FF", Style: 3}, {Type: "left", Color: "0000FF", Style: 3},
@ -668,24 +655,18 @@ func TestSetCellStyleBorder(t *testing.T) {
{Type: "diagonalUp", Color: "A020F0", Style: 8}, {Type: "diagonalUp", Color: "A020F0", Style: 8},
}, },
}) })
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "J21", "L25", style)) 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}}`) 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}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) 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}}`) 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}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "M28", "K24", style)) 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{ style, err = f.NewStyle(&Style{
Border: []Border{ Border: []Border{
{ {
@ -725,9 +706,7 @@ func TestSetCellStyleBorder(t *testing.T) {
Pattern: 1, Pattern: 1,
}, },
}) })
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "O22", "O22", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "O22", "O22", style))
@ -736,30 +715,18 @@ func TestSetCellStyleBorder(t *testing.T) {
func TestSetCellStyleBorderErrors(t *testing.T) { func TestSetCellStyleBorderErrors(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// Set border with invalid style parameter. // Set border with invalid style index number
_, err = f.NewStyle("") _, 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}}})
if !assert.EqualError(t, err, "unexpected end of JSON input") { assert.NoError(t, err)
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()
}
} }
func TestSetCellStyleNumberFormat(t *testing.T) { func TestSetCellStyleNumberFormat(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// 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"} 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} 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"} value := []string{"37947.7500001", "-37947.7500001", "0.007", "2.1", "String"}
@ -781,7 +748,7 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
} else { } else {
assert.NoError(t, f.SetCellValue("Sheet2", c, val)) 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) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()
} }
@ -792,10 +759,8 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
} }
} }
var style int var style int
style, err = f.NewStyle(`{"number_format":-1}`) style, err = f.NewStyle(&Style{NumFmt: -1})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "L33", "L33", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "L33", "L33", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleNumberFormat.xlsx")))
@ -804,23 +769,17 @@ func TestSetCellStyleNumberFormat(t *testing.T) {
func TestSetCellStyleCurrencyNumberFormat(t *testing.T) { func TestSetCellStyleCurrencyNumberFormat(t *testing.T) {
t.Run("TestBook3", func(t *testing.T) { t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3() f, err := prepareTestBook3()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 56)) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 56))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", -32.3)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", -32.3))
var style int var style int
style, err = f.NewStyle(`{"number_format": 188, "decimal_places": -1}`) style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: -1})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(`{"number_format": 188, "decimal_places": 31, "negred": true}`) style, err = f.NewStyle(&Style{NumFmt: 188, DecimalPlaces: 31, NegRed: true})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) 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) { t.Run("TestBook4", func(t *testing.T) {
f, err := prepareTestBook4() f, err := prepareTestBook4()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A2", 42920.5))
_, err = f.NewStyle(`{"number_format": 26, "lang": "zh-tw"}`) _, err = f.NewStyle(&Style{NumFmt: 26, Lang: "zh-tw"})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
style, err := f.NewStyle(`{"number_format": 27}`) style, err := f.NewStyle(&Style{NumFmt: 27})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
style, err = f.NewStyle(`{"number_format": 31, "lang": "ko-kr"}`) style, err = f.NewStyle(&Style{NumFmt: 31, Lang: "ko-kr"})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
style, err = f.NewStyle(`{"number_format": 71, "lang": "th-th"}`) style, err = f.NewStyle(&Style{NumFmt: 71, Lang: "th-th"})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCurrencyNumberFormat.TestBook4.xlsx")))
@ -867,42 +816,40 @@ func TestSetCellStyleCustomNumberFormat(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5)) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 42920.5))
assert.NoError(t, f.SetCellValue("Sheet1", "A2", 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, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style)) 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, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A2", "A2", style)) 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, err)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleCustomNumberFormat.xlsx")))
} }
func TestSetCellStyleFill(t *testing.T) { func TestSetCellStyleFill(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
var style int var style int
// Test set fill for cell with invalid parameter. // Test set fill for cell with invalid parameter
style, err = f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":6}}`) style, err = f.NewStyle(&Style{Fill: Fill{Type: "gradient", Color: []string{"#FFFFFF", "#E0EBF5"}, Shading: 6}})
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) 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, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) 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, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
style, err = f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":19}}`) style, err = f.NewStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"E0EBF5"}, Pattern: 19}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "O23", "O23", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellStyleFill.xlsx")))
@ -910,43 +857,31 @@ func TestSetCellStyleFill(t *testing.T) {
func TestSetCellStyleFont(t *testing.T) { func TestSetCellStyleFont(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
var style int var style int
style, err = f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}`) style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Italic: true, Family: "Times New Roman", Size: 36, Color: "#777777", Underline: "single"}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "A1", "A1", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "A1", "A1", style))
style, err = f.NewStyle(`{"font":{"italic":true,"underline":"double"}}`) style, err = f.NewStyle(&Style{Font: &Font{Italic: true, Underline: "double"}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "A2", "A2", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "A2", "A2", style))
style, err = f.NewStyle(`{"font":{"bold":true}}`) style, err = f.NewStyle(&Style{Font: &Font{Bold: true}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "A3", "A3", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "A3", "A3", style))
style, err = f.NewStyle(`{"font":{"bold":true,"family":"","size":0,"color":"","underline":""}}`) style, err = f.NewStyle(&Style{Font: &Font{Bold: true, Family: "", Size: 0, Color: "", Underline: ""}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "A4", "A4", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "A4", "A4", style))
style, err = f.NewStyle(`{"font":{"color":"#777777","strike":true}}`) style, err = f.NewStyle(&Style{Font: &Font{Color: "#777777", Strike: true}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "A5", "A5", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "A5", "A5", style))
@ -955,40 +890,30 @@ func TestSetCellStyleFont(t *testing.T) {
func TestSetCellStyleProtection(t *testing.T) { func TestSetCellStyleProtection(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
var style int var style int
style, err = f.NewStyle(`{"protection":{"hidden":true, "locked":true}}`) style, err = f.NewStyle(&Style{Protection: &Protection{Hidden: true, Locked: true}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetCellStyle("Sheet2", "A6", "A6", style)) assert.NoError(t, f.SetCellStyle("Sheet2", "A6", "A6", style))
err = f.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx")) err = f.SaveAs(filepath.Join("test", "TestSetCellStyleProtection.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
} }
func TestSetDeleteSheet(t *testing.T) { func TestSetDeleteSheet(t *testing.T) {
t.Run("TestBook3", func(t *testing.T) { t.Run("TestBook3", func(t *testing.T) {
f, err := prepareTestBook3() f, err := prepareTestBook3()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
f.DeleteSheet("XLSXSheet3") assert.NoError(t, f.DeleteSheet("XLSXSheet3"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDeleteSheet.TestBook3.xlsx")))
}) })
t.Run("TestBook4", func(t *testing.T) { t.Run("TestBook4", func(t *testing.T) {
f, err := prepareTestBook4() f, err := prepareTestBook4()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow() assert.NoError(t, f.DeleteSheet("Sheet1"))
}
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.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"))) 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) { func TestSheetVisibility(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetSheetVisible("Sheet2", false)) 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", false))
assert.NoError(t, f.SetSheetVisible("Sheet1", true)) assert.NoError(t, f.SetSheetVisible("Sheet1", true))
visible, err := f.GetSheetVisible("Sheet1") visible, err := f.GetSheetVisible("Sheet1")
@ -1058,123 +982,231 @@ func TestConditionalFormat(t *testing.T) {
var format1, format2, format3, format4 int var format1, format2, format3, format4 int
var err error var err error
// Rose format for bad conditional. // Rose format for bad conditional
format1, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) format1, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"#FEC7CE"}, Pattern: 1}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// Light yellow format for neutral conditional. // Light yellow format for neutral conditional
format2, err = f.NewConditionalStyle(`{"fill":{"type":"pattern","color":["#FEEAA0"],"pattern":1}}`) format2, err = f.NewConditionalStyle(&Style{Fill: Fill{Type: "pattern", Color: []string{"#FEEAA0"}, Pattern: 1}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// Light green format for good conditional. // Light green format for good conditional
format3, err = f.NewConditionalStyle(`{"font":{"color":"#09600B"},"fill":{"type":"pattern","color":["#C7EECF"],"pattern":1}}`) format3, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#09600B"}, Fill: Fill{Type: "pattern", Color: []string{"#C7EECF"}, Pattern: 1}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// conditional style with align and left border. // conditional style with align and left border
format4, err = f.NewConditionalStyle(`{"alignment":{"wrap_text":true},"border":[{"type":"left","color":"#000000","style":1}]}`) format4, err = f.NewConditionalStyle(&Style{Alignment: &Alignment{WrapText: true}, Border: []Border{{Type: "left", Color: "#000000", Style: 1}}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// Color scales: 2 color // 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 // 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... // 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... // 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... // 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... // 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... // 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%. // 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... // 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... // 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 // 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 // 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 // Alignment/Border cells rules
assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"0"}]`, format4))) assert.NoError(t, f.SetConditionalFormat(sheet1, "M1:M10",
[]ConditionalFormatOptions{
// Test set invalid format set in conditional format {
assert.EqualError(t, f.SetConditionalFormat(sheet1, "L1:L10", ""), "unexpected end of JSON input") Type: "cell",
Criteria: ">",
Format: format4,
Value: "0",
},
},
))
// Test set conditional format on not exists worksheet // 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 // 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")) err = f.SaveAs(filepath.Join("test", "TestConditionalFormat.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
// Set conditional format with illegal valid type // 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 // 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 // Set conditional format with file without dxfs element should not return error
f, err = OpenFile(filepath.Join("test", "Book1.xlsx")) f, err = OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
_, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`) _, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "#9A0511"}, Fill: Fill{Type: "", Color: []string{"#FEC7CE"}, Pattern: 1}})
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.Close()) 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) { func TestSharedStrings(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx")) f, err := OpenFile(filepath.Join("test", "SharedStrings.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
rows, err := f.GetRows("Sheet1") rows, err := f.GetRows("Sheet1")
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.Equal(t, "A", rows[0][0]) assert.Equal(t, "A", rows[0][0])
rows, err = f.GetRows("Sheet2") rows, err = f.GetRows("Sheet2")
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.Equal(t, "Test Weight (Kgs)", rows[0][0]) assert.Equal(t, "Test Weight (Kgs)", rows[0][0])
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
} }
func TestSetSheetCol(t *testing.T) { func TestSetSheetCol(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetSheetCol("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})) 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) { func TestSetSheetRow(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.SetSheetRow("Sheet1", "B27", &[]interface{}{"cell", nil, int32(42), float64(42), time.Now().UTC()})) 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) { func TestUnprotectSheet(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow() // Test remove protection on not exists worksheet
}
// Test remove protection on not exists worksheet.
assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN does not exist") assert.EqualError(t, f.UnprotectSheet("SheetN"), "sheet SheetN does not exist")
assert.NoError(t, f.UnprotectSheet("Sheet1")) 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, 24, len(wb.WorkbookProtection.WorkbookSaltValue))
assert.Equal(t, 88, len(wb.WorkbookProtection.WorkbookHashValue)) assert.Equal(t, 88, len(wb.WorkbookProtection.WorkbookHashValue))
assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount) 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 // Test protect workbook with password exceeds the limit length
assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{ assert.EqualError(t, f.ProtectWorkbook(&WorkbookProtectionOptions{
AlgorithmName: "MD4", AlgorithmName: "MD4",
@ -1354,13 +1382,15 @@ func TestProtectWorkbook(t *testing.T) {
AlgorithmName: "RIPEMD-160", AlgorithmName: "RIPEMD-160",
Password: "password", Password: "password",
}), ErrUnsupportedHashAlgorithm.Error()) }), 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) { func TestUnprotectWorkbook(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
assert.NoError(t, f.UnprotectWorkbook()) assert.NoError(t, f.UnprotectWorkbook())
assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error()) assert.EqualError(t, f.UnprotectWorkbook("password"), ErrUnprotectWorkbook.Error())
@ -1382,6 +1412,10 @@ func TestUnprotectWorkbook(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA=====" wb.WorkbookProtection.WorkbookSaltValue = "YWJjZA====="
assert.EqualError(t, f.UnprotectWorkbook("wrongPassword"), "illegal base64 data at input byte 8") 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) { func TestSetDefaultTimeStyle(t *testing.T) {
@ -1409,7 +1443,7 @@ func TestAddVBAProject(t *testing.T) {
} }
func TestContentTypesReader(t *testing.T) { func TestContentTypesReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset
f := NewFile() f := NewFile()
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
@ -1418,7 +1452,7 @@ func TestContentTypesReader(t *testing.T) {
} }
func TestWorkbookReader(t *testing.T) { func TestWorkbookReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset
f := NewFile() f := NewFile()
f.WorkBook = nil f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
@ -1427,7 +1461,7 @@ func TestWorkbookReader(t *testing.T) {
} }
func TestWorkSheetReader(t *testing.T) { func TestWorkSheetReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset
f := NewFile() f := NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) 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, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.UpdateLinkedValue(), "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 = NewFile()
f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`))
@ -1445,7 +1479,7 @@ func TestWorkSheetReader(t *testing.T) {
} }
func TestRelsReader(t *testing.T) { func TestRelsReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset
f := NewFile() f := NewFile()
rels := defaultXMLPathWorkbookRels rels := defaultXMLPathWorkbookRels
f.Relationships.Store(rels, nil) f.Relationships.Store(rels, nil)
@ -1463,7 +1497,7 @@ func TestDeleteSheetFromWorkbookRels(t *testing.T) {
func TestUpdateLinkedValue(t *testing.T) { func TestUpdateLinkedValue(t *testing.T) {
f := NewFile() f := NewFile()
// Test update lined value with unsupported charset workbook. // Test update lined value with unsupported charset workbook
f.WorkBook = nil f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8") 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 return nil, err
} }
err = f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), if err = 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"}); err != nil {
if err != nil {
return nil, err return nil, err
} }
// Test add picture to worksheet with offset, external hyperlink and positioning. // Test add picture to worksheet with offset, external hyperlink and positioning
err = f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.png"), if 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"}`) &PictureOptions{
if err != nil { OffsetX: 10,
OffsetY: 10,
Hyperlink: "https://github.com/xuri/excelize",
HyperlinkType: "External",
Positioning: "oneCell",
},
); err != nil {
return nil, err return nil, err
} }
@ -1500,7 +1539,7 @@ func prepareTestBook1() (*File, error) {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
@ -1510,9 +1549,12 @@ func prepareTestBook1() (*File, error) {
func prepareTestBook3() (*File, error) { func prepareTestBook3() (*File, error) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet1") if _, err := f.NewSheet("XLSXSheet2"); err != nil {
f.NewSheet("XLSXSheet2") return nil, err
f.NewSheet("XLSXSheet3") }
if _, err := f.NewSheet("XLSXSheet3"); err != nil {
return nil, err
}
if err := f.SetCellInt("XLSXSheet2", "A23", 56); err != nil { if err := f.SetCellInt("XLSXSheet2", "A23", 56); err != nil {
return nil, err return nil, err
} }
@ -1520,18 +1562,14 @@ func prepareTestBook3() (*File, error) {
return nil, err return nil, err
} }
f.SetActiveSheet(0) f.SetActiveSheet(0)
scale := 0.5
err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"), if err := f.AddPicture("Sheet1", "H2", filepath.Join("test", "images", "excel.gif"),
`{"x_scale": 0.5, "y_scale": 0.5, "positioning": "absolute"}`) &PictureOptions{XScale: &scale, YScale: &scale, Positioning: "absolute"}); err != nil {
if err != nil {
return nil, err return nil, err
} }
if err := f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), nil); err != nil {
err = f.AddPicture("Sheet1", "C2", filepath.Join("test", "images", "excel.png"), "")
if err != nil {
return nil, err return nil, err
} }
return f, nil return f, nil
} }

15
lib.go
View File

@ -313,15 +313,15 @@ func sortCoordinates(coordinates []int) error {
// coordinatesToRangeRef provides a function to convert a pair of coordinates // coordinatesToRangeRef provides a function to convert a pair of coordinates
// to range reference. // 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 { if len(coordinates) != 4 {
return "", ErrCoordinates return "", ErrCoordinates
} }
firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1]) firstCell, err := CoordinatesToCellName(coordinates[0], coordinates[1], abs...)
if err != nil { if err != nil {
return "", err return "", err
} }
lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3]) lastCell, err := CoordinatesToCellName(coordinates[2], coordinates[3], abs...)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -493,15 +493,6 @@ func (avb *attrValBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
return nil 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 // namespaceStrictToTransitional provides a method to convert Strict and
// Transitional namespaces. // Transitional namespaces.
func namespaceStrictToTransitional(content []byte) []byte { func namespaceStrictToTransitional(content []byte) []byte {

View File

@ -37,7 +37,8 @@ func TestMergeCell(t *testing.T) {
assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value) assert.Equal(t, "SUM(Sheet1!B19,Sheet1!C19)", value)
assert.NoError(t, err) 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", "D11", "F13"))
assert.NoError(t, f.MergeCell("Sheet3", "G10", "K12")) assert.NoError(t, f.MergeCell("Sheet3", "G10", "K12"))

View File

@ -13,7 +13,6 @@ package excelize
import ( import (
"bytes" "bytes"
"encoding/json"
"encoding/xml" "encoding/xml"
"image" "image"
"io" "io"
@ -26,14 +25,28 @@ import (
// parsePictureOptions provides a function to parse the format settings of // parsePictureOptions provides a function to parse the format settings of
// the picture with default value. // the picture with default value.
func parsePictureOptions(opts string) (*pictureOptions, error) { func parsePictureOptions(opts *PictureOptions) *PictureOptions {
format := pictureOptions{ if opts == nil {
FPrintsWithSheet: true, return &PictureOptions{
XScale: 1, PrintObject: boolPtr(true),
YScale: 1, 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 // 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 // package main
// //
// import ( // import (
// "fmt"
// _ "image/gif" // _ "image/gif"
// _ "image/jpeg" // _ "image/jpeg"
// _ "image/png" // _ "image/png"
@ -54,15 +68,33 @@ func parsePictureOptions(opts string) (*pictureOptions, error) {
// func main() { // func main() {
// f := excelize.NewFile() // f := excelize.NewFile()
// // Insert a picture. // // 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) // fmt.Println(err)
// } // }
// // Insert a picture scaling in the cell with location hyperlink. // // 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) // fmt.Println(err)
// } // }
// // Insert a picture offset in the cell with external hyperlink, printing and positioning support. // // 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) // fmt.Println(err)
// } // }
// if err := f.SaveAs("Book1.xlsx"); err != nil { // 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'. // 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 // hyperlink "External" for website or "Location" for moving to one of the
// cells in this workbook. When the "hyperlink_type" is "Location", // cells in this workbook. When the "hyperlink_type" is "Location",
// coordinates need to start with "#". // 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 // 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 // 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. // 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'. // 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'. // 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. // 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. // 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 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. // 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%. // 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 var err error
// Check picture exists first. // Check picture exists first.
if _, err = os.Stat(picture); os.IsNotExist(err) { 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)) file, _ := os.ReadFile(filepath.Clean(picture))
_, name := filepath.Split(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 // 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 { // if err != nil {
// fmt.Println(err) // 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) // fmt.Println(err)
// } // }
// if err := f.SaveAs("Book1.xlsx"); err != nil { // if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err) // 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 drawingHyperlinkRID int
var hyperlinkType string var hyperlinkType string
ext, ok := supportedImageTypes[extension] ext, ok := supportedImageTypes[extension]
if !ok { if !ok {
return ErrImgExt return ErrImgExt
} }
options, err := parsePictureOptions(opts) options := parsePictureOptions(opts)
if err != nil {
return err
}
img, _, err := image.DecodeConfig(bytes.NewReader(file)) img, _, err := image.DecodeConfig(bytes.NewReader(file))
if err != nil { if err != nil {
return err return err
@ -276,20 +305,20 @@ func (f *File) countDrawings() int {
// addDrawingPicture provides a function to add picture by given sheet, // addDrawingPicture provides a function to add picture by given sheet,
// drawingXML, cell, file name, width, height relationship index and format // drawingXML, cell, file name, width, height relationship index and format
// sets. // 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) col, row, err := CellNameToCoordinates(cell)
if err != nil { if err != nil {
return err return err
} }
width, height := img.Width, img.Height 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) width, height, col, row, err = f.drawingResize(sheet, cell, float64(width), float64(height), opts)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
width = int(float64(width) * opts.XScale) width = int(float64(width) * *opts.XScale)
height = int(float64(height) * opts.YScale) height = int(float64(height) * *opts.YScale)
} }
col-- col--
row-- row--
@ -313,7 +342,7 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID,
twoCellAnchor.From = &from twoCellAnchor.From = &from
twoCellAnchor.To = &to twoCellAnchor.To = &to
pic := xlsxPic{} pic := xlsxPic{}
pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.NoChangeAspect pic.NvPicPr.CNvPicPr.PicLocks.NoChangeAspect = opts.LockAspectRatio
pic.NvPicPr.CNvPr.ID = cNvPrID pic.NvPicPr.CNvPr.ID = cNvPrID
pic.NvPicPr.CNvPr.Descr = file pic.NvPicPr.CNvPr.Descr = file
pic.NvPicPr.CNvPr.Name = "Picture " + strconv.Itoa(cNvPrID) 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.Pic = &pic
twoCellAnchor.ClientData = &xdrClientData{ twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.FLocksWithSheet, FLocksWithSheet: *opts.Locked,
FPrintsWithSheet: opts.FPrintsWithSheet, FPrintsWithSheet: *opts.PrintObject,
} }
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
@ -682,7 +711,7 @@ func (f *File) drawingsWriter() {
} }
// drawingResize calculate the height and width after resizing. // 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 var mergeCells []MergeCell
mergeCells, err = f.GetMergeCells(sheet) mergeCells, err = f.GetMergeCells(sheet)
if err != nil { 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 height, width = float64(cellHeight), width*asp
} }
width, height = width-float64(opts.OffsetX), height-float64(opts.OffsetY) 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 return
} }

View File

@ -25,7 +25,7 @@ func BenchmarkAddPictureFromBytes(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 1; i <= b.N; i++ { 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) b.Error(err)
} }
} }
@ -37,32 +37,33 @@ func TestAddPicture(t *testing.T) {
// Test add picture to worksheet with offset and location hyperlink // Test add picture to worksheet with offset and location hyperlink
assert.NoError(t, f.AddPicture("Sheet2", "I9", filepath.Join("test", "images", "excel.jpg"), 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 // Test add picture to worksheet with offset, external hyperlink and positioning
assert.NoError(t, f.AddPicture("Sheet1", "F21", filepath.Join("test", "images", "excel.jpg"), 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")) file, err := os.ReadFile(filepath.Join("test", "images", "excel.png"))
assert.NoError(t, err) assert.NoError(t, err)
// Test add picture to worksheet with autofit // 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", "A30", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}))
assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), `{"x_offset": 10, "y_offset": 10, "autofit": true}`)) assert.NoError(t, f.AddPicture("Sheet1", "B30", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{OffsetX: 10, OffsetY: 10, AutoFit: true}))
f.NewSheet("AddPicture") _, err = f.NewSheet("AddPicture")
assert.NoError(t, err)
assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30)) assert.NoError(t, f.SetRowHeight("AddPicture", 10, 30))
assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9")) assert.NoError(t, f.MergeCell("AddPicture", "B3", "D9"))
assert.NoError(t, f.MergeCell("AddPicture", "B1", "D1")) 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", "C6", filepath.Join("test", "images", "excel.jpg"), &PictureOptions{AutoFit: true}))
assert.NoError(t, f.AddPicture("AddPicture", "A1", filepath.Join("test", "images", "excel.jpg"), `{"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 // 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 // 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", "Q8", filepath.Join("test", "images", "excel.gif"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q15", filepath.Join("test", "images", "excel.jpg"), "")) 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"), "")) assert.NoError(t, f.AddPicture("Sheet1", "Q22", filepath.Join("test", "images", "excel.tif"), nil))
// Test write file to given path // Test write file to given path
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx")))
@ -72,10 +73,10 @@ func TestAddPicture(t *testing.T) {
f = NewFile() f = NewFile()
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) 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 // 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) { func TestAddPictureErrors(t *testing.T) {
@ -83,14 +84,14 @@ func TestAddPictureErrors(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// Test add picture to worksheet with invalid file path // 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 // 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.AddPicture("Sheet1", "G21", filepath.Join("test", "Book1.xlsx"), nil), ErrImgExt.Error())
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "G21", "", "Excel Logo", "jpg", make([]byte, 1)), 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 // 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 // Test add picture with custom image decoder and encoder
decode := func(r io.Reader) (image.Image, error) { return nil, nil } 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("emz", "", decode, decodeConfig)
image.RegisterFormat("wmz", "", decode, decodeConfig) image.RegisterFormat("wmz", "", decode, decodeConfig)
image.RegisterFormat("svg", "", 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", "Q1", filepath.Join("test", "images", "excel.emf"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q7", filepath.Join("test", "images", "excel.wmf"), "")) 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"), "")) 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"), "")) assert.NoError(t, f.AddPicture("Sheet1", "Q19", filepath.Join("test", "images", "excel.wmz"), nil))
assert.NoError(t, f.AddPicture("Sheet1", "Q25", "excelize.svg", `{"x_scale": 2.1}`)) 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.SaveAs(filepath.Join("test", "TestAddPicture2.xlsx")))
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
} }
func TestGetPicture(t *testing.T) { func TestGetPicture(t *testing.T) {
f := NewFile() 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") name, content, err := f.GetPicture("Sheet1", "A1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 13233, len(content)) assert.Equal(t, 13233, len(content))
@ -196,19 +198,20 @@ func TestGetPicture(t *testing.T) {
func TestAddDrawingPicture(t *testing.T) { func TestAddDrawingPicture(t *testing.T) {
// Test addDrawingPicture with illegal cell reference // Test addDrawingPicture with illegal cell reference
f := NewFile() 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" path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset) 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) { func TestAddPictureFromBytes(t *testing.T) {
f := NewFile() f := NewFile()
imgFile, err := os.ReadFile("logo.png") imgFile, err := os.ReadFile("logo.png")
assert.NoError(t, err, "Unable to load logo for test") 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", 1), "logo", ".png", imgFile, nil))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile)) assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "logo", ".png", imgFile, nil))
imageCount := 0 imageCount := 0
f.Pkg.Range(func(fileName, v interface{}) bool { f.Pkg.Range(func(fileName, v interface{}) bool {
if strings.Contains(fileName.(string), "media/image") { if strings.Contains(fileName.(string), "media/image") {
@ -217,16 +220,16 @@ func TestAddPictureFromBytes(t *testing.T) {
return true return true
}) })
assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.") 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 // 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) { func TestDeletePicture(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.DeletePicture("Sheet1", "A1")) 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.DeletePicture("Sheet1", "P1"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeletePicture.xlsx")))
// Test delete picture on not exists worksheet // 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") ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok) assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} 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) { func TestSetContentTypePartImageExtensions(t *testing.T) {

View File

@ -27,26 +27,26 @@ import (
// PivotStyleDark1 - PivotStyleDark28 // PivotStyleDark1 - PivotStyleDark28
type PivotTableOptions struct { type PivotTableOptions struct {
pivotTableSheetName string pivotTableSheetName string
DataRange string `json:"data_range"` DataRange string
PivotTableRange string `json:"pivot_table_range"` PivotTableRange string
Rows []PivotTableField `json:"rows"` Rows []PivotTableField
Columns []PivotTableField `json:"columns"` Columns []PivotTableField
Data []PivotTableField `json:"data"` Data []PivotTableField
Filter []PivotTableField `json:"filter"` Filter []PivotTableField
RowGrandTotals bool `json:"row_grand_totals"` RowGrandTotals bool
ColGrandTotals bool `json:"col_grand_totals"` ColGrandTotals bool
ShowDrill bool `json:"show_drill"` ShowDrill bool
UseAutoFormatting bool `json:"use_auto_formatting"` UseAutoFormatting bool
PageOverThenDown bool `json:"page_over_then_down"` PageOverThenDown bool
MergeItem bool `json:"merge_item"` MergeItem bool
CompactData bool `json:"compact_data"` CompactData bool
ShowError bool `json:"show_error"` ShowError bool
ShowRowHeaders bool `json:"show_row_headers"` ShowRowHeaders bool
ShowColHeaders bool `json:"show_col_headers"` ShowColHeaders bool
ShowRowStripes bool `json:"show_row_stripes"` ShowRowStripes bool
ShowColStripes bool `json:"show_col_stripes"` ShowColStripes bool
ShowLastColumn bool `json:"show_last_column"` ShowLastColumn bool
PivotTableStyleName string `json:"pivot_table_style_name"` PivotTableStyleName string
} }
// PivotTableField directly maps the field settings of the pivot table. // 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 // Name specifies the name of the data field. Maximum 255 characters
// are allowed in data field name, excess characters will be truncated. // are allowed in data field name, excess characters will be truncated.
type PivotTableField struct { type PivotTableField struct {
Compact bool `json:"compact"` Compact bool
Data string `json:"data"` Data string
Name string `json:"name"` Name string
Outline bool `json:"outline"` Outline bool
Subtotal string `json:"subtotal"` Subtotal string
DefaultSubtotal bool `json:"default_subtotal"` DefaultSubtotal bool
} }
// AddPivotTable provides the method to add pivot table by given pivot table // AddPivotTable provides the method to add pivot table by given pivot table

View File

@ -109,7 +109,8 @@ func TestAddPivotTable(t *testing.T) {
ShowLastColumn: true, ShowLastColumn: true,
PivotTableStyleName: "PivotStyleLight19", PivotTableStyleName: "PivotStyleLight19",
})) }))
f.NewSheet("Sheet2") _, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
assert.NoError(t, f.AddPivotTable(&PivotTableOptions{ assert.NoError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!$A$1:$E$31", DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet2!$A$1:$AR$15", PivotTableRange: "Sheet2!$A$1:$AR$15",
@ -232,7 +233,7 @@ func TestAddPivotTable(t *testing.T) {
Rows: []PivotTableField{{Data: "Year"}}, Rows: []PivotTableField{{Data: "Year"}},
}), ErrSheetNameInvalid.Error()) }), ErrSheetNameInvalid.Error())
// Test adjust range with invalid range // Test adjust range with invalid range
_, _, err := f.adjustRange("") _, _, err = f.adjustRange("")
assert.EqualError(t, err, ErrParameterRequired.Error()) assert.EqualError(t, err, ErrParameterRequired.Error())
// Test adjust range with incorrect range // Test adjust range with incorrect range
_, _, err = f.adjustRange("sheet1!") _, _, err = f.adjustRange("sheet1!")

View File

@ -189,7 +189,8 @@ func TestRowHeight(t *testing.T) {
// Test set row height with custom default row height with prepare XML // Test set row height with custom default row height with prepare XML
assert.NoError(t, f.SetCellValue(sheet1, "A10", "A10")) 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)) assert.NoError(t, f.SetCellValue("Sheet2", "A2", true))
height, err = f.GetRowHeight("Sheet2", 1) height, err = f.GetRowHeight("Sheet2", 1)
assert.NoError(t, err) assert.NoError(t, err)
@ -258,10 +259,9 @@ func TestSharedStringsReader(t *testing.T) {
func TestRowVisibility(t *testing.T) { func TestRowVisibility(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow() _, err = f.NewSheet("Sheet3")
} assert.NoError(t, err)
f.NewSheet("Sheet3")
assert.NoError(t, f.SetRowVisible("Sheet3", 2, false)) assert.NoError(t, f.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, f.SetRowVisible("Sheet3", 2, true)) assert.NoError(t, f.SetRowVisible("Sheet3", 2, true))
visible, err := f.GetRowVisible("Sheet3", 2) visible, err := f.GetRowVisible("Sheet3", 2)
@ -320,7 +320,7 @@ func TestRemoveRow(t *testing.T) {
t.FailNow() 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) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()
} }
@ -990,9 +990,9 @@ func TestCheckRow(t *testing.T) {
func TestSetRowStyle(t *testing.T) { func TestSetRowStyle(t *testing.T) {
f := NewFile() 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) 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, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1)) assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1))
assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error()) assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error())

102
shape.go
View File

@ -12,26 +12,38 @@
package excelize package excelize
import ( import (
"encoding/json"
"strconv" "strconv"
"strings" "strings"
) )
// parseShapeOptions provides a function to parse the format settings of the // parseShapeOptions provides a function to parse the format settings of the
// shape with default value. // shape with default value.
func parseShapeOptions(opts string) (*shapeOptions, error) { func parseShapeOptions(opts *Shape) (*Shape, error) {
options := shapeOptions{ if opts == nil {
Width: 160, return nil, ErrParameterInvalid
Height: 160,
Format: pictureOptions{
FPrintsWithSheet: true,
XScale: 1,
YScale: 1,
},
Line: lineOptions{Width: 1},
} }
err := json.Unmarshal([]byte(opts), &options) if opts.Width == nil {
return &options, err 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 // 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) // print settings) and properties set. For example, add text box (rect shape)
// in Sheet1: // in Sheet1:
// //
// err := f.AddShape("Sheet1", "G6", `{ // width, height, lineWidth := 180, 90, 1.2
// "type": "rect", // err := f.AddShape("Sheet1", "G6",
// "color": // &excelize.Shape{
// Type: "rect",
// Color: excelize.ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"},
// Paragraph: []excelize.ShapeParagraph{
// { // {
// "line": "#4286F4", // Text: "Rectangle Shape",
// "fill": "#8eb9ff" // Font: excelize.Font{
// Bold: true,
// Italic: true,
// Family: "Times New Roman",
// Size: 36,
// Color: "#777777",
// Underline: "sng",
// }, // },
// "paragraph": [ // },
// { // },
// "text": "Rectangle Shape", // Width: &width,
// "font": // Height: &height,
// { // Line: excelize.ShapeLine{Width: &lineWidth},
// "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: // The following shows the type of shape supported by excelize:
// //
@ -277,7 +285,7 @@ func parseShapeOptions(opts string) (*shapeOptions, error) {
// wavy // wavy
// wavyHeavy // wavyHeavy
// wavyDbl // wavyDbl
func (f *File) AddShape(sheet, cell, opts string) error { func (f *File) AddShape(sheet, cell string, opts *Shape) error {
options, err := parseShapeOptions(opts) options, err := parseShapeOptions(opts)
if err != nil { if err != nil {
return err 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, // addDrawingShape provides a function to add preset geometry by given sheet,
// drawingXMLand format sets. // 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) fromCol, fromRow, err := CellNameToCoordinates(cell)
if err != nil { if err != nil {
return err return err
@ -321,8 +329,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
colIdx := fromCol - 1 colIdx := fromCol - 1
rowIdx := fromRow - 1 rowIdx := fromRow - 1
width := int(float64(opts.Width) * opts.Format.XScale) width := int(float64(*opts.Width) * *opts.Format.XScale)
height := int(float64(opts.Height) * opts.Format.YScale) 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, colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY,
width, height) 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{ shape.SpPr.Ln = xlsxLineProperties{
W: f.ptToEMUs(opts.Line.Width), W: f.ptToEMUs(*opts.Line.Width),
} }
} }
defaultFont, err := f.GetDefaultFont() defaultFont, err := f.GetDefaultFont()
@ -391,7 +399,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
return err return err
} }
if len(opts.Paragraph) < 1 { if len(opts.Paragraph) < 1 {
opts.Paragraph = []shapeParagraphOptions{ opts.Paragraph = []ShapeParagraph{
{ {
Font: Font{ Font: Font{
Bold: false, Bold: false,
@ -443,8 +451,8 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
} }
twoCellAnchor.Sp = &shape twoCellAnchor.Sp = &shape
twoCellAnchor.ClientData = &xdrClientData{ twoCellAnchor.ClientData = &xdrClientData{
FLocksWithSheet: opts.Format.FLocksWithSheet, FLocksWithSheet: *opts.Format.Locked,
FPrintsWithSheet: opts.Format.FPrintsWithSheet, FPrintsWithSheet: *opts.Format.PrintObject,
} }
content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor) content.TwoCellAnchor = append(content.TwoCellAnchor, &twoCellAnchor)
f.Drawings.Store(drawingXML, content) f.Drawings.Store(drawingXML, content)

View File

@ -12,97 +12,88 @@ func TestAddShape(t *testing.T) {
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()
} }
shape := &Shape{
assert.NoError(t, f.AddShape("Sheet1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`)) Type: "rect",
assert.NoError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`)) Paragraph: []ShapeParagraph{
assert.NoError(t, f.AddShape("Sheet1", "C30", `{"type":"rect","paragraph":[]}`)) {Text: "Rectangle", Font: Font{Color: "CD5C5C"}},
assert.EqualError(t, f.AddShape("Sheet3", "H1", `{ {Text: "Shape", Font: Font{Bold: true, Color: "2980B9"}},
"type": "ellipseRibbon",
"color":
{
"line": "#4286f4",
"fill": "#8eb9ff"
}, },
"paragraph": [
{
"font":
{
"bold": true,
"italic": true,
"family": "Times New Roman",
"size": 36,
"color": "#777777",
"underline": "single"
} }
}], assert.NoError(t, f.AddShape("Sheet1", "A30", shape))
"height": 90 assert.NoError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []ShapeParagraph{{Text: "Rectangle"}, {}}}))
}`), "sheet Sheet3 does not exist") assert.NoError(t, f.AddShape("Sheet1", "C30", &Shape{Type: "rect"}))
assert.EqualError(t, f.AddShape("Sheet3", "H1", ""), "unexpected end of JSON input") assert.EqualError(t, f.AddShape("Sheet3", "H1",
assert.EqualError(t, f.AddShape("Sheet1", "A", `{ &Shape{
"type": "rect", Type: "ellipseRibbon",
"paragraph": [ Color: ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"},
Paragraph: []ShapeParagraph{
{ {
"text": "Rectangle", Font: Font{
"font": Bold: true,
{ Italic: true,
"color": "CD5C5C" Family: "Times New Roman",
} Size: 36,
Color: "#777777",
Underline: "single",
}, },
{ },
"text": "Shape", },
"font": },
{ ), "sheet Sheet3 does not exist")
"bold": true, assert.EqualError(t, f.AddShape("Sheet3", "H1", nil), ErrParameterInvalid.Error())
"color": "2980B9" assert.EqualError(t, f.AddShape("Sheet1", "A", shape), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}
}]
}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))
// Test add first shape for given sheet // Test add first shape for given sheet
f = NewFile() f = NewFile()
assert.NoError(t, f.AddShape("Sheet1", "A1", `{ width, height := 1.2, 90
"type": "ellipseRibbon", assert.NoError(t, f.AddShape("Sheet1", "A1",
"color": &Shape{
Type: "ellipseRibbon",
Color: ShapeColor{Line: "#4286f4", Fill: "#8eb9ff"},
Paragraph: []ShapeParagraph{
{ {
"line": "#4286f4", Font: Font{
"fill": "#8eb9ff" Bold: true,
Italic: true,
Family: "Times New Roman",
Size: 36,
Color: "#777777",
Underline: "single",
}, },
"paragraph": [ },
{ },
"font": Height: &height,
{ Line: ShapeLine{Width: &width},
"bold": true, }))
"italic": true,
"family": "Times New Roman",
"size": 36,
"color": "#777777",
"underline": "single"
}
}],
"height": 90,
"line":
{
"width": 1.2
}
}`))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
// Test add shape with invalid sheet name // 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 // Test add shape with unsupported charset style sheet
f.Styles = nil f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) 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 // Test add shape with unsupported charset content types
f = NewFile() f = NewFile()
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) 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) { func TestAddDrawingShape(t *testing.T) {
f := NewFile() f := NewFile()
path := "xl/drawings/drawing1.xml" path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset) 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")
} }

125
sheet.go
View File

@ -13,7 +13,6 @@ package excelize
import ( import (
"bytes" "bytes"
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
@ -45,7 +44,7 @@ func (f *File) NewSheet(sheet string) (int, error) {
if index != -1 { if index != -1 {
return index, err return index, err
} }
f.DeleteSheet(sheet) _ = f.DeleteSheet(sheet)
f.SheetCount++ f.SheetCount++
wb, _ := f.workbookReader() wb, _ := f.workbookReader()
sheetID := 0 sheetID := 0
@ -682,19 +681,24 @@ func (f *File) copySheet(from, to int) error {
return err return err
} }
// SetSheetVisible provides a function to set worksheet visible by given worksheet // getSheetState returns sheet visible enumeration by given hidden status.
// name. A workbook must contain at least one visible worksheet. If the given func getSheetState(visible bool, veryHidden []bool) string {
// worksheet has been activated, this setting will be invalidated. Sheet state state := "hidden"
// values as defined by https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetstatevalues if !visible && len(veryHidden) > 0 && veryHidden[0] {
// state = "veryHidden"
// visible }
// hidden return state
// veryHidden }
// 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: // For example, hide Sheet1:
// //
// err := f.SetSheetVisible("Sheet1", false) // 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 { if err := checkSheetName(sheet); err != nil {
return err return err
} }
@ -710,9 +714,9 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error {
} }
return err return err
} }
count := 0 count, state := 0, getSheetState(visible, veryHidden)
for _, v := range wb.Sheets.Sheet { for _, v := range wb.Sheets.Sheet {
if v.State != "hidden" { if v.State != state {
count++ count++
} }
} }
@ -726,45 +730,37 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error {
tabSelected = ws.SheetViews.SheetView[0].TabSelected tabSelected = ws.SheetViews.SheetView[0].TabSelected
} }
if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected { if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected {
wb.Sheets.Sheet[k].State = "hidden" wb.Sheets.Sheet[k].State = state
} }
} }
return err 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. // setPanes set create freeze panes and split panes by given options.
func (ws *xlsxWorksheet) setPanes(panes string) error { func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
opts, err := parsePanesOptions(panes) if panes == nil {
if err != nil { return ErrParameterInvalid
return err
} }
p := &xlsxPane{ p := &xlsxPane{
ActivePane: opts.ActivePane, ActivePane: panes.ActivePane,
TopLeftCell: opts.TopLeftCell, TopLeftCell: panes.TopLeftCell,
XSplit: float64(opts.XSplit), XSplit: float64(panes.XSplit),
YSplit: float64(opts.YSplit), YSplit: float64(panes.YSplit),
} }
if opts.Freeze { if panes.Freeze {
p.State = "frozen" p.State = "frozen"
} }
if ws.SheetViews == nil { if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}} ws.SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}}
} }
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = p 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 { if len(ws.SheetViews.SheetView) > 0 {
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Pane = nil
} }
} }
var s []*xlsxSelection var s []*xlsxSelection
for _, p := range opts.Panes { for _, p := range panes.Panes {
s = append(s, &xlsxSelection{ s = append(s, &xlsxSelection{
ActiveCell: p.ActiveCell, ActiveCell: p.ActiveCell,
Pane: p.Pane, Pane: p.Pane,
@ -772,17 +768,17 @@ func (ws *xlsxWorksheet) setPanes(panes string) error {
}) })
} }
ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1].Selection = s 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 // SetPanes provides a function to create and remove freeze panes and split panes
// by given worksheet name and panes options. // 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: // attribute are defined in the following table:
// //
// Enumeration Value | Description // Enumeration Value | Description
// --------------------------------+------------------------------------------------------------- // ---------------------------------+-------------------------------------------------------------
// bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal // bottomLeft (Bottom Left Pane) | Bottom left pane, when both vertical and horizontal
// | splits are applied. // | splits are applied.
// | // |
@ -816,7 +812,7 @@ func (ws *xlsxWorksheet) setPanes(panes string) error {
// Pane state type is restricted to the values supported currently listed in the following table: // Pane state type is restricted to the values supported currently listed in the following table:
// //
// Enumeration Value | Description // Enumeration Value | Description
// --------------------------------+------------------------------------------------------------- // ---------------------------------+-------------------------------------------------------------
// frozen (Frozen) | Panes are frozen, but were not split being frozen. In // frozen (Frozen) | Panes are frozen, but were not split being frozen. In
// | this state, when the panes are unfrozen again, a single // | this state, when the panes are unfrozen again, a single
// | pane results, with no split. // | pane results, with no split.
@ -826,40 +822,74 @@ func (ws *xlsxWorksheet) setPanes(panes string) error {
// split (Split) | Panes are split, but not frozen. In this state, the split // split (Split) | Panes are split, but not frozen. In this state, the split
// | bars are adjustable by the user. // | 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 // 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. // 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 // 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 // number of rows visible in the left pane. The possible values for this
// attribute are defined by the W3C XML Schema double datatype. // 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). // (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. // set of ranges.
// //
// An example of how to freeze column A in the Sheet1 and set the active cell on // An example of how to freeze column A in the Sheet1 and set the active cell on
// Sheet1!K16: // 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 // An example of how to freeze rows 1 to 9 in the Sheet1 and set the active cell
// ranges on Sheet1!A11:XFD11: // 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 // An example of how to create split panes in the Sheet1 and set the active cell
// on Sheet1!J60: // 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: // An example of how to unfreeze and remove all panes on Sheet1:
// //
// f.SetPanes("Sheet1", `{"freeze":false,"split":false}`) // err := f.SetPanes("Sheet1", &excelize.Panes{Freeze: false, Split: false})
func (f *File) SetPanes(sheet, panes string) error { func (f *File) SetPanes(sheet string, panes *Panes) error {
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
return err 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 // GetSheetVisible provides a function to get worksheet visible by given worksheet
// name. For example, get visible state of Sheet1: // name. For example, get visible state of Sheet1:
// //
// f.GetSheetVisible("Sheet1") // visible, err := f.GetSheetVisible("Sheet1")
func (f *File) GetSheetVisible(sheet string) (bool, error) { func (f *File) GetSheetVisible(sheet string) (bool, error) {
var visible bool var visible bool
if err := checkSheetName(sheet); err != nil { if err := checkSheetName(sheet); err != nil {
@ -1509,6 +1539,9 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
// Scope: "Sheet2", // Scope: "Sheet2",
// }) // })
func (f *File) SetDefinedName(definedName *DefinedName) error { func (f *File) SetDefinedName(definedName *DefinedName) error {
if definedName.Name == "" || definedName.RefersTo == "" {
return ErrParameterInvalid
}
wb, err := f.workbookReader() wb, err := f.workbookReader()
if err != nil { if err != nil {
return err return err

View File

@ -15,14 +15,15 @@ import (
func TestNewSheet(t *testing.T) { func TestNewSheet(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") _, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
sheetID, err := f.NewSheet("sheet2") sheetID, err := f.NewSheet("sheet2")
assert.NoError(t, err) assert.NoError(t, err)
f.SetActiveSheet(sheetID) f.SetActiveSheet(sheetID)
// Test delete original sheet // Test delete original sheet
idx, err := f.GetSheetIndex("Sheet1") idx, err := f.GetSheetIndex("Sheet1")
assert.NoError(t, err) 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"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx")))
// Test create new worksheet with already exists name // Test create new worksheet with already exists name
sheetID, err = f.NewSheet("Sheet2") sheetID, err = f.NewSheet("Sheet2")
@ -38,24 +39,79 @@ func TestNewSheet(t *testing.T) {
func TestSetPanes(t *testing.T) { func TestSetPanes(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`))
f.NewSheet("Panes 2") assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false}))
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"}]}`)) _, err := f.NewSheet("Panes 2")
f.NewSheet("Panes 3") assert.NoError(t, err)
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"}]}`)) assert.NoError(t, f.SetPanes("Panes 2",
f.NewSheet("Panes 4") &Panes{
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"}]}`)) Freeze: true,
assert.EqualError(t, f.SetPanes("Panes 4", ""), "unexpected end of JSON input") Split: false,
assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN does not exist") 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 // 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"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))
// Test add pane on empty sheet views worksheet // Test add pane on empty sheet views worksheet
f = NewFile() f = NewFile()
f.checked = nil f.checked = nil
f.Sheet.Delete("xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`))
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) { func TestSearchSheet(t *testing.T) {
@ -108,7 +164,7 @@ func TestSearchSheet(t *testing.T) {
assert.EqualError(t, err, "invalid cell reference [1, 0]") assert.EqualError(t, err, "invalid cell reference [1, 0]")
assert.Equal(t, []string(nil), result) 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.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err = f.SearchSheet("Sheet1", "A") _, err = f.SearchSheet("Sheet1", "A")
@ -204,6 +260,14 @@ func TestDefinedName(t *testing.T) {
assert.EqualError(t, f.DeleteDefinedName(&DefinedName{ assert.EqualError(t, f.DeleteDefinedName(&DefinedName{
Name: "No Exist Defined Name", Name: "No Exist Defined Name",
}), ErrDefinedNameScope.Error()) }), 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.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo)
assert.NoError(t, f.DeleteDefinedName(&DefinedName{ assert.NoError(t, f.DeleteDefinedName(&DefinedName{
Name: "Amount", Name: "Amount",
@ -228,7 +292,8 @@ func TestGroupSheets(t *testing.T) {
f := NewFile() f := NewFile()
sheets := []string{"Sheet2", "Sheet3"} sheets := []string{"Sheet2", "Sheet3"}
for _, sheet := range sheets { 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{"Sheet1", "SheetN"}), "sheet SheetN does not exist")
assert.EqualError(t, f.GroupSheets([]string{"Sheet2", "Sheet3"}), "group worksheet must contain an active worksheet") 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() f := NewFile()
sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"} sheets := []string{"Sheet2", "Sheet3", "Sheet4", "Sheet5"}
for _, sheet := range sheets { for _, sheet := range sheets {
f.NewSheet(sheet) _, err := f.NewSheet(sheet)
assert.NoError(t, err)
} }
assert.NoError(t, f.UngroupSheets()) 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", "B3"))
assert.NoError(t, f.RemovePageBreak("Sheet1", "A3")) 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", "B2"))
assert.NoError(t, f.InsertPageBreak("Sheet2", "C2")) assert.NoError(t, f.InsertPageBreak("Sheet2", "C2"))
assert.NoError(t, f.RemovePageBreak("Sheet2", "B2")) assert.NoError(t, f.RemovePageBreak("Sheet2", "B2"))
@ -381,20 +448,23 @@ func TestDeleteSheet(t *testing.T) {
idx, err := f.NewSheet("Sheet2") idx, err := f.NewSheet("Sheet2")
assert.NoError(t, err) assert.NoError(t, err)
f.SetActiveSheet(idx) f.SetActiveSheet(idx)
f.NewSheet("Sheet3") _, err = f.NewSheet("Sheet3")
f.DeleteSheet("Sheet1") assert.NoError(t, err)
assert.NoError(t, f.DeleteSheet("Sheet1"))
assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex())) assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex()))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx")))
// Test with auto filter defined names // Test with auto filter defined names
f = NewFile() f = NewFile()
f.NewSheet("Sheet2") _, err = f.NewSheet("Sheet2")
f.NewSheet("Sheet3") 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("Sheet1", "A1", "A"))
assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A")) assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A"))
assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A")) assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A"))
assert.NoError(t, f.AutoFilter("Sheet1", "A1", "A1", "")) assert.NoError(t, f.AutoFilter("Sheet1", "A1:A1", nil))
assert.NoError(t, f.AutoFilter("Sheet2", "A1", "A1", "")) assert.NoError(t, f.AutoFilter("Sheet2", "A1:A1", nil))
assert.NoError(t, f.AutoFilter("Sheet3", "A1", "A1", "")) assert.NoError(t, f.AutoFilter("Sheet3", "A1:A1", nil))
assert.NoError(t, f.DeleteSheet("Sheet2")) assert.NoError(t, f.DeleteSheet("Sheet2"))
assert.NoError(t, f.DeleteSheet("Sheet1")) assert.NoError(t, f.DeleteSheet("Sheet1"))
// Test delete sheet with invalid sheet name // Test delete sheet with invalid sheet name
@ -408,9 +478,10 @@ func TestDeleteAndAdjustDefinedNames(t *testing.T) {
} }
func TestGetSheetID(t *testing.T) { func TestGetSheetID(t *testing.T) {
file := NewFile() f := NewFile()
file.NewSheet("Sheet1") _, err := f.NewSheet("Sheet1")
id := file.getSheetID("sheet1") assert.NoError(t, err)
id := f.getSheetID("sheet1")
assert.NotEqual(t, -1, id) assert.NotEqual(t, -1, id)
} }
@ -444,7 +515,7 @@ func TestGetSheetIndex(t *testing.T) {
func TestSetContentTypes(t *testing.T) { func TestSetContentTypes(t *testing.T) {
f := NewFile() f := NewFile()
// Test set content type with unsupported charset content types. // Test set content type with unsupported charset content types
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8") 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) { func TestDeleteSheetFromContentTypes(t *testing.T) {
f := NewFile() 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.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSheetFromContentTypes("/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8") 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() { func newSheetWithSet() {
file := NewFile() file := NewFile()
file.NewSheet("sheet1")
for i := 0; i < 1000; i++ { 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 file = nil
} }
@ -485,9 +555,8 @@ func BenchmarkFile_SaveAs(b *testing.B) {
func newSheetWithSave() { func newSheetWithSave() {
file := NewFile() file := NewFile()
file.NewSheet("sheet1")
for i := 0; i < 1000; i++ { 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() _ = file.Save()
} }
@ -520,12 +589,13 @@ func TestAttrValToFloat(t *testing.T) {
func TestSetSheetBackgroundFromBytes(t *testing.T) { func TestSetSheetBackgroundFromBytes(t *testing.T) {
f := NewFile() 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"} { for i, imageTypes := range []string{".svg", ".emf", ".emz", ".gif", ".jpg", ".png", ".tif", ".wmf", ".wmz"} {
file := fmt.Sprintf("excelize%s", imageTypes) file := fmt.Sprintf("excelize%s", imageTypes)
if i > 0 { if i > 0 {
file = filepath.Join("test", "images", fmt.Sprintf("excel%s", imageTypes)) 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) img, err := os.Open(file)
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -28,10 +28,10 @@ func TestSetView(t *testing.T) {
opts, err := f.GetSheetView("Sheet1", 0) opts, err := f.GetSheetView("Sheet1", 0)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, opts) 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", 1, nil), "view index 1 out of range")
assert.EqualError(t, f.SetSheetView("Sheet1", -2, nil), "view index -2 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") assert.EqualError(t, f.SetSheetView("SheetN", 0, nil), "sheet SheetN does not exist")
} }
@ -39,12 +39,12 @@ func TestGetView(t *testing.T) {
f := NewFile() f := NewFile()
_, err := f.getSheetView("SheetN", 0) _, err := f.getSheetView("SheetN", 0)
assert.EqualError(t, err, "sheet SheetN does not exist") 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) _, err = f.GetSheetView("Sheet1", 1)
assert.EqualError(t, err, "view index 1 out of range") assert.EqualError(t, err, "view index 1 out of range")
_, err = f.GetSheetView("Sheet1", -2) _, err = f.GetSheetView("Sheet1", -2)
assert.EqualError(t, err, "view index -2 out of range") 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) _, err = f.GetSheetView("SheetN", 0)
assert.EqualError(t, err, "sheet SheetN does not exist") assert.EqualError(t, err, "sheet SheetN does not exist")
} }

View File

@ -9,10 +9,11 @@ import (
) )
func TestAddSparkline(t *testing.T) { func TestAddSparkline(t *testing.T) {
f := prepareSparklineDataset() f, err := prepareSparklineDataset()
assert.NoError(t, err)
// Set the columns widths to make the output clearer // 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, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style)) assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style))
viewOpts, err := f.GetSheetView("Sheet1", 0) 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") 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() f := NewFile()
sheet2 := [][]int{ sheet2 := [][]int{
{-2, 2, 3, -1, 0}, {-2, 2, 3, -1, 0},
@ -307,8 +308,12 @@ func prepareSparklineDataset() *File {
{3, -1, 0, -2, 3, 2, 1, 0, 2, 1}, {3, -1, 0, -2, 3, 2, 1, 0, 2, 1},
{0, -2, 3, 2, 1, 0, 1, 2, 3, 1}, {0, -2, 3, 2, 1, 0, 1, 2, 3, 1},
} }
f.NewSheet("Sheet2") if _, err := f.NewSheet("Sheet2"); err != nil {
f.NewSheet("Sheet3") return f, err
}
if _, err := f.NewSheet("Sheet3"); err != nil {
return f, err
}
for row, data := range sheet2 { for row, data := range sheet2 {
if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil { if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil {
fmt.Println(err) fmt.Println(err)
@ -319,5 +324,5 @@ func prepareSparklineDataset() *File {
fmt.Println(err) fmt.Println(err)
} }
} }
return f return f, nil
} }

View File

@ -134,18 +134,19 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
// AddTable creates an Excel table for the StreamWriter using the given // AddTable creates an Excel table for the StreamWriter using the given
// cell range and format set. For example, create a table of A1:D5: // 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: // Create a table of F2:H6 with format set:
// //
// err := sw.AddTable("F2", "H6", `{ // disable := false
// "table_name": "table", // err := sw.AddTable("F2:H6", &excelize.TableOptions{
// "table_style": "TableStyleMedium2", // Name: "table",
// "show_first_column": true, // StyleName: "TableStyleMedium2",
// "show_last_column": true, // ShowFirstColumn: true,
// "show_row_stripes": false, // ShowLastColumn: true,
// "show_column_stripes": true // ShowRowStripes: &disable,
// }`) // ShowColumnStripes: true,
// })
// //
// Note that the table must be at least two lines including the header. The // Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique. // 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. // called after the rows are written but before Flush.
// //
// See File.AddTable for details on the table format. // See File.AddTable for details on the table format.
func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error { func (sw *StreamWriter) AddTable(reference string, opts *TableOptions) error {
options, err := parseTableOptions(opts) options := parseTableOptions(opts)
if err != nil { coordinates, err := rangeRefToCoordinates(reference)
return err
}
coordinates, err := cellRefsToCoordinates(hCell, vCell)
if err != nil { if err != nil {
return err return err
} }
@ -192,7 +189,7 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
tableID := sw.file.countTables() + 1 tableID := sw.file.countTables() + 1
name := options.TableName name := options.Name
if name == "" { if name == "" {
name = "Table" + strconv.Itoa(tableID) name = "Table" + strconv.Itoa(tableID)
} }
@ -211,10 +208,10 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
TableColumn: tableColumn, TableColumn: tableColumn,
}, },
TableStyleInfo: &xlsxTableStyleInfo{ TableStyleInfo: &xlsxTableStyleInfo{
Name: options.TableStyle, Name: options.StyleName,
ShowFirstColumn: options.ShowFirstColumn, ShowFirstColumn: options.ShowFirstColumn,
ShowLastColumn: options.ShowLastColumn, ShowLastColumn: options.ShowLastColumn,
ShowRowStripes: options.ShowRowStripes, ShowRowStripes: *options.ShowRowStripes,
ShowColumnStripes: options.ShowColumnStripes, 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 // 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 // panes by giving panes options for the StreamWriter. Note that you must call
// the 'SetPanes' function before the 'SetRow' function. // the 'SetPanes' function before the 'SetRow' function.
func (sw *StreamWriter) SetPanes(panes string) error { func (sw *StreamWriter) SetPanes(panes *Panes) error {
if sw.sheetWritten { if sw.sheetWritten {
return ErrStreamSetPanes return ErrStreamSetPanes
} }

View File

@ -41,12 +41,12 @@ func TestStreamWriter(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
// Test max characters in a cell. // Test max characters in a cell
row := make([]interface{}, 1) row := make([]interface{}, 1)
row[0] = strings.Repeat("c", TotalCellChars+2) row[0] = strings.Repeat("c", TotalCellChars+2)
assert.NoError(t, streamWriter.SetRow("A1", row)) 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 = make([]interface{}, 1)
row[0] = " characters" row[0] = " characters"
assert.NoError(t, streamWriter.SetRow("A2", row)) assert.NoError(t, streamWriter.SetRow("A2", row))
@ -55,7 +55,7 @@ func TestStreamWriter(t *testing.T) {
row[0] = []byte("Word") row[0] = []byte("Word")
assert.NoError(t, streamWriter.SetRow("A3", row)) 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"}}) styleID, err := file.NewStyle(&Style{Font: &Font{Color: "#777777"}})
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A4", []interface{}{ assert.NoError(t, streamWriter.SetRow("A4", []interface{}{
@ -85,14 +85,14 @@ func TestStreamWriter(t *testing.T) {
} }
assert.NoError(t, streamWriter.Flush()) 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"))) 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.ErrorIs(t, streamWriter.SetRow("XFD51201", []interface{}{"A", "B", "C"}), ErrColumnNumber)
assert.NoError(t, file.Close()) assert.NoError(t, file.Close())
// Test close temporary file error. // Test close temporary file error
file = NewFile() file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1") streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
@ -114,7 +114,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, streamWriter.rawData.tmp.Close()) assert.NoError(t, streamWriter.rawData.tmp.Close())
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name())) 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 = NewFile()
file.Sheet.Delete("xl/worksheets/sheet1.xml") file.Sheet.Delete("xl/worksheets/sheet1.xml")
file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) 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.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, file.Close()) assert.NoError(t, file.Close())
// Test read cell. // Test read cell
file = NewFile() file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1") streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
@ -132,7 +132,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "Data", cellValue) 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")) file, err = OpenFile(filepath.Join("test", "TestStreamWriter.xlsx"))
assert.NoError(t, err) assert.NoError(t, err)
rows, err := file.Rows("Sheet1") rows, err := file.Rows("Sheet1")
@ -166,14 +166,24 @@ func TestStreamSetColWidth(t *testing.T) {
} }
func TestStreamSetPanes(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() { defer func() {
assert.NoError(t, file.Close()) assert.NoError(t, file.Close())
}() }()
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, streamWriter.SetPanes(paneOpts)) 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.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.ErrorIs(t, streamWriter.SetPanes(paneOpts), ErrStreamSetPanes) assert.ErrorIs(t, streamWriter.SetPanes(paneOpts), ErrStreamSetPanes)
} }
@ -185,19 +195,20 @@ func TestStreamTable(t *testing.T) {
}() }()
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
// Test add table without table header
// Write some rows. We want enough rows to force a temp file (>16MB). 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"})) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
row := []interface{}{1, 2, 3} row := []interface{}{1, 2, 3}
for r := 2; r < 10000; r++ { for r := 2; r < 10000; r++ {
assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row)) assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row))
} }
// Write a table. // Write a table
assert.NoError(t, streamWriter.AddTable("A1", "C2", "")) assert.NoError(t, streamWriter.AddTable("A1:C2", nil))
assert.NoError(t, streamWriter.Flush()) assert.NoError(t, streamWriter.Flush())
// Verify the table has names. // Verify the table has names
var table xlsxTable var table xlsxTable
val, ok := file.Pkg.Load("xl/tables/table1.xml") val, ok := file.Pkg.Load("xl/tables/table1.xml")
assert.True(t, ok) 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, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].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. // Test add table with illegal cell reference
assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string") assert.EqualError(t, streamWriter.AddTable("A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test add table with illegal cell reference. assert.EqualError(t, streamWriter.AddTable("A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test add table with unsupported charset content types
assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add table with unsupported charset content types.
file.ContentTypes = nil file.ContentTypes = nil
file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) 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) { func TestStreamMergeCells(t *testing.T) {
@ -227,10 +236,10 @@ func TestStreamMergeCells(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, streamWriter.MergeCell("A1", "D1")) 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.EqualError(t, streamWriter.MergeCell("A", "D1"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, streamWriter.Flush()) 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"))) 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, err)
assert.NoError(t, streamWriter.InsertPageBreak("A1")) assert.NoError(t, streamWriter.InsertPageBreak("A1"))
assert.NoError(t, streamWriter.Flush()) 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"))) assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamInsertPageBreak.xlsx")))
} }
@ -270,7 +279,7 @@ func TestStreamMarshalAttrs(t *testing.T) {
} }
func TestStreamSetRow(t *testing.T) { func TestStreamSetRow(t *testing.T) {
// Test error exceptions. // Test error exceptions
file := NewFile() file := NewFile()
defer func() { defer func() {
assert.NoError(t, file.Close()) assert.NoError(t, file.Close())
@ -278,10 +287,10 @@ func TestStreamSetRow(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) 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.NoError(t, streamWriter.SetRow("A1", []interface{}{}))
assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error()) 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.WorkBook = nil
file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8") 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") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) 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("A1", nil, RowOpts{OutlineLevel: 1}))
assert.NoError(t, streamWriter.SetRow("A2", nil, RowOpts{OutlineLevel: 7})) assert.NoError(t, streamWriter.SetRow("A2", nil, RowOpts{OutlineLevel: 7}))
assert.ErrorIs(t, ErrOutlineLevel, streamWriter.SetRow("A3", nil, RowOpts{OutlineLevel: 8})) assert.ErrorIs(t, ErrOutlineLevel, streamWriter.SetRow("A3", nil, RowOpts{OutlineLevel: 8}))
assert.NoError(t, streamWriter.Flush()) 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"))) assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx")))
file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx")) file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx"))

434
styles.go
View File

@ -13,7 +13,6 @@ package excelize
import ( import (
"bytes" "bytes"
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
@ -1076,35 +1075,25 @@ func (f *File) sharedStringsWriter() {
// parseFormatStyleSet provides a function to parse the format settings of the // parseFormatStyleSet provides a function to parse the format settings of the
// cells and conditional formats. // cells and conditional formats.
func parseFormatStyleSet(style interface{}) (*Style, error) { func parseFormatStyleSet(style *Style) (*Style, error) {
fs := Style{}
var err error var err error
switch v := style.(type) { if style.Font != nil {
case string: if len(style.Font.Family) > MaxFontFamilyLength {
err = json.Unmarshal([]byte(v), &fs) return style, ErrFontLength
case *Style:
fs = *v
default:
err = ErrParameterInvalid
} }
if fs.Font != nil { if style.Font.Size > MaxFontSize {
if len(fs.Font.Family) > MaxFontFamilyLength { return style, ErrFontSize
return &fs, ErrFontLength
}
if fs.Font.Size > MaxFontSize {
return &fs, ErrFontSize
} }
} }
if fs.CustomNumFmt != nil && len(*fs.CustomNumFmt) == 0 { if style.CustomNumFmt != nil && len(*style.CustomNumFmt) == 0 {
err = ErrCustomNumFmt err = ErrCustomNumFmt
} }
return &fs, err return style, err
} }
// NewStyle provides a function to create the style for cells by given structure // NewStyle provides a function to create the style for cells by given style
// pointer or JSON. This function is concurrency safe. Note that // options. This function is concurrency safe. Note that the 'Font.Color' field
// the 'Font.Color' field uses an RGB color represented in 'RRGGBB' hexadecimal // uses an RGB color represented in 'RRGGBB' hexadecimal notation.
// notation.
// //
// The following table shows the border types used in 'Border.Type' supported by // The following table shows the border types used in 'Border.Type' supported by
// excelize: // excelize:
@ -1983,13 +1972,16 @@ func parseFormatStyleSet(style interface{}) (*Style, error) {
// err = f.SetCellStyle("Sheet1", "A6", "A6", style) // err = f.SetCellStyle("Sheet1", "A6", "A6", style)
// //
// Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017 // 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 ( var (
fs *Style fs *Style
font *xlsxFont font *xlsxFont
err error err error
cellXfsID, fontID, borderID, fillID int cellXfsID, fontID, borderID, fillID int
) )
if style == nil {
return cellXfsID, err
}
fs, err = parseFormatStyleSet(style) fs, err = parseFormatStyleSet(style)
if err != nil { if err != nil {
return cellXfsID, err 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 // NewConditionalStyle provides a function to create style for conditional
// format by given style format. The parameters are the same with the NewStyle // 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 // function.
// set font, fills, alignment and borders currently. func (f *File) NewConditionalStyle(style *Style) (int, error) {
func (f *File) NewConditionalStyle(style string) (int, error) {
s, err := f.stylesReader() s, err := f.stylesReader()
if err != nil { if err != nil {
return 0, err return 0, err
@ -2836,51 +2827,51 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
// //
// Type | Parameters // Type | Parameters
// ---------------+------------------------------------ // ---------------+------------------------------------
// cell | criteria // cell | Criteria
// | value // | Value
// | minimum // | Minimum
// | maximum // | Maximum
// date | criteria // date | Criteria
// | value // | Value
// | minimum // | Minimum
// | maximum // | Maximum
// time_period | criteria // time_period | Criteria
// text | criteria // text | Criteria
// | value // | Value
// average | criteria // average | Criteria
// duplicate | (none) // duplicate | (none)
// unique | (none) // unique | (none)
// top | criteria // top | Criteria
// | value // | Value
// bottom | criteria // bottom | Criteria
// | value // | Value
// blanks | (none) // blanks | (none)
// no_blanks | (none) // no_blanks | (none)
// errors | (none) // errors | (none)
// no_errors | (none) // no_errors | (none)
// 2_color_scale | min_type // 2_color_scale | MinType
// | max_type // | MaxType
// | min_value // | MinValue
// | max_value // | MaxValue
// | min_color // | MinColor
// | max_color // | MaxColor
// 3_color_scale | min_type // 3_color_scale | MinType
// | mid_type // | MidType
// | max_type // | MaxType
// | min_value // | MinValue
// | mid_value // | MidValue
// | max_value // | MaxValue
// | min_color // | MinColor
// | mid_color // | MidColor
// | max_color // | MaxColor
// data_bar | min_type // data_bar | MinType
// | max_type // | MaxType
// | min_value // | MinValue
// | max_value // | MaxValue
// | bar_color // | BarColor
// formula | criteria // 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 // will be evaluated. It has no default value. The most common criteria as
// applied to {"type""cell"} are: // 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 // value: The value is generally used along with the criteria parameter to set
// the rule by which the cell data will be evaluated: // 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: // 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 // 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 // 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: // 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 { // if err != nil {
// fmt.Println(err) // 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 // Note: In Excel, a conditional format is superimposed over the existing cell
// format and not all cell format properties can be modified. Properties that // 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: // These can be replicated using the following excelize formats:
// //
// // Rose format for bad conditional. // // 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. // // 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. // // 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 // type: minimum - The minimum parameter is used to set the lower limiting value
// when the criteria is either "between" or "not between". // when the criteria is either "between" or "not between".
// //
// // Highlight cells rules: 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 // 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 // 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: // conditional format:
// //
// // Top/Bottom rules: Above Average... // // 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... // // 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: // type: duplicate - The duplicate type is used to highlight duplicate cells in a range:
// //
// // Highlight cells rules: Duplicate Values... // // 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: // type: unique - The unique type is used to highlight unique cells in a range:
// //
// // Highlight cells rules: Not Equal To... // // 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: // 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. // // 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: // 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 // type: 2_color_scale - The 2_color_scale type is used to specify Excel's "2
// Color Scale" style conditional format: // Color Scale" style conditional format:
// //
// // Color scales: 2 color. // // 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, // This conditional type can be modified with MinType, MaxType, MinValue,
// max_value, min_color and max_color, see below. // MaxValue, MinColor and MaxColor, see below.
// //
// type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3 // type: 3_color_scale - The 3_color_scale type is used to specify Excel's "3
// Color Scale" style conditional format: // Color Scale" style conditional format:
// //
// // Color scales: 3 color. // // 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, // This conditional type can be modified with MinType, MidType, MaxType,
// min_value, mid_value, max_value, min_color, mid_color and max_color, see // MinValue, MidValue, MaxValue, MinColor, MidColor and MaxColor, see
// below. // below.
// //
// type: data_bar - The data_bar type is used to specify Excel's "Data Bar" // type: data_bar - The data_bar type is used to specify Excel's "Data Bar"
// style conditional format. // 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. // // 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: // The available min/mid/max types are:
// //
// min (for min_type only) // min (for MinType only)
// num // num
// percent // percent
// percentile // percentile
// formula // 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 // 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_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
// conditional formatting type is 2_color_scale, 3_color_scale or data_bar. // 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. // // 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. // BarColor - Used for data_bar. Same as MinColor, see above.
// func (f *File) SetConditionalFormat(sheet, reference string, opts []ConditionalFormatOptions) error {
// bar_color - Used for data_bar. Same as min_color, see above. drawContFmtFunc := map[string]func(p int, ct string, fmtCond *ConditionalFormatOptions) *xlsxCfRule{
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{
"cellIs": drawCondFmtCellIs, "cellIs": drawCondFmtCellIs,
"top10": drawCondFmtTop10, "top10": drawCondFmtTop10,
"aboveAverage": drawCondFmtAboveAverage, "aboveAverage": drawCondFmtAboveAverage,
@ -3059,7 +3196,7 @@ func (f *File) SetConditionalFormat(sheet, reference, opts string) error {
return err return err
} }
var cfRule []*xlsxCfRule var cfRule []*xlsxCfRule
for p, v := range format { for p, v := range opts {
var vt, ct string var vt, ct string
var ok bool var ok bool
// "type" is a required parameter, check for valid validation types. // "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" { if ok || vt == "expression" {
drawFunc, ok := drawContFmtFunc[vt] drawFunc, ok := drawContFmtFunc[vt]
if ok { 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 // extractCondFmtCellIs provides a function to extract conditional format
// settings for cell value (include between, not between, equal, not equal, // settings for cell value (include between, not between, equal, not equal,
// greater than and less than) by given conditional formatting rule. // greater than and less than) by given conditional formatting rule.
func extractCondFmtCellIs(c *xlsxCfRule) *conditionalOptions { func extractCondFmtCellIs(c *xlsxCfRule) ConditionalFormatOptions {
format := conditionalOptions{Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID} format := ConditionalFormatOptions{Type: "cell", Criteria: operatorType[c.Operator], Format: *c.DxfID}
if len(c.Formula) == 2 { if len(c.Formula) == 2 {
format.Minimum, format.Maximum = c.Formula[0], c.Formula[1] format.Minimum, format.Maximum = c.Formula[0], c.Formula[1]
return &format return format
} }
format.Value = c.Formula[0] format.Value = c.Formula[0]
return &format return format
} }
// extractCondFmtTop10 provides a function to extract conditional format // extractCondFmtTop10 provides a function to extract conditional format
// settings for top N (default is top 10) by given conditional formatting // settings for top N (default is top 10) by given conditional formatting
// rule. // rule.
func extractCondFmtTop10(c *xlsxCfRule) *conditionalOptions { func extractCondFmtTop10(c *xlsxCfRule) ConditionalFormatOptions {
format := conditionalOptions{ format := ConditionalFormatOptions{
Type: "top", Type: "top",
Criteria: "=", Criteria: "=",
Format: *c.DxfID, Format: *c.DxfID,
@ -3110,14 +3247,14 @@ func extractCondFmtTop10(c *xlsxCfRule) *conditionalOptions {
if c.Bottom { if c.Bottom {
format.Type = "bottom" format.Type = "bottom"
} }
return &format return format
} }
// extractCondFmtAboveAverage provides a function to extract conditional format // extractCondFmtAboveAverage provides a function to extract conditional format
// settings for above average and below average by given conditional formatting // settings for above average and below average by given conditional formatting
// rule. // rule.
func extractCondFmtAboveAverage(c *xlsxCfRule) *conditionalOptions { func extractCondFmtAboveAverage(c *xlsxCfRule) ConditionalFormatOptions {
return &conditionalOptions{ return ConditionalFormatOptions{
Type: "average", Type: "average",
Criteria: "=", Criteria: "=",
Format: *c.DxfID, Format: *c.DxfID,
@ -3128,8 +3265,8 @@ func extractCondFmtAboveAverage(c *xlsxCfRule) *conditionalOptions {
// extractCondFmtDuplicateUniqueValues provides a function to extract // extractCondFmtDuplicateUniqueValues provides a function to extract
// conditional format settings for duplicate and unique values by given // conditional format settings for duplicate and unique values by given
// conditional formatting rule. // conditional formatting rule.
func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) *conditionalOptions { func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) ConditionalFormatOptions {
return &conditionalOptions{ return ConditionalFormatOptions{
Type: map[string]string{ Type: map[string]string{
"duplicateValues": "duplicate", "duplicateValues": "duplicate",
"uniqueValues": "unique", "uniqueValues": "unique",
@ -3142,8 +3279,8 @@ func extractCondFmtDuplicateUniqueValues(c *xlsxCfRule) *conditionalOptions {
// extractCondFmtColorScale provides a function to extract conditional format // extractCondFmtColorScale provides a function to extract conditional format
// settings for color scale (include 2 color scale and 3 color scale) by given // settings for color scale (include 2 color scale and 3 color scale) by given
// conditional formatting rule. // conditional formatting rule.
func extractCondFmtColorScale(c *xlsxCfRule) *conditionalOptions { func extractCondFmtColorScale(c *xlsxCfRule) ConditionalFormatOptions {
var format conditionalOptions var format ConditionalFormatOptions
format.Type, format.Criteria = "2_color_scale", "=" format.Type, format.Criteria = "2_color_scale", "="
values := len(c.ColorScale.Cfvo) values := len(c.ColorScale.Cfvo)
colors := len(c.ColorScale.Color) 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") format.MaxColor = "#" + strings.TrimPrefix(strings.ToUpper(c.ColorScale.Color[2].RGB), "FF")
} }
return &format return format
} }
// extractCondFmtDataBar provides a function to extract conditional format // extractCondFmtDataBar provides a function to extract conditional format
// settings for data bar by given conditional formatting rule. // settings for data bar by given conditional formatting rule.
func extractCondFmtDataBar(c *xlsxCfRule) *conditionalOptions { func extractCondFmtDataBar(c *xlsxCfRule) ConditionalFormatOptions {
format := conditionalOptions{Type: "data_bar", Criteria: "="} format := ConditionalFormatOptions{Type: "data_bar", Criteria: "="}
if c.DataBar != nil { if c.DataBar != nil {
format.MinType = c.DataBar.Cfvo[0].Type format.MinType = c.DataBar.Cfvo[0].Type
format.MaxType = c.DataBar.Cfvo[1].Type format.MaxType = c.DataBar.Cfvo[1].Type
format.BarColor = "#" + strings.TrimPrefix(strings.ToUpper(c.DataBar.Color[0].RGB), "FF") 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 // extractCondFmtExp provides a function to extract conditional format settings
// for expression by given conditional formatting rule. // for expression by given conditional formatting rule.
func extractCondFmtExp(c *xlsxCfRule) *conditionalOptions { func extractCondFmtExp(c *xlsxCfRule) ConditionalFormatOptions {
format := conditionalOptions{Type: "formula", Format: *c.DxfID} format := ConditionalFormatOptions{Type: "formula", Format: *c.DxfID}
if len(c.Formula) > 0 { if len(c.Formula) > 0 {
format.Criteria = c.Formula[0] format.Criteria = c.Formula[0]
} }
return &format return format
} }
// GetConditionalFormats returns conditional format settings by given worksheet // GetConditionalFormats returns conditional format settings by given worksheet
// name. // name.
func (f *File) GetConditionalFormats(sheet string) (map[string]string, error) { func (f *File) GetConditionalFormats(sheet string) (map[string][]ConditionalFormatOptions, error) {
extractContFmtFunc := map[string]func(c *xlsxCfRule) *conditionalOptions{ extractContFmtFunc := map[string]func(c *xlsxCfRule) ConditionalFormatOptions{
"cellIs": extractCondFmtCellIs, "cellIs": extractCondFmtCellIs,
"top10": extractCondFmtTop10, "top10": extractCondFmtTop10,
"aboveAverage": extractCondFmtAboveAverage, "aboveAverage": extractCondFmtAboveAverage,
@ -3211,20 +3348,19 @@ func (f *File) GetConditionalFormats(sheet string) (map[string]string, error) {
"expression": extractCondFmtExp, "expression": extractCondFmtExp,
} }
conditionalFormats := make(map[string]string) conditionalFormats := make(map[string][]ConditionalFormatOptions)
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
return conditionalFormats, err return conditionalFormats, err
} }
for _, cf := range ws.ConditionalFormatting { for _, cf := range ws.ConditionalFormatting {
var opts []*conditionalOptions var opts []ConditionalFormatOptions
for _, cr := range cf.CfRule { for _, cr := range cf.CfRule {
if extractFunc, ok := extractContFmtFunc[cr.Type]; ok { if extractFunc, ok := extractContFmtFunc[cr.Type]; ok {
opts = append(opts, extractFunc(cr)) opts = append(opts, extractFunc(cr))
} }
} }
options, _ := json.Marshal(opts) conditionalFormats[cf.SQRef] = opts
conditionalFormats[cf.SQRef] = string(options)
} }
return conditionalFormats, err return conditionalFormats, err
} }
@ -3248,7 +3384,7 @@ func (f *File) UnsetConditionalFormat(sheet, reference string) error {
// drawCondFmtCellIs provides a function to create conditional formatting rule // drawCondFmtCellIs provides a function to create conditional formatting rule
// for cell value (include between, not between, equal, not equal, greater // for cell value (include between, not between, equal, not equal, greater
// than and less than) by given priority, criteria type and format settings. // 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{ c := &xlsxCfRule{
Priority: p + 1, Priority: p + 1,
Type: validType[format.Type], 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 // drawCondFmtTop10 provides a function to create conditional formatting rule
// for top N (default is top 10) by given priority, criteria type and format // for top N (default is top 10) by given priority, criteria type and format
// settings. // settings.
func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule { func drawCondFmtTop10(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
c := &xlsxCfRule{ c := &xlsxCfRule{
Priority: p + 1, Priority: p + 1,
Bottom: format.Type == "bottom", Bottom: format.Type == "bottom",
@ -3286,7 +3422,7 @@ func drawCondFmtTop10(p int, ct string, format *conditionalOptions) *xlsxCfRule
// drawCondFmtAboveAverage provides a function to create conditional // drawCondFmtAboveAverage provides a function to create conditional
// formatting rule for above average and below average by given priority, // formatting rule for above average and below average by given priority,
// criteria type and format settings. // 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{ return &xlsxCfRule{
Priority: p + 1, Priority: p + 1,
Type: validType[format.Type], Type: validType[format.Type],
@ -3298,7 +3434,7 @@ func drawCondFmtAboveAverage(p int, ct string, format *conditionalOptions) *xlsx
// drawCondFmtDuplicateUniqueValues provides a function to create conditional // drawCondFmtDuplicateUniqueValues provides a function to create conditional
// formatting rule for duplicate and unique values by given priority, criteria // formatting rule for duplicate and unique values by given priority, criteria
// type and format settings. // type and format settings.
func drawCondFmtDuplicateUniqueValues(p int, ct string, format *conditionalOptions) *xlsxCfRule { func drawCondFmtDuplicateUniqueValues(p int, ct string, format *ConditionalFormatOptions) *xlsxCfRule {
return &xlsxCfRule{ return &xlsxCfRule{
Priority: p + 1, Priority: p + 1,
Type: validType[format.Type], Type: validType[format.Type],
@ -3309,7 +3445,7 @@ func drawCondFmtDuplicateUniqueValues(p int, ct string, format *conditionalOptio
// drawCondFmtColorScale provides a function to create conditional formatting // drawCondFmtColorScale provides a function to create conditional formatting
// rule for color scale (include 2 color scale and 3 color scale) by given // rule for color scale (include 2 color scale and 3 color scale) by given
// priority, criteria type and format settings. // 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 minValue := format.MinValue
if minValue == "" { if minValue == "" {
minValue = "0" minValue = "0"
@ -3346,7 +3482,7 @@ func drawCondFmtColorScale(p int, ct string, format *conditionalOptions) *xlsxCf
// drawCondFmtDataBar provides a function to create conditional formatting // drawCondFmtDataBar provides a function to create conditional formatting
// rule for data bar by given priority, criteria type and format settings. // 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{ return &xlsxCfRule{
Priority: p + 1, Priority: p + 1,
Type: validType[format.Type], 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 // drawCondFmtExp provides a function to create conditional formatting rule
// for expression by given priority, criteria type and format settings. // 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{ return &xlsxCfRule{
Priority: p + 1, Priority: p + 1,
Type: validType[format.Type], Type: validType[format.Type],

View File

@ -1,7 +1,6 @@
package excelize package excelize
import ( import (
"fmt"
"math" "math"
"path/filepath" "path/filepath"
"strings" "strings"
@ -13,15 +12,15 @@ import (
func TestStyleFill(t *testing.T) { func TestStyleFill(t *testing.T) {
cases := []struct { cases := []struct {
label string label string
format string format *Style
expectFill bool expectFill bool
}{{ }{{
label: "no_fill", label: "no_fill",
format: `{"alignment":{"wrap_text":true}}`, format: &Style{Alignment: &Alignment{WrapText: true}},
expectFill: false, expectFill: false,
}, { }, {
label: "fill", label: "fill",
format: `{"fill":{"type":"pattern","pattern":1,"color":["#000000"]}}`, format: &Style{Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"#000000"}}},
expectFill: true, expectFill: true,
}} }}
@ -40,9 +39,9 @@ func TestStyleFill(t *testing.T) {
} }
} }
f := NewFile() 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) 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.NoError(t, err)
assert.Equal(t, styleID1, styleID2) assert.Equal(t, styleID1, styleID2)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleFill.xlsx")))
@ -51,23 +50,23 @@ func TestStyleFill(t *testing.T) {
func TestSetConditionalFormat(t *testing.T) { func TestSetConditionalFormat(t *testing.T) {
cases := []struct { cases := []struct {
label string label string
format string format []ConditionalFormatOptions
rules []*xlsxCfRule rules []*xlsxCfRule
}{{ }{{
label: "3_color_scale", label: "3_color_scale",
format: `[{ format: []ConditionalFormatOptions{{
"type":"3_color_scale", Type: "3_color_scale",
"criteria":"=", Criteria: "=",
"min_type":"num", MinType: "num",
"mid_type":"num", MidType: "num",
"max_type":"num", MaxType: "num",
"min_value": "-10", MinValue: "-10",
"mid_value": "0", MidValue: "0",
"max_value": "10", MaxValue: "10",
"min_color":"ff0000", MinColor: "ff0000",
"mid_color":"00ff00", MidColor: "00ff00",
"max_color":"0000ff" MaxColor: "0000ff",
}]`, }},
rules: []*xlsxCfRule{{ rules: []*xlsxCfRule{{
Priority: 1, Priority: 1,
Type: "colorScale", Type: "colorScale",
@ -93,16 +92,16 @@ func TestSetConditionalFormat(t *testing.T) {
}}, }},
}, { }, {
label: "3_color_scale default min/mid/max", label: "3_color_scale default min/mid/max",
format: `[{ format: []ConditionalFormatOptions{{
"type":"3_color_scale", Type: "3_color_scale",
"criteria":"=", Criteria: "=",
"min_type":"num", MinType: "num",
"mid_type":"num", MidType: "num",
"max_type":"num", MaxType: "num",
"min_color":"ff0000", MinColor: "ff0000",
"mid_color":"00ff00", MidColor: "00ff00",
"max_color":"0000ff" MaxColor: "0000ff",
}]`, }},
rules: []*xlsxCfRule{{ rules: []*xlsxCfRule{{
Priority: 1, Priority: 1,
Type: "colorScale", Type: "colorScale",
@ -128,14 +127,14 @@ func TestSetConditionalFormat(t *testing.T) {
}}, }},
}, { }, {
label: "2_color_scale default min/max", label: "2_color_scale default min/max",
format: `[{ format: []ConditionalFormatOptions{{
"type":"2_color_scale", Type: "2_color_scale",
"criteria":"=", Criteria: "=",
"min_type":"num", MinType: "num",
"max_type":"num", MaxType: "num",
"min_color":"ff0000", MinColor: "ff0000",
"max_color":"0000ff" MaxColor: "0000ff",
}]`, }},
rules: []*xlsxCfRule{{ rules: []*xlsxCfRule{{
Priority: 1, Priority: 1,
Type: "colorScale", Type: "colorScale",
@ -177,18 +176,18 @@ func TestSetConditionalFormat(t *testing.T) {
} }
func TestGetConditionalFormats(t *testing.T) { func TestGetConditionalFormats(t *testing.T) {
for _, format := range []string{ for _, format := range [][]ConditionalFormatOptions{
`[{"type":"cell","format":1,"criteria":"greater than","value":"6"}]`, {{Type: "cell", Format: 1, Criteria: "greater than", Value: "6"}},
`[{"type":"cell","format":1,"criteria":"between","minimum":"6","maximum":"8"}]`, {{Type: "cell", Format: 1, Criteria: "between", Minimum: "6", Maximum: "8"}},
`[{"type":"top","format":1,"criteria":"=","value":"6"}]`, {{Type: "top", Format: 1, Criteria: "=", Value: "6"}},
`[{"type":"bottom","format":1,"criteria":"=","value":"6"}]`, {{Type: "bottom", Format: 1, Criteria: "=", Value: "6"}},
`[{"type":"average","above_average":true,"format":1,"criteria":"="}]`, {{Type: "average", AboveAverage: true, Format: 1, Criteria: "="}},
`[{"type":"duplicate","format":1,"criteria":"="}]`, {{Type: "duplicate", Format: 1, Criteria: "="}},
`[{"type":"unique","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: "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":"=","min_type":"num","max_type":"num","min_color":"#FF0000","max_color":"#0000FF"}]`, {{Type: "2_color_scale", Criteria: "=", MinType: "num", MaxType: "num", MinColor: "#FF0000", MaxColor: "#0000FF"}},
`[{"type":"data_bar","criteria":"=","min_type":"min","max_type":"max","bar_color":"#638EC6"}]`, {{Type: "data_bar", Criteria: "=", MinType: "min", MaxType: "max", BarColor: "#638EC6"}},
`[{"type":"formula","format":1,"criteria":"="}]`, {{Type: "formula", Format: 1, Criteria: "="}},
} { } {
f := NewFile() f := NewFile()
err := f.SetConditionalFormat("Sheet1", "A1:A2", format) err := f.SetConditionalFormat("Sheet1", "A1:A2", format)
@ -210,9 +209,9 @@ func TestUnsetConditionalFormat(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7)) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 7))
assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10")) 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, 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")) assert.NoError(t, f.UnsetConditionalFormat("Sheet1", "A1:A10"))
// Test unset conditional format on not exists worksheet // Test unset conditional format on not exists worksheet
assert.EqualError(t, f.UnsetConditionalFormat("SheetN", "A1:A10"), "sheet SheetN does not exist") 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) { func TestNewStyle(t *testing.T) {
f := NewFile() 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) assert.NoError(t, err)
styles, err := f.stylesReader() styles, err := f.stylesReader()
assert.NoError(t, err) assert.NoError(t, err)
@ -234,8 +233,8 @@ func TestNewStyle(t *testing.T) {
assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles") assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
_, err = f.NewStyle(&Style{}) _, err = f.NewStyle(&Style{})
assert.NoError(t, err) assert.NoError(t, err)
_, err = f.NewStyle(Style{}) _, err = f.NewStyle(nil)
assert.EqualError(t, err, ErrParameterInvalid.Error()) assert.NoError(t, err)
var exp string var exp string
_, err = f.NewStyle(&Style{CustomNumFmt: &exp}) _, err = f.NewStyle(&Style{CustomNumFmt: &exp})
@ -326,7 +325,7 @@ func TestNewConditionalStyle(t *testing.T) {
// Test create conditional style with unsupported charset style sheet // Test create conditional style with unsupported charset style sheet
f.Styles = nil f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) 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") 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) { func TestSetCellStyle(t *testing.T) {
f := NewFile() 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") 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()) 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()) 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.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8") 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) styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, -1, styleID) 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.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.getStyleID(&xlsxStyleSheet{ _, err = f.getStyleID(&xlsxStyleSheet{
@ -429,11 +428,11 @@ func TestThemeColor(t *testing.T) {
func TestGetNumFmtID(t *testing.T) { func TestGetNumFmtID(t *testing.T) {
f := NewFile() 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) assert.NoError(t, err)
id1 := getNumFmtID(&xlsxStyleSheet{}, fs1) 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) assert.NoError(t, err)
id2 := getNumFmtID(&xlsxStyleSheet{}, fs2) id2 := getNumFmtID(&xlsxStyleSheet{}, fs2)

122
table.go
View File

@ -12,7 +12,6 @@
package excelize package excelize
import ( import (
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"regexp" "regexp"
@ -22,64 +21,54 @@ import (
// parseTableOptions provides a function to parse the format settings of the // parseTableOptions provides a function to parse the format settings of the
// table with default value. // table with default value.
func parseTableOptions(opts string) (*tableOptions, error) { func parseTableOptions(opts *TableOptions) *TableOptions {
options := tableOptions{ShowRowStripes: true} if opts == nil {
err := json.Unmarshal(fallbackOptions(opts), &options) return &TableOptions{ShowRowStripes: boolPtr(true)}
return &options, err }
if opts.ShowRowStripes == nil {
opts.ShowRowStripes = boolPtr(true)
}
return opts
} }
// AddTable provides the method to add table in a worksheet by given worksheet // 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 // name, range reference and format set. For example, create a table of A1:D5
// on Sheet1: // 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: // Create a table of F2:H6 on Sheet2 with format set:
// //
// err := f.AddTable("Sheet2", "F2", "H6", `{ // err := f.AddTable("Sheet2", "F2:H6", &excelize.TableOptions{
// "table_name": "table", // Name: "table",
// "table_style": "TableStyleMedium2", // StyleName: "TableStyleMedium2",
// "show_first_column": true, // ShowFirstColumn: true,
// "show_last_column": true, // ShowLastColumn: true,
// "show_row_stripes": false, // ShowRowStripes: &disable,
// "show_column_stripes": true // ShowColumnStripes: true,
// }`) // })
// //
// Note that the table must be at least two lines including the header. The // 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 cells must contain strings and must be unique, and must set the
// header row data of the table before calling the AddTable function. Multiple // header row data of the table before calling the AddTable function. Multiple
// tables range reference that can't have an intersection. // 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 // TableStyleLight1 - TableStyleLight21
// TableStyleMedium1 - TableStyleMedium28 // TableStyleMedium1 - TableStyleMedium28
// TableStyleDark1 - TableStyleDark11 // TableStyleDark1 - TableStyleDark11
func (f *File) AddTable(sheet, hCell, vCell, opts string) error { func (f *File) AddTable(sheet, reference string, opts *TableOptions) error {
options, err := parseTableOptions(opts) options := parseTableOptions(opts)
if err != nil {
return err
}
// Coordinate conversion, convert C1:B3 to 2,0,1,2. // Coordinate conversion, convert C1:B3 to 2,0,1,2.
hCol, hRow, err := CellNameToCoordinates(hCell) coordinates, err := rangeRefToCoordinates(reference)
if err != nil { if err != nil {
return err return err
} }
vCol, vRow, err := CellNameToCoordinates(vCell) // Correct table reference range, such correct C1:B3 to B1:C3.
if err != nil { _ = sortCoordinates(coordinates)
return err
}
if vCol < hCol {
vCol, hCol = hCol, vCol
}
if vRow < hRow {
vRow, hRow = hRow, vRow
}
tableID := f.countTables() + 1 tableID := f.countTables() + 1
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml" sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl") tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
@ -91,7 +80,7 @@ func (f *File) AddTable(sheet, hCell, vCell, opts string) error {
return err return err
} }
f.addSheetNameSpace(sheet, SourceRelationship) 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 err
} }
return f.addContentTypePart(tableID, "table") 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, // addTable provides a function to add table by given worksheet name,
// range reference and format set. // 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. // Correct the minimum number of rows, the table at least two lines.
if y1 == y2 { if y1 == y2 {
y2++ y2++
@ -171,7 +160,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab
return err return err
} }
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2) tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
name := opts.TableName name := opts.Name
if name == "" { if name == "" {
name = "Table" + strconv.Itoa(i) 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, TableColumn: tableColumns,
}, },
TableStyleInfo: &xlsxTableStyleInfo{ TableStyleInfo: &xlsxTableStyleInfo{
Name: opts.TableStyle, Name: opts.StyleName,
ShowFirstColumn: opts.ShowFirstColumn, ShowFirstColumn: opts.ShowFirstColumn,
ShowLastColumn: opts.ShowLastColumn, ShowLastColumn: opts.ShowLastColumn,
ShowRowStripes: opts.ShowRowStripes, ShowRowStripes: *opts.ShowRowStripes,
ShowColumnStripes: opts.ShowColumnStripes, ShowColumnStripes: opts.ShowColumnStripes,
}, },
} }
@ -201,36 +190,30 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *tab
return nil 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 // 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 // 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 // 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: // 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: // 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 // criteria
// //
// It isn't sufficient to just specify the filter condition. You must also // 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 // 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. // this isn't part of the file format.
// //
// Setting a filter criteria for a column: // 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: // for setting the filter criteria:
// //
// == // ==
@ -278,28 +261,15 @@ func parseAutoFilterOptions(opts string) (*autoFilterOptions, error) {
// x < 2000 // x < 2000
// col < 2000 // col < 2000
// Price < 2000 // Price < 2000
func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error { func (f *File) AutoFilter(sheet, reference string, opts *AutoFilterOptions) error {
hCol, hRow, err := CellNameToCoordinates(hCell) coordinates, err := rangeRefToCoordinates(reference)
if err != nil { if err != nil {
return err return err
} }
vCol, vRow, err := CellNameToCoordinates(vCell) _ = sortCoordinates(coordinates)
if err != nil { // Correct reference range, such correct C1:B3 to B1:C3.
return err ref, _ := f.coordinatesToRangeRef(coordinates, true)
} filterDB := "_xlnm._FilterDatabase"
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"
wb, err := f.workbookReader() wb, err := f.workbookReader()
if err != nil { if err != nil {
return err return err
@ -332,13 +302,13 @@ func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
} }
} }
refRange := vCol - hCol refRange := coordinates[2] - coordinates[0]
return f.autoFilter(sheet, ref, refRange, hCol, options) return f.autoFilter(sheet, ref, refRange, coordinates[0], opts)
} }
// autoFilter provides a function to extract the tokens from the filter // autoFilter provides a function to extract the tokens from the filter
// expression. The tokens are mainly non-whitespace groups. // 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) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
return err return err
@ -351,7 +321,7 @@ func (f *File) autoFilter(sheet, ref string, refRange, col int, opts *autoFilter
Ref: ref, Ref: ref,
} }
ws.AutoFilter = filter ws.AutoFilter = filter
if opts.Column == "" || opts.Expression == "" { if opts == nil || opts.Column == "" || opts.Expression == "" {
return nil return nil
} }

View File

@ -11,22 +11,28 @@ import (
func TestAddTable(t *testing.T) { func TestAddTable(t *testing.T) {
f, err := prepareTestBook1() f, err := prepareTestBook1()
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.AddTable("Sheet1", "B26", "A21", `{}`)) assert.NoError(t, f.AddTable("Sheet1", "B26:A21", nil))
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", "A2:B5", &TableOptions{
assert.NoError(t, f.AddTable("Sheet2", "F1", "F1", `{"table_style":"TableStyleMedium8"}`)) 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 // Test add table in not exist worksheet
assert.EqualError(t, f.AddTable("SheetN", "B26", "A21", `{}`), "sheet SheetN does not exist") assert.EqualError(t, f.AddTable("SheetN", "B26:A21", nil), "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")
// Test add table with illegal cell reference // 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", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddTable("Sheet1", "A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).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"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx")))
// Test add table with invalid sheet name // 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 // Test addTable with illegal cell reference
f = NewFile() f = NewFile()
assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell reference [0, 0]") 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") outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
f, err := prepareTestBook1() f, err := prepareTestBook1()
assert.NoError(t, err) assert.NoError(t, err)
formats := []string{ for i, opts := range []*AutoFilterOptions{
``, nil,
`{"column":"B","expression":"x != blanks"}`, {Column: "B", Expression: ""},
`{"column":"B","expression":"x == blanks"}`, {Column: "B", Expression: "x != blanks"},
`{"column":"B","expression":"x != nonblanks"}`, {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 == nonblanks"},
`{"column":"B","expression":"x == 1 or x == 2"}`, {Column: "B", Expression: "x <= 1 and x >= 2"},
`{"column":"B","expression":"x == 1 or x == 2*"}`, {Column: "B", Expression: "x == 1 or x == 2"},
} {Column: "B", Expression: "x == 1 or x == 2*"},
for i, format := range formats { } {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) { t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet1", "D4", "B1", format) assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts))
assert.NoError(t, err)
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1))) assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
}) })
} }
// Test add auto filter with invalid sheet name // 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 // 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", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.EqualError(t, f.AutoFilter("Sheet1", "A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add auto filter with unsupported charset workbook // Test add auto filter with unsupported charset workbook
f.WorkBook = nil f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) 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) { func TestAutoFilterError(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx") outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx")
f, err := prepareTestBook1() f, err := prepareTestBook1()
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow() for i, opts := range []*AutoFilterOptions{
} {Column: "B", Expression: "x <= 1 and x >= blanks"},
{Column: "B", Expression: "x -- y or x == *2*"},
formats := []string{ {Column: "B", Expression: "x != y or x ? *2"},
`{"column":"B","expression":"x <= 1 and x >= blanks"}`, {Column: "B", Expression: "x -- y o r x == *2"},
`{"column":"B","expression":"x -- y or x == *2*"}`, {Column: "B", Expression: "x -- y"},
`{"column":"B","expression":"x != y or x ? *2"}`, {Column: "A", Expression: "x -- y"},
`{"column":"B","expression":"x -- y o r x == *2"}`, } {
`{"column":"B","expression":"x -- y"}`,
`{"column":"A","expression":"x -- y"}`,
}
for i, format := range formats {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) { t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet2", "D4", "B1", format) if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) {
if assert.Error(t, err) {
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1))) 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", Column: "A",
Expression: "", Expression: "",
}), "sheet SheetN does not exist") }), "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: "-", Column: "-",
Expression: "-", Expression: "-",
}), newInvalidColumnNameError("-").Error()) }), newInvalidColumnNameError("-").Error())
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &autoFilterOptions{ assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &AutoFilterOptions{
Column: "A", Column: "A",
Expression: "-", Expression: "-",
}), `incorrect index of column 'A'`) }), `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", Column: "A",
Expression: "-", Expression: "-",
}), `incorrect number of tokens in criteria '-'`) }), `incorrect number of tokens in criteria '-'`)

View File

@ -59,8 +59,18 @@ func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
return opts, err return opts, err
} }
// ProtectWorkbook provides a function to prevent other users from accidentally or // ProtectWorkbook provides a function to prevent other users from viewing
// deliberately changing, moving, or deleting data in a workbook. // 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 { func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
wb, err := f.workbookReader() wb, err := f.workbookReader()
if err != nil { if err != nil {
@ -93,8 +103,8 @@ func (f *File) ProtectWorkbook(opts *WorkbookProtectionOptions) error {
} }
// UnprotectWorkbook provides a function to remove protection for workbook, // UnprotectWorkbook provides a function to remove protection for workbook,
// specified the second optional password parameter to remove workbook // specified the optional password parameter to remove workbook protection with
// protection with password verification. // password verification.
func (f *File) UnprotectWorkbook(password ...string) error { func (f *File) UnprotectWorkbook(password ...string) error {
wb, err := f.workbookReader() wb, err := f.workbookReader()
if err != nil { if err != nil {

View File

@ -21,11 +21,11 @@ func TestWorkbookProps(t *testing.T) {
opts, err := f.GetWorkbookProps() opts, err := f.GetWorkbookProps()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, opts) 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.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8") 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.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.GetWorkbookProps() _, err = f.GetWorkbookProps()

View File

@ -15,13 +15,13 @@ import "encoding/xml"
// AppProperties directly maps the document application properties. // AppProperties directly maps the document application properties.
type AppProperties struct { type AppProperties struct {
Application string `json:"application"` Application string
ScaleCrop bool `json:"scale_crop"` ScaleCrop bool
DocSecurity int `json:"doc_security"` DocSecurity int
Company string `json:"company"` Company string
LinksUpToDate bool `json:"links_up_to_date"` LinksUpToDate bool
HyperlinksChanged bool `json:"hyperlinks_changed"` HyperlinksChanged bool
AppVersion string `json:"app_version"` AppVersion string
} }
// xlsxProperties specifies to an OOXML document properties such as the // xlsxProperties specifies to an OOXML document properties such as the

View File

@ -518,136 +518,83 @@ type cPageMargins struct {
T float64 `xml:"t,attr"` T float64 `xml:"t,attr"`
} }
// chartAxisOptions directly maps the format settings of the chart axis. // ChartAxis directly maps the format settings of the chart axis.
type chartAxisOptions struct { type ChartAxis struct {
None bool `json:"none"` None bool
Crossing string `json:"crossing"` MajorGridLines bool
MajorGridlines bool `json:"major_grid_lines"` MinorGridLines bool
MinorGridlines bool `json:"minor_grid_lines"` MajorUnit float64
MajorTickMark string `json:"major_tick_mark"` TickLabelSkip int
MinorTickMark string `json:"minor_tick_mark"` ReverseOrder bool
MinorUnitType string `json:"minor_unit_type"` Maximum *float64
MajorUnit float64 `json:"major_unit"` Minimum *float64
MajorUnitType string `json:"major_unit_type"` Font Font
TickLabelSkip int `json:"tick_label_skip"` LogBase float64
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"`
} }
// chartDimensionOptions directly maps the dimension of the chart. // ChartDimension directly maps the dimension of the chart.
type chartDimensionOptions struct { type ChartDimension struct {
Width int `json:"width"` Width *int
Height int `json:"height"` Height *int
} }
// chartOptions directly maps the format settings of the chart. // ChartPlotArea directly maps the format settings of the plot area.
type chartOptions struct { type ChartPlotArea struct {
Type string `json:"type"` ShowBubbleSize bool
Series []chartSeriesOptions `json:"series"` ShowCatName bool
Format pictureOptions `json:"format"` ShowLeaderLines bool
Dimension chartDimensionOptions `json:"dimension"` ShowPercent bool
Legend chartLegendOptions `json:"legend"` ShowSerName bool
Title chartTitleOptions `json:"title"` ShowVal bool
VaryColors bool `json:"vary_colors"` }
XAxis chartAxisOptions `json:"x_axis"`
YAxis chartAxisOptions `json:"y_axis"` // Chart directly maps the format settings of the chart.
Chartarea struct { type Chart struct {
Border struct { Type string
None bool `json:"none"` Series []ChartSeries
} `json:"border"` Format PictureOptions
Fill struct { Dimension ChartDimension
Color string `json:"color"` Legend ChartLegend
} `json:"fill"` Title ChartTitle
Pattern struct { VaryColors *bool
Pattern string `json:"pattern"` XAxis ChartAxis
FgColor string `json:"fg_color"` YAxis ChartAxis
BgColor string `json:"bg_color"` PlotArea ChartPlotArea
} `json:"pattern"` ShowBlanksAs string
} `json:"chartarea"` HoleSize int
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 order int
} }
// chartLegendOptions directly maps the format settings of the chart legend. // ChartLegend directly maps the format settings of the chart legend.
type chartLegendOptions struct { type ChartLegend struct {
None bool `json:"none"` None bool
DeleteSeries []int `json:"delete_series"` Position *string
Font Font `json:"font"` ShowLegendKey bool
Layout layoutOptions `json:"layout"`
Position string `json:"position"`
ShowLegendEntry bool `json:"show_legend_entry"`
ShowLegendKey bool `json:"show_legend_key"`
} }
// chartSeriesOptions directly maps the format settings of the chart series. // ChartMarker directly maps the format settings of the chart marker.
type chartSeriesOptions struct { type ChartMarker struct {
Name string `json:"name"` Symbol string
Categories string `json:"categories"` Size int
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"`
} }
// chartTitleOptions directly maps the format settings of the chart title. // ChartLine directly maps the format settings of the chart line.
type chartTitleOptions struct { type ChartLine struct {
None bool `json:"none"` Color string
Name string `json:"name"` Smooth bool
Overlay bool `json:"overlay"` Width float64
Layout layoutOptions `json:"layout"`
} }
// layoutOptions directly maps the format settings of the element layout. // ChartSeries directly maps the format settings of the chart series.
type layoutOptions struct { type ChartSeries struct {
X float64 `json:"x"` Name string
Y float64 `json:"y"` Categories string
Width float64 `json:"width"` Values string
Height float64 `json:"height"` Line ChartLine
Marker ChartMarker
}
// ChartTitle directly maps the format settings of the chart title.
type ChartTitle struct {
Name string
} }

View File

@ -74,9 +74,9 @@ type xlsxPhoneticRun struct {
// Comment directly maps the comment information. // Comment directly maps the comment information.
type Comment struct { type Comment struct {
Author string `json:"author"` Author string
AuthorID int `json:"author_id"` AuthorID int
Cell string `json:"cell"` Cell string
Text string `json:"text"` Text string
Runs []RichTextRun `json:"runs"` Runs []RichTextRun
} }

View File

@ -122,6 +122,13 @@ const (
// Excel 2007 or in compatibility mode. Slicer can only be used with // Excel 2007 or in compatibility mode. Slicer can only be used with
// PivotTables created in Excel 2007 or a newer version of Excel. // 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. // ColorMappingType is the type of color transformation.
@ -554,48 +561,48 @@ type xdrTxBody struct {
P []*aP `xml:"a:p"` P []*aP `xml:"a:p"`
} }
// pictureOptions directly maps the format settings of the picture. // PictureOptions directly maps the format settings of the picture.
type pictureOptions struct { type PictureOptions struct {
FPrintsWithSheet bool `json:"print_obj"` PrintObject *bool
FLocksWithSheet bool `json:"locked"` Locked *bool
NoChangeAspect bool `json:"lock_aspect_ratio"` LockAspectRatio bool
Autofit bool `json:"autofit"` AutoFit bool
OffsetX int `json:"x_offset"` OffsetX int
OffsetY int `json:"y_offset"` OffsetY int
XScale float64 `json:"x_scale"` XScale *float64
YScale float64 `json:"y_scale"` YScale *float64
Hyperlink string `json:"hyperlink"` Hyperlink string
HyperlinkType string `json:"hyperlink_type"` HyperlinkType string
Positioning string `json:"positioning"` Positioning string
} }
// shapeOptions directly maps the format settings of the shape. // Shape directly maps the format settings of the shape.
type shapeOptions struct { type Shape struct {
Macro string `json:"macro"` Macro string
Type string `json:"type"` Type string
Width int `json:"width"` Width *int
Height int `json:"height"` Height *int
Format pictureOptions `json:"format"` Format PictureOptions
Color shapeColorOptions `json:"color"` Color ShapeColor
Line lineOptions `json:"line"` Line ShapeLine
Paragraph []shapeParagraphOptions `json:"paragraph"` Paragraph []ShapeParagraph
} }
// shapeParagraphOptions directly maps the format settings of the paragraph in // ShapeParagraph directly maps the format settings of the paragraph in
// the shape. // the shape.
type shapeParagraphOptions struct { type ShapeParagraph struct {
Font Font `json:"font"` Font Font
Text string `json:"text"` Text string
} }
// shapeColorOptions directly maps the color settings of the shape. // ShapeColor directly maps the color settings of the shape.
type shapeColorOptions struct { type ShapeColor struct {
Line string `json:"line"` Line string
Fill string `json:"fill"` Fill string
Effect string `json:"effect"` Effect string
} }
// lineOptions directly maps the line settings of the shape. // ShapeLine directly maps the line settings of the shape.
type lineOptions struct { type ShapeLine struct {
Width float64 `json:"width"` Width *float64
} }

View File

@ -83,6 +83,6 @@ type xlsxRPr struct {
// RichTextRun directly maps the settings of the rich text run. // RichTextRun directly maps the settings of the rich text run.
type RichTextRun struct { type RichTextRun struct {
Font *Font `json:"font"` Font *Font
Text string `json:"text"` Text string
} }

View File

@ -314,63 +314,63 @@ type xlsxStyleColors struct {
// Alignment directly maps the alignment settings of the cells. // Alignment directly maps the alignment settings of the cells.
type Alignment struct { type Alignment struct {
Horizontal string `json:"horizontal"` Horizontal string
Indent int `json:"indent"` Indent int
JustifyLastLine bool `json:"justify_last_line"` JustifyLastLine bool
ReadingOrder uint64 `json:"reading_order"` ReadingOrder uint64
RelativeIndent int `json:"relative_indent"` RelativeIndent int
ShrinkToFit bool `json:"shrink_to_fit"` ShrinkToFit bool
TextRotation int `json:"text_rotation"` TextRotation int
Vertical string `json:"vertical"` Vertical string
WrapText bool `json:"wrap_text"` WrapText bool
} }
// Border directly maps the border settings of the cells. // Border directly maps the border settings of the cells.
type Border struct { type Border struct {
Type string `json:"type"` Type string
Color string `json:"color"` Color string
Style int `json:"style"` Style int
} }
// Font directly maps the font settings of the fonts. // Font directly maps the font settings of the fonts.
type Font struct { type Font struct {
Bold bool `json:"bold"` Bold bool
Italic bool `json:"italic"` Italic bool
Underline string `json:"underline"` Underline string
Family string `json:"family"` Family string
Size float64 `json:"size"` Size float64
Strike bool `json:"strike"` Strike bool
Color string `json:"color"` Color string
ColorIndexed int `json:"color_indexed"` ColorIndexed int
ColorTheme *int `json:"color_theme"` ColorTheme *int
ColorTint float64 `json:"color_tint"` ColorTint float64
VertAlign string `json:"vertAlign"` VertAlign string
} }
// Fill directly maps the fill settings of the cells. // Fill directly maps the fill settings of the cells.
type Fill struct { type Fill struct {
Type string `json:"type"` Type string
Pattern int `json:"pattern"` Pattern int
Color []string `json:"color"` Color []string
Shading int `json:"shading"` Shading int
} }
// Protection directly maps the protection settings of the cells. // Protection directly maps the protection settings of the cells.
type Protection struct { type Protection struct {
Hidden bool `json:"hidden"` Hidden bool
Locked bool `json:"locked"` Locked bool
} }
// Style directly maps the style settings of the cells. // Style directly maps the style settings of the cells.
type Style struct { type Style struct {
Border []Border `json:"border"` Border []Border
Fill Fill `json:"fill"` Fill Fill
Font *Font `json:"font"` Font *Font
Alignment *Alignment `json:"alignment"` Alignment *Alignment
Protection *Protection `json:"protection"` Protection *Protection
NumFmt int `json:"number_format"` NumFmt int
DecimalPlaces int `json:"decimal_places"` DecimalPlaces int
CustomNumFmt *string `json:"custom_number_format"` CustomNumFmt *string
Lang string `json:"lang"` Lang string
NegRed bool `json:"negred"` NegRed bool
} }

View File

@ -196,22 +196,25 @@ type xlsxTableStyleInfo struct {
ShowColumnStripes bool `xml:"showColumnStripes,attr"` ShowColumnStripes bool `xml:"showColumnStripes,attr"`
} }
// tableOptions directly maps the format settings of the table. // TableOptions directly maps the format settings of the table.
type tableOptions struct { type TableOptions struct {
TableName string `json:"table_name"` Name string
TableStyle string `json:"table_style"` StyleName string
ShowFirstColumn bool `json:"show_first_column"` ShowFirstColumn bool
ShowLastColumn bool `json:"show_last_column"` ShowLastColumn bool
ShowRowStripes bool `json:"show_row_stripes"` ShowRowStripes *bool
ShowColumnStripes bool `json:"show_column_stripes"` ShowColumnStripes bool
} }
// autoFilterOptions directly maps the auto filter settings. // AutoFilterListOptions directly maps the auto filter list settings.
type autoFilterOptions struct { type AutoFilterListOptions struct {
Column string `json:"column"` Column string
Expression string `json:"expression"` Value []int
FilterList []struct { }
Column string `json:"column"`
Value []int `json:"value"` // AutoFilterOptions directly maps the auto filter settings.
} `json:"filter_list"` type AutoFilterOptions struct {
Column string
Expression string
FilterList []AutoFilterListOptions
} }

View File

@ -308,23 +308,23 @@ type xlsxCustomWorkbookView struct {
// DefinedName directly maps the name for a cell or cell range on a // DefinedName directly maps the name for a cell or cell range on a
// worksheet. // worksheet.
type DefinedName struct { type DefinedName struct {
Name string `json:"name,omitempty"` Name string
Comment string `json:"comment,omitempty"` Comment string
RefersTo string `json:"refers_to,omitempty"` RefersTo string
Scope string `json:"scope,omitempty"` Scope string
} }
// WorkbookPropsOptions directly maps the settings of workbook proprieties. // WorkbookPropsOptions directly maps the settings of workbook proprieties.
type WorkbookPropsOptions struct { type WorkbookPropsOptions struct {
Date1904 *bool `json:"date_1994,omitempty"` Date1904 *bool
FilterPrivacy *bool `json:"filter_privacy,omitempty"` FilterPrivacy *bool
CodeName *string `json:"code_name,omitempty"` CodeName *string
} }
// WorkbookProtectionOptions directly maps the settings of workbook protection. // WorkbookProtectionOptions directly maps the settings of workbook protection.
type WorkbookProtectionOptions struct { type WorkbookProtectionOptions struct {
AlgorithmName string `json:"algorithmName,omitempty"` AlgorithmName string
Password string `json:"password,omitempty"` Password string
LockStructure bool `json:"lockStructure,omitempty"` LockStructure bool
LockWindows bool `json:"lockWindows,omitempty"` LockWindows bool
} }

View File

@ -794,141 +794,143 @@ type xlsxX14Sparkline struct {
// SparklineOptions directly maps the settings of the sparkline. // SparklineOptions directly maps the settings of the sparkline.
type SparklineOptions struct { type SparklineOptions struct {
Location []string `json:"location"` Location []string
Range []string `json:"range"` Range []string
Max int `json:"max"` Max int
CustMax int `json:"cust_max"` CustMax int
Min int `json:"min"` Min int
CustMin int `json:"cust_min"` CustMin int
Type string `json:"hype"` Type string
Weight float64 `json:"weight"` Weight float64
DateAxis bool `json:"date_axis"` DateAxis bool
Markers bool `json:"markers"` Markers bool
High bool `json:"high"` High bool
Low bool `json:"low"` Low bool
First bool `json:"first"` First bool
Last bool `json:"last"` Last bool
Negative bool `json:"negative"` Negative bool
Axis bool `json:"axis"` Axis bool
Hidden bool `json:"hidden"` Hidden bool
Reverse bool `json:"reverse"` Reverse bool
Style int `json:"style"` Style int
SeriesColor string `json:"series_color"` SeriesColor string
NegativeColor string `json:"negative_color"` NegativeColor string
MarkersColor string `json:"markers_color"` MarkersColor string
FirstColor string `json:"first_color"` FirstColor string
LastColor string `json:"last_color"` LastColor string
HightColor string `json:"hight_color"` HightColor string
LowColor string `json:"low_color"` LowColor string
EmptyCells string `json:"empty_cells"` EmptyCells string
} }
// panesOptions directly maps the settings of the panes. // PaneOptions directly maps the settings of the pane.
type panesOptions struct { type PaneOptions struct {
Freeze bool `json:"freeze"` SQRef string
Split bool `json:"split"` ActiveCell string
XSplit int `json:"x_split"` Pane string
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"`
} }
// conditionalOptions directly maps the conditional format settings of the cells. // Panes directly maps the settings of the panes.
type conditionalOptions struct { type Panes struct {
Type string `json:"type"` Freeze bool
AboveAverage bool `json:"above_average,omitempty"` Split bool
Percent bool `json:"percent,omitempty"` XSplit int
Format int `json:"format,omitempty"` YSplit int
Criteria string `json:"criteria,omitempty"` TopLeftCell string
Value string `json:"value,omitempty"` ActivePane string
Minimum string `json:"minimum,omitempty"` Panes []PaneOptions
Maximum string `json:"maximum,omitempty"` }
MinType string `json:"min_type,omitempty"`
MidType string `json:"mid_type,omitempty"` // ConditionalFormatOptions directly maps the conditional format settings of the cells.
MaxType string `json:"max_type,omitempty"` type ConditionalFormatOptions struct {
MinValue string `json:"min_value,omitempty"` Type string
MidValue string `json:"mid_value,omitempty"` AboveAverage bool
MaxValue string `json:"max_value,omitempty"` Percent bool
MinColor string `json:"min_color,omitempty"` Format int
MidColor string `json:"mid_color,omitempty"` Criteria string
MaxColor string `json:"max_color,omitempty"` Value string
MinLength string `json:"min_length,omitempty"` Minimum string
MaxLength string `json:"max_length,omitempty"` Maximum string
MultiRange string `json:"multi_range,omitempty"` MinType string
BarColor string `json:"bar_color,omitempty"` 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. // SheetProtectionOptions directly maps the settings of worksheet protection.
type SheetProtectionOptions struct { type SheetProtectionOptions struct {
AlgorithmName string `json:"algorithm_name,omitempty"` AlgorithmName string
AutoFilter bool `json:"auto_filter,omitempty"` AutoFilter bool
DeleteColumns bool `json:"delete_columns,omitempty"` DeleteColumns bool
DeleteRows bool `json:"delete_rows,omitempty"` DeleteRows bool
EditObjects bool `json:"edit_objects,omitempty"` EditObjects bool
EditScenarios bool `json:"edit_scenarios,omitempty"` EditScenarios bool
FormatCells bool `json:"format_cells,omitempty"` FormatCells bool
FormatColumns bool `json:"format_columns,omitempty"` FormatColumns bool
FormatRows bool `json:"format_rows,omitempty"` FormatRows bool
InsertColumns bool `json:"insert_columns,omitempty"` InsertColumns bool
InsertHyperlinks bool `json:"insert_hyperlinks,omitempty"` InsertHyperlinks bool
InsertRows bool `json:"insert_rows,omitempty"` InsertRows bool
Password string `json:"password,omitempty"` Password string
PivotTables bool `json:"pivot_tables,omitempty"` PivotTables bool
SelectLockedCells bool `json:"select_locked_cells,omitempty"` SelectLockedCells bool
SelectUnlockedCells bool `json:"select_unlocked_cells,omitempty"` SelectUnlockedCells bool
Sort bool `json:"sort,omitempty"` Sort bool
} }
// HeaderFooterOptions directly maps the settings of header and footer. // HeaderFooterOptions directly maps the settings of header and footer.
type HeaderFooterOptions struct { type HeaderFooterOptions struct {
AlignWithMargins bool `json:"align_with_margins,omitempty"` AlignWithMargins bool
DifferentFirst bool `json:"different_first,omitempty"` DifferentFirst bool
DifferentOddEven bool `json:"different_odd_even,omitempty"` DifferentOddEven bool
ScaleWithDoc bool `json:"scale_with_doc,omitempty"` ScaleWithDoc bool
OddHeader string `json:"odd_header,omitempty"` OddHeader string
OddFooter string `json:"odd_footer,omitempty"` OddFooter string
EvenHeader string `json:"even_header,omitempty"` EvenHeader string
EvenFooter string `json:"even_footer,omitempty"` EvenFooter string
FirstHeader string `json:"first_header,omitempty"` FirstHeader string
FirstFooter string `json:"first_footer,omitempty"` FirstFooter string
} }
// PageLayoutMarginsOptions directly maps the settings of page layout margins. // PageLayoutMarginsOptions directly maps the settings of page layout margins.
type PageLayoutMarginsOptions struct { type PageLayoutMarginsOptions struct {
Bottom *float64 `json:"bottom,omitempty"` Bottom *float64
Footer *float64 `json:"footer,omitempty"` Footer *float64
Header *float64 `json:"header,omitempty"` Header *float64
Left *float64 `json:"left,omitempty"` Left *float64
Right *float64 `json:"right,omitempty"` Right *float64
Top *float64 `json:"top,omitempty"` Top *float64
Horizontally *bool `json:"horizontally,omitempty"` Horizontally *bool
Vertically *bool `json:"vertically,omitempty"` Vertically *bool
} }
// PageLayoutOptions directly maps the settings of page layout. // PageLayoutOptions directly maps the settings of page layout.
type PageLayoutOptions struct { type PageLayoutOptions struct {
// Size defines the paper size of the worksheet. // 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 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 // FirstPageNumber specified the first printed page number. If no value is
// specified, then 'automatic' is assumed. // specified, then 'automatic' is assumed.
FirstPageNumber *uint `json:"first_page_number,omitempty"` FirstPageNumber *uint
// AdjustTo defines the print scaling. This attribute is restricted to // AdjustTo defines the print scaling. This attribute is restricted to
// value ranging from 10 (10%) to 400 (400%). This setting is overridden // value ranging from 10 (10%) to 400 (400%). This setting is overridden
// when fitToWidth and/or fitToHeight are in use. // 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 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 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 specified print black and white.
BlackAndWhite *bool `json:"black_and_white,omitempty"` BlackAndWhite *bool
} }
// ViewOptions directly maps the settings of sheet view. // ViewOptions directly maps the settings of sheet view.
@ -936,37 +938,37 @@ type ViewOptions struct {
// DefaultGridColor indicating that the consuming application should use // DefaultGridColor indicating that the consuming application should use
// the default grid lines color(system dependent). Overrides any color // the default grid lines color(system dependent). Overrides any color
// specified in colorId. // specified in colorId.
DefaultGridColor *bool `json:"default_grid_color,omitempty"` DefaultGridColor *bool
// RightToLeft indicating whether the sheet is in 'right to left' display // 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 // 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 // column left of Column A, and so on. Also, information in cells is
// displayed in the Right to Left format. // 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 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 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 // ShowRowColHeaders indicating whether the sheet should display row and
// column headings. // column headings.
ShowRowColHeaders *bool `json:"show_row_col_headers,omitempty"` ShowRowColHeaders *bool
// ShowRuler indicating this sheet should display ruler. // 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 // ShowZeros indicating whether to "show a zero in cells that have zero
// value". When using a formula to reference another cell which is empty, // value". When using a formula to reference another cell which is empty,
// the referenced value becomes 0 when the flag is true. (Default setting // the referenced value becomes 0 when the flag is true. (Default setting
// is true.) // is true.)
ShowZeros *bool `json:"show_zeros,omitempty"` ShowZeros *bool
// TopLeftCell specifies a location of the top left visible cell Location // TopLeftCell specifies a location of the top left visible cell Location
// of the top left visible cell in the bottom right pane (when in // of the top left visible cell in the bottom right pane (when in
// Left-to-Right mode). // 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 // View indicating how sheet is displayed, by default it uses empty string
// available options: normal, pageLayout, pageBreakPreview // available options: normal, pageLayout, pageBreakPreview
View *string `json:"low_color,omitempty"` View *string
// ZoomScale specifies a window zoom magnification for current view // ZoomScale specifies a window zoom magnification for current view
// representing percent values. This attribute is restricted to values // representing percent values. This attribute is restricted to values
// ranging from 10 to 400. Horizontal & Vertical scale together. // 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. // 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, // 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 // and does not change from user input. This name should be used by code
// to reference a particular sheet. // to reference a particular sheet.
CodeName *string `json:"code_name,omitempty"` CodeName *string
// EnableFormatConditionsCalculation indicating whether the conditional // EnableFormatConditionsCalculation indicating whether the conditional
// formatting calculations shall be evaluated. If set to false, then the // 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 // min/max values of color scales or data bars or threshold values in Top N
// rules shall not be updated. Essentially the conditional // rules shall not be updated. Essentially the conditional
// formatting "calc" is off. // formatting "calc" is off.
EnableFormatConditionsCalculation *bool `json:"enable_format_conditions_calculation,omitempty"` EnableFormatConditionsCalculation *bool
// Published indicating whether the worksheet is published. // Published indicating whether the worksheet is published.
Published *bool `json:"published,omitempty"` Published *bool
// AutoPageBreaks indicating whether the sheet displays Automatic Page // AutoPageBreaks indicating whether the sheet displays Automatic Page
// Breaks. // Breaks.
AutoPageBreaks *bool `json:"auto_page_breaks,omitempty"` AutoPageBreaks *bool
// FitToPage indicating whether the Fit to Page print option is enabled. // 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 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 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, // TabColorTheme represents the zero-based index into the collection,
// referencing a particular value expressed in the Theme part. // 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 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 // OutlineSummaryBelow indicating whether summary rows appear below detail
// in an outline, when applying an outline. // in an outline, when applying an outline.
OutlineSummaryBelow *bool `json:"outline_summary_below,omitempty"` OutlineSummaryBelow *bool
// OutlineSummaryRight indicating whether summary columns appear to the // OutlineSummaryRight indicating whether summary columns appear to the
// right of detail in an outline, when applying an outline. // 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 // BaseColWidth specifies the number of characters of the maximum digit
// width of the normal style's font. This value does not include margin // 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 // padding or extra padding for grid lines. It is only the number of
// characters. // characters.
BaseColWidth *uint8 `json:"base_col_width,omitempty"` BaseColWidth *uint8
// DefaultColWidth specifies the default column width measured as the // DefaultColWidth specifies the default column width measured as the
// number of characters of the maximum digit width of the normal style's // number of characters of the maximum digit width of the normal style's
// font. // font.
DefaultColWidth *float64 `json:"default_col_width,omitempty"` DefaultColWidth *float64
// DefaultRowHeight specifies the default row height measured in point // DefaultRowHeight specifies the default row height measured in point
// size. Optimization so we don't have to write the height on all rows. // 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 // This can be written out if most rows have custom height, to achieve the
// optimization. // optimization.
DefaultRowHeight *float64 `json:"default_row_height,omitempty"` DefaultRowHeight *float64
// CustomHeight specifies the custom height. // CustomHeight specifies the custom height.
CustomHeight *bool `json:"custom_height,omitempty"` CustomHeight *bool
// ZeroHeight specifies if rows are hidden. // 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 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 specifies if rows have a thick bottom border by default.
ThickBottom *bool `json:"thick_bottom,omitempty"` ThickBottom *bool
} }