22 changes: 11 additions & 11 deletions shape.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func parseShapeOptions(opts *Shape) (*Shape, error) {
if opts == nil {
return nil, ErrParameterInvalid
}
if opts.Type == "" {
return nil, ErrParameterInvalid
}
if opts.Width == 0 {
opts.Width = defaultShapeSize
}
Expand All @@ -47,13 +50,13 @@ func parseShapeOptions(opts *Shape) (*Shape, error) {
}

// AddShape provides the method to add shape in a sheet by given worksheet
// index, shape format set (such as offset, scale, aspect ratio setting and
// print settings) and properties set. For example, add text box (rect shape)
// in Sheet1:
// name and shape format set (such as offset, scale, aspect ratio setting and
// print settings). For example, add text box (rect shape) in Sheet1:
//
// lineWidth := 1.2
// err := f.AddShape("Sheet1", "G6",
// err := f.AddShape("Sheet1",
// &excelize.Shape{
// Cell: "G6",
// Type: "rect",
// Line: excelize.ShapeLine{Color: "4286F4", Width: &lineWidth},
// Fill: excelize.Fill{Color: []string{"8EB9FF"}, Pattern: 1},
Expand Down Expand Up @@ -285,12 +288,12 @@ func parseShapeOptions(opts *Shape) (*Shape, error) {
// wavy
// wavyHeavy
// wavyDbl
func (f *File) AddShape(sheet, cell string, opts *Shape) error {
func (f *File) AddShape(sheet string, opts *Shape) error {
options, err := parseShapeOptions(opts)
if err != nil {
return err
}
// Read sheet data.
// Read sheet data
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
Expand All @@ -313,7 +316,7 @@ func (f *File) AddShape(sheet, cell string, opts *Shape) error {
f.addSheetDrawing(sheet, rID)
f.addSheetNameSpace(sheet, SourceRelationship)
}
if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil {
if err = f.addDrawingShape(sheet, drawingXML, opts.Cell, options); err != nil {
return err
}
return f.addContentTypePart(drawingID, "drawings")
Expand All @@ -326,13 +329,10 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *Shape) erro
if err != nil {
return err
}
colIdx := fromCol - 1
rowIdx := fromRow - 1

width := int(float64(opts.Width) * opts.Format.ScaleX)
height := int(float64(opts.Height) * opts.Format.ScaleY)

colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY,
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, fromCol, fromRow, opts.Format.OffsetX, opts.Format.OffsetY,
width, height)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
Expand Down
41 changes: 29 additions & 12 deletions shape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ func TestAddShape(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
shape := &Shape{
assert.NoError(t, f.AddShape("Sheet1", &Shape{
Cell: "A30",
Type: "rect",
Paragraph: []RichTextRun{
{Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
{Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
},
}
assert.NoError(t, f.AddShape("Sheet1", "A30", shape))
assert.NoError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}))
assert.NoError(t, f.AddShape("Sheet1", "C30", &Shape{Type: "rect"}))
assert.EqualError(t, f.AddShape("Sheet3", "H1",
}))
assert.NoError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}))
assert.NoError(t, f.AddShape("Sheet1", &Shape{Cell: "C30", Type: "rect"}))
assert.EqualError(t, f.AddShape("Sheet3",
&Shape{
Cell: "H1",
Type: "ellipseRibbon",
Line: ShapeLine{Color: "4286F4"},
Fill: Fill{Color: []string{"8EB9FF"}},
Expand All @@ -41,15 +42,24 @@ func TestAddShape(t *testing.T) {
},
},
), "sheet Sheet3 does not exist")
assert.EqualError(t, f.AddShape("Sheet3", "H1", nil), ErrParameterInvalid.Error())
assert.EqualError(t, f.AddShape("Sheet1", "A", shape), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AddShape("Sheet3", nil), ErrParameterInvalid.Error())
assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "A1"}), ErrParameterInvalid.Error())
assert.EqualError(t, f.AddShape("Sheet1", &Shape{
Cell: "A",
Type: "rect",
Paragraph: []RichTextRun{
{Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
{Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
},
}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape1.xlsx")))

// Test add first shape for given sheet
f = NewFile()
lineWidth := 1.2
assert.NoError(t, f.AddShape("Sheet1", "A1",
assert.NoError(t, f.AddShape("Sheet1",
&Shape{
Cell: "A1",
Type: "ellipseRibbon",
Line: ShapeLine{Color: "4286F4", Width: &lineWidth},
Fill: Fill{Color: []string{"8EB9FF"}},
Expand All @@ -69,16 +79,23 @@ func TestAddShape(t *testing.T) {
}))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
// Test add shape with invalid sheet name
assert.EqualError(t, f.AddShape("Sheet:1", "A30", shape), ErrSheetNameInvalid.Error())
assert.EqualError(t, f.AddShape("Sheet:1", &Shape{
Cell: "A30",
Type: "rect",
Paragraph: []RichTextRun{
{Text: "Rectangle", Font: &Font{Color: "CD5C5C"}},
{Text: "Shape", Font: &Font{Bold: true, Color: "2980B9"}},
},
}), ErrSheetNameInvalid.Error())
// Test add shape with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
// Test add shape with unsupported charset content types
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", &Shape{Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.AddShape("Sheet1", &Shape{Cell: "B30", Type: "rect", Paragraph: []RichTextRun{{Text: "Rectangle"}, {}}}), "XML syntax error on line 1: invalid UTF-8")
}

func TestAddDrawingShape(t *testing.T) {
Expand Down
81 changes: 62 additions & 19 deletions sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ func (f *File) NewSheet(sheet string) (int, error) {
func (f *File) contentTypesReader() (*xlsxTypes, error) {
if f.ContentTypes == nil {
f.ContentTypes = new(xlsxTypes)
f.ContentTypes.Lock()
defer f.ContentTypes.Unlock()
f.ContentTypes.mu.Lock()
defer f.ContentTypes.mu.Unlock()
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))).
Decode(f.ContentTypes); err != nil && err != io.EOF {
return f.ContentTypes, err
Expand Down Expand Up @@ -217,8 +217,8 @@ func (f *File) setContentTypes(partName, contentType string) error {
if err != nil {
return err
}
content.Lock()
defer content.Unlock()
content.mu.Lock()
defer content.mu.Unlock()
content.Overrides = append(content.Overrides, xlsxOverride{
PartName: partName,
ContentType: contentType,
Expand Down Expand Up @@ -615,8 +615,8 @@ func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) {
// relationships by given relationships ID in the file workbook.xml.rels.
func (f *File) deleteSheetFromWorkbookRels(rID string) string {
rels, _ := f.relsReader(f.getWorkbookRelsPath())
rels.Lock()
defer rels.Unlock()
rels.mu.Lock()
defer rels.mu.Unlock()
for k, v := range rels.Relationships {
if v.ID == rID {
rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...)
Expand All @@ -636,8 +636,8 @@ func (f *File) deleteSheetFromContentTypes(target string) error {
if err != nil {
return err
}
content.Lock()
defer content.Unlock()
content.mu.Lock()
defer content.mu.Unlock()
for k, v := range content.Overrides {
if v.PartName == target {
content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
Expand Down Expand Up @@ -772,7 +772,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
}
}
var s []*xlsxSelection
for _, p := range panes.Panes {
for _, p := range panes.Selection {
s = append(s, &xlsxSelection{
ActiveCell: p.ActiveCell,
Pane: p.Pane,
Expand Down Expand Up @@ -859,7 +859,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
// YSplit: 0,
// TopLeftCell: "B1",
// ActivePane: "topRight",
// Panes: []excelize.PaneOptions{
// Selection: []excelize.Selection{
// {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
// },
// })
Expand All @@ -874,7 +874,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
// YSplit: 9,
// TopLeftCell: "A34",
// ActivePane: "bottomLeft",
// Panes: []excelize.PaneOptions{
// Selection: []excelize.Selection{
// {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
// },
// })
Expand All @@ -889,7 +889,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
// YSplit: 1800,
// TopLeftCell: "N57",
// ActivePane: "bottomLeft",
// Panes: []excelize.PaneOptions{
// Selection: []excelize.Selection{
// {SQRef: "I36", ActiveCell: "I36"},
// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
// {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
Expand All @@ -908,6 +908,50 @@ func (f *File) SetPanes(sheet string, panes *Panes) error {
return ws.setPanes(panes)
}

// getPanes returns freeze panes, split panes, and views of the worksheet.
func (ws *xlsxWorksheet) getPanes() Panes {
var (
panes Panes
section []Selection
)
if ws.SheetViews == nil || len(ws.SheetViews.SheetView) < 1 {
return panes
}
sw := ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1]
for _, s := range sw.Selection {
if s != nil {
section = append(section, Selection{
SQRef: s.SQRef,
ActiveCell: s.ActiveCell,
Pane: s.Pane,
})
}
}
panes.Selection = section
if sw.Pane == nil {
return panes
}
panes.ActivePane = sw.Pane.ActivePane
if sw.Pane.State == "frozen" {
panes.Freeze = true
}
panes.TopLeftCell = sw.Pane.TopLeftCell
panes.XSplit = int(sw.Pane.XSplit)
panes.YSplit = int(sw.Pane.YSplit)
return panes
}

// GetPanes provides a function to get freeze panes, split panes, and worksheet
// views by given worksheet name.
func (f *File) GetPanes(sheet string) (Panes, error) {
var panes Panes
ws, err := f.workSheetReader(sheet)
if err != nil {
return panes, err
}
return ws.getPanes(), err
}

// GetSheetVisible provides a function to get worksheet visible by given worksheet
// name. For example, get visible state of Sheet1:
//
Expand Down Expand Up @@ -977,6 +1021,7 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string,
if sst, err = f.sharedStringsReader(); err != nil {
return
}
regex := regexp.MustCompile(value)
decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name)))
for {
var token xml.Token
Expand All @@ -1001,7 +1046,6 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string,
_ = decoder.DecodeElement(&colCell, &xmlElement)
val, _ := colCell.getValueFrom(f, sst, false)
if regSearch {
regex := regexp.MustCompile(value)
if !regex.MatchString(val) {
continue
}
Expand Down Expand Up @@ -1551,6 +1595,9 @@ func (f *File) SetDefinedName(definedName *DefinedName) error {
if definedName.Name == "" || definedName.RefersTo == "" {
return ErrParameterInvalid
}
if err := checkDefinedName(definedName.Name); err != nil {
return err
}
wb, err := f.workbookReader()
if err != nil {
return err
Expand Down Expand Up @@ -1839,9 +1886,7 @@ func (f *File) relsReader(path string) (*xlsxRelationships, error) {
// fillSheetData ensures there are enough rows, and columns in the chosen
// row to accept data. Missing rows are backfilled and given their row number
// Uses the last populated row as a hint for the size of the next row to add
func prepareSheetXML(ws *xlsxWorksheet, col int, row int) {
ws.Lock()
defer ws.Unlock()
func (ws *xlsxWorksheet) prepareSheetXML(col int, row int) {
rowCount := len(ws.SheetData.Row)
sizeHint := 0
var ht *float64
Expand Down Expand Up @@ -1875,9 +1920,7 @@ func fillColumns(rowData *xlsxRow, col, row int) {
}

// makeContiguousColumns make columns in specific rows as contiguous.
func makeContiguousColumns(ws *xlsxWorksheet, fromRow, toRow, colCount int) {
ws.Lock()
defer ws.Unlock()
func (ws *xlsxWorksheet) makeContiguousColumns(fromRow, toRow, colCount int) {
for ; fromRow < toRow; fromRow++ {
rowData := &ws.SheetData.Row[fromRow-1]
fillColumns(rowData, colCount, fromRow)
Expand Down
56 changes: 40 additions & 16 deletions sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,29 @@ func TestNewSheet(t *testing.T) {
assert.Equal(t, -1, sheetID)
}

func TestSetPanes(t *testing.T) {
func TestPanes(t *testing.T) {
f := NewFile()

assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false}))
_, err := f.NewSheet("Panes 2")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 2",
&Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},

expected := Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
))
}
assert.NoError(t, f.SetPanes("Panes 2", &expected))
panes, err := f.GetPanes("Panes 2")
assert.NoError(t, err)
assert.Equal(t, expected, panes)

_, err = f.NewSheet("Panes 3")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 3",
Expand All @@ -66,7 +70,7 @@ func TestSetPanes(t *testing.T) {
YSplit: 1800,
TopLeftCell: "N57",
ActivePane: "bottomLeft",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "I36", ActiveCell: "I36"},
{SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
{SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
Expand All @@ -84,7 +88,7 @@ func TestSetPanes(t *testing.T) {
YSplit: 9,
TopLeftCell: "A34",
ActivePane: "bottomLeft",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
},
},
Expand All @@ -94,6 +98,26 @@ func TestSetPanes(t *testing.T) {
// Test set panes with invalid sheet name
assert.EqualError(t, f.SetPanes("Sheet:1", &Panes{Freeze: false, Split: false}), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))

// Test get panes with empty sheet views
f = NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{}
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes without panes
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}}
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes without sheet views
ws.(*xlsxWorksheet).SheetViews = nil
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes on not exists worksheet
_, err = f.GetPanes("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")

// Test add pane on empty sheet views worksheet
f = NewFile()
f.checked = nil
Expand All @@ -107,7 +131,7 @@ func TestSetPanes(t *testing.T) {
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
},
Expand Down
11 changes: 8 additions & 3 deletions sheetpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (ws *xlsxWorksheet) setSheetProps(opts *SheetPropsOptions) {
prepareTabColor := func(ws *xlsxWorksheet) {
ws.prepareSheetPr()
if ws.SheetPr.TabColor == nil {
ws.SheetPr.TabColor = new(xlsxTabColor)
ws.SheetPr.TabColor = new(xlsxColor)
}
}
if opts.CodeName != nil {
Expand Down Expand Up @@ -145,7 +145,12 @@ func (ws *xlsxWorksheet) setSheetProps(opts *SheetPropsOptions) {
if !s.Field(i).IsNil() {
prepareTabColor(ws)
name := s.Type().Field(i).Name
reflect.ValueOf(ws.SheetPr.TabColor).Elem().FieldByName(name[8:]).Set(s.Field(i).Elem())
fld := reflect.ValueOf(ws.SheetPr.TabColor).Elem().FieldByName(name[8:])
if s.Field(i).Kind() == reflect.Ptr && fld.Kind() == reflect.Ptr {
fld.Set(s.Field(i))
continue
}
fld.Set(s.Field(i).Elem())
}
}
}
Expand Down Expand Up @@ -206,7 +211,7 @@ func (f *File) GetSheetProps(sheet string) (SheetPropsOptions, error) {
if ws.SheetPr.TabColor != nil {
opts.TabColorIndexed = intPtr(ws.SheetPr.TabColor.Indexed)
opts.TabColorRGB = stringPtr(ws.SheetPr.TabColor.RGB)
opts.TabColorTheme = intPtr(ws.SheetPr.TabColor.Theme)
opts.TabColorTheme = ws.SheetPr.TabColor.Theme
opts.TabColorTint = float64Ptr(ws.SheetPr.TabColor.Tint)
}
}
Expand Down
6 changes: 1 addition & 5 deletions sheetview.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,7 @@ func (view *xlsxSheetView) setSheetView(opts *ViewOptions) {
view.TopLeftCell = *opts.TopLeftCell
}
if opts.View != nil {
if _, ok := map[string]interface{}{
"normal": nil,
"pageLayout": nil,
"pageBreakPreview": nil,
}[*opts.View]; ok {
if inStrSlice([]string{"normal", "pageLayout", "pageBreakPreview"}, *opts.View, true) != -1 {
view.View = *opts.View
}
}
Expand Down
520 changes: 260 additions & 260 deletions sparkline.go

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ type StreamWriter struct {
tableParts string
}

// NewStreamWriter return stream writer struct by given worksheet name for
// generate new worksheet with large amounts of data. Note that after set
// rows, you must call the 'Flush' method to end the streaming writing process
// and ensure that the order of row numbers is ascending, the normal mode
// functions and stream mode functions can't be work mixed to writing data on
// the worksheets, you can't get cell value when in-memory chunks data over
// 16MB. For example, set data for worksheet of size 102400 rows x 50 columns
// with numbers and style:
// NewStreamWriter returns stream writer struct by given worksheet name used for
// writing data on a new existing empty worksheet with large amounts of data.
// Note that after writing data with the stream writer for the worksheet, you
// must call the 'Flush' method to end the streaming writing process, ensure
// that the order of row numbers is ascending when set rows, and the normal
// mode functions and stream mode functions can not be work mixed to writing
// data on the worksheets. The stream writer will try to use temporary files on
// disk to reduce the memory usage when in-memory chunks data over 16MB, and
// you can't get cell value at this time. For example, set data for worksheet
// of size 102400 rows x 50 columns with numbers and style:
//
// f := excelize.NewFile()
// defer func() {
Expand Down
68 changes: 33 additions & 35 deletions stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func TestStreamSetPanes(t *testing.T) {
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
}
Expand Down Expand Up @@ -223,7 +223,7 @@ func TestStreamTable(t *testing.T) {
assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A:B1"}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A1:B"}), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add table with invalid table name
assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A:B1", Name: "1Table"}), newInvalidTableNameError("1Table").Error())
assert.EqualError(t, streamWriter.AddTable(&Table{Range: "A:B1", Name: "1Table"}), newInvalidNameError("1Table").Error())
// Test add table with unsupported charset content types
file.ContentTypes = nil
file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
Expand Down Expand Up @@ -337,11 +337,9 @@ func TestStreamSetRowWithStyle(t *testing.T) {

ws, err := file.workSheetReader("Sheet1")
assert.NoError(t, err)
assert.Equal(t, grayStyleID, ws.SheetData.Row[0].C[0].S)
assert.Equal(t, zeroStyleID, ws.SheetData.Row[0].C[1].S)
assert.Equal(t, zeroStyleID, ws.SheetData.Row[0].C[2].S)
assert.Equal(t, blueStyleID, ws.SheetData.Row[0].C[3].S)
assert.Equal(t, blueStyleID, ws.SheetData.Row[0].C[4].S)
for colIdx, expected := range []int{grayStyleID, zeroStyleID, zeroStyleID, blueStyleID, blueStyleID} {
assert.Equal(t, expected, ws.SheetData.Row[0].C[colIdx].S)
}
}

func TestStreamSetCellValFunc(t *testing.T) {
Expand All @@ -352,25 +350,29 @@ func TestStreamSetCellValFunc(t *testing.T) {
sw, err := f.NewStreamWriter("Sheet1")
assert.NoError(t, err)
c := &xlsxC{}
assert.NoError(t, sw.setCellValFunc(c, 128))
assert.NoError(t, sw.setCellValFunc(c, int8(-128)))
assert.NoError(t, sw.setCellValFunc(c, int16(-32768)))
assert.NoError(t, sw.setCellValFunc(c, int32(-2147483648)))
assert.NoError(t, sw.setCellValFunc(c, int64(-9223372036854775808)))
assert.NoError(t, sw.setCellValFunc(c, uint(128)))
assert.NoError(t, sw.setCellValFunc(c, uint8(255)))
assert.NoError(t, sw.setCellValFunc(c, uint16(65535)))
assert.NoError(t, sw.setCellValFunc(c, uint32(4294967295)))
assert.NoError(t, sw.setCellValFunc(c, uint64(18446744073709551615)))
assert.NoError(t, sw.setCellValFunc(c, float32(100.1588)))
assert.NoError(t, sw.setCellValFunc(c, 100.1588))
assert.NoError(t, sw.setCellValFunc(c, " Hello"))
assert.NoError(t, sw.setCellValFunc(c, []byte(" Hello")))
assert.NoError(t, sw.setCellValFunc(c, time.Now().UTC()))
assert.NoError(t, sw.setCellValFunc(c, time.Duration(1e13)))
assert.NoError(t, sw.setCellValFunc(c, true))
assert.NoError(t, sw.setCellValFunc(c, nil))
assert.NoError(t, sw.setCellValFunc(c, complex64(5+10i)))
for _, val := range []interface{}{
128,
int8(-128),
int16(-32768),
int32(-2147483648),
int64(-9223372036854775808),
uint(128),
uint8(255),
uint16(65535),
uint32(4294967295),
uint64(18446744073709551615),
float32(100.1588),
100.1588,
" Hello",
[]byte(" Hello"),
time.Now().UTC(),
time.Duration(1e13),
true,
nil,
complex64(5 + 10i),
} {
assert.NoError(t, sw.setCellValFunc(c, val))
}
}

func TestStreamWriterOutlineLevel(t *testing.T) {
Expand All @@ -389,14 +391,10 @@ func TestStreamWriterOutlineLevel(t *testing.T) {

file, err = OpenFile(filepath.Join("test", "TestStreamWriterSetRowOutlineLevel.xlsx"))
assert.NoError(t, err)
level, err := file.GetRowOutlineLevel("Sheet1", 1)
assert.NoError(t, err)
assert.Equal(t, uint8(1), level)
level, err = file.GetRowOutlineLevel("Sheet1", 2)
assert.NoError(t, err)
assert.Equal(t, uint8(7), level)
level, err = file.GetRowOutlineLevel("Sheet1", 3)
assert.NoError(t, err)
assert.Equal(t, uint8(0), level)
for rowIdx, expected := range []uint8{1, 7, 0} {
level, err := file.GetRowOutlineLevel("Sheet1", rowIdx+1)
assert.NoError(t, err)
assert.Equal(t, expected, level)
}
assert.NoError(t, file.Close())
}
1,581 changes: 442 additions & 1,139 deletions styles.go

Large diffs are not rendered by default.

126 changes: 121 additions & 5 deletions styles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,7 @@ func TestNewStyle(t *testing.T) {
// Test create currency custom style
f.Styles.NumFmts = nil
styleID, err = f.NewStyle(&Style{
Lang: "ko-kr",
NumFmt: 32, // must not be in currencyNumFmt

})
assert.NoError(t, err)
assert.Equal(t, 3, styleID)
Expand Down Expand Up @@ -330,14 +328,14 @@ func TestNewStyle(t *testing.T) {
f = NewFile()
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = nil
style4, err := f.NewStyle(&Style{NumFmt: 160, Lang: "unknown"})
style4, err := f.NewStyle(&Style{NumFmt: 160})
assert.NoError(t, err)
assert.Equal(t, 0, style4)

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

Expand All @@ -357,10 +355,26 @@ func TestNewStyle(t *testing.T) {

func TestNewConditionalStyle(t *testing.T) {
f := NewFile()
_, err := f.NewConditionalStyle(&Style{Protection: &Protection{Hidden: true, Locked: true}})
assert.NoError(t, err)
_, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(4), NumFmt: 165, NegRed: true})
assert.NoError(t, err)
_, err = f.NewConditionalStyle(&Style{DecimalPlaces: intPtr(-1)})
assert.NoError(t, err)
_, err = f.NewConditionalStyle(&Style{NumFmt: 1})
assert.NoError(t, err)
_, err = f.NewConditionalStyle(&Style{NumFmt: 27})
assert.NoError(t, err)
numFmt := "general"
_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt})
assert.NoError(t, err)
numFmt1 := "0.00"
_, err = f.NewConditionalStyle(&Style{CustomNumFmt: &numFmt1})
assert.NoError(t, err)
// Test create conditional style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err := f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
_, err = f.NewConditionalStyle(&Style{Font: &Font{Color: "9A0511"}, Fill: Fill{Type: "pattern", Color: []string{"FEC7CE"}, Pattern: 1}})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}

Expand Down Expand Up @@ -474,3 +488,105 @@ func TestGetNumFmtID(t *testing.T) {
assert.NotEqual(t, id1, id2)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestStyleNumFmt.xlsx")))
}

func TestGetThemeColor(t *testing.T) {
assert.Empty(t, (&File{}).getThemeColor(&xlsxColor{}))
f := NewFile()
assert.Empty(t, f.getThemeColor(nil))
var theme int
assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{Theme: &theme}))
assert.Equal(t, "FFFFFF", f.getThemeColor(&xlsxColor{RGB: "FFFFFF"}))
assert.Equal(t, "FF8080", f.getThemeColor(&xlsxColor{Indexed: 2, Tint: 0.5}))
assert.Empty(t, f.getThemeColor(&xlsxColor{Indexed: len(IndexedColorMapping), Tint: 0.5}))
}

func TestGetStyle(t *testing.T) {
f := NewFile()
expected := &Style{
Border: []Border{
{Type: "left", Color: "0000FF", Style: 3},
{Type: "right", Color: "FF0000", Style: 6},
{Type: "top", Color: "00FF00", Style: 4},
{Type: "bottom", Color: "FFFF00", Style: 5},
{Type: "diagonalUp", Color: "A020F0", Style: 7},
{Type: "diagonalDown", Color: "A020F0", Style: 7},
},
Fill: Fill{Type: "gradient", Shading: 16, Color: []string{"0000FF", "00FF00"}},
Font: &Font{
Bold: true, Italic: true, Underline: "single", Family: "Arial",
Size: 8.5, Strike: true, Color: "777777", ColorIndexed: 1, ColorTint: 0.1,
},
Alignment: &Alignment{
Horizontal: "center",
Indent: 1,
JustifyLastLine: true,
ReadingOrder: 1,
RelativeIndent: 1,
ShrinkToFit: true,
TextRotation: 180,
Vertical: "center",
WrapText: true,
},
Protection: &Protection{Hidden: true, Locked: true},
NumFmt: 49,
}
styleID, err := f.NewStyle(expected)
assert.NoError(t, err)
style, err := f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.Border, style.Border)
assert.Equal(t, expected.Fill, style.Fill)
assert.Equal(t, expected.Font, style.Font)
assert.Equal(t, expected.Alignment, style.Alignment)
assert.Equal(t, expected.Protection, style.Protection)
assert.Equal(t, expected.NumFmt, style.NumFmt)

expected = &Style{
Fill: Fill{Type: "pattern", Pattern: 1, Color: []string{"0000FF"}},
}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.Fill, style.Fill)

expected = &Style{NumFmt: 27}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)

expected = &Style{NumFmt: 165}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)

expected = &Style{NumFmt: 165, NegRed: true}
styleID, err = f.NewStyle(expected)
assert.NoError(t, err)
style, err = f.GetStyle(styleID)
assert.NoError(t, err)
assert.Equal(t, expected.NumFmt, style.NumFmt)

// Test get style with custom color index
f.Styles.Colors = &xlsxStyleColors{
IndexedColors: xlsxIndexedColors{
RgbColor: []xlsxColor{{RGB: "FF012345"}},
},
}
assert.Equal(t, "012345", f.getThemeColor(&xlsxColor{Indexed: 0}))

// Test get style with invalid style index
style, err = f.GetStyle(-1)
assert.Nil(t, style)
assert.Equal(t, err, newInvalidStyleID(-1))
// Test get style with unsupported charset style sheet
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
style, err = f.GetStyle(1)
assert.Nil(t, style)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
186 changes: 145 additions & 41 deletions table.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,24 @@
package excelize

import (
"bytes"
"encoding/xml"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)

var (
expressionFormat = regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
conditionFormat = regexp.MustCompile(`(or|\|\|)`)
blankFormat = regexp.MustCompile("blanks|nonblanks")
matchFormat = regexp.MustCompile("[*?]")
)

// parseTableOptions provides a function to parse the format settings of the
// table with default value.
func parseTableOptions(opts *Table) (*Table, error) {
Expand All @@ -31,7 +40,7 @@ func parseTableOptions(opts *Table) (*Table, error) {
if opts.ShowRowStripes == nil {
opts.ShowRowStripes = boolPtr(true)
}
if err = checkTableName(opts.Name); err != nil {
if err = checkDefinedName(opts.Name); err != nil {
return opts, err
}
return opts, err
Expand Down Expand Up @@ -75,6 +84,23 @@ func (f *File) AddTable(sheet string, table *Table) error {
if err != nil {
return err
}
var exist bool
f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k.(string), "xl/tables/table") {
var t xlsxTable
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(v.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return true
}
if exist = t.Name == options.Name; exist {
return false
}
}
return true
})
if exist {
return ErrExistsTableName
}
// Coordinate conversion, convert C1:B3 to 2,0,1,2.
coordinates, err := rangeRefToCoordinates(options.Range)
if err != nil {
Expand All @@ -99,6 +125,91 @@ func (f *File) AddTable(sheet string, table *Table) error {
return f.addContentTypePart(tableID, "table")
}

// GetTables provides the method to get all tables in a worksheet by given
// worksheet name.
func (f *File) GetTables(sheet string) ([]Table, error) {
var tables []Table
ws, err := f.workSheetReader(sheet)
if err != nil {
return tables, err
}
if ws.TableParts == nil {
return tables, err
}
for _, tbl := range ws.TableParts.TableParts {
if tbl != nil {
target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
tableXML := strings.ReplaceAll(target, "..", "xl")
content, ok := f.Pkg.Load(tableXML)
if !ok {
continue
}
var t xlsxTable
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return tables, err
}
table := Table{
rID: tbl.RID,
Range: t.Ref,
Name: t.Name,
}
if t.TableStyleInfo != nil {
table.StyleName = t.TableStyleInfo.Name
table.ShowColumnStripes = t.TableStyleInfo.ShowColumnStripes
table.ShowFirstColumn = t.TableStyleInfo.ShowFirstColumn
table.ShowLastColumn = t.TableStyleInfo.ShowLastColumn
table.ShowRowStripes = &t.TableStyleInfo.ShowRowStripes
}
tables = append(tables, table)
}
}
return tables, err
}

// DeleteTable provides the method to delete table by given table name.
func (f *File) DeleteTable(name string) error {
if err := checkDefinedName(name); err != nil {
return err
}
for _, sheet := range f.GetSheetList() {
tables, err := f.GetTables(sheet)
if err != nil {
return err
}
for _, table := range tables {
if table.Name != name {
continue
}
ws, _ := f.workSheetReader(sheet)
for i, tbl := range ws.TableParts.TableParts {
if tbl.RID == table.rID {
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:i], ws.TableParts.TableParts[i+1:]...)
f.deleteSheetRelationships(sheet, tbl.RID)
break
}
}
if ws.TableParts.Count = len(ws.TableParts.TableParts); ws.TableParts.Count == 0 {
ws.TableParts = nil
}
// Delete cell value in the table header
coordinates, err := rangeRefToCoordinates(table.Range)
if err != nil {
return err
}
_ = sortCoordinates(coordinates)
for col := coordinates[0]; col <= coordinates[2]; col++ {
for row := coordinates[1]; row < coordinates[1]+1; row++ {
cell, _ := CoordinatesToCellName(col, row)
err = f.SetCellValue(sheet, cell, nil)
}
}
return err
}
}
return newNoExistTableError(name)
}

// countTables provides a function to get table files count storage in the
// folder xl/tables.
func (f *File) countTables() int {
Expand Down Expand Up @@ -163,13 +274,13 @@ func (f *File) setTableHeader(sheet string, showHeaderRow bool, x1, y1, x2 int)
return tableColumns, nil
}

// checkSheetName check whether there are illegal characters in the table name.
// Verify that the name:
// checkDefinedName check whether there are illegal characters in the defined
// name or table name. Verify that the name:
// 1. Starts with a letter or underscore (_)
// 2. Doesn't include a space or character that isn't allowed
func checkTableName(name string) error {
func checkDefinedName(name string) error {
if utf8.RuneCountInString(name) > MaxFieldLength {
return ErrTableNameLength
return ErrNameLength
}
for i, c := range name {
if string(c) == "_" {
Expand All @@ -181,7 +292,7 @@ func checkTableName(name string) error {
if i > 0 && unicode.IsDigit(c) {
continue
}
return newInvalidTableNameError(name)
return newInvalidNameError(name)
}
return nil
}
Expand Down Expand Up @@ -294,7 +405,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
// x == *b // ends with b
// x != *b // doesn't end with b
// x == *b* // contains b
// x != *b* // doesn't contains b
// x != *b* // doesn't contain b
//
// You can also use '*' to match any character or number and '?' to match any
// single character or number. No other regular expression quantifier is
Expand Down Expand Up @@ -381,8 +492,7 @@ func (f *File) autoFilter(sheet, ref string, columns, col int, opts []AutoFilter
return fmt.Errorf("incorrect index of column '%s'", opt.Column)
}
fc := &xlsxFilterColumn{ColID: offset}
re := regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
token := re.FindAllString(opt.Expression, -1)
token := expressionFormat.FindAllString(opt.Expression, -1)
if len(token) != 3 && len(token) != 7 {
return fmt.Errorf("incorrect number of tokens in criteria '%s'", opt.Expression)
}
Expand All @@ -405,22 +515,23 @@ func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string)
var filters []*xlsxFilter
filters = append(filters, &xlsxFilter{Val: tokens[0]})
fc.Filters = &xlsxFilters{Filter: filters}
} else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
return
}
if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
// Double equality with "or" operator.
var filters []*xlsxFilter
for _, v := range tokens {
filters = append(filters, &xlsxFilter{Val: v})
}
fc.Filters = &xlsxFilters{Filter: filters}
} else {
// Non default custom filter.
expRel := map[int]int{0: 0, 1: 2}
andRel := map[int]bool{0: true, 1: false}
for k, v := range tokens {
f.writeCustomFilter(fc, exp[expRel[k]], v)
if k == 1 {
fc.CustomFilters.And = andRel[exp[k]]
}
return
}
// Non default custom filter.
expRel, andRel := map[int]int{0: 0, 1: 2}, map[int]bool{0: true, 1: false}
for k, v := range tokens {
f.writeCustomFilter(fc, exp[expRel[k]], v)
if k == 1 {
fc.CustomFilters.And = andRel[exp[k]]
}
}
}
Expand All @@ -442,11 +553,11 @@ func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string)
}
if fc.CustomFilters != nil {
fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter)
} else {
var customFilters []*xlsxCustomFilter
customFilters = append(customFilters, &customFilter)
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
return
}
var customFilters []*xlsxCustomFilter
customFilters = append(customFilters, &customFilter)
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
}

// parseFilterExpression provides a function to converts the tokens of a
Expand All @@ -463,10 +574,8 @@ func (f *File) parseFilterExpression(expression string, tokens []string) ([]int,
if len(tokens) == 7 {
// The number of tokens will be either 3 (for 1 expression) or 7 (for 2
// expressions).
conditional := 0
c := tokens[3]
re, _ := regexp.Match(`(or|\|\|)`, []byte(c))
if re {
conditional, c := 0, tokens[3]
if conditionFormat.MatchString(c) {
conditional = 1
}
expression1, token1, err := f.parseFilterTokens(expression, tokens[:3])
Expand All @@ -477,17 +586,13 @@ func (f *File) parseFilterExpression(expression string, tokens []string) ([]int,
if err != nil {
return expressions, t, err
}
expressions = []int{expression1[0], conditional, expression2[0]}
t = []string{token1, token2}
} else {
exp, token, err := f.parseFilterTokens(expression, tokens)
if err != nil {
return expressions, t, err
}
expressions = exp
t = []string{token}
return []int{expression1[0], conditional, expression2[0]}, []string{token1, token2}, nil
}
exp, token, err := f.parseFilterTokens(expression, tokens)
if err != nil {
return expressions, t, err
}
return expressions, t, nil
return exp, []string{token}, nil
}

// parseFilterTokens provides a function to parse the 3 tokens of a filter
Expand All @@ -510,11 +615,11 @@ func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, str
operator, ok := operators[strings.ToLower(tokens[1])]
if !ok {
// Convert the operator from a number to a descriptive string.
return []int{}, "", fmt.Errorf("unknown operator: %s", tokens[1])
return []int{}, "", newUnknownFilterTokenError(tokens[1])
}
token := tokens[2]
// Special handling for Blanks/NonBlanks.
re, _ := regexp.Match("blanks|nonblanks", []byte(strings.ToLower(token)))
re := blankFormat.MatchString(strings.ToLower(token))
if re {
// Only allow Equals or NotEqual in this context.
if operator != 2 && operator != 5 {
Expand All @@ -539,8 +644,7 @@ func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, str
}
// If the string token contains an Excel match character then change the
// operator type to indicate a non "simple" equality.
re, _ = regexp.Match("[*?]", []byte(token))
if operator == 2 && re {
if re = matchFormat.MatchString(token); operator == 2 && re {
operator = 22
}
return []int{operator}, token, nil
Expand Down
73 changes: 64 additions & 9 deletions table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ func TestAddTable(t *testing.T) {
ShowHeaderRow: boolPtr(false),
}))
assert.NoError(t, f.AddTable("Sheet2", &Table{Range: "F1:F1", StyleName: "TableStyleMedium8"}))
// Test get tables in worksheet
tables, err := f.GetTables("Sheet2")
assert.Len(t, tables, 3)
assert.NoError(t, err)

// Test add table with already exist table name
assert.Equal(t, f.AddTable("Sheet2", &Table{Name: "Table1"}), ErrExistsTableName)
// Test add table with invalid table options
assert.Equal(t, f.AddTable("Sheet1", nil), ErrParameterInvalid)
// Test add table in not exist worksheet
Expand All @@ -44,25 +50,74 @@ func TestAddTable(t *testing.T) {
f = NewFile()
assert.EqualError(t, f.addTable("sheet1", "", 0, 0, 0, 0, 0, nil), "invalid cell reference [0, 0]")
assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell reference [0, 0]")
// Test add table with invalid table name
// Test set defined name and add table with invalid name
for _, cases := range []struct {
name string
err error
}{
{name: "1Table", err: newInvalidTableNameError("1Table")},
{name: "-Table", err: newInvalidTableNameError("-Table")},
{name: "'Table", err: newInvalidTableNameError("'Table")},
{name: "Table 1", err: newInvalidTableNameError("Table 1")},
{name: "A&B", err: newInvalidTableNameError("A&B")},
{name: "_1Table'", err: newInvalidTableNameError("_1Table'")},
{name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidTableNameError("\u0f5f\u0fb3\u0f0b\u0f21")},
{name: strings.Repeat("c", MaxFieldLength+1), err: ErrTableNameLength},
{name: "1Table", err: newInvalidNameError("1Table")},
{name: "-Table", err: newInvalidNameError("-Table")},
{name: "'Table", err: newInvalidNameError("'Table")},
{name: "Table 1", err: newInvalidNameError("Table 1")},
{name: "A&B", err: newInvalidNameError("A&B")},
{name: "_1Table'", err: newInvalidNameError("_1Table'")},
{name: "\u0f5f\u0fb3\u0f0b\u0f21", err: newInvalidNameError("\u0f5f\u0fb3\u0f0b\u0f21")},
{name: strings.Repeat("c", MaxFieldLength+1), err: ErrNameLength},
} {
assert.EqualError(t, f.AddTable("Sheet1", &Table{
Range: "A1:B2",
Name: cases.name,
}), cases.err.Error())
assert.EqualError(t, f.SetDefinedName(&DefinedName{
Name: cases.name, RefersTo: "Sheet1!$A$2:$D$5",
}), cases.err.Error())
}
// Test check duplicate table name with unsupported charset table parts
f = NewFile()
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B2"}))
}

func TestGetTables(t *testing.T) {
f := NewFile()
// Test get tables in none table worksheet
tables, err := f.GetTables("Sheet1")
assert.Len(t, tables, 0)
assert.NoError(t, err)
// Test get tables in not exist worksheet
_, err = f.GetTables("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test adjust table with unsupported charset
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21"}))
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
_, err = f.GetTables("Sheet1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test adjust table with no exist table parts
f.Pkg.Delete("xl/tables/table1.xml")
tables, err = f.GetTables("Sheet1")
assert.Len(t, tables, 0)
assert.NoError(t, err)
}

func TestDeleteTable(t *testing.T) {
f := NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B4", Name: "Table1"}))
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "B26:A21", Name: "Table2"}))
assert.NoError(t, f.DeleteTable("Table2"))
assert.NoError(t, f.DeleteTable("Table1"))
// Test delete table with invalid table name
assert.EqualError(t, f.DeleteTable("Table 1"), newInvalidNameError("Table 1").Error())
// Test delete table with no exist table name
assert.EqualError(t, f.DeleteTable("Table"), newNoExistTableError("Table").Error())
// Test delete table with unsupported charset
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteTable("Table1"), "XML syntax error on line 1: invalid UTF-8")
// Test delete table with invalid table range
f = NewFile()
assert.NoError(t, f.AddTable("Sheet1", &Table{Range: "A1:B4", Name: "Table1"}))
f.Pkg.Store("xl/tables/table1.xml", []byte("<table name=\"Table1\" ref=\"-\" />"))
assert.EqualError(t, f.DeleteTable("Table1"), ErrParameterInvalid.Error())
}

func TestSetTableHeader(t *testing.T) {
Expand Down
Binary file modified test/vbaProject.bin
Binary file not shown.
1,063 changes: 1,063 additions & 0 deletions vml.go

Large diffs are not rendered by default.

211 changes: 185 additions & 26 deletions vmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ type vmlDrawing struct {
XMLNSo string `xml:"xmlns:o,attr"`
XMLNSx string `xml:"xmlns:x,attr"`
XMLNSmv string `xml:"xmlns:mv,attr"`
Shapelayout *xlsxShapelayout `xml:"o:shapelayout"`
Shapetype *xlsxShapetype `xml:"v:shapetype"`
ShapeLayout *xlsxShapeLayout `xml:"o:shapelayout"`
ShapeType *xlsxShapeType `xml:"v:shapetype"`
Shape []xlsxShape `xml:"v:shape"`
}

// xlsxShapelayout directly maps the shapelayout element. This element contains
// xlsxShapeLayout directly maps the shapelayout element. This element contains
// child elements that store information used in the editing and layout of
// shapes.
type xlsxShapelayout struct {
type xlsxShapeLayout struct {
Ext string `xml:"v:ext,attr"`
IDmap *xlsxIDmap `xml:"o:idmap"`
}
Expand All @@ -46,16 +46,19 @@ type xlsxShape struct {
ID string `xml:"id,attr"`
Type string `xml:"type,attr"`
Style string `xml:"style,attr"`
Fillcolor string `xml:"fillcolor,attr"`
Insetmode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"`
Strokecolor string `xml:"strokecolor,attr,omitempty"`
Button string `xml:"o:button,attr,omitempty"`
Filled string `xml:"filled,attr,omitempty"`
FillColor string `xml:"fillcolor,attr,omitempty"`
InsetMode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"`
Stroked string `xml:"stroked,attr,omitempty"`
StrokeColor string `xml:"strokecolor,attr,omitempty"`
Val string `xml:",innerxml"`
}

// xlsxShapetype directly maps the shapetype element.
type xlsxShapetype struct {
// xlsxShapeType directly maps the shapetype element.
type xlsxShapeType struct {
ID string `xml:"id,attr"`
Coordsize string `xml:"coordsize,attr"`
CoordSize string `xml:"coordsize,attr"`
Spt int `xml:"o:spt,attr"`
Path string `xml:"path,attr"`
Stroke *xlsxStroke `xml:"v:stroke"`
Expand All @@ -64,13 +67,13 @@ type xlsxShapetype struct {

// xlsxStroke directly maps the stroke element.
type xlsxStroke struct {
Joinstyle string `xml:"joinstyle,attr"`
JoinStyle string `xml:"joinstyle,attr"`
}

// vPath directly maps the v:path element.
type vPath struct {
Gradientshapeok string `xml:"gradientshapeok,attr,omitempty"`
Connecttype string `xml:"o:connecttype,attr"`
GradientShapeOK string `xml:"gradientshapeok,attr,omitempty"`
ConnectType string `xml:"o:connecttype,attr"`
}

// vFill directly maps the v:fill element. This element must be defined within a
Expand All @@ -96,16 +99,24 @@ type vShadow struct {
Obscured string `xml:"obscured,attr"`
}

// vTextbox directly maps the v:textbox element. This element must be defined
// vTextBox directly maps the v:textbox element. This element must be defined
// within a Shape element.
type vTextbox struct {
type vTextBox struct {
Style string `xml:"style,attr"`
Div *xlsxDiv `xml:"div"`
}

// xlsxDiv directly maps the div element.
type xlsxDiv struct {
Style string `xml:"style,attr"`
Style string `xml:"style,attr"`
Font []vmlFont `xml:"font"`
}

type vmlFont struct {
Face string `xml:"face,attr,omitempty"`
Size uint `xml:"size,attr,omitempty"`
Color string `xml:"color,attr,omitempty"`
Content string `xml:",innerxml"`
}

// xClientData (Attached Object Data) directly maps the x:ClientData element.
Expand All @@ -116,31 +127,179 @@ type xlsxDiv struct {
// child elements is appropriate. Relevant groups are identified for each child
// element.
type xClientData struct {
ObjectType string `xml:"ObjectType,attr"`
MoveWithCells string `xml:"x:MoveWithCells,omitempty"`
SizeWithCells string `xml:"x:SizeWithCells,omitempty"`
Anchor string `xml:"x:Anchor"`
AutoFill string `xml:"x:AutoFill"`
Row int `xml:"x:Row"`
Column int `xml:"x:Column"`
ObjectType string `xml:"ObjectType,attr"`
MoveWithCells *string `xml:"x:MoveWithCells"`
SizeWithCells *string `xml:"x:SizeWithCells"`
Anchor string `xml:"x:Anchor"`
Locked string `xml:"x:Locked,omitempty"`
PrintObject string `xml:"x:PrintObject,omitempty"`
AutoFill string `xml:"x:AutoFill,omitempty"`
FmlaMacro string `xml:"x:FmlaMacro,omitempty"`
TextHAlign string `xml:"x:TextHAlign,omitempty"`
TextVAlign string `xml:"x:TextVAlign,omitempty"`
Row *int `xml:"x:Row"`
Column *int `xml:"x:Column"`
Checked int `xml:"x:Checked,omitempty"`
FmlaLink string `xml:"x:FmlaLink,omitempty"`
NoThreeD *string `xml:"x:NoThreeD"`
FirstButton *string `xml:"x:FirstButton"`
Val uint `xml:"x:Val,omitempty"`
Min uint `xml:"x:Min,omitempty"`
Max uint `xml:"x:Max,omitempty"`
Inc uint `xml:"x:Inc,omitempty"`
Page uint `xml:"x:Page,omitempty"`
Horiz *string `xml:"x:Horiz"`
Dx uint `xml:"x:Dx,omitempty"`
}

// decodeVmlDrawing defines the structure used to parse the file
// xl/drawings/vmlDrawing%d.vml.
type decodeVmlDrawing struct {
Shape []decodeShape `xml:"urn:schemas-microsoft-com:vml shape"`
ShapeType decodeShapeType `xml:"urn:schemas-microsoft-com:vml shapetype"`
Shape []decodeShape `xml:"urn:schemas-microsoft-com:vml shape"`
}

// decodeShapeType defines the structure used to parse the shapetype element in
// the file xl/drawings/vmlDrawing%d.vml.
type decodeShapeType struct {
ID string `xml:"id,attr"`
CoordSize string `xml:"coordsize,attr"`
Spt int `xml:"spt,attr"`
Path string `xml:"path,attr"`
}

// decodeShape defines the structure used to parse the particular shape element.
type decodeShape struct {
Val string `xml:",innerxml"`
ID string `xml:"id,attr"`
Type string `xml:"type,attr"`
Style string `xml:"style,attr"`
Button string `xml:"button,attr,omitempty"`
Filled string `xml:"filled,attr,omitempty"`
FillColor string `xml:"fillcolor,attr,omitempty"`
InsetMode string `xml:"urn:schemas-microsoft-com:office:office insetmode,attr,omitempty"`
Stroked string `xml:"stroked,attr,omitempty"`
StrokeColor string `xml:"strokecolor,attr,omitempty"`
Val string `xml:",innerxml"`
}

// decodeShapeVal defines the structure used to parse the sub-element of the
// shape in the file xl/drawings/vmlDrawing%d.vml.
type decodeShapeVal struct {
TextBox decodeVMLTextBox `xml:"textbox"`
ClientData decodeVMLClientData `xml:"ClientData"`
}

// decodeVMLFontU defines the structure used to parse the u element in the VML.
type decodeVMLFontU struct {
Class string `xml:"class,attr"`
Val string `xml:",chardata"`
}

// decodeVMLFontI defines the structure used to parse the i element in the VML.
type decodeVMLFontI struct {
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}

// decodeVMLFontB defines the structure used to parse the b element in the VML.
type decodeVMLFontB struct {
I *decodeVMLFontI `xml:"i"`
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}

// decodeVMLFont defines the structure used to parse the font element in the VML.
type decodeVMLFont struct {
Face string `xml:"face,attr,omitempty"`
Size uint `xml:"size,attr,omitempty"`
Color string `xml:"color,attr,omitempty"`
B *decodeVMLFontB `xml:"b"`
I *decodeVMLFontI `xml:"i"`
U *decodeVMLFontU `xml:"u"`
Val string `xml:",chardata"`
}

// decodeVMLDiv defines the structure used to parse the div element in the VML.
type decodeVMLDiv struct {
Font []decodeVMLFont `xml:"font"`
}

// decodeVMLTextBox defines the structure used to parse the v:textbox element in
// the file xl/drawings/vmlDrawing%d.vml.
type decodeVMLTextBox struct {
Div decodeVMLDiv `xml:"div"`
}

// decodeVMLClientData defines the structure used to parse the x:ClientData
// element in the file xl/drawings/vmlDrawing%d.vml.
type decodeVMLClientData struct {
ObjectType string `xml:"ObjectType,attr"`
Anchor string
FmlaMacro string
Column *int
Row *int
Checked int
FmlaLink string
Val uint
Min uint
Max uint
Inc uint
Page uint
Horiz *string
}

// encodeShape defines the structure used to re-serialization shape element.
type encodeShape struct {
Fill *vFill `xml:"v:fill"`
Shadow *vShadow `xml:"v:shadow"`
Path *vPath `xml:"v:path"`
Textbox *vTextbox `xml:"v:textbox"`
TextBox *vTextBox `xml:"v:textbox"`
ClientData *xClientData `xml:"x:ClientData"`
}

// formCtrlPreset defines the structure used to form control presets.
type formCtrlPreset struct {
autoFill string
fill *vFill
fillColor string
filled string
firstButton *string
noThreeD *string
objectType string
shadow *vShadow
strokeButton string
strokeColor string
stroked string
textHAlign string
textVAlign string
}

// vmlOptions defines the structure used to internal comments and form controls.
type vmlOptions struct {
rows int
cols int
formCtrl bool
sheet string
Comment
FormControl
}

// FormControl directly maps the form controls information.
type FormControl struct {
Cell string
Macro string
Width uint
Height uint
Checked bool
CurrentVal uint
MinVal uint
MaxVal uint
IncChange uint
PageChange uint
Horizontally bool
CellLink string
Text string
Paragraph []RichTextRun
Type FormControlType
Format GraphicOptions
}
410 changes: 410 additions & 0 deletions vml_test.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions workbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ func (f *File) setWorkbook(name string, sheetID, rid int) {
// the spreadsheet.
func (f *File) getWorkbookPath() (path string) {
if rels, _ := f.relsReader("_rels/.rels"); rels != nil {
rels.Lock()
defer rels.Unlock()
rels.mu.Lock()
defer rels.mu.Unlock()
for _, rel := range rels.Relationships {
if rel.Type == SourceRelationshipOfficeDocument {
path = strings.TrimPrefix(rel.Target, "/")
Expand Down
13 changes: 6 additions & 7 deletions xmlChart.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ type cTx struct {
type cRich struct {
BodyPr aBodyPr `xml:"a:bodyPr,omitempty"`
LstStyle string `xml:"a:lstStyle,omitempty"`
P aP `xml:"a:p"`
P []aP `xml:"a:p"`
}

// aBodyPr (Body Properties) directly maps the a:bodyPr element. This element
Expand Down Expand Up @@ -351,6 +351,7 @@ type cAxs struct {
AxPos *attrValString `xml:"axPos"`
MajorGridlines *cChartLines `xml:"majorGridlines"`
MinorGridlines *cChartLines `xml:"minorGridlines"`
Title *cTitle `xml:"title"`
NumFmt *cNumFmt `xml:"numFmt"`
MajorTickMark *attrValString `xml:"majorTickMark"`
MinorTickMark *attrValString `xml:"minorTickMark"`
Expand Down Expand Up @@ -534,11 +535,14 @@ type ChartAxis struct {
MajorUnit float64
TickLabelSkip int
ReverseOrder bool
Secondary bool
Maximum *float64
Minimum *float64
Font Font
LogBase float64
NumFmt ChartNumFmt
Title []RichTextRun
axID int
}

// ChartDimension directly maps the dimension of the chart.
Expand Down Expand Up @@ -566,7 +570,7 @@ type Chart struct {
Format GraphicOptions
Dimension ChartDimension
Legend ChartLegend
Title ChartTitle
Title []RichTextRun
VaryColors *bool
XAxis ChartAxis
YAxis ChartAxis
Expand Down Expand Up @@ -604,8 +608,3 @@ type ChartSeries struct {
Line ChartLine
Marker ChartMarker
}

// ChartTitle directly maps the format settings of the chart title.
type ChartTitle struct {
Name string
}
8 changes: 4 additions & 4 deletions xmlChartSheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ type xlsxChartsheet struct {

// xlsxChartsheetPr specifies chart sheet properties.
type xlsxChartsheetPr struct {
XMLName xml.Name `xml:"sheetPr"`
PublishedAttr bool `xml:"published,attr,omitempty"`
CodeNameAttr string `xml:"codeName,attr,omitempty"`
TabColor *xlsxTabColor `xml:"tabColor"`
XMLName xml.Name `xml:"sheetPr"`
PublishedAttr bool `xml:"published,attr,omitempty"`
CodeNameAttr string `xml:"codeName,attr,omitempty"`
TabColor *xlsxColor `xml:"tabColor"`
}

// xlsxChartsheetViews specifies chart sheet views.
Expand Down
10 changes: 5 additions & 5 deletions xmlComments.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ type xlsxPhoneticRun struct {

// Comment directly maps the comment information.
type Comment struct {
Author string
AuthorID int
Cell string
Text string
Runs []RichTextRun
Author string
AuthorID int
Cell string
Text string
Paragraph []RichTextRun
}
2 changes: 1 addition & 1 deletion xmlContentTypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// parts, it takes a Multipurpose Internet Mail Extension (MIME) media type as a
// value.
type xlsxTypes struct {
sync.Mutex
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/content-types Types"`
Defaults []xlsxDefault `xml:"Default"`
Overrides []xlsxOverride `xml:"Override"`
Expand Down
14 changes: 2 additions & 12 deletions xmlDecodeDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type decodeCellAnchor struct {
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
Sp *decodeSp `xml:"sp"`
Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"`
Content string `xml:",innerxml"`
}
Expand Down Expand Up @@ -72,17 +73,6 @@ type decodeWsDr struct {
TwoCellAnchor []*decodeCellAnchor `xml:"twoCellAnchor,omitempty"`
}

// decodeTwoCellAnchor directly maps the oneCellAnchor (One Cell Anchor Shape
// Size) and twoCellAnchor (Two Cell Anchor Shape Size). This element
// specifies a two cell anchor placeholder for a group, a shape, or a drawing
// element. It moves with cells and its extents are in EMU units.
type decodeTwoCellAnchor struct {
From *decodeFrom `xml:"from"`
To *decodeTo `xml:"to"`
Pic *decodePic `xml:"pic"`
ClientData *decodeClientData `xml:"clientData"`
}

// decodeCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
// information that does not affect the appearance of the picture to be
Expand Down Expand Up @@ -199,7 +189,7 @@ type decodeSpPr struct {
// decodePic elements encompass the definition of pictures within the
// DrawingML framework. While pictures are in many ways very similar to shapes
// they have specific properties that are unique in order to optimize for
// picture- specific scenarios.
// picture-specific scenarios.
type decodePic struct {
NvPicPr decodeNvPicPr `xml:"nvPicPr"`
BlipFill decodeBlipFill `xml:"blipFill"`
Expand Down
9 changes: 7 additions & 2 deletions xmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const (
MaxColumnWidth = 255
MaxFieldLength = 255
MaxFilePathLength = 207
MaxFormControlValue = 30000
MaxFontFamilyLength = 31
MaxFontSize = 409
MaxRowHeight = 409
Expand All @@ -144,7 +145,7 @@ const (
pivotTableVersion = 3
defaultPictureScale = 1.0
defaultChartDimensionWidth = 480
defaultChartDimensionHeight = 290
defaultChartDimensionHeight = 260
defaultChartLegendPosition = "bottom"
defaultChartShowBlanksAs = "gap"
defaultShapeSize = 160
Expand Down Expand Up @@ -218,6 +219,9 @@ var supportedDrawingUnderlineTypes = []string{
"wavyDbl",
}

// supportedPositioning defined supported positioning types.
var supportedPositioning = []string{"absolute", "oneCell", "twoCell"}

// xlsxCNvPr directly maps the cNvPr (Non-Visual Drawing Properties). This
// element specifies non-visual canvas properties. This allows for additional
// information that does not affect the appearance of the picture to be stored.
Expand Down Expand Up @@ -440,7 +444,7 @@ type xlsxPoint2D struct {
// xlsxWsDr directly maps the root element for a part of this content type shall
// wsDr.
type xlsxWsDr struct {
sync.Mutex
mu sync.Mutex
XMLName xml.Name `xml:"xdr:wsDr"`
A string `xml:"xmlns:a,attr,omitempty"`
Xdr string `xml:"xmlns:xdr,attr,omitempty"`
Expand Down Expand Up @@ -606,6 +610,7 @@ type GraphicOptions struct {

// Shape directly maps the format settings of the shape.
type Shape struct {
Cell string
Type string
Macro string
Width uint
Expand Down
6 changes: 5 additions & 1 deletion xmlSharedStrings.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

package excelize

import "encoding/xml"
import (
"encoding/xml"
"sync"
)

// xlsxSST directly maps the sst element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main. String values may
Expand All @@ -21,6 +24,7 @@ import "encoding/xml"
// is an indexed list of string values, shared across the workbook, which allows
// implementations to store values only once.
type xlsxSST struct {
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main sst"`
Count int `xml:"count,attr"`
UniqueCount int `xml:"uniqueCount,attr"`
Expand Down
42 changes: 22 additions & 20 deletions xmlStyles.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (

// xlsxStyleSheet is the root element of the Styles part.
type xlsxStyleSheet struct {
sync.Mutex
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main styleSheet"`
NumFmts *xlsxNumFmts `xml:"numFmts"`
Fonts *xlsxFonts `xml:"fonts"`
Expand Down Expand Up @@ -65,10 +65,10 @@ type xlsxLine struct {

// xlsxColor is a common mapping used for both the fgColor and bgColor elements.
// Foreground color of the cell fill pattern. Cell fill patterns operate with
// two colors: a background color and a foreground color. These combine together
// two colors: a background color and a foreground color. These combine
// to make a patterned cell fill. Background color of the cell fill pattern.
// Cell fill patterns operate with two colors: a background color and a
// foreground color. These combine together to make a patterned cell fill.
// foreground color. These combine to make a patterned cell fill.
type xlsxColor struct {
Auto bool `xml:"auto,attr,omitempty"`
RGB string `xml:"rgb,attr,omitempty"`
Expand Down Expand Up @@ -103,7 +103,7 @@ type xlsxFont struct {
Scheme *attrValString `xml:"scheme"`
}

// xlsxFills directly maps the fills element. This element defines the cell
// xlsxFills directly maps the fills' element. This element defines the cell
// fills portion of the Styles part, consisting of a sequence of fill records. A
// cell fill consists of a background color, foreground color, and pattern to be
// applied across the cell.
Expand Down Expand Up @@ -147,7 +147,7 @@ type xlsxGradientFillStop struct {
Color xlsxColor `xml:"color,omitempty"`
}

// xlsxBorders directly maps the borders element. This element contains borders
// xlsxBorders directly maps the borders' element. This element contains borders
// formatting information, specifying all border definitions for all cells in
// the workbook.
type xlsxBorders struct {
Expand Down Expand Up @@ -205,7 +205,7 @@ type xlsxCellStyleXfs struct {
Xf []xlsxXf `xml:"xf,omitempty"`
}

// xlsxXf directly maps the xf element. A single xf element describes all of the
// xlsxXf directly maps the xf element. A single xf element describes all the
// formatting for a cell.
type xlsxXf struct {
NumFmtID *int `xml:"numFmtId,attr"`
Expand Down Expand Up @@ -236,8 +236,8 @@ type xlsxCellXfs struct {
}

// xlsxDxfs directly maps the dxfs element. This element contains the master
// differential formatting records (dxf's) which define formatting for all non-
// cell formatting in this workbook. Whereas xf records fully specify a
// differential formatting records (dxf's) which define formatting for all
// non-cell formatting in this workbook. Whereas xf records fully specify a
// particular aspect of formatting (e.g., cell borders) by referencing those
// formatting definitions elsewhere in the Styles part, dxf records specify
// incremental (or differential) aspects of formatting directly inline within
Expand All @@ -251,11 +251,6 @@ type xlsxDxfs struct {
// xlsxDxf directly maps the dxf element. A single dxf record, expressing
// incremental formatting to be applied.
type xlsxDxf struct {
Dxf string `xml:",innerxml"`
}

// dxf directly maps the dxf element.
type dxf struct {
Font *xlsxFont `xml:"font"`
NumFmt *xlsxNumFmt `xml:"numFmt"`
Fill *xlsxFill `xml:"fill"`
Expand Down Expand Up @@ -300,16 +295,24 @@ type xlsxNumFmts struct {
// format properties which indicate how to format and render the numeric value
// of a cell.
type xlsxNumFmt struct {
NumFmtID int `xml:"numFmtId,attr"`
FormatCode string `xml:"formatCode,attr,omitempty"`
NumFmtID int `xml:"numFmtId,attr"`
FormatCode string `xml:"formatCode,attr,omitempty"`
FormatCode16 string `xml:"http://schemas.microsoft.com/office/spreadsheetml/2015/02/main formatCode16,attr,omitempty"`
}

// xlsxIndexedColors directly maps the single ARGB entry for the corresponding
// color index.
type xlsxIndexedColors struct {
RgbColor []xlsxColor `xml:"rgbColor"`
}

// xlsxStyleColors directly maps the colors element. Color information
// associated with this stylesheet. This collection is written whenever the
// xlsxStyleColors directly maps the colors' element. Color information
// associated with this style sheet. This collection is written whenever the
// legacy color palette has been modified (backwards compatibility settings) or
// a custom color has been selected while using this workbook.
type xlsxStyleColors struct {
Color string `xml:",innerxml"`
IndexedColors xlsxIndexedColors `xml:"indexedColors"`
MruColors xlsxInnerXML `xml:"mruColors"`
}

// Alignment directly maps the alignment settings of the cells.
Expand Down Expand Up @@ -369,8 +372,7 @@ type Style struct {
Alignment *Alignment
Protection *Protection
NumFmt int
DecimalPlaces int
DecimalPlaces *int
CustomNumFmt *string
Lang string
NegRed bool
}
1 change: 1 addition & 0 deletions xmlTable.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ type xlsxTableStyleInfo struct {

// Table directly maps the format settings of the table.
type Table struct {
rID string
Range string
Name string
StyleName string
Expand Down
6 changes: 3 additions & 3 deletions xmlWorkbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (

// xlsxRelationships describe references from parts to other internal resources in the package or to external resources.
type xlsxRelationships struct {
sync.Mutex
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/package/2006/relationships Relationships"`
Relationships []xlsxRelationship `xml:"Relationship"`
}
Expand Down Expand Up @@ -212,7 +212,7 @@ type xlsxPivotCache struct {
// document are specified in the markup specification and can be used to store
// extensions to the markup specification, whether those are future version
// extensions of the markup specification or are private extensions implemented
// independently from the markup specification. Markup within an extension might
// independently of the markup specification. Markup within an extension might
// not be understood by a consumer.
type xlsxExtLst struct {
Ext string `xml:",innerxml"`
Expand All @@ -229,7 +229,7 @@ type xlsxDefinedNames struct {
// xlsxDefinedName directly maps the definedName element from the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main This element
// defines a defined name within this workbook. A defined name is descriptive
// text that is used to represents a cell, range of cells, formula, or constant
// text that is used to represent a cell, range of cells, formula, or constant
// value. For a descriptions of the attributes see https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.definedname
type xlsxDefinedName struct {
Comment string `xml:"comment,attr,omitempty"`
Expand Down
35 changes: 13 additions & 22 deletions xmlWorksheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
// xlsxWorksheet directly maps the worksheet element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
type xlsxWorksheet struct {
sync.Mutex
mu sync.Mutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
SheetPr *xlsxSheetPr `xml:"sheetPr"`
Dimension *xlsxDimension `xml:"dimension"`
Expand Down Expand Up @@ -241,7 +241,7 @@ type xlsxSheetPr struct {
CodeName string `xml:"codeName,attr,omitempty"`
FilterMode bool `xml:"filterMode,attr,omitempty"`
EnableFormatConditionsCalculation *bool `xml:"enableFormatConditionsCalculation,attr"`
TabColor *xlsxTabColor `xml:"tabColor"`
TabColor *xlsxColor `xml:"tabColor"`
OutlinePr *xlsxOutlinePr `xml:"outlinePr"`
PageSetUpPr *xlsxPageSetUpPr `xml:"pageSetUpPr"`
}
Expand All @@ -261,15 +261,6 @@ type xlsxPageSetUpPr struct {
FitToPage bool `xml:"fitToPage,attr,omitempty"`
}

// xlsxTabColor represents background color of the sheet tab.
type xlsxTabColor struct {
Auto bool `xml:"auto,attr,omitempty"`
Indexed int `xml:"indexed,attr,omitempty"`
RGB string `xml:"rgb,attr,omitempty"`
Theme int `xml:"theme,attr,omitempty"`
Tint float64 `xml:"tint,attr,omitempty"`
}

// xlsxCols defines column width and column formatting for one or more columns
// of the worksheet.
type xlsxCols struct {
Expand Down Expand Up @@ -436,7 +427,7 @@ type xlsxDataValidations struct {
DataValidation []*DataValidation `xml:"dataValidation"`
}

// DataValidation directly maps the a single item of data validation defined
// DataValidation directly maps the single item of data validation defined
// on a range of the worksheet.
type DataValidation struct {
AllowBlank bool `xml:"allowBlank,attr"`
Expand Down Expand Up @@ -850,14 +841,14 @@ type xlsxX14SparklineGroup struct {
MinAxisType string `xml:"minAxisType,attr,omitempty"`
MaxAxisType string `xml:"maxAxisType,attr,omitempty"`
RightToLeft bool `xml:"rightToLeft,attr,omitempty"`
ColorSeries *xlsxTabColor `xml:"x14:colorSeries"`
ColorNegative *xlsxTabColor `xml:"x14:colorNegative"`
ColorSeries *xlsxColor `xml:"x14:colorSeries"`
ColorNegative *xlsxColor `xml:"x14:colorNegative"`
ColorAxis *xlsxColor `xml:"x14:colorAxis"`
ColorMarkers *xlsxTabColor `xml:"x14:colorMarkers"`
ColorFirst *xlsxTabColor `xml:"x14:colorFirst"`
ColorLast *xlsxTabColor `xml:"x14:colorLast"`
ColorHigh *xlsxTabColor `xml:"x14:colorHigh"`
ColorLow *xlsxTabColor `xml:"x14:colorLow"`
ColorMarkers *xlsxColor `xml:"x14:colorMarkers"`
ColorFirst *xlsxColor `xml:"x14:colorFirst"`
ColorLast *xlsxColor `xml:"x14:colorLast"`
ColorHigh *xlsxColor `xml:"x14:colorHigh"`
ColorLow *xlsxColor `xml:"x14:colorLow"`
Sparklines xlsxX14Sparklines `xml:"x14:sparklines"`
}

Expand Down Expand Up @@ -903,8 +894,8 @@ type SparklineOptions struct {
EmptyCells string
}

// PaneOptions directly maps the settings of the pane.
type PaneOptions struct {
// Selection directly maps the settings of the worksheet selection.
type Selection struct {
SQRef string
ActiveCell string
Pane string
Expand All @@ -918,7 +909,7 @@ type Panes struct {
YSplit int
TopLeftCell string
ActivePane string
Panes []PaneOptions
Selection []Selection
}

// ConditionalFormatOptions directly maps the conditional format settings of the cells.
Expand Down