125 changes: 63 additions & 62 deletions pivotTable.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import (
//
// PivotTableStyleName: The built-in pivot table style names
//
// PivotStyleLight1 - PivotStyleLight28
// PivotStyleMedium1 - PivotStyleMedium28
// PivotStyleDark1 - PivotStyleDark28
//
// PivotStyleLight1 - PivotStyleLight28
// PivotStyleMedium1 - PivotStyleMedium28
// PivotStyleDark1 - PivotStyleDark28
type PivotTableOption struct {
pivotTableSheetName string
DataRange string
Expand Down Expand Up @@ -55,17 +54,17 @@ type PivotTableOption struct {
// field. The default value is sum. The possible values for this attribute
// are:
//
// Average
// Count
// CountNums
// Max
// Min
// Product
// StdDev
// StdDevp
// Sum
// Var
// Varp
// Average
// Count
// CountNums
// Max
// Min
// Product
// StdDev
// StdDevp
// Sum
// Var
// Varp
//
// Name specifies the name of the data field. Maximum 255 characters
// are allowed in data field name, excess characters will be truncated.
Expand All @@ -85,51 +84,50 @@ type PivotTableField struct {
// For example, create a pivot table on the Sheet1!$G$2:$M$34 area with the
// region Sheet1!$A$1:$E$31 as the data source, summarize by sum for sales:
//
// package main
//
// import (
// "fmt"
// "math/rand"
// package main
//
// "github.com/xuri/excelize/v2"
// )
// import (
// "fmt"
// "math/rand"
//
// func main() {
// f := excelize.NewFile()
// // Create some data in a sheet
// month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
// year := []int{2017, 2018, 2019}
// types := []string{"Meat", "Dairy", "Beverages", "Produce"}
// region := []string{"East", "West", "North", "South"}
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
// for i := 0; i < 30; i++ {
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)])
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)])
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)])
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000))
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)])
// }
// if err := f.AddPivotTable(&excelize.PivotTableOption{
// DataRange: "Sheet1!$A$1:$E$31",
// PivotTableRange: "Sheet1!$G$2:$M$34",
// Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
// Filter: []excelize.PivotTableField{{Data: "Region"}},
// Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
// Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
// RowGrandTotals: true,
// ColGrandTotals: true,
// ShowDrill: true,
// ShowRowHeaders: true,
// ShowColHeaders: true,
// ShowLastColumn: true,
// }); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
// "github.com/xuri/excelize/v2"
// )
//
// func main() {
// f := excelize.NewFile()
// // Create some data in a sheet
// month := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
// year := []int{2017, 2018, 2019}
// types := []string{"Meat", "Dairy", "Beverages", "Produce"}
// region := []string{"East", "West", "North", "South"}
// f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"})
// for row := 2; row < 32; row++ {
// f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)])
// f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)])
// f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)])
// f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000))
// f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)])
// }
// if err := f.AddPivotTable(&excelize.PivotTableOption{
// DataRange: "Sheet1!$A$1:$E$31",
// PivotTableRange: "Sheet1!$G$2:$M$34",
// Rows: []excelize.PivotTableField{{Data: "Month", DefaultSubtotal: true}, {Data: "Year"}},
// Filter: []excelize.PivotTableField{{Data: "Region"}},
// Columns: []excelize.PivotTableField{{Data: "Type", DefaultSubtotal: true}},
// Data: []excelize.PivotTableField{{Data: "Sales", Name: "Summarize", Subtotal: "Sum"}},
// RowGrandTotals: true,
// ColGrandTotals: true,
// ShowDrill: true,
// ShowRowHeaders: true,
// ShowColHeaders: true,
// ShowLastColumn: true,
// }); err != nil {
// fmt.Println(err)
// }
// if err := f.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// }
func (f *File) AddPivotTable(opt *PivotTableOption) error {
// parameter validation
_, pivotTableSheetPath, err := f.parseFormatPivotTableSet(opt)
Expand All @@ -141,7 +139,7 @@ func (f *File) AddPivotTable(opt *PivotTableOption) error {
pivotCacheID := f.countPivotCache() + 1

sheetRelationshipsPivotTableXML := "../pivotTables/pivotTable" + strconv.Itoa(pivotTableID) + ".xml"
pivotTableXML := strings.Replace(sheetRelationshipsPivotTableXML, "..", "xl", -1)
pivotTableXML := strings.ReplaceAll(sheetRelationshipsPivotTableXML, "..", "xl")
pivotCacheXML := "xl/pivotCache/pivotCacheDefinition" + strconv.Itoa(pivotCacheID) + ".xml"
err = f.addPivotCache(pivotCacheXML, opt)
if err != nil {
Expand Down Expand Up @@ -190,7 +188,7 @@ func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet,
if err != nil {
return dataSheet, "", err
}
pivotTableSheetPath, ok := f.sheetMap[trimSheetName(pivotTableSheetName)]
pivotTableSheetPath, ok := f.getSheetXMLPath(pivotTableSheetName)
if !ok {
return dataSheet, pivotTableSheetPath, fmt.Errorf("sheet %s is not exist", pivotTableSheetName)
}
Expand All @@ -206,7 +204,7 @@ func (f *File) adjustRange(rangeStr string) (string, []int, error) {
if len(rng) != 2 {
return "", []int{}, ErrParameterInvalid
}
trimRng := strings.Replace(rng[1], "$", "", -1)
trimRng := strings.ReplaceAll(rng[1], "$", "")
coordinates, err := areaRefToCoordinates(trimRng)
if err != nil {
return rng[0], []int{}, err
Expand Down Expand Up @@ -542,6 +540,7 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
Name: f.getPivotTableFieldName(name, opt.Rows),
Axis: "axisRow",
DataField: inPivotTableField(opt.Data, name) != -1,
Compact: &rowOptions.Compact,
Outline: &rowOptions.Outline,
DefaultSubtotal: &rowOptions.DefaultSubtotal,
Expand All @@ -554,8 +553,9 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio
}
if inPivotTableField(opt.Filter, name) != -1 {
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
Axis: "axisPage",
Name: f.getPivotTableFieldName(name, opt.Columns),
Axis: "axisPage",
DataField: inPivotTableField(opt.Data, name) != -1,
Name: f.getPivotTableFieldName(name, opt.Columns),
Items: &xlsxItems{
Count: 1,
Item: []*xlsxItem{
Expand All @@ -576,6 +576,7 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio
pt.PivotFields.PivotField = append(pt.PivotFields.PivotField, &xlsxPivotField{
Name: f.getPivotTableFieldName(name, opt.Columns),
Axis: "axisCol",
DataField: inPivotTableField(opt.Data, name) != -1,
Compact: &columnOptions.Compact,
Outline: &columnOptions.Outline,
DefaultSubtotal: &columnOptions.DefaultSubtotal,
Expand Down
12 changes: 6 additions & 6 deletions pivotTable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ func TestAddPivotTable(t *testing.T) {
types := []string{"Meat", "Dairy", "Beverages", "Produce"}
region := []string{"East", "West", "North", "South"}
assert.NoError(t, f.SetSheetRow("Sheet1", "A1", &[]string{"Month", "Year", "Type", "Sales", "Region"}))
for i := 0; i < 30; i++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", i+2), month[rand.Intn(12)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", i+2), year[rand.Intn(3)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", i+2), types[rand.Intn(4)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", i+2), rand.Intn(5000)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", i+2), region[rand.Intn(4)]))
for row := 2; row < 32; row++ {
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), month[rand.Intn(12)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), year[rand.Intn(3)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("C%d", row), types[rand.Intn(4)]))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("D%d", row), rand.Intn(5000)))
assert.NoError(t, f.SetCellValue("Sheet1", fmt.Sprintf("E%d", row), region[rand.Intn(4)]))
}
assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "Sheet1!$A$1:$E$31",
Expand Down
176 changes: 101 additions & 75 deletions rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,28 @@ import (
"github.com/mohae/deepcopy"
)

// GetRows return all the rows in a sheet by given worksheet name
// (case sensitive), returned as a two-dimensional array, where the value of
// the cell is converted to the string type. If the cell format can be
// applied to the value of the cell, the applied value will be used,
// otherwise the original value will be used. GetRows fetched the rows with
// value or formula cells, the tail continuously empty cell will be skipped.
// For example:
//
// rows, err := f.GetRows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for _, row := range rows {
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
// }
//
// GetRows return all the rows in a sheet by given worksheet name, returned as
// a two-dimensional array, where the value of the cell is converted to the
// string type. If the cell format can be applied to the value of the cell,
// the applied value will be used, otherwise the original value will be used.
// GetRows fetched the rows with value or formula cells, the continually blank
// cells in the tail of each row will be skipped, so the length of each row
// may be inconsistent.
//
// For example, get and traverse the value of all cells by rows on a worksheet
// named 'Sheet1':
//
// rows, err := f.GetRows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for _, row := range rows {
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
// }
func (f *File) GetRows(sheet string, opts ...Options) ([][]string, error) {
rows, err := f.Rows(sheet)
if err != nil {
Expand Down Expand Up @@ -77,12 +79,14 @@ type Rows struct {
sst *xlsxSST
decoder *xml.Decoder
token xml.Token
curRowOpts, seekRowOpts RowOpts
}

// Next will return true if find the next row element.
func (rows *Rows) Next() bool {
rows.seekRow++
if rows.curRow >= rows.seekRow {
rows.curRowOpts = rows.seekRowOpts
return true
}
for {
Expand All @@ -98,6 +102,7 @@ func (rows *Rows) Next() bool {
rows.curRow = rowNum
}
rows.token = token
rows.curRowOpts = extractRowOpts(xmlElement.Attr)
return true
}
case xml.EndElement:
Expand All @@ -108,6 +113,11 @@ func (rows *Rows) Next() bool {
}
}

// GetRowOpts will return the RowOpts of the current row.
func (rows *Rows) GetRowOpts() RowOpts {
return rows.curRowOpts
}

// Error will return the error when the error occurs.
func (rows *Rows) Error() error {
return rows.err
Expand All @@ -122,7 +132,9 @@ func (rows *Rows) Close() error {
return nil
}

// Columns return the current row's column values.
// Columns return the current row's column values. This fetches the worksheet
// data as a stream, returns each cell in a row as is, and will not skip empty
// rows in the tail of the worksheet.
func (rows *Rows) Columns(opts ...Options) ([]string, error) {
if rows.curRow > rows.seekRow {
return nil, nil
Expand All @@ -146,6 +158,8 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) {
} else if rows.token == nil {
rows.curRow++
}
rows.token = token
rows.seekRowOpts = extractRowOpts(xmlElement.Attr)
if rows.curRow > rows.seekRow {
rows.token = nil
return rowIterator.columns, rowIterator.err
Expand All @@ -165,6 +179,21 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) {
return rowIterator.columns, rowIterator.err
}

// extractRowOpts extract row element attributes.
func extractRowOpts(attrs []xml.Attr) RowOpts {
rowOpts := RowOpts{Height: defaultRowHeight}
if styleID, err := attrValToInt("s", attrs); err == nil && styleID > 0 && styleID < MaxCellStyles {
rowOpts.StyleID = styleID
}
if hidden, err := attrValToBool("hidden", attrs); err == nil {
rowOpts.Hidden = hidden
}
if height, err := attrValToFloat("ht", attrs); err == nil {
rowOpts.Height = height
}
return rowOpts
}

// appendSpace append blank characters to slice by given length and source slice.
func appendSpace(l int, s []string) []string {
for i := 1; i < l; i++ {
Expand Down Expand Up @@ -211,27 +240,26 @@ func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.Sta
// Rows returns a rows iterator, used for streaming reading data for a
// worksheet with a large data. For example:
//
// rows, err := f.Rows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// if err != nil {
// fmt.Println(err)
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
// }
// if err = rows.Close(); err != nil {
// fmt.Println(err)
// }
//
// rows, err := f.Rows("Sheet1")
// if err != nil {
// fmt.Println(err)
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// if err != nil {
// fmt.Println(err)
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// }
// fmt.Println()
// }
// if err = rows.Close(); err != nil {
// fmt.Println(err)
// }
func (f *File) Rows(sheet string) (*Rows, error) {
name, ok := f.sheetMap[trimSheetName(sheet)]
name, ok := f.getSheetXMLPath(sheet)
if !ok {
return nil, ErrSheetNotExist{sheet}
}
Expand Down Expand Up @@ -315,8 +343,7 @@ func (f *File) xmlDecoder(name string) (bool, *xml.Decoder, *os.File, error) {
// SetRowHeight provides a function to set the height of a single row. For
// example, set the height of the first row in Sheet1:
//
// err := f.SetRowHeight("Sheet1", 1, 50)
//
// err := f.SetRowHeight("Sheet1", 1, 50)
func (f *File) SetRowHeight(sheet string, row int, height float64) error {
if row < 1 {
return newInvalidRowNumberError(row)
Expand Down Expand Up @@ -356,8 +383,7 @@ func (f *File) getRowHeight(sheet string, row int) int {
// GetRowHeight provides a function to get row height by given worksheet name
// and row number. For example, get the height of the first row in Sheet1:
//
// height, err := f.GetRowHeight("Sheet1", 1)
//
// height, err := f.GetRowHeight("Sheet1", 1)
func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
if row < 1 {
return defaultRowHeightPixels, newInvalidRowNumberError(row)
Expand Down Expand Up @@ -488,8 +514,7 @@ func roundPrecision(text string, prec int) string {
// SetRowVisible provides a function to set visible of a single row by given
// worksheet name and Excel row number. For example, hide row 2 in Sheet1:
//
// err := f.SetRowVisible("Sheet1", 2, false)
//
// err := f.SetRowVisible("Sheet1", 2, false)
func (f *File) SetRowVisible(sheet string, row int, visible bool) error {
if row < 1 {
return newInvalidRowNumberError(row)
Expand All @@ -508,8 +533,7 @@ func (f *File) SetRowVisible(sheet string, row int, visible bool) error {
// worksheet name and Excel row number. For example, get visible state of row
// 2 in Sheet1:
//
// visible, err := f.GetRowVisible("Sheet1", 2)
//
// visible, err := f.GetRowVisible("Sheet1", 2)
func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
if row < 1 {
return false, newInvalidRowNumberError(row)
Expand All @@ -529,8 +553,7 @@ func (f *File) GetRowVisible(sheet string, row int) (bool, error) {
// single row by given worksheet name and Excel row number. The value of
// parameter 'level' is 1-7. For example, outline row 2 in Sheet1 to level 1:
//
// err := f.SetRowOutlineLevel("Sheet1", 2, 1)
//
// err := f.SetRowOutlineLevel("Sheet1", 2, 1)
func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
if row < 1 {
return newInvalidRowNumberError(row)
Expand All @@ -551,8 +574,7 @@ func (f *File) SetRowOutlineLevel(sheet string, row int, level uint8) error {
// single row by given worksheet name and Excel row number. For example, get
// outline number of row 2 in Sheet1:
//
// level, err := f.GetRowOutlineLevel("Sheet1", 2)
//
// level, err := f.GetRowOutlineLevel("Sheet1", 2)
func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) {
if row < 1 {
return 0, newInvalidRowNumberError(row)
Expand All @@ -570,7 +592,7 @@ func (f *File) GetRowOutlineLevel(sheet string, row int) (uint8, error) {
// RemoveRow provides a function to remove single row by given worksheet name
// and Excel row number. For example, remove row 3 in Sheet1:
//
// err := f.RemoveRow("Sheet1", 3)
// err := f.RemoveRow("Sheet1", 3)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
Expand Down Expand Up @@ -604,7 +626,7 @@ func (f *File) RemoveRow(sheet string, row int) error {
// number starting from 1. For example, create a new row before row 3 in
// Sheet1:
//
// err := f.InsertRow("Sheet1", 3)
// err := f.InsertRow("Sheet1", 3)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
Expand All @@ -619,7 +641,7 @@ func (f *File) InsertRow(sheet string, row int) error {

// DuplicateRow inserts a copy of specified row (by its Excel row number) below
//
// err := f.DuplicateRow("Sheet1", 2)
// err := f.DuplicateRow("Sheet1", 2)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
Expand All @@ -632,7 +654,7 @@ func (f *File) DuplicateRow(sheet string, row int) error {
// DuplicateRowTo inserts a copy of specified row by it Excel number
// to specified row position moving down exists rows after target position
//
// err := f.DuplicateRowTo("Sheet1", 2, 7)
// err := f.DuplicateRowTo("Sheet1", 2, 7)
//
// Use this method with caution, which will affect changes in references such
// as formulas, charts, and so on. If there is any referenced value of the
Expand Down Expand Up @@ -729,24 +751,24 @@ func (f *File) duplicateMergeCells(sheet string, ws *xlsxWorksheet, row, row2 in
// checkRow provides a function to check and fill each column element for all
// rows and make that is continuous in a worksheet of XML. For example:
//
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
// <c r="A15" s="2" />
// <c r="B15" s="2" />
// <c r="F15" s="1" />
// <c r="G15" s="1" />
// </row>
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
// <c r="A15" s="2" />
// <c r="B15" s="2" />
// <c r="F15" s="1" />
// <c r="G15" s="1" />
// </row>
//
// in this case, we should to change it to
//
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
// <c r="A15" s="2" />
// <c r="B15" s="2" />
// <c r="C15" s="2" />
// <c r="D15" s="2" />
// <c r="E15" s="2" />
// <c r="F15" s="1" />
// <c r="G15" s="1" />
// </row>
// <row r="15" spans="1:22" x14ac:dyDescent="0.2">
// <c r="A15" s="2" />
// <c r="B15" s="2" />
// <c r="C15" s="2" />
// <c r="D15" s="2" />
// <c r="E15" s="2" />
// <c r="F15" s="1" />
// <c r="G15" s="1" />
// </row>
//
// Noteice: this method could be very slow for large spreadsheets (more than
// 3000 rows one sheet).
Expand Down Expand Up @@ -814,12 +836,11 @@ func checkRow(ws *xlsxWorksheet) error {
//
// For example set style of row 1 on Sheet1:
//
// err = f.SetRowStyle("Sheet1", 1, 1, styleID)
// err = f.SetRowStyle("Sheet1", 1, 1, styleID)
//
// Set style of rows 1 to 10 on Sheet1:
//
// err = f.SetRowStyle("Sheet1", 1, 10, styleID)
//
// err = f.SetRowStyle("Sheet1", 1, 10, styleID)
func (f *File) SetRowStyle(sheet string, start, end, styleID int) error {
if end < start {
start, end = end, start
Expand All @@ -841,6 +862,11 @@ func (f *File) SetRowStyle(sheet string, start, end, styleID int) error {
for row := start - 1; row < end; row++ {
ws.SheetData.Row[row].S = styleID
ws.SheetData.Row[row].CustomFormat = true
for i := range ws.SheetData.Row[row].C {
if _, rowNum, err := CellNameToCoordinates(ws.SheetData.Row[row].C[i].R); err == nil && rowNum-1 == row {
ws.SheetData.Row[row].C[i].S = styleID
}
}
}
return nil
}
Expand Down
48 changes: 41 additions & 7 deletions rows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,32 @@ func TestRowsIterator(t *testing.T) {
assert.Equal(t, expectedNumRow, rowCount)
}

func TestRowsGetRowOpts(t *testing.T) {
sheetName := "Sheet2"
expectedRowStyleID1 := RowOpts{Height: 17.0, Hidden: false, StyleID: 1}
expectedRowStyleID2 := RowOpts{Height: 17.0, Hidden: false, StyleID: 0}
expectedRowStyleID3 := RowOpts{Height: 17.0, Hidden: false, StyleID: 2}
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
require.NoError(t, err)

rows, err := f.Rows(sheetName)
require.NoError(t, err)

assert.Equal(t, true, rows.Next())
_, err = rows.Columns()
require.NoError(t, err)
rowOpts := rows.GetRowOpts()
assert.Equal(t, expectedRowStyleID1, rowOpts)
assert.Equal(t, true, rows.Next())
rowOpts = rows.GetRowOpts()
assert.Equal(t, expectedRowStyleID2, rowOpts)
assert.Equal(t, true, rows.Next())
_, err = rows.Columns()
require.NoError(t, err)
rowOpts = rows.GetRowOpts()
assert.Equal(t, expectedRowStyleID3, rowOpts)
}

func TestRowsError(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
Expand Down Expand Up @@ -322,7 +348,7 @@ func TestInsertRow(t *testing.T) {
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRow.xlsx")))
}

// Testing internal structure state after insert operations. It is important
// Test internal structure state after insert operations. It is important
// for insert workflow to be constant to avoid side effect with functions
// related to internal structure.
func TestInsertRowInEmptyFile(t *testing.T) {
Expand Down Expand Up @@ -915,16 +941,24 @@ func TestCheckRow(t *testing.T) {

func TestSetRowStyle(t *testing.T) {
f := NewFile()
styleID, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`)
style1, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#63BE7B"],"pattern":1}}`)
assert.NoError(t, err)
style2, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`)
assert.NoError(t, err)
assert.EqualError(t, f.SetRowStyle("Sheet1", 10, -1, styleID), newInvalidRowNumberError(-1).Error())
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, TotalRows+1, styleID), ErrMaxRows.Error())
assert.NoError(t, f.SetCellStyle("Sheet1", "B2", "B2", style1))
assert.EqualError(t, f.SetRowStyle("Sheet1", 5, -1, style2), newInvalidRowNumberError(-1).Error())
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, TotalRows+1, style2), ErrMaxRows.Error())
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, -1), newInvalidStyleID(-1).Error())
assert.EqualError(t, f.SetRowStyle("SheetN", 1, 1, styleID), "sheet SheetN is not exist")
assert.NoError(t, f.SetRowStyle("Sheet1", 10, 1, styleID))
assert.EqualError(t, f.SetRowStyle("SheetN", 1, 1, style2), "sheet SheetN is not exist")
assert.NoError(t, f.SetRowStyle("Sheet1", 5, 1, style2))
cellStyleID, err := f.GetCellStyle("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, styleID, cellStyleID)
assert.Equal(t, style2, cellStyleID)
// Test cell inheritance rows style
assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil))
cellStyleID, err = f.GetCellStyle("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, style2, cellStyleID)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx")))
}

Expand Down
483 changes: 240 additions & 243 deletions shape.go

Large diffs are not rendered by default.

771 changes: 400 additions & 371 deletions sheet.go

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ func TestSetPane(t *testing.T) {
assert.NoError(t, f.SetPanes("Panes 4", ""))
assert.EqualError(t, f.SetPanes("SheetN", ""), "sheet SheetN is not exist")
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))
// Test add pane on empty sheet views worksheet
f = NewFile()
f.checked = nil
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`))
assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`))
}

func TestPageLayoutOption(t *testing.T) {
Expand Down Expand Up @@ -459,6 +465,13 @@ func TestDeleteAndAdjustDefinedNames(t *testing.T) {
deleteAndAdjustDefinedNames(&xlsxWorkbook{}, 0)
}

func TestGetSheetID(t *testing.T) {
file := NewFile()
file.NewSheet("Sheet1")
id := file.getSheetID("sheet1")
assert.NotEqual(t, -1, id)
}

func BenchmarkNewSheet(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Expand Down Expand Up @@ -492,3 +505,29 @@ func newSheetWithSave() {
}
_ = file.Save()
}

func TestAttrValToBool(t *testing.T) {
_, err := attrValToBool("hidden", []xml.Attr{
{Name: xml.Name{Local: "hidden"}},
})
assert.EqualError(t, err, `strconv.ParseBool: parsing "": invalid syntax`)

got, err := attrValToBool("hidden", []xml.Attr{
{Name: xml.Name{Local: "hidden"}, Value: "1"},
})
assert.NoError(t, err)
assert.Equal(t, true, got)
}

func TestAttrValToFloat(t *testing.T) {
_, err := attrValToFloat("ht", []xml.Attr{
{Name: xml.Name{Local: "ht"}},
})
assert.EqualError(t, err, `strconv.ParseFloat: parsing "": invalid syntax`)

got, err := attrValToFloat("ht", []xml.Attr{
{Name: xml.Name{Local: "ht"}, Value: "42.1"},
})
assert.NoError(t, err)
assert.Equal(t, 42.1, got)
}
171 changes: 124 additions & 47 deletions sheetpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ type (
Published bool
// FitToPage is a SheetPrOption
FitToPage bool
// TabColor is a SheetPrOption
TabColor string
// TabColorIndexed is a TabColor option, within SheetPrOption
TabColorIndexed int
// TabColorRGB is a TabColor option, within SheetPrOption
TabColorRGB string
// TabColorTheme is a TabColor option, within SheetPrOption
TabColorTheme int
// TabColorTint is a TabColor option, within SheetPrOption
TabColorTint float64
// AutoPageBreaks is a SheetPrOption
AutoPageBreaks bool
// OutlineSummaryBelow is an outlinePr, within SheetPr option
Expand Down Expand Up @@ -129,9 +135,28 @@ func (o *FitToPage) getSheetPrOption(pr *xlsxSheetPr) {
*o = FitToPage(pr.PageSetUpPr.FitToPage)
}

// setSheetPrOption implements the SheetPrOption interface and sets the
// TabColor Indexed.
func (o TabColorIndexed) setSheetPrOption(pr *xlsxSheetPr) {
if pr.TabColor == nil {
pr.TabColor = new(xlsxTabColor)
}
pr.TabColor.Indexed = int(o)
}

// getSheetPrOption implements the SheetPrOptionPtr interface and gets the
// TabColor Indexed. Defaults to -1 if no indexed has been set.
func (o *TabColorIndexed) getSheetPrOption(pr *xlsxSheetPr) {
if pr == nil || pr.TabColor == nil {
*o = TabColorIndexed(ColorMappingTypeUnset)
return
}
*o = TabColorIndexed(pr.TabColor.Indexed)
}

// setSheetPrOption implements the SheetPrOption interface and specifies a
// stable name of the sheet.
func (o TabColor) setSheetPrOption(pr *xlsxSheetPr) {
func (o TabColorRGB) setSheetPrOption(pr *xlsxSheetPr) {
if pr.TabColor == nil {
if string(o) == "" {
return
Expand All @@ -143,12 +168,50 @@ func (o TabColor) setSheetPrOption(pr *xlsxSheetPr) {

// getSheetPrOption implements the SheetPrOptionPtr interface and get the
// stable name of the sheet.
func (o *TabColor) getSheetPrOption(pr *xlsxSheetPr) {
func (o *TabColorRGB) getSheetPrOption(pr *xlsxSheetPr) {
if pr == nil || pr.TabColor == nil {
*o = ""
return
}
*o = TabColor(strings.TrimPrefix(pr.TabColor.RGB, "FF"))
*o = TabColorRGB(strings.TrimPrefix(pr.TabColor.RGB, "FF"))
}

// setSheetPrOption implements the SheetPrOption interface and sets the
// TabColor Theme. Warning: it does not create a clrScheme!
func (o TabColorTheme) setSheetPrOption(pr *xlsxSheetPr) {
if pr.TabColor == nil {
pr.TabColor = new(xlsxTabColor)
}
pr.TabColor.Theme = int(o)
}

// getSheetPrOption implements the SheetPrOptionPtr interface and gets the
// TabColor Theme. Defaults to -1 if no theme has been set.
func (o *TabColorTheme) getSheetPrOption(pr *xlsxSheetPr) {
if pr == nil || pr.TabColor == nil {
*o = TabColorTheme(ColorMappingTypeUnset)
return
}
*o = TabColorTheme(pr.TabColor.Theme)
}

// setSheetPrOption implements the SheetPrOption interface and sets the
// TabColor Tint.
func (o TabColorTint) setSheetPrOption(pr *xlsxSheetPr) {
if pr.TabColor == nil {
pr.TabColor = new(xlsxTabColor)
}
pr.TabColor.Tint = float64(o)
}

// getSheetPrOption implements the SheetPrOptionPtr interface and gets the
// TabColor Tint. Defaults to 0.0 if no tint has been set.
func (o *TabColorTint) getSheetPrOption(pr *xlsxSheetPr) {
if pr == nil || pr.TabColor == nil {
*o = 0.0
return
}
*o = TabColorTint(pr.TabColor.Tint)
}

// setSheetPrOption implements the SheetPrOption interface.
Expand All @@ -175,14 +238,19 @@ func (o *AutoPageBreaks) getSheetPrOption(pr *xlsxSheetPr) {
// SetSheetPrOptions provides a function to sets worksheet properties.
//
// Available options:
// CodeName(string)
// EnableFormatConditionsCalculation(bool)
// Published(bool)
// FitToPage(bool)
// AutoPageBreaks(bool)
// OutlineSummaryBelow(bool)
func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
ws, err := f.workSheetReader(name)
//
// CodeName(string)
// EnableFormatConditionsCalculation(bool)
// Published(bool)
// FitToPage(bool)
// TabColorIndexed(int)
// TabColorRGB(string)
// TabColorTheme(int)
// TabColorTint(float64)
// AutoPageBreaks(bool)
// OutlineSummaryBelow(bool)
func (f *File) SetSheetPrOptions(sheet string, opts ...SheetPrOption) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
Expand All @@ -201,14 +269,19 @@ func (f *File) SetSheetPrOptions(name string, opts ...SheetPrOption) error {
// GetSheetPrOptions provides a function to gets worksheet properties.
//
// Available options:
// CodeName(string)
// EnableFormatConditionsCalculation(bool)
// Published(bool)
// FitToPage(bool)
// AutoPageBreaks(bool)
// OutlineSummaryBelow(bool)
func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error {
ws, err := f.workSheetReader(name)
//
// CodeName(string)
// EnableFormatConditionsCalculation(bool)
// Published(bool)
// FitToPage(bool)
// TabColorIndexed(int)
// TabColorRGB(string)
// TabColorTheme(int)
// TabColorTint(float64)
// AutoPageBreaks(bool)
// OutlineSummaryBelow(bool)
func (f *File) GetSheetPrOptions(sheet string, opts ...SheetPrOptionPtr) error {
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
Expand Down Expand Up @@ -341,12 +414,13 @@ type PageMarginsOptionsPtr interface {
// SetPageMargins provides a function to set worksheet page margins.
//
// Available options:
// PageMarginBottom(float64)
// PageMarginFooter(float64)
// PageMarginHeader(float64)
// PageMarginLeft(float64)
// PageMarginRight(float64)
// PageMarginTop(float64)
//
// PageMarginBottom(float64)
// PageMarginFooter(float64)
// PageMarginHeader(float64)
// PageMarginLeft(float64)
// PageMarginRight(float64)
// PageMarginTop(float64)
func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error {
s, err := f.workSheetReader(sheet)
if err != nil {
Expand All @@ -367,12 +441,13 @@ func (f *File) SetPageMargins(sheet string, opts ...PageMarginsOptions) error {
// GetPageMargins provides a function to get worksheet page margins.
//
// Available options:
// PageMarginBottom(float64)
// PageMarginFooter(float64)
// PageMarginHeader(float64)
// PageMarginLeft(float64)
// PageMarginRight(float64)
// PageMarginTop(float64)
//
// PageMarginBottom(float64)
// PageMarginFooter(float64)
// PageMarginHeader(float64)
// PageMarginLeft(float64)
// PageMarginRight(float64)
// PageMarginTop(float64)
func (f *File) GetPageMargins(sheet string, opts ...PageMarginsOptionsPtr) error {
s, err := f.workSheetReader(sheet)
if err != nil {
Expand Down Expand Up @@ -534,13 +609,14 @@ func (p *ThickBottom) getSheetFormatPr(fp *xlsxSheetFormatPr) {
// SetSheetFormatPr provides a function to set worksheet formatting properties.
//
// Available options:
// BaseColWidth(uint8)
// DefaultColWidth(float64)
// DefaultRowHeight(float64)
// CustomHeight(bool)
// ZeroHeight(bool)
// ThickTop(bool)
// ThickBottom(bool)
//
// BaseColWidth(uint8)
// DefaultColWidth(float64)
// DefaultRowHeight(float64)
// CustomHeight(bool)
// ZeroHeight(bool)
// ThickTop(bool)
// ThickBottom(bool)
func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOptions) error {
s, err := f.workSheetReader(sheet)
if err != nil {
Expand All @@ -560,13 +636,14 @@ func (f *File) SetSheetFormatPr(sheet string, opts ...SheetFormatPrOptions) erro
// GetSheetFormatPr provides a function to get worksheet formatting properties.
//
// Available options:
// BaseColWidth(uint8)
// DefaultColWidth(float64)
// DefaultRowHeight(float64)
// CustomHeight(bool)
// ZeroHeight(bool)
// ThickTop(bool)
// ThickBottom(bool)
//
// BaseColWidth(uint8)
// DefaultColWidth(float64)
// DefaultRowHeight(float64)
// CustomHeight(bool)
// ZeroHeight(bool)
// ThickTop(bool)
// ThickBottom(bool)
func (f *File) GetSheetFormatPr(sheet string, opts ...SheetFormatPrOptionsPtr) error {
s, err := f.workSheetReader(sheet)
if err != nil {
Expand Down
42 changes: 33 additions & 9 deletions sheetpr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ var _ = []SheetPrOption{
EnableFormatConditionsCalculation(false),
Published(false),
FitToPage(true),
TabColor("#FFFF00"),
TabColorIndexed(42),
TabColorRGB("#FFFF00"),
TabColorTheme(ColorMappingTypeLight2),
TabColorTint(0.5),
AutoPageBreaks(true),
OutlineSummaryBelow(true),
}
Expand All @@ -23,7 +26,10 @@ var _ = []SheetPrOptionPtr{
(*EnableFormatConditionsCalculation)(nil),
(*Published)(nil),
(*FitToPage)(nil),
(*TabColor)(nil),
(*TabColorIndexed)(nil),
(*TabColorRGB)(nil),
(*TabColorTheme)(nil),
(*TabColorTint)(nil),
(*AutoPageBreaks)(nil),
(*OutlineSummaryBelow)(nil),
}
Expand All @@ -37,7 +43,10 @@ func ExampleFile_SetSheetPrOptions() {
EnableFormatConditionsCalculation(false),
Published(false),
FitToPage(true),
TabColor("#FFFF00"),
TabColorIndexed(42),
TabColorRGB("#FFFF00"),
TabColorTheme(ColorMappingTypeLight2),
TabColorTint(0.5),
AutoPageBreaks(true),
OutlineSummaryBelow(false),
); err != nil {
Expand All @@ -55,7 +64,10 @@ func ExampleFile_GetSheetPrOptions() {
enableFormatConditionsCalculation EnableFormatConditionsCalculation
published Published
fitToPage FitToPage
tabColor TabColor
tabColorIndexed TabColorIndexed
tabColorRGB TabColorRGB
tabColorTheme TabColorTheme
tabColorTint TabColorTint
autoPageBreaks AutoPageBreaks
outlineSummaryBelow OutlineSummaryBelow
)
Expand All @@ -65,7 +77,10 @@ func ExampleFile_GetSheetPrOptions() {
&enableFormatConditionsCalculation,
&published,
&fitToPage,
&tabColor,
&tabColorIndexed,
&tabColorRGB,
&tabColorTheme,
&tabColorTint,
&autoPageBreaks,
&outlineSummaryBelow,
); err != nil {
Expand All @@ -76,7 +91,10 @@ func ExampleFile_GetSheetPrOptions() {
fmt.Println("- enableFormatConditionsCalculation:", enableFormatConditionsCalculation)
fmt.Println("- published:", published)
fmt.Println("- fitToPage:", fitToPage)
fmt.Printf("- tabColor: %q\n", tabColor)
fmt.Printf("- tabColorIndexed: %d\n", tabColorIndexed)
fmt.Printf("- tabColorRGB: %q\n", tabColorRGB)
fmt.Printf("- tabColorTheme: %d\n", tabColorTheme)
fmt.Printf("- tabColorTint: %f\n", tabColorTint)
fmt.Println("- autoPageBreaks:", autoPageBreaks)
fmt.Println("- outlineSummaryBelow:", outlineSummaryBelow)
// Output:
Expand All @@ -85,7 +103,10 @@ func ExampleFile_GetSheetPrOptions() {
// - enableFormatConditionsCalculation: true
// - published: true
// - fitToPage: false
// - tabColor: ""
// - tabColorIndexed: -1
// - tabColorRGB: ""
// - tabColorTheme: -1
// - tabColorTint: 0.000000
// - autoPageBreaks: false
// - outlineSummaryBelow: true
}
Expand All @@ -101,7 +122,10 @@ func TestSheetPrOptions(t *testing.T) {
{new(EnableFormatConditionsCalculation), EnableFormatConditionsCalculation(false)},
{new(Published), Published(false)},
{new(FitToPage), FitToPage(true)},
{new(TabColor), TabColor("FFFF00")},
{new(TabColorIndexed), TabColorIndexed(42)},
{new(TabColorRGB), TabColorRGB("FFFF00")},
{new(TabColorTheme), TabColorTheme(ColorMappingTypeLight2)},
{new(TabColorTint), TabColorTint(0.5)},
{new(AutoPageBreaks), AutoPageBreaks(true)},
{new(OutlineSummaryBelow), OutlineSummaryBelow(false)},
}
Expand Down Expand Up @@ -154,7 +178,7 @@ func TestSheetPrOptions(t *testing.T) {

func TestSetSheetPrOptions(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetSheetPrOptions("Sheet1", TabColor("")))
assert.NoError(t, f.SetSheetPrOptions("Sheet1", TabColorRGB("")))
// Test SetSheetPrOptions on not exists worksheet.
assert.EqualError(t, f.SetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
}
Expand Down
61 changes: 32 additions & 29 deletions sheetview.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error)
if err != nil {
return nil, err
}
if ws.SheetViews == nil {
ws.SheetViews = &xlsxSheetViews{
SheetView: []xlsxSheetView{{WorkbookViewID: 0}},
}
}
if viewIndex < 0 {
if viewIndex < -len(ws.SheetViews.SheetView) {
return nil, fmt.Errorf("view index %d out of range", viewIndex)
Expand All @@ -180,23 +185,22 @@ func (f *File) getSheetView(sheet string, viewIndex int) (*xlsxSheetView, error)
//
// Available options:
//
// DefaultGridColor(bool)
// ShowFormulas(bool)
// ShowGridLines(bool)
// ShowRowColHeaders(bool)
// ShowZeros(bool)
// RightToLeft(bool)
// ShowRuler(bool)
// View(string)
// TopLeftCell(string)
// ZoomScale(float64)
// DefaultGridColor(bool)
// ShowFormulas(bool)
// ShowGridLines(bool)
// ShowRowColHeaders(bool)
// ShowZeros(bool)
// RightToLeft(bool)
// ShowRuler(bool)
// View(string)
// TopLeftCell(string)
// ZoomScale(float64)
//
// Example:
//
// err = f.SetSheetViewOptions("Sheet1", -1, ShowGridLines(false))
//
func (f *File) SetSheetViewOptions(name string, viewIndex int, opts ...SheetViewOption) error {
view, err := f.getSheetView(name, viewIndex)
// err = f.SetSheetViewOptions("Sheet1", -1, ShowGridLines(false))
func (f *File) SetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOption) error {
view, err := f.getSheetView(sheet, viewIndex)
if err != nil {
return err
}
Expand All @@ -212,24 +216,23 @@ func (f *File) SetSheetViewOptions(name string, viewIndex int, opts ...SheetView
//
// Available options:
//
// DefaultGridColor(bool)
// ShowFormulas(bool)
// ShowGridLines(bool)
// ShowRowColHeaders(bool)
// ShowZeros(bool)
// RightToLeft(bool)
// ShowRuler(bool)
// View(string)
// TopLeftCell(string)
// ZoomScale(float64)
// DefaultGridColor(bool)
// ShowFormulas(bool)
// ShowGridLines(bool)
// ShowRowColHeaders(bool)
// ShowZeros(bool)
// RightToLeft(bool)
// ShowRuler(bool)
// View(string)
// TopLeftCell(string)
// ZoomScale(float64)
//
// Example:
//
// var showGridLines excelize.ShowGridLines
// err = f.GetSheetViewOptions("Sheet1", -1, &showGridLines)
//
func (f *File) GetSheetViewOptions(name string, viewIndex int, opts ...SheetViewOptionPtr) error {
view, err := f.getSheetView(name, viewIndex)
// var showGridLines excelize.ShowGridLines
// err = f.GetSheetViewOptions("Sheet1", -1, &showGridLines)
func (f *File) GetSheetViewOptions(sheet string, viewIndex int, opts ...SheetViewOptionPtr) error {
view, err := f.getSheetView(sheet, viewIndex)
if err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions sheetview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,9 @@ func TestSheetViewOptionsErrors(t *testing.T) {
assert.NoError(t, f.SetSheetViewOptions(sheet, -1))
assert.Error(t, f.SetSheetViewOptions(sheet, 1))
assert.Error(t, f.SetSheetViewOptions(sheet, -2))

ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = nil
assert.NoError(t, f.GetSheetViewOptions(sheet, 0))
}
39 changes: 19 additions & 20 deletions sparkline.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,29 +365,28 @@ func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup {
// Excel 2007, but they won't be displayed. For example, add a grouped
// sparkline. Changes are applied to all three:
//
// err := f.AddSparkline("Sheet1", &excelize.SparklineOption{
// Location: []string{"A1", "A2", "A3"},
// Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"},
// Markers: true,
// })
// err := f.AddSparkline("Sheet1", &excelize.SparklineOption{
// Location: []string{"A1", "A2", "A3"},
// Range: []string{"Sheet2!A1:J1", "Sheet2!A2:J2", "Sheet2!A3:J3"},
// Markers: true,
// })
//
// The following shows the formatting options of sparkline supported by excelize:
//
// Parameter | Description
// -----------+--------------------------------------------
// Location | Required, must have the same number with 'Range' parameter
// Range | Required, must have the same number with 'Location' parameter
// Type | Enumeration value: line, column, win_loss
// Style | Value range: 0 - 35
// Hight | Toggle sparkline high points
// Low | Toggle sparkline low points
// First | Toggle sparkline first points
// Last | Toggle sparkline last points
// Negative | Toggle sparkline negative points
// Markers | Toggle sparkline markers
// ColorAxis | An RGB Color is specified as RRGGBB
// Axis | Show sparkline axis
//
// Parameter | Description
// -----------+--------------------------------------------
// Location | Required, must have the same number with 'Range' parameter
// Range | Required, must have the same number with 'Location' parameter
// Type | Enumeration value: line, column, win_loss
// Style | Value range: 0 - 35
// Hight | Toggle sparkline high points
// Low | Toggle sparkline low points
// First | Toggle sparkline first points
// Last | Toggle sparkline last points
// Negative | Toggle sparkline negative points
// Markers | Toggle sparkline markers
// ColorAxis | An RGB Color is specified as RRGGBB
// Axis | Show sparkline axis
func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) {
var (
ws *xlsxWorksheet
Expand Down
116 changes: 59 additions & 57 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,53 +47,52 @@ type StreamWriter struct {
// example, set data for worksheet of size 102400 rows x 50 columns with
// numbers and style:
//
// file := excelize.NewFile()
// streamWriter, err := file.NewStreamWriter("Sheet1")
// if err != nil {
// fmt.Println(err)
// }
// styleID, err := file.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "#777777"}})
// if err != nil {
// fmt.Println(err)
// }
// if err := streamWriter.SetRow("A1", []interface{}{excelize.Cell{StyleID: styleID, Value: "Data"}},
// excelize.RowOpts{Height: 45, Hidden: false}); err != nil {
// fmt.Println(err)
// }
// for rowID := 2; rowID <= 102400; rowID++ {
// row := make([]interface{}, 50)
// for colID := 0; colID < 50; colID++ {
// row[colID] = rand.Intn(640000)
// }
// cell, _ := excelize.CoordinatesToCellName(1, rowID)
// if err := streamWriter.SetRow(cell, row); err != nil {
// fmt.Println(err)
// }
// }
// if err := streamWriter.Flush(); err != nil {
// fmt.Println(err)
// }
// if err := file.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
// file := excelize.NewFile()
// streamWriter, err := file.NewStreamWriter("Sheet1")
// if err != nil {
// fmt.Println(err)
// }
// styleID, err := file.NewStyle(&excelize.Style{Font: &excelize.Font{Color: "#777777"}})
// if err != nil {
// fmt.Println(err)
// }
// if err := streamWriter.SetRow("A1", []interface{}{excelize.Cell{StyleID: styleID, Value: "Data"}},
// excelize.RowOpts{Height: 45, Hidden: false}); err != nil {
// fmt.Println(err)
// }
// for rowID := 2; rowID <= 102400; rowID++ {
// row := make([]interface{}, 50)
// for colID := 0; colID < 50; colID++ {
// row[colID] = rand.Intn(640000)
// }
// cell, _ := excelize.CoordinatesToCellName(1, rowID)
// if err := streamWriter.SetRow(cell, row); err != nil {
// fmt.Println(err)
// }
// }
// if err := streamWriter.Flush(); err != nil {
// fmt.Println(err)
// }
// if err := file.SaveAs("Book1.xlsx"); err != nil {
// fmt.Println(err)
// }
//
// Set cell value and cell formula for a worksheet with stream writer:
//
// err := streamWriter.SetRow("A1", []interface{}{
// excelize.Cell{Value: 1},
// excelize.Cell{Value: 2},
// excelize.Cell{Formula: "SUM(A1,B1)"}});
// err := streamWriter.SetRow("A1", []interface{}{
// excelize.Cell{Value: 1},
// excelize.Cell{Value: 2},
// excelize.Cell{Formula: "SUM(A1,B1)"}});
//
// Set cell value and rows style for a worksheet with stream writer:
//
// err := streamWriter.SetRow("A1", []interface{}{
// excelize.Cell{Value: 1}},
// excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false});
//
// err := streamWriter.SetRow("A1", []interface{}{
// excelize.Cell{Value: 1}},
// excelize.RowOpts{StyleID: styleID, Height: 20, Hidden: false});
func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
sheetID := f.getSheetID(sheet)
if sheetID == -1 {
return nil, fmt.Errorf("sheet %s is not exist", sheet)
return nil, newNoExistSheetError(sheet)
}
sw := &StreamWriter{
File: f,
Expand All @@ -106,11 +105,11 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
return nil, err
}

sheetPath := f.sheetMap[trimSheetName(sheet)]
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
if f.streams == nil {
f.streams = make(map[string]*StreamWriter)
}
f.streams[sheetPath] = sw
f.streams[sheetXMLPath] = sw

_, _ = sw.rawData.WriteString(xml.Header + `<worksheet` + templateNamespaceIDMap)
bulkAppendFields(&sw.rawData, sw.worksheet, 2, 5)
Expand All @@ -120,18 +119,18 @@ func (f *File) NewStreamWriter(sheet string) (*StreamWriter, error) {
// AddTable creates an Excel table for the StreamWriter using the given
// coordinate area and format set. For example, create a table of A1:D5:
//
// err := sw.AddTable("A1", "D5", "")
// err := sw.AddTable("A1", "D5", "")
//
// Create a table of F2:H6 with format set:
//
// err := sw.AddTable("F2", "H6", `{
// "table_name": "table",
// "table_style": "TableStyleMedium2",
// "show_first_column": true,
// "show_last_column": true,
// "show_row_stripes": false,
// "show_column_stripes": true
// }`)
// err := sw.AddTable("F2", "H6", `{
// "table_name": "table",
// "table_style": "TableStyleMedium2",
// "show_first_column": true,
// "show_last_column": true,
// "show_row_stripes": false,
// "show_column_stripes": true
// }`)
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique.
Expand Down Expand Up @@ -206,7 +205,7 @@ func (sw *StreamWriter) AddTable(hCell, vCell, format string) error {
}

sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")

// Add first table for given sheet.
sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)]
Expand Down Expand Up @@ -327,6 +326,9 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}, opts ...RowOpt
}
fmt.Fprintf(&sw.rawData, `<row r="%d"%s>`, row, attrs)
for i, val := range values {
if val == nil {
continue
}
axis, err := CoordinatesToCellName(col+i, row)
if err != nil {
return err
Expand Down Expand Up @@ -381,16 +383,12 @@ func marshalRowAttrs(opts ...RowOpts) (attrs string, err error) {
// the 'SetColWidth' function before the 'SetRow' function. For example set
// the width column B:C as 20:
//
// err := streamWriter.SetColWidth(2, 3, 20)
//
// err := streamWriter.SetColWidth(2, 3, 20)
func (sw *StreamWriter) SetColWidth(min, max int, width float64) error {
if sw.sheetWritten {
return ErrStreamSetColWidth
}
if min > TotalColumns || max > TotalColumns {
return ErrColumnNumber
}
if min < 1 || max < 1 {
if min < MinColumns || min > MaxColumns || max < MinColumns || max > MaxColumns {
return ErrColumnNumber
}
if width > MaxColumnWidth {
Expand Down Expand Up @@ -440,7 +438,11 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) (err error) {
c.T, c.V = setCellDuration(val)
case time.Time:
var isNum bool
c.T, c.V, isNum, err = setCellTime(val)
date1904, wb := false, sw.File.workbookReader()
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
c.T, c.V, isNum, err = setCellTime(val, date1904)
if isNum && c.S == 0 {
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
c.S = style
Expand Down
19 changes: 16 additions & 3 deletions stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))

// Test set cell column overflow.
assert.EqualError(t, streamWriter.SetRow("XFD1", []interface{}{"A", "B", "C"}), ErrColumnNumber.Error())
assert.ErrorIs(t, streamWriter.SetRow("XFD1", []interface{}{"A", "B", "C"}), ErrColumnNumber)

// Test close temporary file error.
file = NewFile()
Expand Down Expand Up @@ -129,6 +129,8 @@ func TestStreamWriter(t *testing.T) {
}
assert.NoError(t, rows.Close())
assert.Equal(t, 2559558, cells)
// Save spreadsheet with password.
assert.NoError(t, file.SaveAs(filepath.Join("test", "EncryptionTestStreamWriter.xlsx"), Options{Password: "password"}))
assert.NoError(t, file.Close())
}

Expand All @@ -137,8 +139,8 @@ func TestStreamSetColWidth(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetColWidth(3, 2, 20))
assert.EqualError(t, streamWriter.SetColWidth(0, 3, 20), ErrColumnNumber.Error())
assert.EqualError(t, streamWriter.SetColWidth(TotalColumns+1, 3, 20), ErrColumnNumber.Error())
assert.ErrorIs(t, streamWriter.SetColWidth(0, 3, 20), ErrColumnNumber)
assert.ErrorIs(t, streamWriter.SetColWidth(MaxColumns+1, 3, 20), ErrColumnNumber)
assert.EqualError(t, streamWriter.SetColWidth(1, 3, MaxColumnWidth+1), ErrColumnWidth.Error())
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
assert.EqualError(t, streamWriter.SetColWidth(2, 3, 20), ErrStreamSetColWidth.Error())
Expand Down Expand Up @@ -207,6 +209,17 @@ func TestSetRow(t *testing.T) {
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
}

func TestSetRowNilValues(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
streamWriter.SetRow("A1", []interface{}{nil, nil, Cell{Value: "foo"}})
streamWriter.Flush()
ws, err := file.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.NotEqual(t, ws.SheetData.Row[0].C[0].XMLName.Local, "c")
}

func TestSetCellValFunc(t *testing.T) {
f := NewFile()
sw, err := f.NewStreamWriter("Sheet1")
Expand Down
1,949 changes: 971 additions & 978 deletions styles.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions styles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,14 @@ func TestNewStyle(t *testing.T) {
f.Styles.CellXfs.Xf = nil
style4, err := f.NewStyle(&Style{NumFmt: 160, Lang: "unknown"})
assert.NoError(t, err)
assert.Equal(t, 1, style4)
assert.Equal(t, 0, style4)

f = NewFile()
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = nil
style5, err := f.NewStyle(&Style{NumFmt: 160, Lang: "zh-cn"})
assert.NoError(t, err)
assert.Equal(t, 1, style5)
assert.Equal(t, 0, style5)
}

func TestGetDefaultFont(t *testing.T) {
Expand Down
142 changes: 72 additions & 70 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,7 @@ import (
// parseFormatTableSet provides a function to parse the format settings of the
// table with default value.
func parseFormatTableSet(formatSet string) (*formatTable, error) {
format := formatTable{
TableStyle: "",
ShowRowStripes: true,
}
format := formatTable{ShowRowStripes: true}
err := json.Unmarshal(parseFormatSet(formatSet), &format)
return &format, err
}
Expand All @@ -35,18 +32,18 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) {
// name, coordinate area and format set. For example, create a table of A1:D5
// on Sheet1:
//
// err := f.AddTable("Sheet1", "A1", "D5", "")
// err := f.AddTable("Sheet1", "A1", "D5", "")
//
// Create a table of F2:H6 on Sheet2 with format set:
//
// err := f.AddTable("Sheet2", "F2", "H6", `{
// "table_name": "table",
// "table_style": "TableStyleMedium2",
// "show_first_column": true,
// "show_last_column": true,
// "show_row_stripes": false,
// "show_column_stripes": true
// }`)
// err := f.AddTable("Sheet2", "F2", "H6", `{
// "table_name": "table",
// "table_style": "TableStyleMedium2",
// "show_first_column": true,
// "show_last_column": true,
// "show_row_stripes": false,
// "show_column_stripes": true
// }`)
//
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique, and must set the
Expand All @@ -57,10 +54,9 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) {
//
// table_style: The built-in table style names
//
// TableStyleLight1 - TableStyleLight21
// TableStyleMedium1 - TableStyleMedium28
// TableStyleDark1 - TableStyleDark11
//
// TableStyleLight1 - TableStyleLight21
// TableStyleMedium1 - TableStyleMedium28
// TableStyleDark1 - TableStyleDark11
func (f *File) AddTable(sheet, hCell, vCell, format string) error {
formatSet, err := parseFormatTableSet(format)
if err != nil {
Expand All @@ -86,9 +82,10 @@ func (f *File) AddTable(sheet, hCell, vCell, format string) error {

tableID := f.countTables() + 1
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
tableXML := strings.ReplaceAll(sheetRelationshipsTableXML, "..", "xl")
// Add first table for given sheet.
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
if err = f.addSheetTable(sheet, rID); err != nil {
return err
Expand Down Expand Up @@ -132,28 +129,18 @@ func (f *File) addSheetTable(sheet string, rID int) error {
return err
}

// addTable provides a function to add table by given worksheet name,
// coordinate area and format set.
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error {
// Correct the minimum number of rows, the table at least two lines.
if y1 == y2 {
y2++
}

// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
if err != nil {
return err
}

var tableColumn []*xlsxTableColumn

idx := 0
// setTableHeader provides a function to set cells value in header row for the
// table.
func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
var (
tableColumns []*xlsxTableColumn
idx int
)
for i := x1; i <= x2; i++ {
idx++
cell, err := CoordinatesToCellName(i, y1)
if err != nil {
return err
return tableColumns, err
}
name, _ := f.GetCellValue(sheet, cell)
if _, err := strconv.Atoi(name); err == nil {
Expand All @@ -163,11 +150,28 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
name = "Column" + strconv.Itoa(idx)
_ = f.SetCellStr(sheet, cell, name)
}
tableColumn = append(tableColumn, &xlsxTableColumn{
tableColumns = append(tableColumns, &xlsxTableColumn{
ID: idx,
Name: name,
})
}
return tableColumns, nil
}

// addTable provides a function to add table by given worksheet name,
// coordinate area and format set.
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error {
// Correct the minimum number of rows, the table at least two lines.
if y1 == y2 {
y2++
}

// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
if err != nil {
return err
}
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
name := formatSet.TableName
if name == "" {
name = "Table" + strconv.Itoa(i)
Expand All @@ -182,8 +186,8 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
Ref: ref,
},
TableColumns: &xlsxTableColumns{
Count: idx,
TableColumn: tableColumn,
Count: len(tableColumns),
TableColumn: tableColumns,
},
TableStyleInfo: &xlsxTableStyleInfo{
Name: formatSet.TableStyle,
Expand Down Expand Up @@ -211,11 +215,11 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) {
// way of filtering a 2D range of data based on some simple criteria. For
// example applying an autofilter to a cell range A1:D4 in the Sheet1:
//
// err := f.AutoFilter("Sheet1", "A1", "D4", "")
// err := f.AutoFilter("Sheet1", "A1", "D4", "")
//
// Filter data in an autofilter:
//
// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`)
// err := f.AutoFilter("Sheet1", "A1", "D4", `{"column":"B","expression":"x != blanks"}`)
//
// column defines the filter columns in a autofilter range based on simple
// criteria
Expand All @@ -230,38 +234,38 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) {
// expression defines the conditions, the following operators are available
// for setting the filter criteria:
//
// ==
// !=
// >
// <
// >=
// <=
// and
// or
// ==
// !=
// >
// <
// >=
// <=
// and
// or
//
// An expression can comprise a single statement or two statements separated
// by the 'and' and 'or' operators. For example:
//
// x < 2000
// x > 2000
// x == 2000
// x > 2000 and x < 5000
// x == 2000 or x == 5000
// x < 2000
// x > 2000
// x == 2000
// x > 2000 and x < 5000
// x == 2000 or x == 5000
//
// Filtering of blank or non-blank data can be achieved by using a value of
// Blanks or NonBlanks in the expression:
//
// x == Blanks
// x == NonBlanks
// x == Blanks
// x == NonBlanks
//
// Excel also allows some simple string matching operations:
//
// x == b* // begins with b
// x != b* // doesn't begin with b
// x == *b // ends with b
// x != *b // doesn't end with b
// x == *b* // contains b
// x != *b* // doesn't contains b
// x == b* // begins with b
// x != b* // doesn't begin with b
// x == *b // ends with b
// x != *b // doesn't end with b
// x == *b* // contains b
// x != *b* // doesn't contains b
//
// You can also use '*' to match any character or number and '?' to match any
// single character or number. No other regular expression quantifier is
Expand All @@ -272,10 +276,9 @@ func parseAutoFilterSet(formatSet string) (*formatAutoFilter, error) {
// simple string. The actual placeholder name is ignored internally so the
// following are all equivalent:
//
// x < 2000
// col < 2000
// Price < 2000
//
// x < 2000
// col < 2000
// Price < 2000
func (f *File) AutoFilter(sheet, hCell, vCell, format string) error {
hCol, hRow, err := CellNameToCoordinates(hCell)
if err != nil {
Expand Down Expand Up @@ -431,9 +434,8 @@ func (f *File) writeCustomFilter(filter *xlsxAutoFilter, operator int, val strin
//
// Examples:
//
// ('x', '==', 2000) -> exp1
// ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2
//
// ('x', '==', 2000) -> exp1
// ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2
func (f *File) parseFilterExpression(expression string, tokens []string) ([]int, []string, error) {
var expressions []int
var t []string
Expand Down
8 changes: 7 additions & 1 deletion table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func TestAddTable(t *testing.T) {
assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
}

func TestSetTableHeader(t *testing.T) {
f := NewFile()
_, err := f.setTableHeader("Sheet1", 1, 0, 1)
assert.EqualError(t, err, "invalid cell coordinates [1, 0]")
}

func TestAutoFilter(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")

Expand Down Expand Up @@ -72,7 +78,7 @@ func TestAutoFilter(t *testing.T) {
})
}

// testing AutoFilter with illegal cell coordinates.
// Test AutoFilter with illegal cell coordinates.
assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
}
Expand Down
Binary file modified test/Book1.xlsx
Binary file not shown.
Binary file added test/images/excel.emf
Binary file not shown.
Binary file added test/images/excel.emz
Binary file not shown.
Binary file added test/images/excel.wmf
Binary file not shown.
Binary file added test/images/excel.wmz
Binary file not shown.
35 changes: 29 additions & 6 deletions workbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ type WorkbookPrOptionPtr interface {
}

type (
// Date1904 is an option used for WorkbookPrOption, that indicates whether
// to use a 1900 or 1904 date system when converting serial date-times in
// the workbook to dates
Date1904 bool
// FilterPrivacy is an option used for WorkbookPrOption
FilterPrivacy bool
)
Expand Down Expand Up @@ -116,8 +120,10 @@ func (f *File) workBookWriter() {
// SetWorkbookPrOptions provides a function to sets workbook properties.
//
// Available options:
// FilterPrivacy(bool)
// CodeName(string)
//
// Date1904(bool)
// FilterPrivacy(bool)
// CodeName(string)
func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error {
wb := f.workbookReader()
pr := wb.WorkbookPr
Expand All @@ -131,6 +137,11 @@ func (f *File) SetWorkbookPrOptions(opts ...WorkbookPrOption) error {
return nil
}

// setWorkbookPrOption implements the WorkbookPrOption interface.
func (o Date1904) setWorkbookPrOption(pr *xlsxWorkbookPr) {
pr.Date1904 = bool(o)
}

// setWorkbookPrOption implements the WorkbookPrOption interface.
func (o FilterPrivacy) setWorkbookPrOption(pr *xlsxWorkbookPr) {
pr.FilterPrivacy = bool(o)
Expand All @@ -144,8 +155,10 @@ func (o CodeName) setWorkbookPrOption(pr *xlsxWorkbookPr) {
// GetWorkbookPrOptions provides a function to gets workbook properties.
//
// Available options:
// FilterPrivacy(bool)
// CodeName(string)
//
// Date1904(bool)
// FilterPrivacy(bool)
// CodeName(string)
func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error {
wb := f.workbookReader()
pr := wb.WorkbookPr
Expand All @@ -156,7 +169,17 @@ func (f *File) GetWorkbookPrOptions(opts ...WorkbookPrOptionPtr) error {
}

// getWorkbookPrOption implements the WorkbookPrOption interface and get the
// filter privacy of thw workbook.
// date1904 of the workbook.
func (o *Date1904) getWorkbookPrOption(pr *xlsxWorkbookPr) {
if pr == nil {
*o = false
return
}
*o = Date1904(pr.Date1904)
}

// getWorkbookPrOption implements the WorkbookPrOption interface and get the
// filter privacy of the workbook.
func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) {
if pr == nil {
*o = false
Expand All @@ -166,7 +189,7 @@ func (o *FilterPrivacy) getWorkbookPrOption(pr *xlsxWorkbookPr) {
}

// getWorkbookPrOption implements the WorkbookPrOption interface and get the
// code name of thw workbook.
// code name of the workbook.
func (o *CodeName) getWorkbookPrOption(pr *xlsxWorkbookPr) {
if pr == nil {
*o = ""
Expand Down
12 changes: 12 additions & 0 deletions workbook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func ExampleFile_SetWorkbookPrOptions() {
f := NewFile()
if err := f.SetWorkbookPrOptions(
Date1904(false),
FilterPrivacy(false),
CodeName("code"),
); err != nil {
Expand All @@ -21,27 +22,38 @@ func ExampleFile_SetWorkbookPrOptions() {
func ExampleFile_GetWorkbookPrOptions() {
f := NewFile()
var (
date1904 Date1904
filterPrivacy FilterPrivacy
codeName CodeName
)
if err := f.GetWorkbookPrOptions(&date1904); err != nil {
fmt.Println(err)
}
if err := f.GetWorkbookPrOptions(&filterPrivacy); err != nil {
fmt.Println(err)
}
if err := f.GetWorkbookPrOptions(&codeName); err != nil {
fmt.Println(err)
}
fmt.Println("Defaults:")
fmt.Printf("- date1904: %t\n", date1904)
fmt.Printf("- filterPrivacy: %t\n", filterPrivacy)
fmt.Printf("- codeName: %q\n", codeName)
// Output:
// Defaults:
// - date1904: false
// - filterPrivacy: true
// - codeName: ""
}

func TestWorkbookPr(t *testing.T) {
f := NewFile()
wb := f.workbookReader()
wb.WorkbookPr = nil
var date1904 Date1904
assert.NoError(t, f.GetWorkbookPrOptions(&date1904))
assert.Equal(t, false, bool(date1904))

wb.WorkbookPr = nil
var codeName CodeName
assert.NoError(t, f.GetWorkbookPrOptions(&codeName))
Expand Down
107 changes: 53 additions & 54 deletions xmlCalcChain.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,60 +21,59 @@ type xlsxCalcChain struct {

// xlsxCalcChainC directly maps the c element.
//
// Attributes | Attributes
// --------------------------+----------------------------------------------------------
// a (Array) | A Boolean flag indicating whether the cell's formula
// | is an array formula. True if this cell's formula is
// | an array formula, false otherwise. If there is a
// | conflict between this attribute and the t attribute
// | of the f element (§18.3.1.40), the t attribute takes
// | precedence. The possible values for this attribute
// | are defined by the W3C XML Schema boolean datatype.
// |
// i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is
// | omitted, it is assumed to be the same as the i value
// | of the previous cell.The possible values for this
// | attribute are defined by the W3C XML Schema int datatype.
// |
// l (New Dependency Level) | A Boolean flag indicating that the cell's formula
// | starts a new dependency level. True if the formula
// | starts a new dependency level, false otherwise.
// | Starting a new dependency level means that all
// | concurrent calculations, and child calculations, shall
// | be completed - and the cells have new values - before
// | the calc chain can continue. In other words, this
// | dependency level might depend on levels that came before
// | it, and any later dependency levels might depend on
// | this level; but not later dependency levels can have
// | any calculations started until this dependency level
// | completes.The possible values for this attribute are
// | defined by the W3C XML Schema boolean datatype.
// |
// r (Cell Reference) | An A-1 style reference to a cell.The possible values
// | for this attribute are defined by the ST_CellRef
// | simple type (§18.18.7).
// |
// s (Child Chain) | A Boolean flag indicating whether the cell's formula
// | is on a child chain. True if this cell is part of a
// | child chain, false otherwise. If this is omitted, it
// | is assumed to be the same as the s value of the
// | previous cell .A child chain is a list of calculations
// | that occur which depend on the parent to the chain.
// | There shall not be cross dependencies between child
// | chains. Child chains are not the same as dependency
// | levels - a child chain and its parent are all on the
// | same dependency level. Child chains are series of
// | calculations that can be independently farmed out to
// | other threads or processors.The possible values for
// | this attribute is defined by the W3C XML Schema
// | boolean datatype.
// |
// t (New Thread) | A Boolean flag indicating whether the cell's formula
// | starts a new thread. True if the cell's formula starts
// | a new thread, false otherwise.The possible values for
// | this attribute is defined by the W3C XML Schema
// | boolean datatype.
//
// Attributes | Attributes
// --------------------------+----------------------------------------------------------
// a (Array) | A Boolean flag indicating whether the cell's formula
// | is an array formula. True if this cell's formula is
// | an array formula, false otherwise. If there is a
// | conflict between this attribute and the t attribute
// | of the f element (§18.3.1.40), the t attribute takes
// | precedence. The possible values for this attribute
// | are defined by the W3C XML Schema boolean datatype.
// |
// i (Sheet Id) | A sheet Id of a sheet the cell belongs to. If this is
// | omitted, it is assumed to be the same as the i value
// | of the previous cell.The possible values for this
// | attribute are defined by the W3C XML Schema int datatype.
// |
// l (New Dependency Level) | A Boolean flag indicating that the cell's formula
// | starts a new dependency level. True if the formula
// | starts a new dependency level, false otherwise.
// | Starting a new dependency level means that all
// | concurrent calculations, and child calculations, shall
// | be completed - and the cells have new values - before
// | the calc chain can continue. In other words, this
// | dependency level might depend on levels that came before
// | it, and any later dependency levels might depend on
// | this level; but not later dependency levels can have
// | any calculations started until this dependency level
// | completes.The possible values for this attribute are
// | defined by the W3C XML Schema boolean datatype.
// |
// r (Cell Reference) | An A-1 style reference to a cell.The possible values
// | for this attribute are defined by the ST_CellRef
// | simple type (§18.18.7).
// |
// s (Child Chain) | A Boolean flag indicating whether the cell's formula
// | is on a child chain. True if this cell is part of a
// | child chain, false otherwise. If this is omitted, it
// | is assumed to be the same as the s value of the
// | previous cell .A child chain is a list of calculations
// | that occur which depend on the parent to the chain.
// | There shall not be cross dependencies between child
// | chains. Child chains are not the same as dependency
// | levels - a child chain and its parent are all on the
// | same dependency level. Child chains are series of
// | calculations that can be independently farmed out to
// | other threads or processors.The possible values for
// | this attribute is defined by the W3C XML Schema
// | boolean datatype.
// |
// t (New Thread) | A Boolean flag indicating whether the cell's formula
// | starts a new thread. True if the cell's formula starts
// | a new thread, false otherwise.The possible values for
// | this attribute is defined by the W3C XML Schema
// | boolean datatype.
type xlsxCalcChainC struct {
R string `xml:"r,attr"`
I int `xml:"i,attr"`
Expand Down
2 changes: 1 addition & 1 deletion xmlContentTypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import (
type xlsxTypes struct {
sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"`
Overrides []xlsxOverride `xml:"Override"`
Defaults []xlsxDefault `xml:"Default"`
Overrides []xlsxOverride `xml:"Override"`
}

// xlsxOverride directly maps the override element in the namespace
Expand Down
92 changes: 46 additions & 46 deletions xmlCore.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,61 +31,61 @@ type DocProperties struct {
Version string
}

// decodeDcTerms directly maps the DCMI metadata terms for the coreProperties.
type decodeDcTerms struct {
Text string `xml:",chardata"`
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
}

// decodeCoreProperties directly maps the root element for a part of this
// content type shall coreProperties. In order to solve the problem that the
// label structure is changed after serialization and deserialization, two
// different structures are defined. decodeCoreProperties just for
// deserialization.
type decodeCoreProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"`
Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"`
Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"`
Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created struct {
Text string `xml:",chardata"`
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
} `xml:"http://purl.org/dc/terms/ created"`
Modified struct {
Text string `xml:",chardata"`
Type string `xml:"http://www.w3.org/2001/XMLSchema-instance type,attr"`
} `xml:"http://purl.org/dc/terms/ modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Title string `xml:"http://purl.org/dc/elements/1.1/ title,omitempty"`
Subject string `xml:"http://purl.org/dc/elements/1.1/ subject,omitempty"`
Creator string `xml:"http://purl.org/dc/elements/1.1/ creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"http://purl.org/dc/elements/1.1/ description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"http://purl.org/dc/elements/1.1/ language,omitempty"`
Identifier string `xml:"http://purl.org/dc/elements/1.1/ identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created *decodeDcTerms `xml:"http://purl.org/dc/terms/ created"`
Modified *decodeDcTerms `xml:"http://purl.org/dc/terms/ modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
}

// xlsxDcTerms directly maps the DCMI metadata terms for the coreProperties.
type xlsxDcTerms struct {
Text string `xml:",chardata"`
Type string `xml:"xsi:type,attr"`
}

// xlsxCoreProperties directly maps the root element for a part of this
// content type shall coreProperties.
type xlsxCoreProperties struct {
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Dc string `xml:"xmlns:dc,attr"`
Dcterms string `xml:"xmlns:dcterms,attr"`
Dcmitype string `xml:"xmlns:dcmitype,attr"`
XSI string `xml:"xmlns:xsi,attr"`
Title string `xml:"dc:title,omitempty"`
Subject string `xml:"dc:subject,omitempty"`
Creator string `xml:"dc:creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"dc:description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"dc:language,omitempty"`
Identifier string `xml:"dc:identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created struct {
Text string `xml:",chardata"`
Type string `xml:"xsi:type,attr"`
} `xml:"dcterms:created"`
Modified struct {
Text string `xml:",chardata"`
Type string `xml:"xsi:type,attr"`
} `xml:"dcterms:modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/metadata/core-properties coreProperties"`
Dc string `xml:"xmlns:dc,attr"`
Dcterms string `xml:"xmlns:dcterms,attr"`
Dcmitype string `xml:"xmlns:dcmitype,attr"`
XSI string `xml:"xmlns:xsi,attr"`
Title string `xml:"dc:title,omitempty"`
Subject string `xml:"dc:subject,omitempty"`
Creator string `xml:"dc:creator"`
Keywords string `xml:"keywords,omitempty"`
Description string `xml:"dc:description,omitempty"`
LastModifiedBy string `xml:"lastModifiedBy"`
Language string `xml:"dc:language,omitempty"`
Identifier string `xml:"dc:identifier,omitempty"`
Revision string `xml:"revision,omitempty"`
Created *xlsxDcTerms `xml:"dcterms:created"`
Modified *xlsxDcTerms `xml:"dcterms:modified"`
ContentStatus string `xml:"contentStatus,omitempty"`
Category string `xml:"category,omitempty"`
Version string `xml:"version,omitempty"`
}
31 changes: 28 additions & 3 deletions xmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,15 @@ const (
StreamChunkSize = 1 << 24
MaxFontFamilyLength = 31
MaxFontSize = 409
MaxFileNameLength = 207
MaxFilePathLength = 207
MaxFieldLength = 255
MaxColumnWidth = 255
MaxRowHeight = 409
MaxCellStyles = 64000
MinFontSize = 1
TotalRows = 1048576
TotalColumns = 16384
MinColumns = 1
MaxColumns = 16384
TotalSheetHyperlinks = 65529
TotalCellChars = 32767
// pivotTableVersion should be greater than 3. One or more of the
Expand All @@ -118,7 +121,28 @@ const (
pivotTableVersion = 3
)

var supportImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff"}
// ColorMappingType is the type of color transformation.
type ColorMappingType byte

// Color transformation types enumeration.
const (
ColorMappingTypeLight1 ColorMappingType = iota
ColorMappingTypeDark1
ColorMappingTypeLight2
ColorMappingTypeDark2
ColorMappingTypeAccent1
ColorMappingTypeAccent2
ColorMappingTypeAccent3
ColorMappingTypeAccent4
ColorMappingTypeAccent5
ColorMappingTypeAccent6
ColorMappingTypeHyperlink
ColorMappingTypeFollowedHyperlink
ColorMappingTypeUnset int = -1
)

// supportedImageTypes defined supported image types.
var supportedImageTypes = map[string]string{".gif": ".gif", ".jpg": ".jpeg", ".jpeg": ".jpeg", ".png": ".png", ".tif": ".tiff", ".tiff": ".tiff", ".emf": ".emf", ".wmf": ".wmf", ".emz": ".emz", ".wmz": ".wmz"}

// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
Expand Down Expand Up @@ -477,6 +501,7 @@ type formatPicture struct {

// formatShape directly maps the format settings of the shape.
type formatShape struct {
Macro string `json:"macro"`
Type string `json:"type"`
Width int `json:"width"`
Height int `json:"height"`
Expand Down
1 change: 1 addition & 0 deletions xmlStyles.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ type Font struct {
Size float64 `json:"size"`
Strike bool `json:"strike"`
Color string `json:"color"`
VertAlign string `json:"vertAlign"`
}

// Fill directly maps the fill settings of the cells.
Expand Down
75 changes: 37 additions & 38 deletions xmlWorksheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,17 @@ type xlsxDrawing struct {
// footers on the first page can differ from those on odd- and even-numbered
// pages. In the latter case, the first page is not considered an odd page.
type xlsxHeaderFooter struct {
XMLName xml.Name `xml:"headerFooter"`
AlignWithMargins bool `xml:"alignWithMargins,attr,omitempty"`
DifferentFirst bool `xml:"differentFirst,attr,omitempty"`
DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"`
ScaleWithDoc bool `xml:"scaleWithDoc,attr,omitempty"`
OddHeader string `xml:"oddHeader,omitempty"`
OddFooter string `xml:"oddFooter,omitempty"`
EvenHeader string `xml:"evenHeader,omitempty"`
EvenFooter string `xml:"evenFooter,omitempty"`
FirstFooter string `xml:"firstFooter,omitempty"`
FirstHeader string `xml:"firstHeader,omitempty"`
DrawingHF *xlsxDrawingHF `xml:"drawingHF"`
XMLName xml.Name `xml:"headerFooter"`
DifferentOddEven bool `xml:"differentOddEven,attr,omitempty"`
DifferentFirst bool `xml:"differentFirst,attr,omitempty"`
ScaleWithDoc bool `xml:"scaleWithDoc,attr,omitempty"`
AlignWithMargins bool `xml:"alignWithMargins,attr,omitempty"`
OddHeader string `xml:"oddHeader,omitempty"`
OddFooter string `xml:"oddFooter,omitempty"`
EvenHeader string `xml:"evenHeader,omitempty"`
EvenFooter string `xml:"evenFooter,omitempty"`
FirstHeader string `xml:"firstHeader,omitempty"`
FirstFooter string `xml:"firstFooter,omitempty"`
}

// xlsxDrawingHF (Drawing Reference in Header Footer) specifies the usage of
Expand Down Expand Up @@ -147,12 +146,12 @@ type xlsxPrintOptions struct {
// a sheet or a custom sheet view.
type xlsxPageMargins struct {
XMLName xml.Name `xml:"pageMargins"`
Bottom float64 `xml:"bottom,attr"`
Footer float64 `xml:"footer,attr"`
Header float64 `xml:"header,attr"`
Left float64 `xml:"left,attr"`
Right float64 `xml:"right,attr"`
Top float64 `xml:"top,attr"`
Bottom float64 `xml:"bottom,attr"`
Header float64 `xml:"header,attr"`
Footer float64 `xml:"footer,attr"`
}

// xlsxSheetFormatPr directly maps the sheetFormatPr element in the namespace
Expand Down Expand Up @@ -450,25 +449,27 @@ type DataValidation struct {
//
// This simple type is restricted to the values listed in the following table:
//
// Enumeration Value | Description
// ---------------------------+---------------------------------
// b (Boolean) | Cell containing a boolean.
// d (Date) | Cell contains a date in the ISO 8601 format.
// e (Error) | Cell containing an error.
// inlineStr (Inline String) | Cell containing an (inline) rich string, i.e., one not in the shared string table. If this cell type is used, then the cell value is in the is element rather than the v element in the cell (c element).
// n (Number) | Cell containing a number.
// s (Shared String) | Cell containing a shared string.
// str (String) | Cell containing a formula string.
//
// Enumeration Value | Description
// ---------------------------+---------------------------------
// b (Boolean) | Cell containing a boolean.
// d (Date) | Cell contains a date in the ISO 8601 format.
// e (Error) | Cell containing an error.
// inlineStr (Inline String) | Cell containing an (inline) rich string, i.e., one not in the shared string table. If this cell type is used, then the cell value is in the is element rather than the v element in the cell (c element).
// n (Number) | Cell containing a number.
// s (Shared String) | Cell containing a shared string.
// str (String) | Cell containing a formula string.
type xlsxC struct {
XMLName xml.Name `xml:"c"`
XMLSpace xml.Attr `xml:"space,attr,omitempty"`
R string `xml:"r,attr,omitempty"` // Cell ID, e.g. A1
S int `xml:"s,attr,omitempty"` // Style reference.
// Str string `xml:"str,attr,omitempty"` // Style reference.
T string `xml:"t,attr,omitempty"` // Type.
F *xlsxF `xml:"f,omitempty"` // Formula
V string `xml:"v,omitempty"` // Value
T string `xml:"t,attr,omitempty"` // Type.
Cm *uint `xml:"cm,attr,omitempty"` //
Vm *uint `xml:"vm,attr,omitempty"` //
Ph *bool `xml:"ph,attr,omitempty"` //
F *xlsxF `xml:"f,omitempty"` // Formula
V string `xml:"v,omitempty"` // Value
IS *xlsxSI `xml:"is"`
}

Expand Down Expand Up @@ -642,13 +643,12 @@ type xlsxHyperlink struct {
// size of the sample. To reference the table, just add the tableParts element,
// of course after having created and stored the table part. For example:
//
// <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
// ...
// <tableParts count="1">
// <tablePart r:id="rId1" />
// </tableParts>
// </worksheet>
//
// <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
// ...
// <tableParts count="1">
// <tablePart r:id="rId1" />
// </tableParts>
// </worksheet>
type xlsxTableParts struct {
XMLName xml.Name `xml:"tableParts"`
Count int `xml:"count,attr,omitempty"`
Expand All @@ -665,8 +665,7 @@ type xlsxTablePart struct {
// http://schemas.openxmlformats.org/spreadsheetml/2006/main - Background sheet
// image. For example:
//
// <picture r:id="rId1"/>
//
// <picture r:id="rId1"/>
type xlsxPicture struct {
XMLName xml.Name `xml:"picture"`
RID string `xml:"http://schemas.openxmlformats.org/officeDocument/2006/relationships id,attr,omitempty"`
Expand Down Expand Up @@ -880,8 +879,8 @@ type FormatHeaderFooter struct {
OddFooter string
EvenHeader string
EvenFooter string
FirstFooter string
FirstHeader string
FirstFooter string
}

// FormatPageMargins directly maps the settings of page margins
Expand Down