175 changes: 138 additions & 37 deletions rows.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand All @@ -10,22 +10,33 @@
package excelize

import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"log"
"math"
"strconv"
)

// GetRows return all the rows in a sheet by given worksheet name (case
// sensitive). For example:
//
// rows, err := f.GetRows("Sheet1")
// for _, row := range rows {
// rows, err := f.Rows("Sheet1")
// if err != nil {
// println(err.Error())
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// if err != nil {
// println(err.Error())
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// print(colCell, "\t")
// }
// fmt.Println()
// println()
// }
//
func (f *File) GetRows(sheet string) ([][]string, error) {
Expand All @@ -49,15 +60,18 @@ func (f *File) GetRows(sheet string) ([][]string, error) {

// Rows defines an iterator to a sheet
type Rows struct {
err error
f *File
rows []xlsxRow
curRow int
err error
curRow, totalRow, stashRow int
sheet string
rows []xlsxRow
f *File
decoder *xml.Decoder
}

// Next will return true if find the next row element.
func (rows *Rows) Next() bool {
return rows.curRow < len(rows.rows)
rows.curRow++
return rows.curRow <= rows.totalRow
}

// Error will return the error when the find next row element
Expand All @@ -67,20 +81,62 @@ func (rows *Rows) Error() error {

// Columns return the current row's column values
func (rows *Rows) Columns() ([]string, error) {
curRow := rows.rows[rows.curRow]
rows.curRow++
var (
err error
inElement string
row, cellCol int
columns []string
)

if rows.stashRow >= rows.curRow {
return columns, err
}

columns := make([]string, len(curRow.C))
d := rows.f.sharedStringsReader()
for _, colCell := range curRow.C {
col, _, err := CellNameToCoordinates(colCell.R)
if err != nil {
return columns, err
for {
token, _ := rows.decoder.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "row" {
for _, attr := range startElement.Attr {
if attr.Name.Local == "r" {
row, err = strconv.Atoi(attr.Value)
if err != nil {
return columns, err
}
if row > rows.curRow {
rows.stashRow = row - 1
return columns, err
}
}
}
}
if inElement == "c" {
colCell := xlsxC{}
_ = rows.decoder.DecodeElement(&colCell, &startElement)
cellCol, _, err = CellNameToCoordinates(colCell.R)
if err != nil {
return columns, err
}
blank := cellCol - len(columns)
for i := 1; i < blank; i++ {
columns = append(columns, "")
}
val, _ := colCell.getValueFrom(rows.f, d)
columns = append(columns, val)
}
case xml.EndElement:
inElement = startElement.Name.Local
if inElement == "row" {
return columns, err
}
}
val, _ := colCell.getValueFrom(rows.f, d)
columns[col-1] = val
}
return columns, nil
return columns, err
}

// ErrSheetNotExist defines an error of sheet is not exist
Expand All @@ -89,37 +145,70 @@ type ErrSheetNotExist struct {
}

func (err ErrSheetNotExist) Error() string {
return fmt.Sprintf("Sheet %s is not exist", string(err.SheetName))
return fmt.Sprintf("sheet %s is not exist", string(err.SheetName))
}

// Rows return a rows iterator. For example:
//
// rows, err := f.Rows("Sheet1")
// if err != nil {
// println(err.Error())
// return
// }
// for rows.Next() {
// row, err := rows.Columns()
// if err != nil {
// println(err.Error())
// }
// for _, colCell := range row {
// fmt.Print(colCell, "\t")
// print(colCell, "\t")
// }
// fmt.Println()
// println()
// }
//
func (f *File) Rows(sheet string) (*Rows, error) {
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return nil, err
}
name, ok := f.sheetMap[trimSheetName(sheet)]
if !ok {
return nil, ErrSheetNotExist{sheet}
}
if xlsx != nil {
data := f.readXML(name)
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(namespaceStrictToTransitional(data)))
if f.Sheet[name] != nil {
// flush data
output, _ := xml.Marshal(f.Sheet[name])
f.saveFileList(name, replaceWorkSheetsRelationshipsNameSpaceBytes(output))
}
var (
err error
inElement string
row int
rows Rows
)
decoder := f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
for {
token, _ := decoder.Token()
if token == nil {
break
}
switch startElement := token.(type) {
case xml.StartElement:
inElement = startElement.Name.Local
if inElement == "row" {
for _, attr := range startElement.Attr {
if attr.Name.Local == "r" {
row, err = strconv.Atoi(attr.Value)
if err != nil {
return &rows, err
}
}
}
rows.totalRow = row
}
default:
}
}
return &Rows{
f: f,
rows: xlsx.SheetData.Row,
}, nil
rows.f = f
rows.sheet = name
rows.decoder = f.xmlNewDecoder(bytes.NewReader(f.readXML(name)))
return &rows, nil
}

// SetRowHeight provides a function to set the height of a single row. For
Expand Down Expand Up @@ -187,15 +276,21 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
// sharedStringsReader provides a function to get the pointer to the structure
// after deserialization of xl/sharedStrings.xml.
func (f *File) sharedStringsReader() *xlsxSST {
var err error

if f.SharedStrings == nil {
var sharedStrings xlsxSST
ss := f.readXML("xl/sharedStrings.xml")
if len(ss) == 0 {
ss = f.readXML("xl/SharedStrings.xml")
}
_ = xml.Unmarshal(namespaceStrictToTransitional(ss), &sharedStrings)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
Decode(&sharedStrings); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
f.SharedStrings = &sharedStrings
}

return f.SharedStrings
}

Expand All @@ -207,11 +302,17 @@ func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
case "s":
xlsxSI := 0
xlsxSI, _ = strconv.Atoi(xlsx.V)
return f.formattedValue(xlsx.S, d.SI[xlsxSI].String()), nil
if len(d.SI) > xlsxSI {
return f.formattedValue(xlsx.S, d.SI[xlsxSI].String()), nil
}
return f.formattedValue(xlsx.S, xlsx.V), nil
case "str":
return f.formattedValue(xlsx.S, xlsx.V), nil
case "inlineStr":
return f.formattedValue(xlsx.S, xlsx.IS.String()), nil
if xlsx.IS != nil {
return f.formattedValue(xlsx.S, xlsx.IS.String()), nil
}
return f.formattedValue(xlsx.S, xlsx.V), nil
default:
return f.formattedValue(xlsx.S, xlsx.V), nil
}
Expand Down
198 changes: 148 additions & 50 deletions rows_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
package excelize

import (
"bytes"
"fmt"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRows(t *testing.T) {
const sheet2 = "Sheet2"

xlsx, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) {
t.FailNow()
}

rows, err := xlsx.Rows(sheet2)
rows, err := f.Rows(sheet2)
if !assert.NoError(t, err) {
t.FailNow()
}

collectedRows := make([][]string, 0)
var collectedRows [][]string
for rows.Next() {
columns, err := rows.Columns()
assert.NoError(t, err)
Expand All @@ -31,14 +33,52 @@ func TestRows(t *testing.T) {
t.FailNow()
}

returnedRows, err := xlsx.GetRows(sheet2)
returnedRows, err := f.GetRows(sheet2)
assert.NoError(t, err)
for i := range returnedRows {
returnedRows[i] = trimSliceSpace(returnedRows[i])
}
if !assert.Equal(t, collectedRows, returnedRows) {
t.FailNow()
}

f = NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)
_, err = f.Rows("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
}

func TestRowsIterator(t *testing.T) {
const (
sheet2 = "Sheet2"
expectedNumRow = 11
)
f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
require.NoError(t, err)

rows, err := f.Rows(sheet2)
require.NoError(t, err)
var rowCount int
for rows.Next() {
rowCount++
require.True(t, rowCount <= expectedNumRow, "rowCount is greater than expected")
}
assert.Equal(t, expectedNumRow, rowCount)

// Valued cell sparse distribution test
f = NewFile()
cells := []string{"C1", "E1", "A3", "B3", "C3", "D3", "E3"}
for _, cell := range cells {
assert.NoError(t, f.SetCellValue("Sheet1", cell, 1))
}
rows, err = f.Rows("Sheet1")
require.NoError(t, err)
rowCount = 0
for rows.Next() {
rowCount++
require.True(t, rowCount <= 3, "rowCount is greater than expected")
}
assert.Equal(t, 3, rowCount)
}

func TestRowsError(t *testing.T) {
Expand Down Expand Up @@ -92,81 +132,130 @@ func TestRowHeight(t *testing.T) {
convertColWidthToPixels(0)
}

func TestColumns(t *testing.T) {
f := NewFile()
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)

rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="2"><c r="A1" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
_, err = rows.Columns()
assert.NoError(t, err)
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="2"><c r="A1" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
rows.curRow = 1
_, err = rows.Columns()
assert.NoError(t, err)

rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="A"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)))
rows.stashRow, rows.curRow = 0, 1
_, err = rows.Columns()
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)

rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)))
_, err = rows.Columns()
assert.NoError(t, err)

rows.curRow = 3
rows.decoder = f.xmlNewDecoder(bytes.NewReader([]byte(`<worksheet><sheetData><row r="1"><c r="A" t="s"><v>1</v></c></row></sheetData></worksheet>`)))
_, err = rows.Columns()
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)

// Test token is nil
rows.decoder = f.xmlNewDecoder(bytes.NewReader(nil))
_, err = rows.Columns()
assert.NoError(t, err)
}

func TestSharedStringsReader(t *testing.T) {
f := NewFile()
f.XLSX["xl/sharedStrings.xml"] = MacintoshCyrillicCharset
f.sharedStringsReader()
}

func TestRowVisibility(t *testing.T) {
xlsx, err := prepareTestBook1()
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}
xlsx.NewSheet("Sheet3")
assert.NoError(t, xlsx.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, xlsx.SetRowVisible("Sheet3", 2, true))
xlsx.GetRowVisible("Sheet3", 2)
xlsx.GetRowVisible("Sheet3", 25)
assert.EqualError(t, xlsx.SetRowVisible("Sheet3", 0, true), "invalid row number 0")
f.NewSheet("Sheet3")
assert.NoError(t, f.SetRowVisible("Sheet3", 2, false))
assert.NoError(t, f.SetRowVisible("Sheet3", 2, true))
visiable, err := f.GetRowVisible("Sheet3", 2)
assert.Equal(t, true, visiable)
assert.NoError(t, err)
visiable, err = f.GetRowVisible("Sheet3", 25)
assert.Equal(t, false, visiable)
assert.NoError(t, err)
assert.EqualError(t, f.SetRowVisible("Sheet3", 0, true), "invalid row number 0")
assert.EqualError(t, f.SetRowVisible("SheetN", 2, false), "sheet SheetN is not exist")

visible, err := xlsx.GetRowVisible("Sheet3", 0)
visible, err := f.GetRowVisible("Sheet3", 0)
assert.Equal(t, false, visible)
assert.EqualError(t, err, "invalid row number 0")
_, err = f.GetRowVisible("SheetN", 1)
assert.EqualError(t, err, "sheet SheetN is not exist")

assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRowVisibility.xlsx")))
}

