From a07c8cd0b4a2009f147b5111472565e0a3f85020 Mon Sep 17 00:00:00 2001 From: xuri Date: Fri, 28 Jul 2023 00:24:08 +0800 Subject: [PATCH] This closes #1588, closes #1591, breaking changes for the `AddChart` function - Removed exported `ChartTitle` data type - The `AddChart` function now supports formatting and setting rich text titles for the chart - New exported function `GetFormControl` for getting form control - Made case in-sensitive for internal worksheet XML path to improve compatibility - Update the unit tests - Update the documentation and internal comments on the codes --- README.md | 6 +- README_zh.md | 6 +- chart.go | 26 +++++-- chart_test.go | 150 ++++++++++++++++++------------------- drawing.go | 62 +--------------- lib.go | 2 +- picture.go | 2 +- shape.go | 2 +- vml.go | 90 +++++++++++++++++++++- vmlDrawing.go | 9 +++ vml_test.go | 202 +++++++++++++++++++++++++++++++++++++------------- xmlChart.go | 7 +- 12 files changed, 352 insertions(+), 212 deletions(-) diff --git a/README.md b/README.md index 0177eaf..460ace4 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,10 @@ func main() { Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4", }}, - Title: excelize.ChartTitle{ - Name: "Fruit 3D Clustered Column Chart", + Title: []excelize.RichTextRun{ + { + Text: "Fruit 3D Clustered Column Chart", + }, }, }); err != nil { fmt.Println(err) diff --git a/README_zh.md b/README_zh.md index c6ad907..8bb3068 100644 --- a/README_zh.md +++ b/README_zh.md @@ -165,8 +165,10 @@ func main() { Categories: "Sheet1!$B$1:$D$1", Values: "Sheet1!$B$4:$D$4", }}, - Title: excelize.ChartTitle{ - Name: "Fruit 3D Clustered Column Chart", + Title: []excelize.RichTextRun{ + { + Text: "Fruit 3D Clustered Column Chart", + }, }, }); err != nil { fmt.Println(err) diff --git a/chart.go b/chart.go index 83b6537..ac13729 100644 --- a/chart.go +++ b/chart.go @@ -507,8 +507,16 @@ func parseChartOptions(opts *Chart) (*Chart, error) { if opts.Legend.Position == "" { opts.Legend.Position = defaultChartLegendPosition } - if opts.Title.Name == "" { - opts.Title.Name = " " + for i := range opts.Title { + if opts.Title[i].Font == nil { + opts.Title[i].Font = &Font{} + } + if opts.Title[i].Font.Color == "" { + opts.Title[i].Font.Color = "595959" + } + if opts.Title[i].Font.Size == 0 { + opts.Title[i].Font.Size = 14 + } } if opts.VaryColors == nil { opts.VaryColors = boolPtr(true) @@ -569,8 +577,10 @@ func parseChartOptions(opts *Chart) (*Chart, error) { // Values: "Sheet1!$B$4:$D$4", // }, // }, -// Title: excelize.ChartTitle{ -// Name: "Fruit 3D Clustered Column Chart", +// Title: []excelize.RichTextRun{ +// { +// Text: "Fruit 3D Clustered Column Chart", +// }, // }, // Legend: excelize.ChartLegend{ // ShowLegendKey: false, @@ -727,7 +737,7 @@ func parseChartOptions(opts *Chart) (*Chart, error) { // // Title // -// Name: Set the name (title) for the chart. The name is displayed above the +// Title: 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. @@ -912,8 +922,10 @@ func parseChartOptions(opts *Chart) (*Chart, error) { // LockAspectRatio: false, // Locked: &disable, // }, -// Title: excelize.ChartTitle{ -// Name: "Clustered Column - Line Chart", +// Title: []excelize.RichTextRun{ +// { +// Text: "Clustered Column - Line Chart", +// }, // }, // Legend: excelize.ChartLegend{ // Position: "left", diff --git a/chart_test.go b/chart_test.go index 49b8355..4d61c9b 100644 --- a/chart_test.go +++ b/chart_test.go @@ -52,7 +52,7 @@ func TestChartSize(t *testing.T) { {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"}, + Title: []RichTextRun{{Text: "3D Clustered Column Chart"}}, })) var buffer bytes.Buffer @@ -206,69 +206,69 @@ func TestAddChart(t *testing.T) { sheetName, cell string opts *Chart }{ - {sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", ShowLegendKey: true}, Title: ChartTitle{Name: "2D Column Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Color: "000000"}, Title: []RichTextRun{{Text: "Primary Horizontal Axis Title"}}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "777777"}, Title: []RichTextRun{{Text: "Primary Vertical Axis Title", Font: &Font{Color: "777777", Bold: true, Italic: true, Size: 12}}}}}}, - {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: "bottom", 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: "top_right", 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: "top", ShowLegendKey: false}, Title: ChartTitle{Name: "3D Line Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}}}, - {sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: Scatter, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", 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: "right", 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: "top", 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: "bottom", 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: "bottom", ShowLegendKey: false}, Title: ChartTitle{Name: "Pie Chart"}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false, NumFmt: ChartNumFmt{CustomNumFmt: "0.00%;0;;"}}, ShowBlanksAs: "gap"}}, + {sheetName: "Sheet1", cell: "P1", opts: &Chart{Type: Col, Series: series, Format: format, Legend: ChartLegend{Position: "none", ShowLegendKey: true}, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{Font: Font{Bold: true, Italic: true, Underline: "dbl", Color: "000000"}, Title: []RichTextRun{{Text: "Primary Horizontal Axis Title"}}}, YAxis: ChartAxis{Font: Font{Bold: false, Italic: false, Underline: "sng", Color: "777777"}, Title: []RichTextRun{{Text: "Primary Vertical Axis Title", Font: &Font{Color: "777777", Bold: true, Italic: true, Size: 12}}}}}}, + {sheetName: "Sheet1", cell: "X1", opts: &Chart{Type: ColStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "P16", opts: &Chart{Type: ColPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "100% Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "X16", opts: &Chart{Type: Col3DClustered, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Clustered Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "P30", opts: &Chart{Type: Col3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "X30", opts: &Chart{Type: Col3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "X45", opts: &Chart{Type: Radar, Series: series, Format: format, Legend: ChartLegend{Position: "top_right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Radar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "span"}}, + {sheetName: "Sheet1", cell: "AF1", opts: &Chart{Type: Col3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AF16", opts: &Chart{Type: Col3DConeClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cone Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AF30", opts: &Chart{Type: Col3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "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: []RichTextRun{{Text: "3D Column Cone Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AN1", opts: &Chart{Type: Col3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "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: []RichTextRun{{Text: "3D Column Pyramid Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AN30", opts: &Chart{Type: Col3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "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: []RichTextRun{{Text: "3D Column Pyramid Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV1", opts: &Chart{Type: Col3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV16", opts: &Chart{Type: Col3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "AV30", opts: &Chart{Type: Col3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "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: []RichTextRun{{Text: "3D Column Cylinder Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet1", cell: "P45", opts: &Chart{Type: Col3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P1", opts: &Chart{Type: Line3D, Series: series2, Format: format, Legend: ChartLegend{Position: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Line Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, TickLabelSkip: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}, YAxis: ChartAxis{MajorGridLines: true, MinorGridLines: true, MajorUnit: 1, NumFmt: ChartNumFmt{CustomNumFmt: "General"}}}}, + {sheetName: "Sheet2", cell: "X1", opts: &Chart{Type: Scatter, Series: series, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Scatter Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P16", opts: &Chart{Type: Doughnut, Series: series3, Format: format, Legend: ChartLegend{Position: "right", ShowLegendKey: false}, Title: []RichTextRun{{Text: "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: "top", ShowLegendKey: false}, Title: []RichTextRun{{Text: "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: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "3D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "X32", opts: &Chart{Type: Pie, Series: series3, Format: format, Legend: ChartLegend{Position: "bottom", ShowLegendKey: false}, Title: []RichTextRun{{Text: "Pie Chart"}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: false, ShowVal: false, NumFmt: ChartNumFmt{CustomNumFmt: "0.00%;0;;"}}, 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, Secondary: true, Minimum: &zero}, YAxis: ChartAxis{ReverseOrder: true, Minimum: &zero}}}, + {sheetName: "Sheet2", cell: "P48", opts: &Chart{Type: Bar, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Clustered Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "X48", opts: &Chart{Type: BarStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P64", opts: &Chart{Type: BarPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked 100% Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "X64", opts: &Chart{Type: Bar3DClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Clustered Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "P80", opts: &Chart{Type: Bar3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "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: []RichTextRun{{Text: "3D 100% Stacked Bar Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{ReverseOrder: true, Secondary: 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"}}, + {sheetName: "Sheet2", cell: "AF1", opts: &Chart{Type: Area, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN1", opts: &Chart{Type: AreaStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF16", opts: &Chart{Type: AreaPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D 100% Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN16", opts: &Chart{Type: Area3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF32", opts: &Chart{Type: Area3DStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN32", opts: &Chart{Type: Area3DPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D 100% Stacked Area Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // cylinder series chart - {sheetName: "Sheet2", cell: "AF48", opts: &Chart{Type: Bar3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AF64", opts: &Chart{Type: Bar3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AF80", opts: &Chart{Type: Bar3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cylinder Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF48", opts: &Chart{Type: Bar3DCylinderStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF64", opts: &Chart{Type: Bar3DCylinderClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AF80", opts: &Chart{Type: Bar3DCylinderPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cylinder Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // cone series chart - {sheetName: "Sheet2", cell: "AN48", opts: &Chart{Type: Bar3DConeStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AN64", opts: &Chart{Type: Bar3DConeClustered, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AN80", opts: &Chart{Type: Bar3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Cone Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AV48", opts: &Chart{Type: Bar3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AV64", opts: &Chart{Type: Bar3DPyramidClustered, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Clustered Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "AV80", opts: &Chart{Type: Bar3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Bar Pyramid Percent Stacked Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN48", opts: &Chart{Type: Bar3DConeStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN64", opts: &Chart{Type: Bar3DConeClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AN80", opts: &Chart{Type: Bar3DConePercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Cone Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV48", opts: &Chart{Type: Bar3DPyramidStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV64", opts: &Chart{Type: Bar3DPyramidClustered, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Clustered Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV80", opts: &Chart{Type: Bar3DPyramidPercentStacked, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Bar Pyramid Percent Stacked Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // surface series chart - {sheetName: "Sheet2", cell: "AV1", opts: &Chart{Type: Surface3D, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Surface Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, - {sheetName: "Sheet2", cell: "AV16", opts: &Chart{Type: WireframeSurface3D, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "3D Wireframe Surface Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, - {sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: Contour, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: WireframeContour, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Wireframe Contour Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "AV1", opts: &Chart{Type: Surface3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Surface Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "AV16", opts: &Chart{Type: WireframeSurface3D, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "3D Wireframe Surface Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "AV32", opts: &Chart{Type: Contour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "BD1", opts: &Chart{Type: WireframeContour, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Wireframe Contour Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, // bubble chart - {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: Bubble, Series: series4, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, - {sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: Bubble3D, Series: series4, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble 3D Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "BD16", opts: &Chart{Type: Bubble, Series: series4, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}}, + {sheetName: "Sheet2", cell: "BD32", opts: &Chart{Type: Bubble3D, Series: series4, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, // pie of pie chart - {sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: PieOfPie, Series: series3, Format: format, Legend: legend, Title: ChartTitle{Name: "Pie of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "BD48", opts: &Chart{Type: PieOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Pie of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, // bar of pie chart - {sheetName: "Sheet2", cell: "BD64", opts: &Chart{Type: BarOfPie, Series: series3, Format: format, Legend: legend, Title: ChartTitle{Name: "Bar of Pie Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}}, + {sheetName: "Sheet2", cell: "BD64", opts: &Chart{Type: BarOfPie, Series: series3, Format: format, Legend: legend, Title: []RichTextRun{{Text: "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)) } @@ -280,32 +280,32 @@ func TestAddChart(t *testing.T) { {"I1", Doughnut, "Clustered Column - Doughnut Chart"}, } for _, props := range clusteredColumnCombo { - assert.NoError(t, f.AddChart("Combo Charts", props[0].(string), &Chart{Type: Col, Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[2].(string)}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}, YAxis: ChartAxis{Secondary: true}})) + assert.NoError(t, f.AddChart("Combo Charts", props[0].(string), &Chart{Type: Col, Series: series[:4], Format: format, Legend: legend, Title: []RichTextRun{{Text: props[2].(string)}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[1].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}, YAxis: ChartAxis{Secondary: true}})) } stackedAreaCombo := map[string][]interface{}{ "A16": {Line, "Stacked Area - Line Chart"}, "I16": {Doughnut, "Stacked Area - Doughnut Chart"}, } for axis, props := range stackedAreaCombo { - assert.NoError(t, f.AddChart("Combo Charts", axis, &Chart{Type: AreaStacked, Series: series[:4], Format: format, Legend: legend, Title: ChartTitle{Name: props[1].(string)}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[0].(ChartType), 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.AddChart("Combo Charts", axis, &Chart{Type: AreaStacked, Series: series[:4], Format: format, Legend: legend, Title: []RichTextRun{{Text: props[1].(string)}}, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}}, &Chart{Type: props[0].(ChartType), Series: series[4:], Format: format, Legend: legend, PlotArea: ChartPlotArea{ShowBubbleSize: true, ShowCatName: false, ShowLeaderLines: false, ShowPercent: true, ShowSerName: true, ShowVal: true}})) } assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChart.xlsx"))) // Test with invalid sheet name assert.EqualError(t, f.AddChart("Sheet:1", "A1", &Chart{Type: Col, Series: series[:1]}), ErrSheetNameInvalid.Error()) // Test with illegal cell reference - 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()) + assert.EqualError(t, f.AddChart("Sheet2", "A", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) // Test with unsupported chart type - assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: 0x37, Series: series, Format: format, Legend: legend, Title: ChartTitle{Name: "Bubble 3D Chart"}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newUnsupportedChartType(0x37).Error()) + assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: 0x37, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bubble 3D Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}), newUnsupportedChartType(0x37).Error()) // Test add combo chart with invalid format set - 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()) + assert.EqualError(t, f.AddChart("Sheet2", "BD32", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"}, nil), ErrParameterInvalid.Error()) // Test add combo chart with unsupported chart type - assert.EqualError(t, f.AddChart("Sheet2", "BD64", &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: 0x37, 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}}), newUnsupportedChartType(0x37).Error()) + 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: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}, &Chart{Type: 0x37, Series: []ChartSeries{{Name: "Sheet1!$A$30", Categories: "Sheet1!$A$30:$D$37", Values: "Sheet1!$B$30:$B$37"}}, Format: format, Legend: legend, Title: []RichTextRun{{Text: "Bar of Pie Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero", XAxis: ChartAxis{MajorGridLines: true}, YAxis: ChartAxis{MajorGridLines: true}}), newUnsupportedChartType(0x37).Error()) assert.NoError(t, f.Close()) // Test add chart with unsupported charset content types. f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - assert.EqualError(t, f.AddChart("Sheet1", "P1", &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") + 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: []RichTextRun{{Text: "2D Column Chart"}}}), "XML syntax error on line 1: invalid UTF-8") } func TestAddChartSheet(t *testing.T) { @@ -323,7 +323,7 @@ func TestAddChartSheet(t *testing.T) { {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"}})) + assert.NoError(t, f.AddChartSheet("Chart1", &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}})) // Test set the chartsheet as active sheet var sheetIdx int for idx, sheetName := range f.GetSheetList() { @@ -338,11 +338,11 @@ func TestAddChartSheet(t *testing.T) { assert.EqualError(t, f.SetCellValue("Chart1", "A1", true), "sheet Chart1 is not a worksheet") // Test add chartsheet on already existing name sheet - assert.EqualError(t, f.AddChartSheet("Sheet1", &Chart{Type: Col3DClustered, Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), ErrExistsSheet.Error()) + assert.EqualError(t, f.AddChartSheet("Sheet1", &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), ErrExistsSheet.Error()) // Test add chartsheet with invalid sheet name - assert.EqualError(t, f.AddChartSheet("Sheet:1", nil, &Chart{Type: Col3DClustered, Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), ErrSheetNameInvalid.Error()) + assert.EqualError(t, f.AddChartSheet("Sheet:1", nil, &Chart{Type: Col3DClustered, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), ErrSheetNameInvalid.Error()) // Test with unsupported chart type - assert.EqualError(t, f.AddChartSheet("Chart2", &Chart{Type: 0x37, Series: series, Title: ChartTitle{Name: "Fruit 3D Clustered Column Chart"}}), newUnsupportedChartType(0x37).Error()) + assert.EqualError(t, f.AddChartSheet("Chart2", &Chart{Type: 0x37, Series: series, Title: []RichTextRun{{Text: "Fruit 3D Clustered Column Chart"}}}), newUnsupportedChartType(0x37).Error()) assert.NoError(t, f.UpdateLinkedValue()) @@ -351,7 +351,7 @@ func TestAddChartSheet(t *testing.T) { f = NewFile() f.ContentTypes = nil f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) - 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") + 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: []RichTextRun{{Text: "2D Column Chart"}}}), "XML syntax error on line 1: invalid UTF-8") } func TestDeleteChart(t *testing.T) { @@ -386,7 +386,7 @@ func TestDeleteChart(t *testing.T) { 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.AddChart("Sheet1", "P1", &Chart{Type: Col, Series: series, Format: format, Legend: legend, Title: []RichTextRun{{Text: "2D Column Chart"}}, PlotArea: plotArea, ShowBlanksAs: "zero"})) assert.NoError(t, f.DeleteChart("Sheet1", "P1")) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteChart.xlsx"))) // Test delete chart with invalid sheet name @@ -435,12 +435,12 @@ func TestChartWithLogarithmicBase(t *testing.T) { 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}}}, + {cell: "C1", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[0], Height: dimension[1]}, Series: series, Title: []RichTextRun{{Text: "Line chart without log scaling"}}}}, + {cell: "M1", opts: &Chart{Type: Line, Dimension: ChartDimension{Width: dimension[0], Height: dimension[1]}, Series: series, Title: []RichTextRun{{Text: "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: []RichTextRun{{Text: "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: []RichTextRun{{Text: "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: []RichTextRun{{Text: "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: []RichTextRun{{Text: "Line chart with log 1000 scaling"}}, YAxis: ChartAxis{LogBase: 1000}}}, } { // Add two chart, one without and one with log scaling assert.NoError(t, f.AddChart(sheet1, c.cell, c.opts)) diff --git a/drawing.go b/drawing.go index 9e2c9f3..400c990 100644 --- a/drawing.go +++ b/drawing.go @@ -63,67 +63,7 @@ func (f *File) addChart(opts *Chart, comboCharts []*Chart) { Lang: &attrValString{Val: stringPtr("en-US")}, RoundedCorners: &attrValBool{Val: boolPtr(false)}, Chart: cChart{ - Title: &cTitle{ - Tx: cTx{ - Rich: &cRich{ - P: []aP{ - { - PPr: &aPPr{ - DefRPr: aRPr{ - Kern: 1200, - Strike: "noStrike", - U: "none", - Sz: 1400, - SolidFill: &aSolidFill{ - SchemeClr: &aSchemeClr{ - Val: "tx1", - LumMod: &attrValInt{ - Val: intPtr(65000), - }, - LumOff: &attrValInt{ - Val: intPtr(35000), - }, - }, - }, - Ea: &aEa{ - Typeface: "+mn-ea", - }, - Cs: &aCs{ - Typeface: "+mn-cs", - }, - Latin: &xlsxCTTextFont{ - Typeface: "+mn-lt", - }, - }, - }, - R: &aR{ - RPr: aRPr{ - Lang: "en-US", - AltLang: "en-US", - }, - T: opts.Title.Name, - }, - }, - }, - }, - }, - TxPr: cTxPr{ - P: aP{ - PPr: &aPPr{ - DefRPr: aRPr{ - Kern: 1200, - U: "none", - Sz: 14000, - Strike: "noStrike", - }, - }, - EndParaRPr: &aEndParaRPr{ - Lang: "en-US", - }, - }, - }, - Overlay: &attrValBool{Val: boolPtr(false)}, - }, + Title: f.drawPlotAreaTitles(opts.Title, ""), View3D: &cView3D{ RotX: &attrValInt{Val: intPtr(chartView3DRotX[opts.Type])}, RotY: &attrValInt{Val: intPtr(chartView3DRotY[opts.Type])}, diff --git a/lib.go b/lib.go index 887946a..3798807 100644 --- a/lib.go +++ b/lib.go @@ -53,7 +53,7 @@ func (f *File) ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) { continue } } - if strings.HasPrefix(fileName, "xl/worksheets/sheet") { + if strings.HasPrefix(strings.ToLower(fileName), "xl/worksheets/sheet") { worksheets++ if fileSize > f.options.UnzipXMLSizeLimit && !v.FileInfo().IsDir() { if tempFile, err := f.unzipToTemp(v); err == nil { diff --git a/picture.go b/picture.go index 152ab9a..802f515 100644 --- a/picture.go +++ b/picture.go @@ -216,7 +216,7 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error { if err != nil { return err } - // Read sheet data. + // Read sheet data f.mu.Lock() ws, err := f.workSheetReader(sheet) if err != nil { diff --git a/shape.go b/shape.go index 73cc5a3..53f1649 100644 --- a/shape.go +++ b/shape.go @@ -293,7 +293,7 @@ func (f *File) AddShape(sheet string, opts *Shape) error { if err != nil { return err } - // Read sheet data. + // Read sheet data ws, err := f.workSheetReader(sheet) if err != nil { return err diff --git a/vml.go b/vml.go index b99a979..540e36b 100644 --- a/vml.go +++ b/vml.go @@ -390,7 +390,7 @@ func (f *File) DeleteFormControl(sheet, cell string) error { VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"}, }, } - // load exist VML shapes from xl/drawings/vmlDrawing%d.vml + // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml d, err := f.decodeVMLDrawingReader(drawingVML) if err != nil { return err @@ -477,7 +477,7 @@ func (f *File) vmlDrawingWriter() { // addVMLObject provides a function to create VML drawing parts and // relationships for comments and form controls. func (f *File) addVMLObject(opts vmlOptions) error { - // Read sheet data. + // Read sheet data ws, err := f.workSheetReader(opts.sheet) if err != nil { return err @@ -836,7 +836,7 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er VPath: &vPath{GradientShapeOK: "t", ConnectType: "rect"}, }, } - // load exist VML shapes from xl/drawings/vmlDrawing%d.vml + // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml d, err := f.decodeVMLDrawingReader(drawingVML) if err != nil { return err @@ -883,3 +883,87 @@ func (f *File) addDrawingVML(dataID int, drawingVML string, opts *vmlOptions) er f.VMLDrawing[drawingVML] = vml return err } + +// GetFormControls retrieves all form controls in a worksheet by a given +// worksheet name. Note that, this function does not support getting the width, +// height, text, rich text, and format currently. +func (f *File) GetFormControls(sheet string) ([]FormControl, error) { + var formControls []FormControl + // Read sheet data + ws, err := f.workSheetReader(sheet) + if err != nil { + return formControls, err + } + if ws.LegacyDrawing == nil { + return formControls, err + } + target := f.getSheetRelationshipsTargetByID(sheet, ws.LegacyDrawing.RID) + drawingVML := strings.ReplaceAll(target, "..", "xl") + vml := f.VMLDrawing[drawingVML] + if vml == nil { + // Load exist VML shapes from xl/drawings/vmlDrawing%d.vml + d, err := f.decodeVMLDrawingReader(drawingVML) + if err != nil { + return formControls, err + } + for _, sp := range d.Shape { + if sp.Type != "#_x0000_t201" { + continue + } + formControl, err := extractFormControl(sp.Val) + if err != nil { + return formControls, err + } + if formControl.Type == FormControlNote || formControl.Cell == "" { + continue + } + formControls = append(formControls, formControl) + } + return formControls, err + } + for _, sp := range vml.Shape { + if sp.Type != "#_x0000_t201" { + continue + } + formControl, err := extractFormControl(sp.Val) + if err != nil { + return formControls, err + } + if formControl.Type == FormControlNote || formControl.Cell == "" { + continue + } + formControls = append(formControls, formControl) + } + return formControls, err +} + +// extractFormControl provides a function to extract form controls for a +// worksheets by given client data. +func extractFormControl(clientData string) (FormControl, error) { + var ( + err error + formControl FormControl + shapeVal decodeShapeVal + ) + if err = xml.Unmarshal([]byte(fmt.Sprintf("%s", clientData)), &shapeVal); err != nil { + return formControl, err + } + for formCtrlType, preset := range formCtrlPresets { + if shapeVal.ClientData.ObjectType == preset.objectType { + formControl.Type = formCtrlType + if formControl.Cell, err = CoordinatesToCellName(shapeVal.ClientData.Column+1, shapeVal.ClientData.Row+1); err != nil { + return formControl, err + } + formControl.Macro = shapeVal.ClientData.FmlaMacro + formControl.Checked = shapeVal.ClientData.Checked != 0 + formControl.CellLink = shapeVal.ClientData.FmlaLink + formControl.CurrentVal = shapeVal.ClientData.Val + formControl.MinVal = shapeVal.ClientData.Min + formControl.MaxVal = shapeVal.ClientData.Max + formControl.IncChange = shapeVal.ClientData.Inc + formControl.PageChange = shapeVal.ClientData.Page + formControl.Horizontally = shapeVal.ClientData.Horiz != nil + } + } + return formControl, err +} diff --git a/vmlDrawing.go b/vmlDrawing.go index d09054f..76e9011 100644 --- a/vmlDrawing.go +++ b/vmlDrawing.go @@ -192,8 +192,17 @@ type decodeShapeVal struct { // element in the file xl/drawings/vmlDrawing%d.vml. type decodeVMLClientData struct { ObjectType string `xml:"ObjectType,attr"` + FmlaMacro string Column int Row int + Checked int + FmlaLink string + Val uint + Min uint + Max uint + Inc uint + Page uint + Horiz *string } // encodeShape defines the structure used to re-serialization shape element. diff --git a/vml_test.go b/vml_test.go index aba262d..52a7dad 100644 --- a/vml_test.go +++ b/vml_test.go @@ -13,6 +13,7 @@ package excelize import ( "encoding/xml" + "fmt" "os" "path/filepath" "strings" @@ -157,62 +158,83 @@ func TestAddDrawingVML(t *testing.T) { func TestFormControl(t *testing.T) { f := NewFile() - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "D1", Type: FormControlButton, Macro: "Button1_Click", - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "A1", Type: FormControlButton, Macro: "Button1_Click", - Width: 140, Height: 60, Text: "Button 1\r\n", - Paragraph: []RichTextRun{ - { - Font: &Font{ - Bold: true, - Italic: true, - Underline: "single", - Family: "Times New Roman", - Size: 14, - Color: "777777", + formControls := []FormControl{ + { + Cell: "D1", Type: FormControlButton, Macro: "Button1_Click", + }, + { + Cell: "A1", Type: FormControlButton, Macro: "Button1_Click", + Width: 140, Height: 60, Text: "Button 1\r\n", + Paragraph: []RichTextRun{ + { + Font: &Font{ + Bold: true, + Italic: true, + Underline: "single", + Family: "Times New Roman", + Size: 14, + Color: "777777", + }, + Text: "C1=A1+B1", }, - Text: "C1=A1+B1", + }, + Format: GraphicOptions{PrintObject: boolPtr(true), Positioning: "absolute"}, + }, + { + Cell: "A5", Type: FormControlCheckBox, Text: "Check Box 1", + Checked: true, Format: GraphicOptions{ + PrintObject: boolPtr(false), Positioning: "oneCell", }, }, - Format: GraphicOptions{PrintObject: boolPtr(true), Positioning: "absolute"}, - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "A5", Type: FormControlCheckBox, Text: "Check Box 1", - Checked: true, Format: GraphicOptions{ - PrintObject: boolPtr(false), Positioning: "oneCell", + { + Cell: "A6", Type: FormControlCheckBox, Text: "Check Box 2", + Format: GraphicOptions{Positioning: "twoCell"}, }, - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "A6", Type: FormControlCheckBox, Text: "Check Box 2", - Format: GraphicOptions{Positioning: "twoCell"}, - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "A7", Type: FormControlOptionButton, Text: "Option Button 1", Checked: true, - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "A8", Type: FormControlOptionButton, Text: "Option Button 2", - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "D3", Type: FormControlGroupBox, Text: "Group Box 1", - Width: 140, Height: 60, - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "A9", Type: FormControlLabel, Text: "Label 1", Width: 140, - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "C5", Type: FormControlSpinButton, Width: 40, Height: 60, - CurrentVal: 7, MinVal: 5, MaxVal: 10, IncChange: 1, CellLink: "C2", - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "D7", Type: FormControlScrollBar, Width: 140, Height: 20, - CurrentVal: 50, MinVal: 10, MaxVal: 100, IncChange: 1, PageChange: 1, Horizontally: true, CellLink: "C3", - })) - assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ - Cell: "G1", Type: FormControlScrollBar, Width: 20, Height: 140, - CurrentVal: 50, MinVal: 1000, MaxVal: 100, IncChange: 1, PageChange: 1, CellLink: "C4", - })) + { + Cell: "A7", Type: FormControlOptionButton, Text: "Option Button 1", Checked: true, + }, + { + Cell: "A8", Type: FormControlOptionButton, Text: "Option Button 2", + }, + { + Cell: "D3", Type: FormControlGroupBox, Text: "Group Box 1", + Width: 140, Height: 60, + }, + { + Cell: "A9", Type: FormControlLabel, Text: "Label 1", Width: 140, + }, + { + Cell: "C5", Type: FormControlSpinButton, Width: 40, Height: 60, + CurrentVal: 7, MinVal: 5, MaxVal: 10, IncChange: 1, CellLink: "C2", + }, + { + Cell: "D7", Type: FormControlScrollBar, Width: 140, Height: 20, + CurrentVal: 50, MinVal: 10, MaxVal: 100, IncChange: 1, PageChange: 1, Horizontally: true, CellLink: "C3", + }, + { + Cell: "G1", Type: FormControlScrollBar, Width: 20, Height: 140, + CurrentVal: 50, MinVal: 1000, MaxVal: 100, IncChange: 1, PageChange: 1, CellLink: "C4", + }, + } + for _, formCtrl := range formControls { + assert.NoError(t, f.AddFormControl("Sheet1", formCtrl)) + } + // Test get from controls + result, err := f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, result, 11) + for i, formCtrl := range formControls { + assert.Equal(t, formCtrl.Type, result[i].Type) + assert.Equal(t, formCtrl.Cell, result[i].Cell) + assert.Equal(t, formCtrl.Macro, result[i].Macro) + assert.Equal(t, formCtrl.Checked, result[i].Checked) + assert.Equal(t, formCtrl.CurrentVal, result[i].CurrentVal) + assert.Equal(t, formCtrl.MinVal, result[i].MinVal) + assert.Equal(t, formCtrl.MaxVal, result[i].MaxVal) + assert.Equal(t, formCtrl.IncChange, result[i].IncChange) + assert.Equal(t, formCtrl.Horizontally, result[i].Horizontally) + assert.Equal(t, formCtrl.CellLink, result[i].CellLink) + } assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")})) file, err := os.ReadFile(filepath.Join("test", "vbaProject.bin")) assert.NoError(t, err) @@ -221,9 +243,18 @@ func TestFormControl(t *testing.T) { assert.NoError(t, f.Close()) f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm")) assert.NoError(t, err) + // Test get from controls before add form controls + result, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, result, 11) + // Test add from control to a worksheet which already contains form controls assert.NoError(t, f.AddFormControl("Sheet1", FormControl{ Cell: "D4", Type: FormControlButton, Macro: "Button1_Click", Text: "Button 2", })) + // Test get from controls after add form controls + result, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, result, 12) // Test add unsupported form control assert.Equal(t, f.AddFormControl("Sheet1", FormControl{ Cell: "A1", Type: 0x37, Macro: "Button1_Click", @@ -251,9 +282,13 @@ func TestFormControl(t *testing.T) { assert.NoError(t, err) assert.NoError(t, f.DeleteFormControl("Sheet1", "D1")) assert.NoError(t, f.DeleteFormControl("Sheet1", "A1")) + // Test get from controls after delete form controls + result, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, result, 9) // Test delete form control on not exists worksheet assert.Equal(t, f.DeleteFormControl("SheetN", "A1"), newNoExistSheetError("SheetN")) - // Test delete form control on not exists worksheet + // Test delete form control with illegal cell link reference assert.Equal(t, f.DeleteFormControl("Sheet1", "A"), newCellNameToCoordinatesError("A", newInvalidCellNameError("A"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteFormControl.xlsm"))) assert.NoError(t, f.Close()) @@ -266,4 +301,65 @@ func TestFormControl(t *testing.T) { // Test delete form control on a worksheet without form control f = NewFile() assert.NoError(t, f.DeleteFormControl("Sheet1", "A1")) + // Test get form controls on a worksheet without form control + _, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + // Test get form controls on not exists worksheet + _, err = f.GetFormControls("SheetN") + assert.Equal(t, err, newNoExistSheetError("SheetN")) + // Test get form controls with unsupported charset VML drawing + f, err = OpenFile(filepath.Join("test", "TestAddFormControl.xlsm")) + assert.NoError(t, err) + f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset) + _, err = f.GetFormControls("Sheet1") + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + // Test get form controls with unsupported shape type + f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ + Shape: []decodeShape{{Type: "_x0000_t202"}}, + } + formControls, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, formControls, 0) + // Test get form controls with invalid column number + f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ + Shape: []decodeShape{{Type: "#_x0000_t201", Val: fmt.Sprintf("%d", MaxColumns)}}, + } + formControls, err = f.GetFormControls("Sheet1") + assert.Equal(t, err, ErrColumnNumber) + assert.Len(t, formControls, 0) + // Test get form controls with comment (Note) shape type + f.DecodeVMLDrawing["xl/drawings/vmlDrawing1.vml"] = &decodeVmlDrawing{ + Shape: []decodeShape{{Type: "#_x0000_t201", Val: ""}}, + } + formControls, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, formControls, 0) + // Test get form controls with unsupported shape type + f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ + Shape: []xlsxShape{{Type: "_x0000_t202"}}, + } + formControls, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, formControls, 0) + // Test get form controls with invalid column number + f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ + Shape: []xlsxShape{{Type: "#_x0000_t201", Val: fmt.Sprintf("%d", MaxColumns)}}, + } + formControls, err = f.GetFormControls("Sheet1") + assert.Equal(t, err, ErrColumnNumber) + assert.Len(t, formControls, 0) + // Test get form controls with comment (Note) shape type + f.VMLDrawing["xl/drawings/vmlDrawing1.vml"] = &vmlDrawing{ + Shape: []xlsxShape{{Type: "#_x0000_t201", Val: ""}}, + } + formControls, err = f.GetFormControls("Sheet1") + assert.NoError(t, err) + assert.Len(t, formControls, 0) + assert.NoError(t, f.Close()) +} + +func TestExtractFormControl(t *testing.T) { + // Test extract form control with unsupported charset + _, err := extractFormControl(string(MacintoshCyrillicCharset)) + assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") } diff --git a/xmlChart.go b/xmlChart.go index 7be783b..c69a6af 100644 --- a/xmlChart.go +++ b/xmlChart.go @@ -570,7 +570,7 @@ type Chart struct { Format GraphicOptions Dimension ChartDimension Legend ChartLegend - Title ChartTitle + Title []RichTextRun VaryColors *bool XAxis ChartAxis YAxis ChartAxis @@ -608,8 +608,3 @@ type ChartSeries struct { Line ChartLine Marker ChartMarker } - -// ChartTitle directly maps the format settings of the chart title. -type ChartTitle struct { - Name string -}