Breaking changes: changed the function signature for 4 exported functions

- Change
    `func (f *File) AddVBAProject(bin string) error`
    to
    `func (f *File) AddVBAProject(file []byte) error`
- Change
    `func (f *File) GetComments() (map[string][]Comment, error)`
    to
    `func (f *File) GetComments(sheet string) ([]Comment, error)`
- Change
    `func (f *File) AddTable(sheet, rangeRef string, opts *TableOptions) error`
    to
    `func (f *File) AddTable(sheet string, table *Table) error`
- Change
    `func (sw *StreamWriter) AddTable(rangeRef string, opts *TableOptions) error`
    to
    `func (sw *StreamWriter) AddTable(table *Table) error`
- Rename exported data type `TableOptions` to `Table`
- Simplify the assert statements in the unit tests
- Update documents for the functions
- Update unit tests
This commit is contained in:
xuri 2023-03-25 13:30:13 +08:00
parent 5878fbd282
commit 60b9d029a6
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
20 changed files with 164 additions and 147 deletions

View File

@ -11,7 +11,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.Equal(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{ MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{ Cells: []*xlsxMergeCell{
{ {
@ -19,8 +19,8 @@ func TestAdjustMergeCells(t *testing.T) {
}, },
}, },
}, },
}, rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) }, rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{ assert.Equal(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{ MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{ Cells: []*xlsxMergeCell{
{ {
@ -28,7 +28,7 @@ func TestAdjustMergeCells(t *testing.T) {
}, },
}, },
}, },
}, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) }, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{ assert.NoError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{ MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{ Cells: []*xlsxMergeCell{
@ -272,7 +272,7 @@ func TestAdjustMergeCells(t *testing.T) {
} }
for _, c := range cases { for _, c := range cases {
assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, -1)) assert.NoError(t, f.adjustMergeCells(c.ws, c.dir, c.num, -1))
assert.Equal(t, 0, len(c.ws.MergeCells.Cells), c.label) assert.Len(t, c.ws.MergeCells.Cells, 0, c.label)
} }
f = NewFile() f = NewFile()
@ -293,22 +293,23 @@ func TestAdjustAutoFilter(t *testing.T) {
}, },
}, 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.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{ AutoFilter: &xlsxAutoFilter{
Ref: "A:B1", Ref: "A:B1",
}, },
}, rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) }, rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{ assert.Equal(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{ AutoFilter: &xlsxAutoFilter{
Ref: "A1:B", Ref: "A1:B",
}, },
}, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) }, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
} }
func TestAdjustTable(t *testing.T) { func TestAdjustTable(t *testing.T) {
f, sheetName := NewFile(), "Sheet1" f, sheetName := NewFile(), "Sheet1"
for idx, reference := 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, reference, &TableOptions{ assert.NoError(t, f.AddTable(sheetName, &Table{
Range: reference,
Name: fmt.Sprintf("table%d", idx), Name: fmt.Sprintf("table%d", idx),
StyleName: "TableStyleMedium2", StyleName: "TableStyleMedium2",
ShowFirstColumn: true, ShowFirstColumn: true,
@ -323,7 +324,7 @@ 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", nil)) assert.NoError(t, f.AddTable(sheetName, &Table{Range: "A1:D5"}))
// 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))
@ -346,8 +347,8 @@ func TestAdjustHelper(t *testing.T) {
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.Equal(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.Equal(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
// 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")
} }
@ -363,7 +364,7 @@ func TestAdjustCalcChain(t *testing.T) {
assert.NoError(t, f.InsertRows("Sheet1", 1, 1)) assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
f.CalcChain.C[1].R = "invalid coordinates" f.CalcChain.C[1].R = "invalid coordinates"
assert.EqualError(t, f.InsertCols("Sheet1", "A", 1), newCellNameToCoordinatesError("invalid coordinates", newInvalidCellNameError("invalid coordinates")).Error()) assert.Equal(t, f.InsertCols("Sheet1", "A", 1), newCellNameToCoordinatesError("invalid coordinates", newInvalidCellNameError("invalid coordinates")))
f.CalcChain = nil f.CalcChain = nil
assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
} }

View File

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

View File

@ -571,7 +571,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", &TableOptions{Name: "Table1", StyleName: "TableStyleMedium2"})) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:C2", 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")))

View File

@ -21,46 +21,43 @@ import (
"strings" "strings"
) )
// GetComments retrieves all comments and returns a map of worksheet name to // GetComments retrieves all comments in a worksheet by given worksheet name.
// the worksheet comments. func (f *File) GetComments(sheet string) ([]Comment, error) {
func (f *File) GetComments() (map[string][]Comment, error) { var comments []Comment
comments := map[string][]Comment{} sheetXMLPath, ok := f.getSheetXMLPath(sheet)
for n, path := range f.sheetMap { if !ok {
target := f.getSheetComments(filepath.Base(path)) return comments, newNoExistSheetError(sheet)
if target == "" {
continue
} }
if !strings.HasPrefix(target, "/") { commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
target = "xl" + strings.TrimPrefix(target, "..") if !strings.HasPrefix(commentsXML, "/") {
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
} }
cmts, err := f.commentsReader(strings.TrimPrefix(target, "/")) commentsXML = strings.TrimPrefix(commentsXML, "/")
cmts, err := f.commentsReader(commentsXML)
if err != nil { if err != nil {
return comments, err return comments, err
} }
if cmts != nil { if cmts != nil {
var sheetComments []Comment for _, cmt := range cmts.CommentList.Comment {
for _, comment := range cmts.CommentList.Comment { comment := Comment{}
sheetComment := Comment{} if cmt.AuthorID < len(cmts.Authors.Author) {
if comment.AuthorID < len(cmts.Authors.Author) { comment.Author = cmts.Authors.Author[cmt.AuthorID]
sheetComment.Author = cmts.Authors.Author[comment.AuthorID]
} }
sheetComment.Cell = comment.Ref comment.Cell = cmt.Ref
sheetComment.AuthorID = comment.AuthorID comment.AuthorID = cmt.AuthorID
if comment.Text.T != nil { if cmt.Text.T != nil {
sheetComment.Text += *comment.Text.T comment.Text += *cmt.Text.T
} }
for _, text := range comment.Text.R { for _, text := range cmt.Text.R {
if text.T != nil { if text.T != nil {
run := RichTextRun{Text: text.T.Val} run := RichTextRun{Text: text.T.Val}
if text.RPr != nil { if text.RPr != nil {
run.Font = newFont(text.RPr) run.Font = newFont(text.RPr)
} }
sheetComment.Runs = append(sheetComment.Runs, run) comment.Runs = append(comment.Runs, run)
} }
} }
sheetComments = append(sheetComments, sheetComment) comments = append(comments, comment)
}
comments[n] = sheetComments
} }
} }
return comments, nil return comments, nil

View File

@ -34,21 +34,25 @@ func TestAddComment(t *testing.T) {
assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist") assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist")
// Test add comment on with illegal cell reference // Test add comment on with illegal cell reference
assert.EqualError(t, f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
comments, err := f.GetComments() comments, err := f.GetComments("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) {
assert.Len(t, comments, 2) assert.Len(t, comments, 2)
} comments, err = f.GetComments("Sheet2")
assert.NoError(t, err)
assert.Len(t, comments, 1)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx")))
f.Comments["xl/comments2.xml"] = nil f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`)) f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`))
comments, err = f.GetComments() comments, err = f.GetComments("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 2, len(comments["Sheet1"])) assert.Len(t, comments, 2)
assert.EqualValues(t, 1, len(comments["Sheet2"])) comments, err = f.GetComments("Sheet2")
comments, err = NewFile().GetComments()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, len(comments), 0) assert.Len(t, comments, 1)
comments, err = NewFile().GetComments("Sheet1")
assert.NoError(t, err)
assert.Len(t, comments, 0)
// Test add comments with invalid sheet name // Test add comments with invalid sheet name
assert.EqualError(t, f.AddComment("Sheet:1", Comment{Cell: "A1", Author: "Excelize", Text: "This is a comment."}), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.AddComment("Sheet:1", Comment{Cell: "A1", Author: "Excelize", Text: "This is a comment."}), ErrSheetNameInvalid.Error())
@ -56,7 +60,7 @@ func TestAddComment(t *testing.T) {
// Test add comments with unsupported charset // Test add comments with unsupported charset
f.Comments["xl/comments2.xml"] = nil f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset) f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
_, err = f.GetComments() _, err = f.GetComments("Sheet2")
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 add comments with unsupported charset // Test add comments with unsupported charset
@ -68,6 +72,11 @@ func TestAddComment(t *testing.T) {
f.Styles = nil f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8")
// Test get comments on not exists worksheet
comments, err = f.GetComments("SheetN")
assert.Len(t, comments, 0)
assert.EqualError(t, err, "sheet SheetN does not exist")
} }
func TestDeleteComment(t *testing.T) { func TestDeleteComment(t *testing.T) {
@ -85,13 +94,13 @@ func TestDeleteComment(t *testing.T) {
assert.NoError(t, f.DeleteComment("Sheet2", "A40")) assert.NoError(t, f.DeleteComment("Sheet2", "A40"))
comments, err := f.GetComments() comments, err := f.GetComments("Sheet2")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 5, len(comments["Sheet2"])) assert.Len(t, comments, 5)
comments, err = NewFile().GetComments() comments, err = NewFile().GetComments("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, len(comments), 0) assert.Len(t, comments, 0)
// Test delete comment with invalid sheet name // Test delete comment with invalid sheet name
assert.EqualError(t, f.DeleteComment("Sheet:1", "A1"), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.DeleteComment("Sheet:1", "A1"), ErrSheetNameInvalid.Error())
@ -99,9 +108,9 @@ func TestDeleteComment(t *testing.T) {
assert.NoError(t, f.DeleteComment("Sheet2", "A41")) assert.NoError(t, f.DeleteComment("Sheet2", "A41"))
assert.NoError(t, f.DeleteComment("Sheet2", "C41")) assert.NoError(t, f.DeleteComment("Sheet2", "C41"))
assert.NoError(t, f.DeleteComment("Sheet2", "C42")) assert.NoError(t, f.DeleteComment("Sheet2", "C42"))
comments, err = f.GetComments() comments, err = f.GetComments("Sheet2")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 0, len(comments["Sheet2"])) assert.EqualValues(t, 0, len(comments))
// Test delete comment on not exists worksheet // Test delete comment on not exists worksheet
assert.EqualError(t, f.DeleteComment("SheetN", "A1"), "sheet SheetN does not exist") assert.EqualError(t, f.DeleteComment("SheetN", "A1"), "sheet SheetN does not exist")
// Test delete comment with worksheet part // Test delete comment with worksheet part

View File

@ -35,7 +35,7 @@ func TestDataValidation(t *testing.T) {
dataValidations, err := f.GetDataValidations("Sheet1") dataValidations, err := f.GetDataValidations("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(dataValidations), 1) assert.Len(t, dataValidations, 1)
assert.NoError(t, f.SaveAs(resultFile)) assert.NoError(t, f.SaveAs(resultFile))
@ -47,7 +47,7 @@ func TestDataValidation(t *testing.T) {
dataValidations, err = f.GetDataValidations("Sheet1") dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(dataValidations), 2) assert.Len(t, dataValidations, 2)
assert.NoError(t, f.SaveAs(resultFile)) assert.NoError(t, f.SaveAs(resultFile))
@ -62,10 +62,10 @@ func TestDataValidation(t *testing.T) {
assert.NoError(t, f.AddDataValidation("Sheet2", dv)) assert.NoError(t, f.AddDataValidation("Sheet2", dv))
dataValidations, err = f.GetDataValidations("Sheet1") dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(dataValidations), 2) assert.Len(t, dataValidations, 2)
dataValidations, err = f.GetDataValidations("Sheet2") dataValidations, err = f.GetDataValidations("Sheet2")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(dataValidations), 1) assert.Len(t, dataValidations, 1)
dv = NewDataValidation(true) dv = NewDataValidation(true)
dv.Sqref = "A5:B6" dv.Sqref = "A5:B6"
@ -87,7 +87,7 @@ func TestDataValidation(t *testing.T) {
dataValidations, err = f.GetDataValidations("Sheet1") dataValidations, err = f.GetDataValidations("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, len(dataValidations), 3) assert.Len(t,dataValidations, 3)
// Test get data validation on no exists worksheet // Test get data validation on no exists worksheet
_, err = f.GetDataValidations("SheetN") _, err = f.GetDataValidations("SheetN")

View File

@ -129,7 +129,7 @@ var (
ErrInvalidFormula = errors.New("formula not valid") ErrInvalidFormula = errors.New("formula not valid")
// ErrAddVBAProject defined the error message on add the VBA project in // ErrAddVBAProject defined the error message on add the VBA project in
// the workbook. // the workbook.
ErrAddVBAProject = errors.New("unsupported VBA project extension") ErrAddVBAProject = errors.New("unsupported VBA project")
// ErrMaxRows defined the error message on receive a row number exceeds maximum limit. // ErrMaxRows defined the error message on receive a row number exceeds maximum limit.
ErrMaxRows = errors.New("row number exceeds maximum limit") ErrMaxRows = errors.New("row number exceeds maximum limit")
// ErrMaxRowHeight defined the error message on receive an invalid row // ErrMaxRowHeight defined the error message on receive an invalid row

View File

@ -16,10 +16,8 @@ import (
"archive/zip" "archive/zip"
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"os" "os"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -460,27 +458,33 @@ func (f *File) UpdateLinkedValue() error {
} }
// AddVBAProject provides the method to add vbaProject.bin file which contains // AddVBAProject provides the method to add vbaProject.bin file which contains
// functions and/or macros. The file extension should be .xlsm. For example: // functions and/or macros. The file extension should be XLSM or XLTM. For
// example:
// //
// codeName := "Sheet1" // codeName := "Sheet1"
// if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{ // if err := f.SetSheetProps("Sheet1", &excelize.SheetPropsOptions{
// CodeName: &codeName, // CodeName: &codeName,
// }); err != nil { // }); err != nil {
// fmt.Println(err) // fmt.Println(err)
// return
// } // }
// if err := f.AddVBAProject("vbaProject.bin"); err != nil { // file, err := os.ReadFile("vbaProject.bin")
// if err != nil {
// fmt.Println(err) // fmt.Println(err)
// return
// }
// if err := f.AddVBAProject(file); err != nil {
// fmt.Println(err)
// return
// } // }
// if err := f.SaveAs("macros.xlsm"); err != nil { // if err := f.SaveAs("macros.xlsm"); err != nil {
// fmt.Println(err) // fmt.Println(err)
// return
// } // }
func (f *File) AddVBAProject(bin string) error { func (f *File) AddVBAProject(file []byte) error {
var err error var err error
// Check vbaProject.bin exists first. // Check vbaProject.bin exists first.
if _, err = os.Stat(bin); os.IsNotExist(err) { if !bytes.Contains(file, oleIdentifier) {
return fmt.Errorf("stat %s: no such file or directory", bin)
}
if path.Ext(bin) != ".bin" {
return ErrAddVBAProject return ErrAddVBAProject
} }
rels, err := f.relsReader(f.getWorkbookRelsPath()) rels, err := f.relsReader(f.getWorkbookRelsPath())
@ -509,7 +513,6 @@ func (f *File) AddVBAProject(bin string) error {
Type: SourceRelationshipVBAProject, Type: SourceRelationshipVBAProject,
}) })
} }
file, _ := os.ReadFile(filepath.Clean(bin))
f.Pkg.Store("xl/vbaProject.bin", file) f.Pkg.Store("xl/vbaProject.bin", file)
return err return err
} }

View File

@ -1319,8 +1319,8 @@ func TestProtectSheet(t *testing.T) {
})) }))
ws, err = f.workSheetReader(sheetName) ws, err = f.workSheetReader(sheetName)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 24, len(ws.SheetProtection.SaltValue)) assert.Len(t, ws.SheetProtection.SaltValue, 24)
assert.Equal(t, 88, len(ws.SheetProtection.HashValue)) assert.Len(t, ws.SheetProtection.HashValue, 88)
assert.Equal(t, int(sheetProtectionSpinCount), ws.SheetProtection.SpinCount) assert.Equal(t, int(sheetProtectionSpinCount), ws.SheetProtection.SpinCount)
// Test remove sheet protection with an incorrect password // Test remove sheet protection with an incorrect password
assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error()) assert.EqualError(t, f.UnprotectSheet(sheetName, "wrongPassword"), ErrUnprotectSheetPassword.Error())
@ -1387,8 +1387,8 @@ func TestProtectWorkbook(t *testing.T) {
wb, err := f.workbookReader() wb, err := f.workbookReader()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "SHA-512", wb.WorkbookProtection.WorkbookAlgorithmName) assert.Equal(t, "SHA-512", wb.WorkbookProtection.WorkbookAlgorithmName)
assert.Equal(t, 24, len(wb.WorkbookProtection.WorkbookSaltValue)) assert.Len(t, wb.WorkbookProtection.WorkbookSaltValue, 24)
assert.Equal(t, 88, len(wb.WorkbookProtection.WorkbookHashValue)) assert.Len(t, wb.WorkbookProtection.WorkbookHashValue, 88)
assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount) assert.Equal(t, int(workbookProtectionSpinCount), wb.WorkbookProtection.WorkbookSpinCount)
// Test protect workbook with password exceeds the limit length // Test protect workbook with password exceeds the limit length
@ -1448,17 +1448,20 @@ func TestSetDefaultTimeStyle(t *testing.T) {
func TestAddVBAProject(t *testing.T) { func TestAddVBAProject(t *testing.T) {
f := NewFile() f := NewFile()
file, err := os.ReadFile(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")})) assert.NoError(t, f.SetSheetProps("Sheet1", &SheetPropsOptions{CodeName: stringPtr("Sheet1")}))
assert.EqualError(t, f.AddVBAProject("macros.bin"), "stat macros.bin: no such file or directory") assert.EqualError(t, f.AddVBAProject(file), ErrAddVBAProject.Error())
assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "Book1.xlsx")), ErrAddVBAProject.Error()) file, err = os.ReadFile(filepath.Join("test", "vbaProject.bin"))
assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin"))) assert.NoError(t, err)
assert.NoError(t, f.AddVBAProject(file))
// Test add VBA project twice // Test add VBA project twice
assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin"))) assert.NoError(t, f.AddVBAProject(file))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm")))
// Test add VBA with unsupported charset workbook relationships // Test add VBA with unsupported charset workbook relationships
f.Relationships.Delete(defaultXMLPathWorkbookRels) f.Relationships.Delete(defaultXMLPathWorkbookRels)
f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin")), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.AddVBAProject(file), "XML syntax error on line 1: invalid UTF-8")
} }
func TestContentTypesReader(t *testing.T) { func TestContentTypesReader(t *testing.T) {

View File

@ -39,12 +39,8 @@ func NewFile() *File {
f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes)) f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes))
f.SheetCount = 1 f.SheetCount = 1
f.CalcChain, _ = f.calcChainReader() f.CalcChain, _ = f.calcChainReader()
f.Comments = make(map[string]*xlsxComments)
f.ContentTypes, _ = f.contentTypesReader() f.ContentTypes, _ = f.contentTypesReader()
f.Drawings = sync.Map{}
f.Styles, _ = f.stylesReader() f.Styles, _ = f.stylesReader()
f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing)
f.VMLDrawing = make(map[string]*vmlDrawing)
f.WorkBook, _ = f.workbookReader() f.WorkBook, _ = f.workbookReader()
f.Relationships = sync.Map{} f.Relationships = sync.Map{}
rels, _ := f.relsReader(defaultXMLPathWorkbookRels) rels, _ := f.relsReader(defaultXMLPathWorkbookRels)