func TestRemoveRow(t *testing.T) {
xlsx := NewFile()
sheet1 := xlsx.GetSheetName(1)
r, err := xlsx.workSheetReader(sheet1)
f := NewFile()
sheet1 := f.GetSheetName(1)
r, err := f.workSheetReader(sheet1)
assert.NoError(t, err)
const (
colCount = 10
rowCount = 10
)
fillCells(xlsx, sheet1, colCount, rowCount)
fillCells(f, sheet1, colCount, rowCount)

xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))

assert.EqualError(t, xlsx.RemoveRow(sheet1, -1), "invalid row number -1")
assert.EqualError(t, f.RemoveRow(sheet1, -1), "invalid row number -1")

assert.EqualError(t, xlsx.RemoveRow(sheet1, 0), "invalid row number 0")
assert.EqualError(t, f.RemoveRow(sheet1, 0), "invalid row number 0")

assert.NoError(t, xlsx.RemoveRow(sheet1, 4))
assert.NoError(t, f.RemoveRow(sheet1, 4))
if !assert.Len(t, r.SheetData.Row, rowCount-1) {
t.FailNow()
}

xlsx.MergeCell(sheet1, "B3", "B5")
assert.NoError(t, f.MergeCell(sheet1, "B3", "B5"))

assert.NoError(t, xlsx.RemoveRow(sheet1, 2))
assert.NoError(t, f.RemoveRow(sheet1, 2))
if !assert.Len(t, r.SheetData.Row, rowCount-2) {
t.FailNow()
}

assert.NoError(t, xlsx.RemoveRow(sheet1, 4))
assert.NoError(t, f.RemoveRow(sheet1, 4))
if !assert.Len(t, r.SheetData.Row, rowCount-3) {
t.FailNow()
}

err = xlsx.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`)
err = f.AutoFilter(sheet1, "A2", "A2", `{"column":"A","expression":"x != blanks"}`)
if !assert.NoError(t, err) {
t.FailNow()
}

assert.NoError(t, xlsx.RemoveRow(sheet1, 1))
assert.NoError(t, f.RemoveRow(sheet1, 1))
if !assert.Len(t, r.SheetData.Row, rowCount-4) {
t.FailNow()
}

assert.NoError(t, xlsx.RemoveRow(sheet1, 2))
assert.NoError(t, f.RemoveRow(sheet1, 2))
if !assert.Len(t, r.SheetData.Row, rowCount-5) {
t.FailNow()
}

assert.NoError(t, xlsx.RemoveRow(sheet1, 1))
assert.NoError(t, f.RemoveRow(sheet1, 1))
if !assert.Len(t, r.SheetData.Row, rowCount-6) {
t.FailNow()
}

assert.NoError(t, xlsx.RemoveRow(sheet1, 10))
assert.NoError(t, xlsx.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx")))
assert.NoError(t, f.RemoveRow(sheet1, 10))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestRemoveRow.xlsx")))

// Test remove row on not exist worksheet
assert.EqualError(t, f.RemoveRow("SheetN", 1), `sheet SheetN is not exist`)
}

func TestInsertRow(t *testing.T) {
Expand All @@ -180,7 +269,7 @@ func TestInsertRow(t *testing.T) {
)
fillCells(xlsx, sheet1, colCount, rowCount)

xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
assert.NoError(t, xlsx.SetCellHyperLink(sheet1, "A5", "https://github.com/360EntSecGroup-Skylar/excelize", "External"))

assert.EqualError(t, xlsx.InsertRow(sheet1, -1), "invalid row number -1")

Expand Down Expand Up @@ -230,8 +319,8 @@ func TestDuplicateRowFromSingleRow(t *testing.T) {

t.Run("FromSingleRow", func(t *testing.T) {
xlsx := NewFile()
xlsx.SetCellStr(sheet, "A1", cells["A1"])
xlsx.SetCellStr(sheet, "B1", cells["B1"])
assert.NoError(t, xlsx.SetCellStr(sheet, "A1", cells["A1"]))
assert.NoError(t, xlsx.SetCellStr(sheet, "B1", cells["B1"]))

assert.NoError(t, xlsx.DuplicateRow(sheet, 1))
if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.FromSingleRow_1"))) {
Expand Down Expand Up @@ -283,13 +372,13 @@ func TestDuplicateRowUpdateDuplicatedRows(t *testing.T) {

t.Run("UpdateDuplicatedRows", func(t *testing.T) {
xlsx := NewFile()
xlsx.SetCellStr(sheet, "A1", cells["A1"])
xlsx.SetCellStr(sheet, "B1", cells["B1"])
assert.NoError(t, xlsx.SetCellStr(sheet, "A1", cells["A1"]))
assert.NoError(t, xlsx.SetCellStr(sheet, "B1", cells["B1"]))

assert.NoError(t, xlsx.DuplicateRow(sheet, 1))

xlsx.SetCellStr(sheet, "A2", cells["A2"])
xlsx.SetCellStr(sheet, "B2", cells["B2"])
assert.NoError(t, xlsx.SetCellStr(sheet, "A2", cells["A2"]))
assert.NoError(t, xlsx.SetCellStr(sheet, "B2", cells["B2"]))

if !assert.NoError(t, xlsx.SaveAs(fmt.Sprintf(outFile, "TestDuplicateRow.UpdateDuplicatedRows"))) {
t.FailNow()
Expand Down Expand Up @@ -324,8 +413,7 @@ func TestDuplicateRowFirstOfMultipleRows(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)

assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
Expand Down Expand Up @@ -439,8 +527,7 @@ func TestDuplicateRowWithLargeOffsetToMiddleOfData(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)

assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
Expand Down Expand Up @@ -485,8 +572,7 @@ func TestDuplicateRowWithLargeOffsetToEmptyRows(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)

assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
Expand Down Expand Up @@ -531,8 +617,7 @@ func TestDuplicateRowInsertBefore(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)

assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
Expand Down Expand Up @@ -578,8 +663,7 @@ func TestDuplicateRowInsertBeforeWithLargeOffset(t *testing.T) {
newFileWithDefaults := func() *File {
f := NewFile()
for cell, val := range cells {
f.SetCellStr(sheet, cell, val)

assert.NoError(t, f.SetCellStr(sheet, cell, val))
}
return f
}
Expand Down Expand Up @@ -629,7 +713,7 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
t.Run(name, func(t *testing.T) {
xlsx := NewFile()
for col, val := range cells {
xlsx.SetCellStr(sheet, col, val)
assert.NoError(t, xlsx.SetCellStr(sheet, col, val))
}

assert.EqualError(t, xlsx.DuplicateRow(sheet, row), fmt.Sprintf("invalid row number %d", row))
Expand All @@ -651,7 +735,7 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
t.Run(name, func(t *testing.T) {
xlsx := NewFile()
for col, val := range cells {
xlsx.SetCellStr(sheet, col, val)
assert.NoError(t, xlsx.SetCellStr(sheet, col, val))
}

assert.EqualError(t, xlsx.DuplicateRowTo(sheet, row1, row2), fmt.Sprintf("invalid row number %d", row1))
Expand All @@ -669,9 +753,23 @@ func TestDuplicateRowInvalidRownum(t *testing.T) {
}
}

func TestGetValueFrom(t *testing.T) {
c := &xlsxC{T: "inlineStr"}
f := NewFile()
d := &xlsxSST{}
val, err := c.getValueFrom(f, d)
assert.NoError(t, err)
assert.Equal(t, "", val)
}

func TestErrSheetNotExistError(t *testing.T) {
err := ErrSheetNotExist{SheetName: "Sheet1"}
assert.EqualValues(t, err.Error(), "sheet Sheet1 is not exist")
}

func BenchmarkRows(b *testing.B) {
f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
for i := 0; i < b.N; i++ {
f, _ := OpenFile(filepath.Join("test", "Book1.xlsx"))
rows, _ := f.Rows("Sheet2")
for rows.Next() {
row, _ := rows.Columns()
Expand Down
11 changes: 5 additions & 6 deletions shape.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -275,8 +275,7 @@ func (f *File) AddShape(sheet, cell, format string) error {
drawingXML = strings.Replace(sheetRelationshipsDrawingXML, "..", "xl", -1)
} else {
// Add first shape for given sheet.
name, _ := f.sheetMap[trimSheetName(sheet)]
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipDrawingML, sheetRelationshipsDrawingXML, "")
f.addSheetDrawing(sheet, rID)
}
Expand Down Expand Up @@ -362,7 +361,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
FontRef: &aFontRef{
Idx: "minor",
SchemeClr: &attrValString{
Val: "tx1",
Val: stringPtr("tx1"),
},
},
},
Expand Down Expand Up @@ -422,7 +421,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, formatSet *format
if len(srgbClr) == 6 {
paragraph.R.RPr.SolidFill = &aSolidFill{
SrgbClr: &attrValString{
Val: srgbClr,
Val: stringPtr(srgbClr),
},
}
}
Expand Down Expand Up @@ -454,7 +453,7 @@ func setShapeRef(color string, i int) *aRef {
return &aRef{
Idx: i,
SrgbClr: &attrValString{
Val: strings.Replace(strings.ToUpper(color), "#", "", -1),
Val: stringPtr(strings.Replace(strings.ToUpper(color), "#", "", -1)),
},
}
}
28 changes: 28 additions & 0 deletions shape_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package excelize

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAddShape(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}

assert.NoError(t, f.AddShape("Sheet1", "A30", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`))
assert.NoError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`))
assert.NoError(t, f.AddShape("Sheet1", "C30", `{"type":"rect","paragraph":[]}`))
assert.EqualError(t, f.AddShape("Sheet3", "H1", `{"type":"ellipseRibbon", "color":{"line":"#4286f4","fill":"#8eb9ff"}, "paragraph":[{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}], "height": 90}`), "sheet Sheet3 is not exist")
assert.EqualError(t, f.AddShape("Sheet3", "H1", ""), "unexpected end of JSON input")
assert.EqualError(t, f.AddShape("Sheet1", "A", `{"type":"rect","paragraph":[{"text":"Rectangle","font":{"color":"CD5C5C"}},{"text":"Shape","font":{"bold":true,"color":"2980B9"}}]}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))

// Test add first shape for given sheet.
f = NewFile()
assert.NoError(t, f.AddShape("Sheet1", "A1", `{"type":"ellipseRibbon", "color":{"line":"#4286f4","fill":"#8eb9ff"}, "paragraph":[{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777","underline":"single"}}], "height": 90}`))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
}
241 changes: 166 additions & 75 deletions sheet.go

Large diffs are not rendered by default.

53 changes: 45 additions & 8 deletions sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ func ExampleFile_SetPageLayout() {
"Sheet1",
excelize.PageLayoutOrientation(excelize.OrientationLandscape),
); err != nil {
panic(err)
println(err.Error())
}
if err := f.SetPageLayout(
"Sheet1",
excelize.PageLayoutPaperSize(10),
excelize.FitToHeight(2),
excelize.FitToWidth(2),
); err != nil {
panic(err)
println(err.Error())
}
// Output:
}
Expand All @@ -41,17 +41,17 @@ func ExampleFile_GetPageLayout() {
fitToWidth excelize.FitToWidth
)
if err := f.GetPageLayout("Sheet1", &orientation); err != nil {
panic(err)
println(err.Error())
}
if err := f.GetPageLayout("Sheet1", &paperSize); err != nil {
panic(err)
println(err.Error())
}
if err := f.GetPageLayout("Sheet1", &fitToHeight); err != nil {
panic(err)
println(err.Error())
}

if err := f.GetPageLayout("Sheet1", &fitToWidth); err != nil {
panic(err)
println(err.Error())
}
fmt.Println("Defaults:")
fmt.Printf("- orientation: %q\n", orientation)
Expand All @@ -66,6 +66,29 @@ func ExampleFile_GetPageLayout() {
// - fit to width: 1
}

func TestNewSheet(t *testing.T) {
f := excelize.NewFile()
sheetID := f.NewSheet("Sheet2")
f.SetActiveSheet(sheetID)
// delete original sheet
f.DeleteSheet(f.GetSheetName(f.GetSheetIndex("Sheet1")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestNewSheet.xlsx")))
}

func TestSetPane(t *testing.T) {
f := excelize.NewFile()
assert.NoError(t, f.SetPanes("Sheet1", `{"freeze":false,"split":false}`))
f.NewSheet("Panes 2")
assert.NoError(t, f.SetPanes("Panes 2", `{"freeze":true,"split":false,"x_split":1,"y_split":0,"top_left_cell":"B1","active_pane":"topRight","panes":[{"sqref":"K16","active_cell":"K16","pane":"topRight"}]}`))
f.NewSheet("Panes 3")
assert.NoError(t, f.SetPanes("Panes 3", `{"freeze":false,"split":true,"x_split":3270,"y_split":1800,"top_left_cell":"N57","active_pane":"bottomLeft","panes":[{"sqref":"I36","active_cell":"I36"},{"sqref":"G33","active_cell":"G33","pane":"topRight"},{"sqref":"J60","active_cell":"J60","pane":"bottomLeft"},{"sqref":"O60","active_cell":"O60","pane":"bottomRight"}]}`))
f.NewSheet("Panes 4")
assert.NoError(t, f.SetPanes("Panes 4", `{"freeze":true,"split":false,"x_split":0,"y_split":9,"top_left_cell":"A34","active_pane":"bottomLeft","panes":[{"sqref":"A11:XFD11","active_cell":"A11","pane":"bottomLeft"}]}`))
assert.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")))
}

func TestPageLayoutOption(t *testing.T) {
const sheet = "Sheet1"

Expand Down Expand Up @@ -147,6 +170,12 @@ func TestSearchSheet(t *testing.T) {
result, err = f.SearchSheet("Sheet1", "[0-9]", true)
assert.NoError(t, err)
assert.EqualValues(t, expected, result)

// Test search worksheet data after set cell value
f = excelize.NewFile()
assert.NoError(t, f.SetCellValue("Sheet1", "A1", true))
_, err = f.SearchSheet("Sheet1", "")
assert.NoError(t, err)
}

func TestSetPageLayout(t *testing.T) {
Expand All @@ -163,7 +192,7 @@ func TestGetPageLayout(t *testing.T) {

func TestSetHeaderFooter(t *testing.T) {
f := excelize.NewFile()
f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter")
assert.NoError(t, f.SetCellStr("Sheet1", "A1", "Test SetHeaderFooter"))
// Test set header and footer on not exists worksheet.
assert.EqualError(t, f.SetHeaderFooter("SheetN", nil), "sheet SheetN is not exist")
// Test set header and footer with illegal setting.
Expand Down Expand Up @@ -201,8 +230,16 @@ func TestDefinedName(t *testing.T) {
Name: "Amount",
RefersTo: "Sheet1!$A$2:$D$5",
Comment: "defined name comment",
}), "the same name already exists on scope")
}), "the same name already exists on the scope")
assert.EqualError(t, f.DeleteDefinedName(&excelize.DefinedName{
Name: "No Exist Defined Name",
}), "no defined name on the scope")
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[1].RefersTo)
assert.NoError(t, f.DeleteDefinedName(&excelize.DefinedName{
Name: "Amount",
}))
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
assert.Exactly(t, 1, len(f.GetDefinedName()))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx")))
}

Expand Down
168 changes: 167 additions & 1 deletion sheetpr.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -191,3 +191,169 @@ func (f *File) GetSheetPrOptions(name string, opts ...SheetPrOptionPtr) error {
}
return err
}

type (
// PageMarginBottom specifies the bottom margin for the page.
PageMarginBottom float64
// PageMarginFooter specifies the footer margin for the page.
PageMarginFooter float64
// PageMarginHeader specifies the header margin for the page.
PageMarginHeader float64
// PageMarginLeft specifies the left margin for the page.
PageMarginLeft float64
// PageMarginRight specifies the right margin for the page.
PageMarginRight float64
// PageMarginTop specifies the top margin for the page.
PageMarginTop float64
)

// setPageMargins provides a method to set the bottom margin for the worksheet.
func (p PageMarginBottom) setPageMargins(pm *xlsxPageMargins) {
pm.Bottom = float64(p)
}

// setPageMargins provides a method to get the bottom margin for the worksheet.
func (p *PageMarginBottom) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.75
if pm == nil || pm.Bottom == 0 {
*p = 0.75
return
}
*p = PageMarginBottom(pm.Bottom)
}

// setPageMargins provides a method to set the footer margin for the worksheet.
func (p PageMarginFooter) setPageMargins(pm *xlsxPageMargins) {
pm.Footer = float64(p)
}

// setPageMargins provides a method to get the footer margin for the worksheet.
func (p *PageMarginFooter) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.3
if pm == nil || pm.Footer == 0 {
*p = 0.3
return
}
*p = PageMarginFooter(pm.Footer)
}

// setPageMargins provides a method to set the header margin for the worksheet.
func (p PageMarginHeader) setPageMargins(pm *xlsxPageMargins) {
pm.Header = float64(p)
}

// setPageMargins provides a method to get the header margin for the worksheet.
func (p *PageMarginHeader) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.3
if pm == nil || pm.Header == 0 {
*p = 0.3
return
}
*p = PageMarginHeader(pm.Header)
}

// setPageMargins provides a method to set the left margin for the worksheet.
func (p PageMarginLeft) setPageMargins(pm *xlsxPageMargins) {
pm.Left = float64(p)
}

// setPageMargins provides a method to get the left margin for the worksheet.
func (p *PageMarginLeft) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.7
if pm == nil || pm.Left == 0 {
*p = 0.7
return
}
*p = PageMarginLeft(pm.Left)
}

// setPageMargins provides a method to set the right margin for the worksheet.
func (p PageMarginRight) setPageMargins(pm *xlsxPageMargins) {
pm.Right = float64(p)
}

// setPageMargins provides a method to get the right margin for the worksheet.
func (p *PageMarginRight) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.7
if pm == nil || pm.Right == 0 {
*p = 0.7
return
}
*p = PageMarginRight(pm.Right)
}

// setPageMargins provides a method to set the top margin for the worksheet.
func (p PageMarginTop) setPageMargins(pm *xlsxPageMargins) {
pm.Top = float64(p)
}

// setPageMargins provides a method to get the top margin for the worksheet.
func (p *PageMarginTop) getPageMargins(pm *xlsxPageMargins) {
// Excel default: 0.75
if pm == nil || pm.Top == 0 {
*p = 0.75
return
}
*p = PageMarginTop(pm.Top)
}

// PageMarginsOptions is an option of a page margin of a worksheet. See
// SetPageMargins().
type PageMarginsOptions interface {
setPageMargins(layout *xlsxPageMargins)
}

// PageMarginsOptionsPtr is a writable PageMarginsOptions. See
// GetPageMargins().
type PageMarginsOptionsPtr interface {
PageMarginsOptions
getPageMargins(layout *xlsxPageMargins)
}

// SetPageMargins provides a function to set worksheet page margins.
//
// Available options:
// PageMarginBotom(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 {
return err
}
pm := s.PageMargins
if pm == nil {
pm = new(xlsxPageMargins)
s.PageMargins = pm
}

for _, opt := range opts {
opt.setPageMargins(pm)
}
return err
}

// GetPageMargins provides a function to get worksheet page margins.
//
// Available options:
// PageMarginBotom(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 {
return err
}
pm := s.PageMargins

for _, opt := range opts {
opt.getPageMargins(pm)
}
return err
}
165 changes: 163 additions & 2 deletions sheetpr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func ExampleFile_SetSheetPrOptions() {
excelize.AutoPageBreaks(true),
excelize.OutlineSummaryBelow(false),
); err != nil {
panic(err)
println(err.Error())
}
// Output:
}
Expand All @@ -66,7 +66,7 @@ func ExampleFile_GetSheetPrOptions() {
&autoPageBreaks,
&outlineSummaryBelow,
); err != nil {
panic(err)
println(err.Error())
}
fmt.Println("Defaults:")
fmt.Printf("- codeName: %q\n", codeName)
Expand Down Expand Up @@ -146,3 +146,164 @@ func TestSheetPrOptions(t *testing.T) {
})
}
}