View File

@ -274,7 +274,7 @@ func TestBytesReplace(t *testing.T) {
} }
func TestGetRootElement(t *testing.T) { func TestGetRootElement(t *testing.T) {
assert.Equal(t, 0, len(getRootElement(xml.NewDecoder(strings.NewReader(""))))) assert.Len(t, getRootElement(xml.NewDecoder(strings.NewReader(""))), 0)
} }
func TestSetIgnorableNameSpace(t *testing.T) { func TestSetIgnorableNameSpace(t *testing.T) {

View File

@ -93,7 +93,7 @@ func TestMergeCellOverlap(t *testing.T) {
} }
mc, err := f.GetMergeCells("Sheet1") mc, err := f.GetMergeCells("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, len(mc)) assert.Len(t, mc, 1)
assert.Equal(t, "A1", mc[0].GetStartAxis()) assert.Equal(t, "A1", mc[0].GetStartAxis())
assert.Equal(t, "D3", mc[0].GetEndAxis()) assert.Equal(t, "D3", mc[0].GetEndAxis())
assert.Equal(t, "", mc[0].GetCellValue()) assert.Equal(t, "", mc[0].GetCellValue())

View File

@ -56,7 +56,7 @@ func parseShapeOptions(opts *Shape) (*Shape, error) {
// &excelize.Shape{ // &excelize.Shape{
// Type: "rect", // Type: "rect",
// Line: excelize.ShapeLine{Color: "4286F4", Width: &lineWidth}, // Line: excelize.ShapeLine{Color: "4286F4", Width: &lineWidth},
// Fill: excelize.Fill{Color: []string{"8EB9FF"}}, // Fill: excelize.Fill{Color: []string{"8EB9FF"}, Pattern: 1},
// Paragraph: []excelize.RichTextRun{ // Paragraph: []excelize.RichTextRun{
// { // {
// Text: "Rectangle Shape", // Text: "Rectangle Shape",

View File

@ -273,7 +273,7 @@ func TestDefinedName(t *testing.T) {
Name: "Amount", Name: "Amount",
})) }))
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo) assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
assert.Exactly(t, 1, len(f.GetDefinedName())) assert.Len(t, f.GetDefinedName(), 1)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx")))
// Test set defined name with unsupported charset workbook // Test set defined name with unsupported charset workbook
f.WorkBook = nil f.WorkBook = nil
@ -376,7 +376,7 @@ func TestGetSheetMap(t *testing.T) {
for idx, name := range sheetMap { for idx, name := range sheetMap {
assert.Equal(t, expectedMap[idx], name) assert.Equal(t, expectedMap[idx], name)
} }
assert.Equal(t, len(sheetMap), 2) assert.Len(t, sheetMap, 2)
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile() f = NewFile()

View File

@ -139,12 +139,13 @@ 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", nil) // err := sw.AddTable(&excelize.Table{Range: "A1:D5"})
// //
// Create a table of F2:H6 with format set: // Create a table of F2:H6 with format set:
// //
// disable := false // disable := false
// err := sw.AddTable("F2:H6", &excelize.TableOptions{ // err := sw.AddTable(&excelize.Table{
// Range: "F2:H6",
// Name: "table", // Name: "table",
// StyleName: "TableStyleMedium2", // StyleName: "TableStyleMedium2",
// ShowFirstColumn: true, // ShowFirstColumn: true,
@ -160,12 +161,12 @@ 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(rangeRef string, opts *TableOptions) error { func (sw *StreamWriter) AddTable(table *Table) error {
options, err := parseTableOptions(opts) options, err := parseTableOptions(table)
if err != nil { if err != nil {
return err return err
} }
coordinates, err := rangeRefToCoordinates(rangeRef) coordinates, err := rangeRefToCoordinates(options.Range)
if err != nil { if err != nil {
return err return err
} }
@ -202,7 +203,7 @@ func (sw *StreamWriter) AddTable(rangeRef string, opts *TableOptions) error {
name = "Table" + strconv.Itoa(tableID) name = "Table" + strconv.Itoa(tableID)
} }
table := xlsxTable{ tbl := xlsxTable{
XMLNS: NameSpaceSpreadSheet.Value, XMLNS: NameSpaceSpreadSheet.Value,
ID: tableID, ID: tableID,
Name: name, Name: name,
@ -237,7 +238,7 @@ func (sw *StreamWriter) AddTable(rangeRef string, opts *TableOptions) error {
if err = sw.file.addContentTypePart(tableID, "table"); err != nil { if err = sw.file.addContentTypePart(tableID, "table"); err != nil {
return err return err
} }
b, _ := xml.Marshal(table) b, _ := xml.Marshal(tbl)
sw.file.saveFileList(tableXML, b) sw.file.saveFileList(tableXML, b)
return err return err
} }

View File

@ -196,7 +196,7 @@ 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 // Test add table without table header
assert.EqualError(t, streamWriter.AddTable("A1:C2", nil), "XML syntax error on line 2: unexpected EOF") assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}), "XML syntax error on line 2: unexpected EOF")
// Write some rows. We want enough rows to force a temp file (>16MB) // 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}
@ -205,7 +205,7 @@ func TestStreamTable(t *testing.T) {
} }
// Write a table // Write a table
assert.NoError(t, streamWriter.AddTable("A1:C2", nil)) assert.NoError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}))
assert.NoError(t, streamWriter.Flush()) assert.NoError(t, streamWriter.Flush())
// Verify the table has names // Verify the table has names
@ -217,17 +217,17 @@ 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", nil)) assert.NoError(t, streamWriter.AddTable(&Table{Range: "A1:C1"}))
// Test add table with illegal cell reference // Test add table with illegal cell reference
assert.EqualError(t, streamWriter.AddTable("A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A:B1"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, streamWriter.AddTable("A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:B"}), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add table with invalid table name // Test add table with invalid table name
assert.EqualError(t, streamWriter.AddTable("A:B1", &TableOptions{Name: "1Table"}), newInvalidTableNameError("1Table").Error()) assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A:B1", Name: "1Table"}), newInvalidTableNameError("1Table").Error())
// Test add table with unsupported charset content types // 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", nil), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:C2"}), "XML syntax error on line 1: invalid UTF-8")
} }
func TestStreamMergeCells(t *testing.T) { func TestStreamMergeCells(t *testing.T) {

View File

@ -23,10 +23,10 @@ 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 *TableOptions) (*TableOptions, error) { func parseTableOptions(opts *Table) (*Table, error) {
var err error var err error
if opts == nil { if opts == nil {
return &TableOptions{ShowRowStripes: boolPtr(true)}, err return &Table{ShowRowStripes: boolPtr(true)}, err
} }
if opts.ShowRowStripes == nil { if opts.ShowRowStripes == nil {
opts.ShowRowStripes = boolPtr(true) opts.ShowRowStripes = boolPtr(true)
@ -41,12 +41,13 @@ func parseTableOptions(opts *TableOptions) (*TableOptions, error) {
// 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", nil) // err := f.AddTable("Sheet1", &excelize.Table{Range: "A1:D5"})
// //
// Create a table of F2:H6 on Sheet2 with format set: // Create a table of F2:H6 on Sheet2 with format set:
// //
// disable := false // disable := false
// err := f.AddTable("Sheet2", "F2:H6", &excelize.TableOptions{ // err := f.AddTable("Sheet2", &excelize.Table{
// Range: "F2:H6",
// Name: "table", // Name: "table",
// StyleName: "TableStyleMedium2", // StyleName: "TableStyleMedium2",
// ShowFirstColumn: true, // ShowFirstColumn: true,
@ -69,13 +70,13 @@ func parseTableOptions(opts *TableOptions) (*TableOptions, error) {
// TableStyleLight1 - TableStyleLight21 // TableStyleLight1 - TableStyleLight21
// TableStyleMedium1 - TableStyleMedium28 // TableStyleMedium1 - TableStyleMedium28
// TableStyleDark1 - TableStyleDark11 // TableStyleDark1 - TableStyleDark11
func (f *File) AddTable(sheet, rangeRef string, opts *TableOptions) error { func (f *File) AddTable(sheet string, table *Table) error {
options, err := parseTableOptions(opts) options, err := parseTableOptions(table)
if err != nil { if err != nil {
return err return err
} }
// Coordinate conversion, convert C1:B3 to 2,0,1,2. // Coordinate conversion, convert C1:B3 to 2,0,1,2.
coordinates, err := rangeRefToCoordinates(rangeRef) coordinates, err := rangeRefToCoordinates(options.Range)
if err != nil { if err != nil {
return err return err
} }
@ -187,7 +188,7 @@ func checkTableName(name string) error {
// 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 *Table) 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++

View File

@ -12,8 +12,9 @@ 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", nil)) assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"}))
assert.NoError(t, f.AddTable("Sheet2", "A2:B5", &TableOptions{ assert.NoError(t, f.AddTable("Sheet2", &Table{
Range: "A2:B5",
Name: "table", Name: "table",
StyleName: "TableStyleMedium2", StyleName: "TableStyleMedium2",
ShowColumnStripes: true, ShowColumnStripes: true,
@ -21,21 +22,24 @@ func TestAddTable(t *testing.T) {
ShowLastColumn: true, ShowLastColumn: true,
ShowRowStripes: boolPtr(true), ShowRowStripes: boolPtr(true),
})) }))
assert.NoError(t, f.AddTable("Sheet2", "D1:D11", &TableOptions{ assert.NoError(t, f.AddTable("Sheet2", &Table{
Range: "D1:D11",
ShowHeaderRow: boolPtr(false), ShowHeaderRow: boolPtr(false),
})) }))
assert.NoError(t, f.AddTable("Sheet2", "F1:F1", &TableOptions{StyleName: "TableStyleMedium8"})) assert.NoError(t, f.AddTable("Sheet2", &Table{Range: "F1:F1", StyleName: "TableStyleMedium8"}))
// Test add table with invalid table options
assert.Equal(t, f.AddTable("Sheet1", nil), ErrParameterInvalid)
// Test add table in not exist worksheet // Test add table in not exist worksheet
assert.EqualError(t, f.AddTable("SheetN", "B26:A21", nil), "sheet SheetN does not exist") assert.EqualError(t, f.AddTable("SheetN", &Table{Range: "B26:A21"}), "sheet SheetN does not exist")
// Test add table with illegal cell reference // Test add table with illegal cell reference
assert.EqualError(t, f.AddTable("Sheet1", "A:B1", nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A:B1"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")))
assert.EqualError(t, f.AddTable("Sheet1", "A1:B", nil), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.Equal(t, f.AddTable("Sheet1", &Table{Range: "A1:B"}), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")))
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", nil), ErrSheetNameInvalid.Error()) assert.EqualError(t, f.AddTable("Sheet:1", &Table{Range: "B26:A21"}), 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]")
@ -54,7 +58,8 @@ func TestAddTable(t *testing.T) {
{name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidTableNameError("\u0f5f\u0fb3\u0f0b\u0f21")}, {name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidTableNameError("\u0f5f\u0fb3\u0f0b\u0f21")},
{name: strings.Repeat("c", MaxFieldLength+1), err: ErrTableNameLength}, {name: strings.Repeat("c", MaxFieldLength+1), err: ErrTableNameLength},
} { } {
assert.EqualError(t, f.AddTable("Sheet1", "A1:B2", &TableOptions{ assert.EqualError(t, f.AddTable("Sheet1", &Table{
Range: "A1:B2",
Name: cases.name, Name: cases.name,
}), cases.err.Error()) }), cases.err.Error())
} }

BIN
test/vbaProject.bin Executable file → Normal file

Binary file not shown.

View File

@ -196,8 +196,9 @@ type xlsxTableStyleInfo struct {
ShowColumnStripes bool `xml:"showColumnStripes,attr"` ShowColumnStripes bool `xml:"showColumnStripes,attr"`
} }
// TableOptions directly maps the format settings of the table. // Table directly maps the format settings of the table.
type TableOptions struct { type Table struct {
Range string
Name string Name string
StyleName string StyleName string
ShowColumnStripes bool ShowColumnStripes bool