func TestSetSheetrOptions(t *testing.T) {
f := excelize.NewFile()
// Test SetSheetrOptions on not exists worksheet.
assert.EqualError(t, f.SetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
}

func TestGetSheetPrOptions(t *testing.T) {
f := excelize.NewFile()
// Test GetSheetPrOptions on not exists worksheet.
assert.EqualError(t, f.GetSheetPrOptions("SheetN"), "sheet SheetN is not exist")
}

var _ = []excelize.PageMarginsOptions{
excelize.PageMarginBottom(1.0),
excelize.PageMarginFooter(1.0),
excelize.PageMarginHeader(1.0),
excelize.PageMarginLeft(1.0),
excelize.PageMarginRight(1.0),
excelize.PageMarginTop(1.0),
}

var _ = []excelize.PageMarginsOptionsPtr{
(*excelize.PageMarginBottom)(nil),
(*excelize.PageMarginFooter)(nil),
(*excelize.PageMarginHeader)(nil),
(*excelize.PageMarginLeft)(nil),
(*excelize.PageMarginRight)(nil),
(*excelize.PageMarginTop)(nil),
}

func ExampleFile_SetPageMargins() {
f := excelize.NewFile()
const sheet = "Sheet1"

if err := f.SetPageMargins(sheet,
excelize.PageMarginBottom(1.0),
excelize.PageMarginFooter(1.0),
excelize.PageMarginHeader(1.0),
excelize.PageMarginLeft(1.0),
excelize.PageMarginRight(1.0),
excelize.PageMarginTop(1.0),
); err != nil {
println(err.Error())
}
// Output:
}

func ExampleFile_GetPageMargins() {
f := excelize.NewFile()
const sheet = "Sheet1"

var (
marginBottom excelize.PageMarginBottom
marginFooter excelize.PageMarginFooter
marginHeader excelize.PageMarginHeader
marginLeft excelize.PageMarginLeft
marginRight excelize.PageMarginRight
marginTop excelize.PageMarginTop
)

if err := f.GetPageMargins(sheet,
&marginBottom,
&marginFooter,
&marginHeader,
&marginLeft,
&marginRight,
&marginTop,
); err != nil {
println(err.Error())
}
fmt.Println("Defaults:")
fmt.Println("- marginBottom:", marginBottom)
fmt.Println("- marginFooter:", marginFooter)
fmt.Println("- marginHeader:", marginHeader)
fmt.Println("- marginLeft:", marginLeft)
fmt.Println("- marginRight:", marginRight)
fmt.Println("- marginTop:", marginTop)
// Output:
// Defaults:
// - marginBottom: 0.75
// - marginFooter: 0.3
// - marginHeader: 0.3
// - marginLeft: 0.7
// - marginRight: 0.7
// - marginTop: 0.75
}

func TestPageMarginsOption(t *testing.T) {
const sheet = "Sheet1"

testData := []struct {
container excelize.PageMarginsOptionsPtr
nonDefault excelize.PageMarginsOptions
}{
{new(excelize.PageMarginTop), excelize.PageMarginTop(1.0)},
{new(excelize.PageMarginBottom), excelize.PageMarginBottom(1.0)},
{new(excelize.PageMarginLeft), excelize.PageMarginLeft(1.0)},
{new(excelize.PageMarginRight), excelize.PageMarginRight(1.0)},
{new(excelize.PageMarginHeader), excelize.PageMarginHeader(1.0)},
{new(excelize.PageMarginFooter), excelize.PageMarginFooter(1.0)},
}

for i, test := range testData {
t.Run(fmt.Sprintf("TestData%d", i), func(t *testing.T) {

opt := test.nonDefault
t.Logf("option %T", opt)

def := deepcopy.Copy(test.container).(excelize.PageMarginsOptionsPtr)
val1 := deepcopy.Copy(def).(excelize.PageMarginsOptionsPtr)
val2 := deepcopy.Copy(def).(excelize.PageMarginsOptionsPtr)

f := excelize.NewFile()
// Get the default value
assert.NoError(t, f.GetPageMargins(sheet, def), opt)
// Get again and check
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
if !assert.Equal(t, val1, def, opt) {
t.FailNow()
}
// Set the same value
assert.NoError(t, f.SetPageMargins(sheet, val1), opt)
// Get again and check
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
if !assert.Equal(t, val1, def, "%T: value should not have changed", opt) {
t.FailNow()
}
// Set a different value
assert.NoError(t, f.SetPageMargins(sheet, test.nonDefault), opt)
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
// Get again and compare
assert.NoError(t, f.GetPageMargins(sheet, val2), opt)
if !assert.Equal(t, val1, val2, "%T: value should not have changed", opt) {
t.FailNow()
}
// Value should not be the same as the default
if !assert.NotEqual(t, def, val1, "%T: value should have changed from default", opt) {
t.FailNow()
}
// Restore the default value
assert.NoError(t, f.SetPageMargins(sheet, def), opt)
assert.NoError(t, f.GetPageMargins(sheet, val1), opt)
if !assert.Equal(t, def, val1) {
t.FailNow()
}
})
}
}

func TestSetPageMargins(t *testing.T) {
f := excelize.NewFile()
// Test set page margins on not exists worksheet.
assert.EqualError(t, f.SetPageMargins("SheetN"), "sheet SheetN is not exist")
}

func TestGetPageMargins(t *testing.T) {
f := excelize.NewFile()
// Test get page margins on not exists worksheet.
assert.EqualError(t, f.GetPageMargins("SheetN"), "sheet SheetN is not exist")
}
2 changes: 1 addition & 1 deletion sheetview.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
24 changes: 12 additions & 12 deletions sheetview_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,30 @@ func ExampleFile_SetSheetViewOptions() {
excelize.ZoomScale(80),
excelize.TopLeftCell("C3"),
); err != nil {
panic(err)
println(err.Error())
}

var zoomScale excelize.ZoomScale
fmt.Println("Default:")
fmt.Println("- zoomScale: 80")

if err := f.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(500)); err != nil {
panic(err)
println(err.Error())
}

if err := f.GetSheetViewOptions(sheet, 0, &zoomScale); err != nil {
panic(err)
println(err.Error())
}

fmt.Println("Used out of range value:")
fmt.Println("- zoomScale:", zoomScale)

if err := f.SetSheetViewOptions(sheet, 0, excelize.ZoomScale(123)); err != nil {
panic(err)
println(err.Error())
}

if err := f.GetSheetViewOptions(sheet, 0, &zoomScale); err != nil {
panic(err)
println(err.Error())
}

fmt.Println("Used correct value:")
Expand Down Expand Up @@ -111,7 +111,7 @@ func ExampleFile_GetSheetViewOptions() {
&zoomScale,
&topLeftCell,
); err != nil {
panic(err)
println(err.Error())
}

fmt.Println("Default:")
Expand All @@ -125,27 +125,27 @@ func ExampleFile_GetSheetViewOptions() {
fmt.Println("- topLeftCell:", `"`+topLeftCell+`"`)

if err := f.SetSheetViewOptions(sheet, 0, excelize.TopLeftCell("B2")); err != nil {
panic(err)
println(err.Error())
}

if err := f.GetSheetViewOptions(sheet, 0, &topLeftCell); err != nil {
panic(err)
println(err.Error())
}

if err := f.SetSheetViewOptions(sheet, 0, excelize.ShowGridLines(false)); err != nil {
panic(err)
println(err.Error())
}

if err := f.GetSheetViewOptions(sheet, 0, &showGridLines); err != nil {
panic(err)
println(err.Error())
}

if err := f.SetSheetViewOptions(sheet, 0, excelize.ShowZeros(false)); err != nil {
panic(err)
println(err.Error())
}

if err := f.GetSheetViewOptions(sheet, 0, &showZeros); err != nil {
panic(err)
println(err.Error())
}

fmt.Println("After change:")
Expand Down
116 changes: 78 additions & 38 deletions sparkline.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand All @@ -10,8 +10,10 @@
package excelize

import (
"bytes"
"encoding/xml"
"errors"
"io"
"strings"
)

Expand Down Expand Up @@ -386,23 +388,33 @@ func (f *File) addSparklineGroupByStyle(ID int) *xlsxX14SparklineGroup {
// ColorAxis | An RGB Color is specified as RRGGBB
// Axis | Show sparkline axis
//
func (f *File) AddSparkline(sheet string, opt *SparklineOption) error {
func (f *File) AddSparkline(sheet string, opt *SparklineOption) (err error) {
var (
ws *xlsxWorksheet
sparkType string
sparkTypes map[string]string
specifiedSparkTypes string
ok bool
group *xlsxX14SparklineGroup
groups *xlsxX14SparklineGroups
sparklineGroupsBytes, extBytes []byte
)

// parameter validation
ws, err := f.parseFormatAddSparklineSet(sheet, opt)
if err != nil {
return err
if ws, err = f.parseFormatAddSparklineSet(sheet, opt); err != nil {
return
}
// Handle the sparkline type
sparkType := "line"
sparkTypes := map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
sparkType = "line"
sparkTypes = map[string]string{"line": "line", "column": "column", "win_loss": "stacked"}
if opt.Type != "" {
specifiedSparkTypes, ok := sparkTypes[opt.Type]
if !ok {
return errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'")
if specifiedSparkTypes, ok = sparkTypes[opt.Type]; !ok {
err = errors.New("parameter 'Type' must be 'line', 'column' or 'win_loss'")
return
}
sparkType = specifiedSparkTypes
}
group := f.addSparklineGroupByStyle(opt.Style)
group = f.addSparklineGroupByStyle(opt.Style)
group.Type = sparkType
group.ColorAxis = &xlsxColor{RGB: "FF000000"}
group.DisplayEmptyCellsAs = "gap"
Expand All @@ -423,43 +435,27 @@ func (f *File) AddSparkline(sheet string, opt *SparklineOption) error {
}
f.addSparkline(opt, group)
if ws.ExtLst.Ext != "" { // append mode ext
decodeExtLst := decodeWorksheetExt{}
err = xml.Unmarshal([]byte("<extLst>"+ws.ExtLst.Ext+"</extLst>"), &decodeExtLst)
if err != nil {
return err
}
for idx, ext := range decodeExtLst.Ext {
if ext.URI == ExtURISparklineGroups {
decodeSparklineGroups := decodeX14SparklineGroups{}
_ = xml.Unmarshal([]byte(ext.Content), &decodeSparklineGroups)
sparklineGroupBytes, _ := xml.Marshal(group)
groups := xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
Content: decodeSparklineGroups.Content + string(sparklineGroupBytes),
}
sparklineGroupsBytes, _ := xml.Marshal(groups)
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
}
}
extLstBytes, _ := xml.Marshal(decodeExtLst)
extLst := string(extLstBytes)
ws.ExtLst = &xlsxExtLst{
Ext: strings.TrimSuffix(strings.TrimPrefix(extLst, "<extLst>"), "</extLst>"),
if err = f.appendSparkline(ws, group, groups); err != nil {
return
}
} else {
groups := xlsxX14SparklineGroups{
groups = &xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
SparklineGroups: []*xlsxX14SparklineGroup{group},
}
sparklineGroupsBytes, _ := xml.Marshal(groups)
extLst := xlsxWorksheetExt{
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
return
}
if extBytes, err = xml.Marshal(&xlsxWorksheetExt{
URI: ExtURISparklineGroups,
Content: string(sparklineGroupsBytes),
}); err != nil {
return
}
extBytes, _ := xml.Marshal(extLst)
ws.ExtLst.Ext = string(extBytes)
}
return nil

return
}

// parseFormatAddSparklineSet provides a function to validate sparkline
Expand Down Expand Up @@ -501,3 +497,47 @@ func (f *File) addSparkline(opt *SparklineOption, group *xlsxX14SparklineGroup)
})
}
}

// appendSparkline provides a function to append sparkline to sparkline
// groups.
func (f *File) appendSparkline(ws *xlsxWorksheet, group *xlsxX14SparklineGroup, groups *xlsxX14SparklineGroups) (err error) {
var (
idx int
decodeExtLst *decodeWorksheetExt
decodeSparklineGroups *decodeX14SparklineGroups
ext *xlsxWorksheetExt
sparklineGroupsBytes, sparklineGroupBytes, extLstBytes []byte
)
decodeExtLst = new(decodeWorksheetExt)
if err = f.xmlNewDecoder(bytes.NewReader([]byte("<extLst>" + ws.ExtLst.Ext + "</extLst>"))).
Decode(decodeExtLst); err != nil && err != io.EOF {
return
}
for idx, ext = range decodeExtLst.Ext {
if ext.URI == ExtURISparklineGroups {
decodeSparklineGroups = new(decodeX14SparklineGroups)
if err = f.xmlNewDecoder(bytes.NewReader([]byte(ext.Content))).
Decode(decodeSparklineGroups); err != nil && err != io.EOF {
return
}
if sparklineGroupBytes, err = xml.Marshal(group); err != nil {
return
}
groups = &xlsxX14SparklineGroups{
XMLNSXM: NameSpaceSpreadSheetExcel2006Main,
Content: decodeSparklineGroups.Content + string(sparklineGroupBytes),
}
if sparklineGroupsBytes, err = xml.Marshal(groups); err != nil {
return
}
decodeExtLst.Ext[idx].Content = string(sparklineGroupsBytes)
}
}
if extLstBytes, err = xml.Marshal(decodeExtLst); err != nil {
return
}
ws.ExtLst = &xlsxExtLst{
Ext: strings.TrimSuffix(strings.TrimPrefix(string(extLstBytes), "<extLst>"), "</extLst>"),
}
return
}
69 changes: 41 additions & 28 deletions sparkline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,148 +17,148 @@ func TestAddSparkline(t *testing.T) {
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", style))
assert.NoError(t, f.SetSheetViewOptions("Sheet1", 0, ZoomScale(150)))

f.SetColWidth("Sheet1", "A", "A", 14)
f.SetColWidth("Sheet1", "B", "B", 50)
assert.NoError(t, f.SetColWidth("Sheet1", "A", "A", 14))
assert.NoError(t, f.SetColWidth("Sheet1", "B", "B", 50))
// Headings
f.SetCellValue("Sheet1", "A1", "Sparkline")
f.SetCellValue("Sheet1", "B1", "Description")
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "Sparkline"))
assert.NoError(t, f.SetCellValue("Sheet1", "B1", "Description"))

f.SetCellValue("Sheet1", "B2", `A default "line" sparkline.`)
assert.NoError(t, f.SetCellValue("Sheet1", "B2", `A default "line" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A2"},
Range: []string{"Sheet3!A1:J1"},
}))

f.SetCellValue("Sheet1", "B3", `A default "column" sparkline.`)
assert.NoError(t, f.SetCellValue("Sheet1", "B3", `A default "column" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A3"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))

f.SetCellValue("Sheet1", "B4", `A default "win/loss" sparkline.`)
assert.NoError(t, f.SetCellValue("Sheet1", "B4", `A default "win/loss" sparkline.`))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A4"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
}))

f.SetCellValue("Sheet1", "B6", "Line with markers.")
assert.NoError(t, f.SetCellValue("Sheet1", "B6", "Line with markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A6"},
Range: []string{"Sheet3!A1:J1"},
Markers: true,
}))

f.SetCellValue("Sheet1", "B7", "Line with high and low points.")
assert.NoError(t, f.SetCellValue("Sheet1", "B7", "Line with high and low points."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A7"},
Range: []string{"Sheet3!A1:J1"},
High: true,
Low: true,
}))

f.SetCellValue("Sheet1", "B8", "Line with first and last point markers.")
assert.NoError(t, f.SetCellValue("Sheet1", "B8", "Line with first and last point markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A8"},
Range: []string{"Sheet3!A1:J1"},
First: true,
Last: true,
}))

f.SetCellValue("Sheet1", "B9", "Line with negative point markers.")
assert.NoError(t, f.SetCellValue("Sheet1", "B9", "Line with negative point markers."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A9"},
Range: []string{"Sheet3!A1:J1"},
Negative: true,
}))

f.SetCellValue("Sheet1", "B10", "Line with axis.")
assert.NoError(t, f.SetCellValue("Sheet1", "B10", "Line with axis."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A10"},
Range: []string{"Sheet3!A1:J1"},
Axis: true,
}))

f.SetCellValue("Sheet1", "B12", "Column with default style (1).")
assert.NoError(t, f.SetCellValue("Sheet1", "B12", "Column with default style (1)."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A12"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
}))

f.SetCellValue("Sheet1", "B13", "Column with style 2.")
assert.NoError(t, f.SetCellValue("Sheet1", "B13", "Column with style 2."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A13"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 2,
}))

f.SetCellValue("Sheet1", "B14", "Column with style 3.")
assert.NoError(t, f.SetCellValue("Sheet1", "B14", "Column with style 3."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A14"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 3,
}))

f.SetCellValue("Sheet1", "B15", "Column with style 4.")
assert.NoError(t, f.SetCellValue("Sheet1", "B15", "Column with style 4."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A15"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 4,
}))

f.SetCellValue("Sheet1", "B16", "Column with style 5.")
assert.NoError(t, f.SetCellValue("Sheet1", "B16", "Column with style 5."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A16"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 5,
}))

f.SetCellValue("Sheet1", "B17", "Column with style 6.")
assert.NoError(t, f.SetCellValue("Sheet1", "B17", "Column with style 6."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A17"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
Style: 6,
}))

f.SetCellValue("Sheet1", "B18", "Column with a user defined color.")
assert.NoError(t, f.SetCellValue("Sheet1", "B18", "Column with a user defined color."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A18"},
Range: []string{"Sheet3!A2:J2"},
Type: "column",
SeriesColor: "#E965E0",
}))

f.SetCellValue("Sheet1", "B20", "A win/loss sparkline.")
assert.NoError(t, f.SetCellValue("Sheet1", "B20", "A win/loss sparkline."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A20"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
}))

f.SetCellValue("Sheet1", "B21", "A win/loss sparkline with negative points highlighted.")
assert.NoError(t, f.SetCellValue("Sheet1", "B21", "A win/loss sparkline with negative points highlighted."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A21"},
Range: []string{"Sheet3!A3:J3"},
Type: "win_loss",
Negative: true,
}))

f.SetCellValue("Sheet1", "B23", "A left to right column (the default).")
assert.NoError(t, f.SetCellValue("Sheet1", "B23", "A left to right column (the default)."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A23"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
}))

f.SetCellValue("Sheet1", "B24", "A right to left column.")
assert.NoError(t, f.SetCellValue("Sheet1", "B24", "A right to left column."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A24"},
Range: []string{"Sheet3!A4:J4"},
Expand All @@ -167,16 +167,16 @@ func TestAddSparkline(t *testing.T) {
Reverse: true,
}))

f.SetCellValue("Sheet1", "B25", "Sparkline and text in one cell.")
assert.NoError(t, f.SetCellValue("Sheet1", "B25", "Sparkline and text in one cell."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A25"},
Range: []string{"Sheet3!A4:J4"},
Type: "column",
Style: 20,
}))
f.SetCellValue("Sheet1", "A25", "Growth")
assert.NoError(t, f.SetCellValue("Sheet1", "A25", "Growth"))

f.SetCellValue("Sheet1", "B27", "A grouped sparkline. Changes are applied to all three.")
assert.NoError(t, f.SetCellValue("Sheet1", "B27", "A grouped sparkline. Changes are applied to all three."))
assert.NoError(t, f.AddSparkline("Sheet1", &SparklineOption{
Location: []string{"A27", "A28", "A29"},
Range: []string{"Sheet3!A5:J5", "Sheet3!A6:J6", "Sheet3!A7:J7"},
Expand Down Expand Up @@ -269,6 +269,15 @@ func TestAddSparkline(t *testing.T) {
}), "XML syntax error on line 6: element <sparklineGroup> closed by </sparklines>")
}

func TestAppendSparkline(t *testing.T) {
// Test unsupport charset.
f := NewFile()
ws, err := f.workSheetReader("Sheet1")
assert.NoError(t, err)
ws.ExtLst = &xlsxExtLst{Ext: string(MacintoshCyrillicCharset)}
assert.EqualError(t, f.appendSparkline(ws, &xlsxX14SparklineGroup{}, &xlsxX14SparklineGroups{}), "XML syntax error on line 1: invalid UTF-8")
}

func prepareSparklineDataset() *File {
f := NewFile()
sheet2 := [][]int{
Expand All @@ -288,10 +297,14 @@ func prepareSparklineDataset() *File {
f.NewSheet("Sheet2")
f.NewSheet("Sheet3")
for row, data := range sheet2 {
f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data)
if err := f.SetSheetRow("Sheet2", fmt.Sprintf("A%d", row+1), &data); err != nil {
println(err.Error())
}
}
for row, data := range sheet3 {
f.SetSheetRow("Sheet3", fmt.Sprintf("A%d", row+1), &data)
if err := f.SetSheetRow("Sheet3", fmt.Sprintf("A%d", row+1), &data); err != nil {
println(err.Error())
}
}
return f
}
520 changes: 520 additions & 0 deletions stream.go

Large diffs are not rendered by default.

167 changes: 167 additions & 0 deletions stream_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package excelize

import (
"encoding/xml"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func BenchmarkStreamWriter(b *testing.B) {
file := NewFile()

row := make([]interface{}, 10)
for colID := 0; colID < 10; colID++ {
row[colID] = colID
}

for n := 0; n < b.N; n++ {
streamWriter, _ := file.NewStreamWriter("Sheet1")
for rowID := 10; rowID <= 110; rowID++ {
cell, _ := CoordinatesToCellName(1, rowID)
streamWriter.SetRow(cell, row)
}
}

b.ReportAllocs()
}

func TestStreamWriter(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)

// Test max characters in a cell.
row := make([]interface{}, 1)
row[0] = strings.Repeat("c", 32769)
assert.NoError(t, streamWriter.SetRow("A1", row))

// Test leading and ending space(s) character characters in a cell.
row = make([]interface{}, 1)
row[0] = " characters"
assert.NoError(t, streamWriter.SetRow("A2", row))

row = make([]interface{}, 1)
row[0] = []byte("Word")
assert.NoError(t, streamWriter.SetRow("A3", row))

// Test set cell with style.
styleID, err := file.NewStyle(`{"font":{"color":"#777777"}}`)
assert.NoError(t, err)
assert.NoError(t, streamWriter.SetRow("A4", []interface{}{Cell{StyleID: styleID}}))
assert.NoError(t, streamWriter.SetRow("A5", []interface{}{&Cell{StyleID: styleID, Value: "cell"}}))
assert.EqualError(t, streamWriter.SetRow("A6", []interface{}{time.Now()}), "only UTC time expected")

for rowID := 10; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
}
cell, _ := CoordinatesToCellName(1, rowID)
assert.NoError(t, streamWriter.SetRow(cell, row))
}

assert.NoError(t, streamWriter.Flush())
// Save xlsx file by the given path.
assert.NoError(t, file.SaveAs(filepath.Join("test", "TestStreamWriter.xlsx")))

// Test close temporary file error.
file = NewFile()
streamWriter, err = file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
for rowID := 10; rowID <= 51200; rowID++ {
row := make([]interface{}, 50)
for colID := 0; colID < 50; colID++ {
row[colID] = rand.Intn(640000)
}
cell, _ := CoordinatesToCellName(1, rowID)
assert.NoError(t, streamWriter.SetRow(cell, row))
}
assert.NoError(t, streamWriter.rawData.Close())
assert.Error(t, streamWriter.Flush())

streamWriter.rawData.tmp, err = ioutil.TempFile(os.TempDir(), "excelize-")
assert.NoError(t, err)
_, err = streamWriter.rawData.Reader()
assert.NoError(t, err)
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
}

func TestStreamTable(t *testing.T) {
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)

// Write some rows. We want enough rows to force a temp file (>16MB).
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{"A", "B", "C"}))
row := []interface{}{1, 2, 3}
for r := 2; r < 10000; r++ {
assert.NoError(t, streamWriter.SetRow(fmt.Sprintf("A%d", r), row))
}

// Write a table.
assert.NoError(t, streamWriter.AddTable("A1", "C2", ``))
assert.NoError(t, streamWriter.Flush())

// Verify the table has names.
var table xlsxTable
assert.NoError(t, xml.Unmarshal(file.XLSX["xl/tables/table1.xml"], &table))
assert.Equal(t, "A", table.TableColumns.TableColumn[0].Name)
assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name)

assert.NoError(t, streamWriter.AddTable("A1", "C1", ``))

// Test add table with illegal formatset.
assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
// Test add table with illegal cell coordinates.
assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
}

func TestNewStreamWriter(t *testing.T) {
// Test error exceptions
file := NewFile()
_, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
_, err = file.NewStreamWriter("SheetN")
assert.EqualError(t, err, "sheet SheetN is not exist")
}

func TestSetRow(t *testing.T) {
// Test error exceptions
file := NewFile()
streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
}

func TestSetCellValFunc(t *testing.T) {
c := &xlsxC{}
assert.NoError(t, setCellValFunc(c, 128))
assert.NoError(t, setCellValFunc(c, int8(-128)))
assert.NoError(t, setCellValFunc(c, int16(-32768)))
assert.NoError(t, setCellValFunc(c, int32(-2147483648)))
assert.NoError(t, setCellValFunc(c, int64(-9223372036854775808)))
assert.NoError(t, setCellValFunc(c, uint(128)))
assert.NoError(t, setCellValFunc(c, uint8(255)))
assert.NoError(t, setCellValFunc(c, uint16(65535)))
assert.NoError(t, setCellValFunc(c, uint32(4294967295)))
assert.NoError(t, setCellValFunc(c, uint64(18446744073709551615)))
assert.NoError(t, setCellValFunc(c, float32(100.1588)))
assert.NoError(t, setCellValFunc(c, float64(100.1588)))
assert.NoError(t, setCellValFunc(c, " Hello"))
assert.NoError(t, setCellValFunc(c, []byte(" Hello")))
assert.NoError(t, setCellValFunc(c, time.Now().UTC()))
assert.NoError(t, setCellValFunc(c, time.Duration(1e13)))
assert.NoError(t, setCellValFunc(c, true))
assert.NoError(t, setCellValFunc(c, nil))
assert.NoError(t, setCellValFunc(c, complex64(5+10i)))
}
60 changes: 38 additions & 22 deletions styles.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand All @@ -10,9 +10,12 @@
package excelize

import (
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
"math"
"strconv"
"strings"
Expand Down Expand Up @@ -997,11 +1000,16 @@ func is12HourTime(format string) bool {
// stylesReader provides a function to get the pointer to the structure after
// deserialization of xl/styles.xml.
func (f *File) stylesReader() *xlsxStyleSheet {
var err error

if f.Styles == nil {
var styleSheet xlsxStyleSheet
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/styles.xml")), &styleSheet)
f.Styles = &styleSheet
f.Styles = new(xlsxStyleSheet)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/styles.xml")))).
Decode(f.Styles); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
}
}

return f.Styles
}

Expand Down Expand Up @@ -1949,13 +1957,13 @@ func (f *File) NewConditionalStyle(style string) (int, error) {
// Documents generated by excelize start with Calibri.
func (f *File) GetDefaultFont() string {
font := f.readDefaultFont()
return font.Name.Val
return *font.Name.Val
}

// SetDefaultFont changes the default font in the workbook.
func (f *File) SetDefaultFont(fontName string) {
font := f.readDefaultFont()
font.Name.Val = fontName
font.Name.Val = stringPtr(fontName)
s := f.stylesReader()
s.Fonts.Font[0] = font
custom := true
Expand All @@ -1979,27 +1987,27 @@ func (f *File) setFont(formatStyle *formatStyle) *xlsxFont {
formatStyle.Font.Color = "#000000"
}
fnt := xlsxFont{
Sz: &attrValFloat{Val: formatStyle.Font.Size},
Sz: &attrValFloat{Val: float64Ptr(formatStyle.Font.Size)},
Color: &xlsxColor{RGB: getPaletteColor(formatStyle.Font.Color)},
Name: &attrValString{Val: formatStyle.Font.Family},
Family: &attrValInt{Val: 2},
Name: &attrValString{Val: stringPtr(formatStyle.Font.Family)},
Family: &attrValInt{Val: intPtr(2)},
}
if formatStyle.Font.Bold {
fnt.B = &formatStyle.Font.Bold
}
if formatStyle.Font.Italic {
fnt.I = &formatStyle.Font.Italic
}
if fnt.Name.Val == "" {
fnt.Name.Val = f.GetDefaultFont()
if *fnt.Name.Val == "" {
*fnt.Name.Val = f.GetDefaultFont()
}
if formatStyle.Font.Strike {
strike := true
fnt.Strike = &strike
}
val, ok := fontUnderlineType[formatStyle.Font.Underline]
if ok {
fnt.U = &attrValString{Val: val}
fnt.U = &attrValString{Val: stringPtr(val)}
}
return &fnt
}
Expand Down Expand Up @@ -2313,7 +2321,7 @@ func (f *File) GetCellStyle(sheet, axis string) (int, error) {
//
// style, err := f.NewStyle(`{"border":[{"type":"left","color":"0000FF","style":3},{"type":"top","color":"00FF00","style":4},{"type":"bottom","color":"FFFF00","style":5},{"type":"right","color":"FF0000","style":6},{"type":"diagonalDown","color":"A020F0","style":7},{"type":"diagonalUp","color":"A020F0","style":8}]}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
Expand All @@ -2322,23 +2330,23 @@ func (f *File) GetCellStyle(sheet, axis string) (int, error) {
//
// style, err := f.NewStyle(`{"fill":{"type":"gradient","color":["#FFFFFF","#E0EBF5"],"shading":1}}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set solid style pattern fill for cell H9 on Sheet1:
//
// style, err := f.NewStyle(`{"fill":{"type":"pattern","color":["#E0EBF5"],"pattern":1}}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set alignment style for cell H9 on Sheet1:
//
// style, err := f.NewStyle(`{"alignment":{"horizontal":"center","ident":1,"justify_last_line":true,"reading_order":0,"relative_indent":1,"shrink_to_fit":true,"text_rotation":45,"vertical":"","wrap_text":true}}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
Expand All @@ -2349,23 +2357,23 @@ func (f *File) GetCellStyle(sheet, axis string) (int, error) {
// f.SetCellValue("Sheet1", "H9", 42920.5)
// style, err := f.NewStyle(`{"number_format": 22}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Set font style for cell H9 on Sheet1:
//
// style, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
// Hide and lock for cell H9 on Sheet1:
//
// style, err := f.NewStyle(`{"protection":{"hidden":true, "locked":true}}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// err = f.SetCellStyle("Sheet1", "H9", "H9", style)
//
Expand Down Expand Up @@ -2499,7 +2507,7 @@ func (f *File) SetCellStyle(sheet, hcell, vcell string, styleID int) error {
//
// format, err = f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
// if err != nil {
// fmt.Println(err)
// println(err.Error())
// }
// f.SetConditionalFormat("Sheet1", "A1:A10", fmt.Sprintf(`[{"type":"cell","criteria":">","format":%d,"value":"6"}]`, format))
//
Expand Down Expand Up @@ -2803,8 +2811,16 @@ func getPaletteColor(color string) string {
// themeReader provides a function to get the pointer to the xl/theme/theme1.xml
// structure after deserialization.
func (f *File) themeReader() *xlsxTheme {
var theme xlsxTheme
_ = xml.Unmarshal(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")), &theme)
var (
err error
theme xlsxTheme
)

if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML("xl/theme/theme1.xml")))).
Decode(&theme); err != nil && err != io.EOF {
log.Printf("xml decoder error: %s", err)
}

return &theme
}

Expand Down
6 changes: 2 additions & 4 deletions styles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,11 @@ func TestSetConditionalFormat(t *testing.T) {
func TestNewStyle(t *testing.T) {
f := NewFile()
styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`)
if err != nil {
t.Fatal(err)
}
assert.NoError(t, err)
styles := f.stylesReader()
fontID := styles.CellXfs.Xf[styleID].FontID
font := styles.Fonts.Font[fontID]
assert.Contains(t, font.Name.Val, "Times New Roman", "Stored font should contain font name")
assert.Contains(t, *font.Name.Val, "Times New Roman", "Stored font should contain font name")
assert.Equal(t, 2, styles.CellXfs.Count, "Should have 2 styles")
}

Expand Down
13 changes: 7 additions & 6 deletions table.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -39,8 +39,10 @@ func parseFormatTableSet(formatSet string) (*formatTable, error) {
//
// 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 at least two lines include string type header. Multiple
// tables coordinate areas can't have an intersection.
// Note that the table must be at least two lines including the header. The
// header cells must contain strings and must be unique, and must set the
// header row data of the table before calling the AddTable function. Multiple
// tables coordinate areas that can't have an intersection.
//
// table_name: The name of the table, in the same worksheet name of the table should be unique
//
Expand Down Expand Up @@ -77,8 +79,7 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
sheetRelationshipsTableXML := "../tables/table" + strconv.Itoa(tableID) + ".xml"
tableXML := strings.Replace(sheetRelationshipsTableXML, "..", "xl", -1)
// Add first table for given sheet.
sheetPath, _ := f.sheetMap[trimSheetName(sheet)]
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetPath, "xl/worksheets/") + ".rels"
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(f.sheetMap[trimSheetName(sheet)], "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipTable, sheetRelationshipsTableXML, "")
f.addSheetTable(sheet, rID)
err = f.addTable(sheet, tableXML, hcol, hrow, vcol, vrow, tableID, formatSet)
Expand Down Expand Up @@ -140,7 +141,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
}
name, _ := f.GetCellValue(sheet, cell)
if _, err := strconv.Atoi(name); err == nil {
f.SetCellStr(sheet, cell, name)
_ = f.SetCellStr(sheet, cell, name)
}
if name == "" {
name = "Column" + strconv.Itoa(idx)
Expand Down
125 changes: 125 additions & 0 deletions table_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package excelize

import (
"fmt"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAddTable(t *testing.T) {
f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}

err = f.AddTable("Sheet1", "B26", "A21", `{}`)
if !assert.NoError(t, err) {
t.FailNow()
}

err = f.AddTable("Sheet2", "A2", "B5", `{"table_name":"table","table_style":"TableStyleMedium2", "show_first_column":true,"show_last_column":true,"show_row_stripes":false,"show_column_stripes":true}`)
if !assert.NoError(t, err) {
t.FailNow()
}

err = f.AddTable("Sheet2", "F1", "F1", `{"table_style":"TableStyleMedium8"}`)
if !assert.NoError(t, err) {
t.FailNow()
}

// Test add table with illegal formatset.
assert.EqualError(t, f.AddTable("Sheet1", "B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
// Test add table with illegal cell coordinates.
assert.EqualError(t, f.AddTable("Sheet1", "A", "B1", `{}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.AddTable("Sheet1", "A1", "B", `{}`), `cannot convert cell "B" to coordinates: invalid cell name "B"`)

assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddTable.xlsx")))

// Test addTable with illegal cell coordinates.
f = NewFile()
assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
}

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

f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}

formats := []string{
``,
`{"column":"B","expression":"x != blanks"}`,
`{"column":"B","expression":"x == blanks"}`,
`{"column":"B","expression":"x != nonblanks"}`,
`{"column":"B","expression":"x == nonblanks"}`,
`{"column":"B","expression":"x <= 1 and x >= 2"}`,
`{"column":"B","expression":"x == 1 or x == 2"}`,
`{"column":"B","expression":"x == 1 or x == 2*"}`,
}

for i, format := range formats {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet1", "D4", "B1", format)
assert.NoError(t, err)
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
})
}

// testing AutoFilter with illegal cell coordinates.
assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), `cannot convert cell "B" to coordinates: invalid cell name "B"`)
}

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

f, err := prepareTestBook1()
if !assert.NoError(t, err) {
t.FailNow()
}

formats := []string{
`{"column":"B","expression":"x <= 1 and x >= blanks"}`,
`{"column":"B","expression":"x -- y or x == *2*"}`,
`{"column":"B","expression":"x != y or x ? *2"}`,
`{"column":"B","expression":"x -- y o r x == *2"}`,
`{"column":"B","expression":"x -- y"}`,
`{"column":"A","expression":"x -- y"}`,
}
for i, format := range formats {
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
err = f.AutoFilter("Sheet3", "D4", "B1", format)
if assert.Error(t, err) {
assert.NoError(t, f.SaveAs(fmt.Sprintf(outFile, i+1)))
}
})
}

assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &formatAutoFilter{
Column: "-",
Expression: "-",
}), `invalid column name "-"`)
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &formatAutoFilter{
Column: "A",
Expression: "-",
}), `incorrect index of column 'A'`)
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &formatAutoFilter{
Column: "A",
Expression: "-",
}), `incorrect number of tokens in criteria '-'`)
}

func TestParseFilterTokens(t *testing.T) {
f := NewFile()
// Test with unknown operator.
_, _, err := f.parseFilterTokens("", []string{"", "!"})
assert.EqualError(t, err, "unknown operator: !")
// Test invalid operator in context.
_, _, err = f.parseFilterTokens("", []string{"", "<", "x != blanks"})
assert.EqualError(t, err, "the operator '<' in expression '' is not valid in relation to Blanks/NonBlanks'")
}
6 changes: 4 additions & 2 deletions templates.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -29,7 +29,7 @@ const templateWorkbook = `<workbook xmlns="http://schemas.openxmlformats.org/spr

const templateStyles = `<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac x16r2" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main"><fonts count="1" x14ac:knownFonts="1"><font><sz val="11"/><color theme="1"/><name val="Calibri"/><family val="2"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles><dxfs count="0"/><tableStyles count="0" defaultTableStyle="TableStyleMedium2" defaultPivotStyle="PivotStyleLight16"/></styleSheet>`

const templateSheet = `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><dimension ref="A1"/><sheetViews><sheetView tabSelected="1" workbookViewId="0"/></sheetViews><sheetFormatPr defaultRowHeight="15"/><sheetData/><pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/></worksheet>`
const templateSheet = `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><dimension ref="A1"/><sheetViews><sheetView tabSelected="1" workbookViewId="0"/></sheetViews><sheetFormatPr defaultRowHeight="15"/><sheetData/></worksheet>`

const templateWorkbookRels = `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="theme/theme1.xml"/></Relationships>`

Expand All @@ -38,3 +38,5 @@ const templateDocpropsCore = `<cp:coreProperties xmlns:cp="http://schemas.openxm
const templateRels = `<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>`

const templateTheme = `<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office Theme"><a:themeElements><a:clrScheme name="Office"><a:dk1><a:sysClr val="windowText" lastClr="000000"/></a:dk1><a:lt1><a:sysClr val="window" lastClr="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="44546A"/></a:dk2><a:lt2><a:srgbClr val="E7E6E6"/></a:lt2><a:accent1><a:srgbClr val="5B9BD5"/></a:accent1><a:accent2><a:srgbClr val="ED7D31"/></a:accent2><a:accent3><a:srgbClr val="A5A5A5"/></a:accent3><a:accent4><a:srgbClr val="FFC000"/></a:accent4><a:accent5><a:srgbClr val="4472C4"/></a:accent5><a:accent6><a:srgbClr val="70AD47"/></a:accent6><a:hlink><a:srgbClr val="0563C1"/></a:hlink><a:folHlink><a:srgbClr val="954F72"/></a:folHlink></a:clrScheme><a:fontScheme name="Office"><a:majorFont><a:latin typeface="Calibri Light" panose="020F0302020204030204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック Light"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线 Light"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Times New Roman"/><a:font script="Hebr" typeface="Times New Roman"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="MoolBoran"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Times New Roman"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:majorFont><a:minorFont><a:latin typeface="Calibri" panose="020F0502020204030204"/><a:ea typeface=""/><a:cs typeface=""/><a:font script="Jpan" typeface="游ゴシック"/><a:font script="Hang" typeface="맑은 고딕"/><a:font script="Hans" typeface="等线"/><a:font script="Hant" typeface="新細明體"/><a:font script="Arab" typeface="Arial"/><a:font script="Hebr" typeface="Arial"/><a:font script="Thai" typeface="Tahoma"/><a:font script="Ethi" typeface="Nyala"/><a:font script="Beng" typeface="Vrinda"/><a:font script="Gujr" typeface="Shruti"/><a:font script="Khmr" typeface="DaunPenh"/><a:font script="Knda" typeface="Tunga"/><a:font script="Guru" typeface="Raavi"/><a:font script="Cans" typeface="Euphemia"/><a:font script="Cher" typeface="Plantagenet Cherokee"/><a:font script="Yiii" typeface="Microsoft Yi Baiti"/><a:font script="Tibt" typeface="Microsoft Himalaya"/><a:font script="Thaa" typeface="MV Boli"/><a:font script="Deva" typeface="Mangal"/><a:font script="Telu" typeface="Gautami"/><a:font script="Taml" typeface="Latha"/><a:font script="Syrc" typeface="Estrangelo Edessa"/><a:font script="Orya" typeface="Kalinga"/><a:font script="Mlym" typeface="Kartika"/><a:font script="Laoo" typeface="DokChampa"/><a:font script="Sinh" typeface="Iskoola Pota"/><a:font script="Mong" typeface="Mongolian Baiti"/><a:font script="Viet" typeface="Arial"/><a:font script="Uigh" typeface="Microsoft Uighur"/><a:font script="Geor" typeface="Sylfaen"/></a:minorFont></a:fontScheme><a:fmtScheme name="Office"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:lumMod val="110000"/><a:satMod val="105000"/><a:tint val="67000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="103000"/><a:tint val="73000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="105000"/><a:satMod val="109000"/><a:tint val="81000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:satMod val="103000"/><a:lumMod val="102000"/><a:tint val="94000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:satMod val="110000"/><a:lumMod val="100000"/><a:shade val="100000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:lumMod val="99000"/><a:satMod val="120000"/><a:shade val="78000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:fillStyleLst><a:lnStyleLst><a:ln w="6350" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="12700" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln><a:ln w="19050" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/><a:miter lim="800000"/></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst><a:outerShdw blurRad="57150" dist="19050" dir="5400000" algn="ctr" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="63000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"><a:tint val="95000"/><a:satMod val="170000"/></a:schemeClr></a:solidFill><a:gradFill rotWithShape="1"><a:gsLst><a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="93000"/><a:satMod val="150000"/><a:shade val="98000"/><a:lumMod val="102000"/></a:schemeClr></a:gs><a:gs pos="50000"><a:schemeClr val="phClr"><a:tint val="98000"/><a:satMod val="130000"/><a:shade val="90000"/><a:lumMod val="103000"/></a:schemeClr></a:gs><a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="63000"/><a:satMod val="120000"/></a:schemeClr></a:gs></a:gsLst><a:lin ang="5400000" scaled="0"/></a:gradFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements><a:objectDefaults/><a:extraClrSchemeLst/></a:theme>`

const templateNamespaceIDMap = ` xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:ap="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:op="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" xmlns:cdr="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing" xmlns:comp="http://schemas.openxmlformats.org/drawingml/2006/compatibility" xmlns:dgm="http://schemas.openxmlformats.org/drawingml/2006/diagram" xmlns:lc="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:sl="http://schemas.openxmlformats.org/schemaLibrary/2006/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:xne="http://schemas.microsoft.com/office/excel/2006/main" xmlns:mso="http://schemas.microsoft.com/office/2006/01/customui" xmlns:ax="http://schemas.microsoft.com/office/2006/activeX" xmlns:cppr="http://schemas.microsoft.com/office/2006/coverPageProps" xmlns:cdip="http://schemas.microsoft.com/office/2006/customDocumentInformationPanel" xmlns:ct="http://schemas.microsoft.com/office/2006/metadata/contentType" xmlns:ntns="http://schemas.microsoft.com/office/2006/metadata/customXsn" xmlns:lp="http://schemas.microsoft.com/office/2006/metadata/longProperties" xmlns:ma="http://schemas.microsoft.com/office/2006/metadata/properties/metaAttributes" xmlns:msink="http://schemas.microsoft.com/ink/2010/main" xmlns:c14="http://schemas.microsoft.com/office/drawing/2007/8/2/chart" xmlns:cdr14="http://schemas.microsoft.com/office/drawing/2010/chartDrawing" xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" xmlns:pic14="http://schemas.microsoft.com/office/drawing/2010/picture" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:xdr14="http://schemas.microsoft.com/office/excel/2010/spreadsheetDrawing" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:dsp="http://schemas.microsoft.com/office/drawing/2008/diagram" xmlns:mso14="http://schemas.microsoft.com/office/2009/07/customui" xmlns:dgm14="http://schemas.microsoft.com/office/drawing/2010/diagram" xmlns:x15="http://schemas.microsoft.com/office/spreadsheetml/2010/11/main" xmlns:x12ac="http://schemas.microsoft.com/office/spreadsheetml/2011/1/ac" xmlns:x15ac="http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac" xmlns:xr="http://schemas.microsoft.com/office/spreadsheetml/2014/revision" xmlns:xr2="http://schemas.microsoft.com/office/spreadsheetml/2015/revision2" xmlns:xr3="http://schemas.microsoft.com/office/spreadsheetml/2016/revision3" xmlns:xr4="http://schemas.microsoft.com/office/spreadsheetml/2016/revision4" xmlns:xr5="http://schemas.microsoft.com/office/spreadsheetml/2016/revision5" xmlns:xr6="http://schemas.microsoft.com/office/spreadsheetml/2016/revision6" xmlns:xr7="http://schemas.microsoft.com/office/spreadsheetml/2016/revision7" xmlns:xr8="http://schemas.microsoft.com/office/spreadsheetml/2016/revision8" xmlns:xr9="http://schemas.microsoft.com/office/spreadsheetml/2016/revision9" xmlns:xr10="http://schemas.microsoft.com/office/spreadsheetml/2016/revision10" xmlns:xr11="http://schemas.microsoft.com/office/spreadsheetml/2016/revision11" xmlns:xr12="http://schemas.microsoft.com/office/spreadsheetml/2016/revision12" xmlns:xr13="http://schemas.microsoft.com/office/spreadsheetml/2016/revision13" xmlns:xr14="http://schemas.microsoft.com/office/spreadsheetml/2016/revision14" xmlns:xr15="http://schemas.microsoft.com/office/spreadsheetml/2016/revision15" xmlns:x16="http://schemas.microsoft.com/office/spreadsheetml/2014/11/main" xmlns:x16r2="http://schemas.microsoft.com/office/spreadsheetml/2015/02/main" mc:Ignorable="c14 cdr14 a14 pic14 x14 xdr14 x14ac dsp mso14 dgm14 x15 x12ac x15ac xr xr2 xr3 xr4 xr5 xr6 xr7 xr8 xr9 xr10 xr11 xr12 xr13 xr14 xr15 x15 x16 x16r2 mo mx mv o v" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml" xr:uid="{00000000-0001-0000-0000-000000000000}">`
2 changes: 1 addition & 1 deletion vmlDrawing.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
2 changes: 1 addition & 1 deletion xmlApp.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
2 changes: 1 addition & 1 deletion xmlCalcChain.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
38 changes: 25 additions & 13 deletions xmlChart.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -56,11 +56,11 @@ type cChart struct {
// cTitle (Title) directly maps the title element. This element specifies a
// title.
type cTitle struct {
Tx cTx `xml:"tx,omitempty"`
Layout string `xml:"layout,omitempty"`
Overlay attrValBool `xml:"overlay,omitempty"`
SpPr cSpPr `xml:"spPr,omitempty"`
TxPr cTxPr `xml:"txPr,omitempty"`
Tx cTx `xml:"tx,omitempty"`
Layout string `xml:"layout,omitempty"`
Overlay *attrValBool `xml:"overlay"`
SpPr cSpPr `xml:"spPr,omitempty"`
TxPr cTxPr `xml:"txPr,omitempty"`
}

// cTx (Chart Text) directly maps the tx element. This element specifies text
Expand Down Expand Up @@ -141,25 +141,25 @@ type aSchemeClr struct {
// attrValInt directly maps the val element with integer data type as an
// attribute。
type attrValInt struct {
Val int `xml:"val,attr"`
Val *int `xml:"val,attr"`
}

// attrValFloat directly maps the val element with float64 data type as an
// attribute。
type attrValFloat struct {
Val float64 `xml:"val,attr"`
Val *float64 `xml:"val,attr"`
}

// attrValBool directly maps the val element with boolean data type as an
// attribute。
type attrValBool struct {
Val bool `xml:"val,attr"`
Val *bool `xml:"val,attr"`
}

// attrValString directly maps the val element with string data type as an
// attribute。
type attrValString struct {
Val string `xml:"val,attr"`
Val *string `xml:"val,attr"`
}

// aCs directly maps the a:cs element.
Expand Down Expand Up @@ -312,6 +312,7 @@ type cPlotArea struct {
LineChart *cCharts `xml:"lineChart"`
PieChart *cCharts `xml:"pieChart"`
Pie3DChart *cCharts `xml:"pie3DChart"`
OfPieChart *cCharts `xml:"ofPieChart"`
RadarChart *cCharts `xml:"radarChart"`
ScatterChart *cCharts `xml:"scatterChart"`
Surface3DChart *cCharts `xml:"surface3DChart"`
Expand All @@ -329,9 +330,11 @@ type cCharts struct {
Grouping *attrValString `xml:"grouping"`
RadarStyle *attrValString `xml:"radarStyle"`
ScatterStyle *attrValString `xml:"scatterStyle"`
OfPieType *attrValString `xml:"ofPieType"`
VaryColors *attrValBool `xml:"varyColors"`
Wireframe *attrValBool `xml:"wireframe"`
Ser *[]cSer `xml:"ser"`
SerLines *attrValString `xml:"serLines"`
DLbls *cDLbls `xml:"dLbls"`
Shape *attrValString `xml:"shape"`
HoleSize *attrValInt `xml:"holeSize"`
Expand All @@ -347,6 +350,7 @@ type cAxs struct {
Delete *attrValBool `xml:"delete"`
AxPos *attrValString `xml:"axPos"`
MajorGridlines *cChartLines `xml:"majorGridlines"`
MinorGridlines *cChartLines `xml:"minorGridlines"`
NumFmt *cNumFmt `xml:"numFmt"`
MajorTickMark *attrValString `xml:"majorTickMark"`
MinorTickMark *attrValString `xml:"minorTickMark"`
Expand All @@ -356,9 +360,13 @@ type cAxs struct {
CrossAx *attrValInt `xml:"crossAx"`
Crosses *attrValString `xml:"crosses"`
CrossBetween *attrValString `xml:"crossBetween"`
MajorUnit *attrValFloat `xml:"majorUnit"`
MinorUnit *attrValFloat `xml:"minorUnit"`
Auto *attrValBool `xml:"auto"`
LblAlgn *attrValString `xml:"lblAlgn"`
LblOffset *attrValInt `xml:"lblOffset"`
TickLblSkip *attrValInt `xml:"tickLblSkip"`
TickMarkSkip *attrValInt `xml:"tickMarkSkip"`
NoMultiLvlLbl *attrValBool `xml:"noMultiLvlLbl"`
}

Expand Down Expand Up @@ -514,11 +522,13 @@ type cPageMargins struct {
type formatChartAxis struct {
Crossing string `json:"crossing"`
MajorGridlines bool `json:"major_grid_lines"`
MinorGridlines bool `json:"minor_grid_lines"`
MajorTickMark string `json:"major_tick_mark"`
MinorTickMark string `json:"minor_tick_mark"`
MinorUnitType string `json:"minor_unit_type"`
MajorUnit int `json:"major_unit"`
MajorUnit float64 `json:"major_unit"`
MajorUnitType string `json:"major_unit_type"`
TickLabelSkip int `json:"tick_label_skip"`
DisplayUnits string `json:"display_units"`
DisplayUnitsVisible bool `json:"display_units_visible"`
DateAxis bool `json:"date_axis"`
Expand Down Expand Up @@ -587,6 +597,7 @@ type formatChart struct {
ShowHiddenData bool `json:"show_hidden_data"`
SetRotation int `json:"set_rotation"`
SetHoleSize int `json:"set_hole_size"`
order int
}

// formatChartLegend directly maps the format settings of the chart legend.
Expand All @@ -606,8 +617,9 @@ type formatChartSeries struct {
Categories string `json:"categories"`
Values string `json:"values"`
Line struct {
None bool `json:"none"`
Color string `json:"color"`
None bool `json:"none"`
Color string `json:"color"`
Width float64 `json:"width"`
} `json:"line"`
Marker struct {
Type string `json:"type"`
Expand Down
2 changes: 1 addition & 1 deletion xmlComments.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
2 changes: 1 addition & 1 deletion xmlContentTypes.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
2 changes: 1 addition & 1 deletion xmlCore.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
2 changes: 1 addition & 1 deletion xmlDecodeDrawing.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
10 changes: 5 additions & 5 deletions xmlDrawing.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -47,10 +47,10 @@ const (
NameSpaceDublinCore = "http://purl.org/dc/elements/1.1/"
NameSpaceDublinCoreTerms = "http://purl.org/dc/terms/"
NameSpaceDublinCoreMetadataIntiative = "http://purl.org/dc/dcmitype/"
// The extLst child element ([ISO/IEC29500-1:2016] section 18.2.10) of the
// worksheet element ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by
// the addition of new child ext elements ([ISO/IEC29500-1:2016] section
// 18.2.7)
// ExtURIConditionalFormattings is the extLst child element
// ([ISO/IEC29500-1:2016] section 18.2.10) of the worksheet element
// ([ISO/IEC29500-1:2016] section 18.3.1.99) is extended by the addition of
// new child ext elements ([ISO/IEC29500-1:2016] section 18.2.7)
ExtURIConditionalFormattings = "{78C0D931-6437-407D-A8EE-F0AAD7539E65}"
ExtURIDataValidations = "{CCE6A557-97BC-4B89-ADB6-D9C93CAAB3DF}"
ExtURISparklineGroups = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"
Expand Down
4 changes: 2 additions & 2 deletions xmlPivotCache.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type xlsxPivotCacheDefinition struct {
// PivotTable.
type xlsxCacheSource struct {
Type string `xml:"type,attr"`
ConnectionId int `xml:"connectionId,attr,omitempty"`
ConnectionID int `xml:"connectionId,attr,omitempty"`
WorksheetSource *xlsxWorksheetSource `xml:"worksheetSource"`
Consolidation *xlsxConsolidation `xml:"consolidation"`
ExtLst *xlsxExtLst `xml:"extLst"`
Expand Down Expand Up @@ -89,7 +89,7 @@ type xlsxCacheField struct {
PropertyName string `xml:"propertyName,attr,omitempty"`
ServerField bool `xml:"serverField,attr,omitempty"`
UniqueList bool `xml:"uniqueList,attr,omitempty"`
NumFmtId int `xml:"numFmtId,attr"`
NumFmtID int `xml:"numFmtId,attr"`
Formula string `xml:"formula,attr,omitempty"`
SQLType int `xml:"sqlType,attr,omitempty"`
Hierarchy int `xml:"hierarchy,attr,omitempty"`
Expand Down
8 changes: 4 additions & 4 deletions xmlPivotTable.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -125,7 +125,7 @@ type xlsxPivotField struct {
UniqueMemberProperty string `xml:"uniqueMemberProperty,attr,omitempty"`
Compact bool `xml:"compact,attr"`
AllDrilled bool `xml:"allDrilled,attr,omitempty"`
NumFmtId string `xml:"numFmtId,attr,omitempty"`
NumFmtID string `xml:"numFmtId,attr,omitempty"`
Outline bool `xml:"outline,attr"`
SubtotalTop bool `xml:"subtotalTop,attr,omitempty"`
DragToRow bool `xml:"dragToRow,attr,omitempty"`
Expand Down Expand Up @@ -187,7 +187,7 @@ type xlsxItem struct {
F bool `xml:"f,attr,omitempty"`
M bool `xml:"m,attr,omitempty"`
C bool `xml:"c,attr,omitempty"`
X int `xml:"x,attr,omitempty,omitempty"`
X int `xml:"x,attr,omitempty"`
D bool `xml:"d,attr,omitempty"`
E bool `xml:"e,attr,omitempty"`
}
Expand Down Expand Up @@ -273,7 +273,7 @@ type xlsxDataField struct {
ShowDataAs string `xml:"showDataAs,attr,omitempty"`
BaseField int `xml:"baseField,attr,omitempty"`
BaseItem int64 `xml:"baseItem,attr,omitempty"`
NumFmtId string `xml:"numFmtId,attr,omitempty"`
NumFmtID string `xml:"numFmtId,attr,omitempty"`
ExtLst *xlsxExtLst `xml:"extLst"`
}

Expand Down
2 changes: 1 addition & 1 deletion xmlSharedStrings.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
2 changes: 1 addition & 1 deletion xmlStyles.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
3 changes: 2 additions & 1 deletion xmlTable.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down Expand Up @@ -44,6 +44,7 @@ type xlsxTable struct {
// applied column by column to a table of data in the worksheet. This collection
// expresses AutoFilter settings.
type xlsxAutoFilter struct {
XMLName xml.Name `xml:"autoFilter"`
Ref string `xml:"ref,attr"`
FilterColumn *xlsxFilterColumn `xml:"filterColumn"`
}
Expand Down
2 changes: 1 addition & 1 deletion xmlTheme.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2016 - 2019 The excelize Authors. All rights reserved. Use of
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
// this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
//
Expand Down
Loading