This is a breaking change, remove partial internal error log print, throw XML deserialize error

- Add error return value for the `GetComments`, `GetDefaultFont` and `SetDefaultFont` functions
- Update unit tests
This commit is contained in:
xuri 2022-11-12 00:02:11 +08:00
parent 58b5dae5eb
commit bd5dd17673
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
30 changed files with 655 additions and 297 deletions

View File

@ -10,7 +10,7 @@ import (
func TestAdjustMergeCells(t *testing.T) {
f := NewFile()
// testing adjustAutoFilter with illegal cell reference.
// Test adjustAutoFilter with illegal cell reference.
assert.EqualError(t, f.adjustMergeCells(&xlsxWorksheet{
MergeCells: &xlsxMergeCells{
Cells: []*xlsxMergeCell{
@ -57,7 +57,7 @@ func TestAdjustMergeCells(t *testing.T) {
},
}, columns, 1, -1))
// testing adjustMergeCells
// Test adjustMergeCells.
var cases []struct {
label string
ws *xlsxWorksheet
@ -68,7 +68,7 @@ func TestAdjustMergeCells(t *testing.T) {
expectRect []int
}
// testing insert
// Test insert.
cases = []struct {
label string
ws *xlsxWorksheet
@ -139,7 +139,7 @@ func TestAdjustMergeCells(t *testing.T) {
assert.Equal(t, c.expectRect, c.ws.MergeCells.Cells[0].rect, c.label)
}
// testing delete
// Test delete,
cases = []struct {
label string
ws *xlsxWorksheet
@ -227,7 +227,7 @@ func TestAdjustMergeCells(t *testing.T) {
assert.Equal(t, c.expect, c.ws.MergeCells.Cells[0].Ref, c.label)
}
// testing delete one row/column
// Test delete one row or column
cases = []struct {
label string
ws *xlsxWorksheet
@ -324,13 +324,13 @@ func TestAdjustTable(t *testing.T) {
f = NewFile()
assert.NoError(t, f.AddTable(sheetName, "A1", "D5", ""))
// Test adjust table with non-table part
// Test adjust table with non-table part.
f.Pkg.Delete("xl/tables/table1.xml")
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with unsupported charset
// Test adjust table with unsupported charset.
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with invalid table range reference
// Test adjust table with invalid table range reference.
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
assert.NoError(t, f.RemoveRow(sheetName, 1))
}

View File

@ -15,23 +15,19 @@ import (
"bytes"
"encoding/xml"
"io"
"log"
)
// calcChainReader provides a function to get the pointer to the structure
// after deserialization of xl/calcChain.xml.
func (f *File) calcChainReader() *xlsxCalcChain {
var err error
func (f *File) calcChainReader() (*xlsxCalcChain, error) {
if f.CalcChain == nil {
f.CalcChain = new(xlsxCalcChain)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))).
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathCalcChain)))).
Decode(f.CalcChain); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
return f.CalcChain, err
}
}
return f.CalcChain
return f.CalcChain, nil
}
// calcChainWriter provides a function to save xl/calcChain.xml after
@ -45,8 +41,11 @@ func (f *File) calcChainWriter() {
// deleteCalcChain provides a function to remove cell reference on the
// calculation chain.
func (f *File) deleteCalcChain(index int, cell string) {
calc := f.calcChainReader()
func (f *File) deleteCalcChain(index int, cell string) error {
calc, err := f.calcChainReader()
if err != nil {
return err
}
if calc != nil {
calc.C = xlsxCalcChainCollection(calc.C).Filter(func(c xlsxCalcChainC) bool {
return !((c.I == index && c.R == cell) || (c.I == index && cell == "") || (c.I == 0 && c.R == cell))
@ -64,6 +63,7 @@ func (f *File) deleteCalcChain(index int, cell string) {
}
}
}
return err
}
type xlsxCalcChainCollection []xlsxCalcChainC

View File

@ -1,12 +1,18 @@
package excelize
import "testing"
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCalcChainReader(t *testing.T) {
f := NewFile()
// Test read calculation chain with unsupported charset.
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
f.calcChainReader()
_, err := f.calcChainReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteCalcChain(t *testing.T) {
@ -15,5 +21,19 @@ func TestDeleteCalcChain(t *testing.T) {
f.ContentTypes.Overrides = append(f.ContentTypes.Overrides, xlsxOverride{
PartName: "/xl/calcChain.xml",
})
f.deleteCalcChain(1, "A1")
assert.NoError(t, f.deleteCalcChain(1, "A1"))
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellFormula("Sheet1", "A1", ""), "XML syntax error on line 1: invalid UTF-8")
formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8")
}

92
cell.go
View File

@ -66,7 +66,11 @@ var cellTypes = map[string]CellType{
// values will be the same in a merged range.
func (f *File) GetCellValue(sheet, cell string, opts ...Options) (string, error) {
return f.getCellStringFunc(sheet, cell, func(x *xlsxWorksheet, c *xlsxC) (string, bool, error) {
val, err := c.getValueFrom(f, f.sharedStringsReader(), parseOptions(opts...).RawCellValue)
sst, err := f.sharedStringsReader()
if err != nil {
return "", true, err
}
val, err := c.getValueFrom(f, sst, parseOptions(opts...).RawCellValue)
return val, true, err
})
}
@ -173,23 +177,26 @@ func (c *xlsxC) hasValue() bool {
}
// removeFormula delete formula for the cell.
func (f *File) removeFormula(c *xlsxC, ws *xlsxWorksheet, sheet string) {
func (f *File) removeFormula(c *xlsxC, ws *xlsxWorksheet, sheet string) error {
if c.F != nil && c.Vm == nil {
sheetID := f.getSheetID(sheet)
f.deleteCalcChain(sheetID, c.R)
if err := f.deleteCalcChain(sheetID, c.R); err != nil {
return err
}
if c.F.T == STCellFormulaTypeShared && c.F.Ref != "" {
si := c.F.Si
for r, row := range ws.SheetData.Row {
for col, cell := range row.C {
if cell.F != nil && cell.F.Si != nil && *cell.F.Si == *si {
ws.SheetData.Row[r].C[col].F = nil
f.deleteCalcChain(sheetID, cell.R)
_ = f.deleteCalcChain(sheetID, cell.R)
}
}
}
}
c.F = nil
}
return nil
}
// setCellIntFunc is a wrapper of SetCellInt.
@ -289,8 +296,7 @@ func (f *File) SetCellInt(sheet, cell string, value int) error {
c.S = f.prepareCellStyle(ws, col, row, c.S)
c.T, c.V = setCellInt(value)
c.IS = nil
f.removeFormula(c, ws, sheet)
return err
return f.removeFormula(c, ws, sheet)
}
// setCellInt prepares cell type and string type cell value by a given
@ -316,8 +322,7 @@ func (f *File) SetCellBool(sheet, cell string, value bool) error {
c.S = f.prepareCellStyle(ws, col, row, c.S)
c.T, c.V = setCellBool(value)
c.IS = nil
f.removeFormula(c, ws, sheet)
return err
return f.removeFormula(c, ws, sheet)
}
// setCellBool prepares cell type and string type cell value by a given
@ -354,8 +359,7 @@ func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSiz
c.S = f.prepareCellStyle(ws, col, row, c.S)
c.T, c.V = setCellFloat(value, precision, bitSize)
c.IS = nil
f.removeFormula(c, ws, sheet)
return err
return f.removeFormula(c, ws, sheet)
}
// setCellFloat prepares cell type and string type cell value by a given
@ -379,10 +383,11 @@ func (f *File) SetCellStr(sheet, cell, value string) error {
ws.Lock()
defer ws.Unlock()
c.S = f.prepareCellStyle(ws, col, row, c.S)
c.T, c.V, err = f.setCellString(value)
if c.T, c.V, err = f.setCellString(value); err != nil {
return err
}
c.IS = nil
f.removeFormula(c, ws, sheet)
return err
return f.removeFormula(c, ws, sheet)
}
// setCellString provides a function to set string type to shared string
@ -429,7 +434,10 @@ func (f *File) setSharedString(val string) (int, error) {
if err := f.sharedStringsLoader(); err != nil {
return 0, err
}
sst := f.sharedStringsReader()
sst, err := f.sharedStringsReader()
if err != nil {
return 0, err
}
f.Lock()
defer f.Unlock()
if i, ok := f.sharedStringsMap[val]; ok {
@ -498,7 +506,7 @@ func (c *xlsxC) getCellBool(f *File, raw bool) (string, error) {
return "FALSE", nil
}
}
return f.formattedValue(c.S, c.V, raw), nil
return f.formattedValue(c.S, c.V, raw)
}
// setCellDefault prepares cell type and string type cell value by a given
@ -529,7 +537,7 @@ func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
c.V = strconv.FormatFloat(excelTime, 'G', 15, 64)
}
}
return f.formattedValue(c.S, c.V, raw), nil
return f.formattedValue(c.S, c.V, raw)
}
// getValueFrom return a value from a column/row cell, this function is
@ -548,18 +556,18 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
xlsxSI := 0
xlsxSI, _ = strconv.Atoi(c.V)
if _, ok := f.tempFiles.Load(defaultXMLPathSharedStrings); ok {
return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw), nil
return f.formattedValue(c.S, f.getFromStringItem(xlsxSI), raw)
}
if len(d.SI) > xlsxSI {
return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw), nil
return f.formattedValue(c.S, d.SI[xlsxSI].String(), raw)
}
}
return f.formattedValue(c.S, c.V, raw), nil
return f.formattedValue(c.S, c.V, raw)
case "inlineStr":
if c.IS != nil {
return f.formattedValue(c.S, c.IS.String(), raw), nil
return f.formattedValue(c.S, c.IS.String(), raw)
}
return f.formattedValue(c.S, c.V, raw), nil
return f.formattedValue(c.S, c.V, raw)
default:
if isNum, precision, decimal := isNumeric(c.V); isNum && !raw {
if precision > 15 {
@ -568,7 +576,7 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
c.V = strconv.FormatFloat(decimal, 'f', -1, 64)
}
}
return f.formattedValue(c.S, c.V, raw), nil
return f.formattedValue(c.S, c.V, raw)
}
}
@ -587,8 +595,7 @@ func (f *File) SetCellDefault(sheet, cell, value string) error {
defer ws.Unlock()
c.S = f.prepareCellStyle(ws, col, row, c.S)
c.setCellDefault(value)
f.removeFormula(c, ws, sheet)
return err
return f.removeFormula(c, ws, sheet)
}
// GetCellFormula provides a function to get formula from cell by given
@ -698,8 +705,7 @@ func (f *File) SetCellFormula(sheet, cell, formula string, opts ...FormulaOpts)
}
if formula == "" {
c.F = nil
f.deleteCalcChain(f.getSheetID(sheet), cell)
return err
return f.deleteCalcChain(f.getSheetID(sheet), cell)
}
if c.F != nil {
@ -926,7 +932,10 @@ func (f *File) GetCellRichText(sheet, cell string) (runs []RichTextRun, err erro
if err != nil || c.T != "s" {
return
}
sst := f.sharedStringsReader()
sst, err := f.sharedStringsReader()
if err != nil {
return
}
if len(sst.SI) <= siIdx || siIdx < 0 {
return
}
@ -1145,7 +1154,11 @@ func (f *File) SetCellRichText(sheet, cell string, runs []RichTextRun) error {
return err
}
c.S = f.prepareCellStyle(ws, col, row, c.S)
si, sst := xlsxSI{}, f.sharedStringsReader()
si := xlsxSI{}
sst, err := f.sharedStringsReader()
if err != nil {
return err
}
if si.R, err = setRichText(runs); err != nil {
return err
}
@ -1286,19 +1299,22 @@ func (f *File) getCellStringFunc(sheet, cell string, fn func(x *xlsxWorksheet, c
// formattedValue provides a function to returns a value after formatted. If
// it is possible to apply a format to the cell value, it will do so, if not
// then an error will be returned, along with the raw value of the cell.
func (f *File) formattedValue(s int, v string, raw bool) string {
func (f *File) formattedValue(s int, v string, raw bool) (string, error) {
if raw {
return v
return v, nil
}
if s == 0 {
return v
return v, nil
}
styleSheet, err := f.stylesReader()
if err != nil {
return v, err
}
styleSheet := f.stylesReader()
if styleSheet.CellXfs == nil {
return v
return v, err
}
if s >= len(styleSheet.CellXfs.Xf) {
return v
return v, err
}
var numFmtID int
if styleSheet.CellXfs.Xf[s].NumFmtID != nil {
@ -1309,17 +1325,17 @@ func (f *File) formattedValue(s int, v string, raw bool) string {
date1904 = wb.WorkbookPr.Date1904
}
if ok := builtInNumFmtFunc[numFmtID]; ok != nil {
return ok(v, builtInNumFmt[numFmtID], date1904)
return ok(v, builtInNumFmt[numFmtID], date1904), err
}
if styleSheet.NumFmts == nil {
return v
return v, err
}
for _, xlsxFmt := range styleSheet.NumFmts.NumFmt {
if xlsxFmt.NumFmtID == numFmtID {
return format(v, xlsxFmt.FormatCode, date1904)
return format(v, xlsxFmt.FormatCode, date1904), err
}
}
return v
return v, err
}
// prepareCellStyle provides a function to prepare style index of cell in

View File

@ -188,6 +188,11 @@ func TestSetCellValue(t *testing.T) {
B2, err := f.GetCellValue("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "0.50", B2)
// Test set cell value with unsupported charset shared strings table
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetCellValues(t *testing.T) {
@ -199,7 +204,7 @@ func TestSetCellValues(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, v, "12/31/10 00:00")
// test date value lower than min date supported by Excel
// Test date value lower than min date supported by Excel
err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
assert.NoError(t, err)
@ -377,6 +382,12 @@ func TestGetCellValue(t *testing.T) {
"2020-07-10 15:00:00.000",
}, rows[0])
assert.NoError(t, err)
// Test get cell value with unsupported charset shared strings table.
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, value := f.GetCellValue("Sheet1", "A1")
assert.EqualError(t, value, "XML syntax error on line 1: invalid UTF-8")
}
func TestGetCellType(t *testing.T) {
@ -395,7 +406,9 @@ func TestGetCellType(t *testing.T) {
func TestGetValueFrom(t *testing.T) {
f := NewFile()
c := xlsxC{T: "s"}
value, err := c.getValueFrom(f, f.sharedStringsReader(), false)
sst, err := f.sharedStringsReader()
assert.NoError(t, err)
value, err := c.getValueFrom(f, sst, false)
assert.NoError(t, err)
assert.Equal(t, "", value)
}
@ -566,36 +579,46 @@ func TestGetCellRichText(t *testing.T) {
runsSource[1].Font.Color = strings.ToUpper(runsSource[1].Font.Color)
assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font")
// Test get cell rich text when string item index overflow
// Test get cell rich text when string item index overflow.
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "2"
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 0, len(runs))
// Test get cell rich text when string item index is negative
// Test get cell rich text when string item index is negative.
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "-1"
runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err)
assert.Equal(t, 0, len(runs))
// Test get cell rich text on invalid string item index
// Test get cell rich text on invalid string item index.
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "x"
_, err = f.GetCellRichText("Sheet1", "A1")
assert.EqualError(t, err, "strconv.Atoi: parsing \"x\": invalid syntax")
// Test set cell rich text on not exists worksheet
// Test set cell rich text on not exists worksheet.
_, err = f.GetCellRichText("SheetN", "A1")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test set cell rich text with illegal cell reference
// Test set cell rich text with illegal cell reference.
_, err = f.GetCellRichText("Sheet1", "A")
assert.EqualError(t, err, newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set rich text color theme without tint
// Test set rich text color theme without tint.
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTheme: &theme}}}))
// Test set rich text color tint without theme
// Test set rich text color tint without theme.
assert.NoError(t, f.SetCellRichText("Sheet1", "A1", []RichTextRun{{Font: &Font{ColorTint: 0.5}}}))
// Test set cell rich text with unsupported charset shared strings table.
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", runsSource), "XML syntax error on line 1: invalid UTF-8")
// Test get cell rich text with unsupported charset shared strings table.
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err = f.GetCellRichText("Sheet1", "A1")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetCellRichText(t *testing.T) {
@ -689,80 +712,108 @@ func TestSetCellRichText(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "A1", style))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetCellRichText.xlsx")))
// Test set cell rich text on not exists worksheet
// Test set cell rich text on not exists worksheet.
assert.EqualError(t, f.SetCellRichText("SheetN", "A1", richTextRun), "sheet SheetN does not exist")
// Test set cell rich text with illegal cell reference
// Test set cell rich text with illegal cell reference.
assert.EqualError(t, f.SetCellRichText("Sheet1", "A", richTextRun), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
richTextRun = []RichTextRun{{Text: strings.Repeat("s", TotalCellChars+1)}}
// Test set cell rich text with characters over the maximum limit
// Test set cell rich text with characters over the maximum limit.
assert.EqualError(t, f.SetCellRichText("Sheet1", "A1", richTextRun), ErrCellCharsLength.Error())
}
func TestFormattedValue2(t *testing.T) {
func TestFormattedValue(t *testing.T) {
f := NewFile()
assert.Equal(t, "43528", f.formattedValue(0, "43528", false))
result, err := f.formattedValue(0, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
assert.Equal(t, "43528", f.formattedValue(15, "43528", false))
result, err = f.formattedValue(15, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
assert.Equal(t, "43528", f.formattedValue(1, "43528", false))
result, err = f.formattedValue(1, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
customNumFmt := "[$-409]MM/DD/YYYY"
_, err := f.NewStyle(&Style{
_, err = f.NewStyle(&Style{
CustomNumFmt: &customNumFmt,
})
assert.NoError(t, err)
assert.Equal(t, "03/04/2019", f.formattedValue(1, "43528", false))
result, err = f.formattedValue(1, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "03/04/2019", result)
// formatted value with no built-in number format ID
// Test format value with no built-in number format ID.
numFmtID := 5
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
assert.Equal(t, "43528", f.formattedValue(2, "43528", false))
result, err = f.formattedValue(2, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// formatted value with invalid number format ID
// Test format value with invalid number format ID.
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: nil,
})
assert.Equal(t, "43528", f.formattedValue(3, "43528", false))
result, err = f.formattedValue(3, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// formatted value with empty number format
// Test format value with empty number format.
f.Styles.NumFmts = nil
f.Styles.CellXfs.Xf = append(f.Styles.CellXfs.Xf, xlsxXf{
NumFmtID: &numFmtID,
})
assert.Equal(t, "43528", f.formattedValue(1, "43528", false))
result, err = f.formattedValue(1, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
// formatted decimal value with build-in number format ID
// Test format decimal value with build-in number format ID.
styleID, err := f.NewStyle(&Style{
NumFmt: 1,
})
assert.NoError(t, err)
assert.Equal(t, "311", f.formattedValue(styleID, "310.56", false))
result, err = f.formattedValue(styleID, "310.56", false)
assert.NoError(t, err)
assert.Equal(t, "311", result)
for _, fn := range builtInNumFmtFunc {
assert.Equal(t, "0_0", fn("0_0", "", false))
}
// Test format value with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.formattedValue(1, "43528", false)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestFormattedValueNilXfs(t *testing.T) {
// Set the CellXfs to nil and verify that the formattedValue function does not crash.
f := NewFile()
f.Styles.CellXfs = nil
assert.Equal(t, "43528", f.formattedValue(3, "43528", false))
result, err := f.formattedValue(3, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestFormattedValueNilNumFmts(t *testing.T) {
// Set the NumFmts value to nil and verify that the formattedValue function does not crash.
f := NewFile()
f.Styles.NumFmts = nil
assert.Equal(t, "43528", f.formattedValue(3, "43528", false))
result, err := f.formattedValue(3, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestFormattedValueNilWorkbook(t *testing.T) {
// Set the Workbook value to nil and verify that the formattedValue function does not crash.
f := NewFile()
f.WorkBook = nil
assert.Equal(t, "43528", f.formattedValue(3, "43528", false))
result, err := f.formattedValue(3, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestFormattedValueNilWorkbookPr(t *testing.T) {
@ -770,7 +821,9 @@ func TestFormattedValueNilWorkbookPr(t *testing.T) {
// crash.
f := NewFile()
f.WorkBook.WorkbookPr = nil
assert.Equal(t, "43528", f.formattedValue(3, "43528", false))
result, err := f.formattedValue(3, "43528", false)
assert.NoError(t, err)
assert.Equal(t, "43528", result)
}
func TestSharedStringsError(t *testing.T) {

View File

@ -95,6 +95,24 @@ func TestChartSize(t *testing.T) {
func TestAddDrawingChart(t *testing.T) {
f := NewFile()
assert.EqualError(t, f.addDrawingChart("SheetN", "", "", 0, 0, 0, nil), newCellNameToCoordinatesError("", newInvalidCellNameError("")).Error())
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingChart("Sheet1", path, "A1", 0, 0, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddSheetDrawingChart(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addSheetDrawingChart(path, 0, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteDrawing(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteDrawing(0, 0, path, "Chart"), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddChart(t *testing.T) {

78
col.go
View File

@ -38,6 +38,7 @@ type Cols struct {
sheet string
f *File
sheetXML []byte
sst *xlsxSST
}
// GetCols gets the value of all cells by columns on the worksheet based on the
@ -87,17 +88,14 @@ func (cols *Cols) Error() error {
// Rows return the current column's row values.
func (cols *Cols) Rows(opts ...Options) ([]string, error) {
var (
err error
inElement string
cellCol, cellRow int
rows []string
)
var rowIterator rowXMLIterator
if cols.stashCol >= cols.curCol {
return rows, err
return rowIterator.cells, rowIterator.err
}
cols.rawCellValue = parseOptions(opts...).RawCellValue
d := cols.f.sharedStringsReader()
if cols.sst, rowIterator.err = cols.f.sharedStringsReader(); rowIterator.err != nil {
return rowIterator.cells, rowIterator.err
}
decoder := cols.f.xmlNewDecoder(bytes.NewReader(cols.sheetXML))
for {
token, _ := decoder.Token()
@ -106,42 +104,25 @@ func (cols *Cols) Rows(opts ...Options) ([]string, error) {
}
switch xmlElement := token.(type) {
case xml.StartElement:
inElement = xmlElement.Name.Local
if inElement == "row" {
cellCol = 0
cellRow++
rowIterator.inElement = xmlElement.Name.Local
if rowIterator.inElement == "row" {
rowIterator.cellCol = 0
rowIterator.cellRow++
attrR, _ := attrValToInt("r", xmlElement.Attr)
if attrR != 0 {
cellRow = attrR
rowIterator.cellRow = attrR
}
}
if inElement == "c" {
cellCol++
for _, attr := range xmlElement.Attr {
if attr.Name.Local == "r" {
if cellCol, cellRow, err = CellNameToCoordinates(attr.Value); err != nil {
return rows, err
}
}
}
blank := cellRow - len(rows)
for i := 1; i < blank; i++ {
rows = append(rows, "")
}
if cellCol == cols.curCol {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, &xmlElement)
val, _ := colCell.getValueFrom(cols.f, d, cols.rawCellValue)
rows = append(rows, val)
}
if cols.rowXMLHandler(&rowIterator, &xmlElement, decoder); rowIterator.err != nil {
return rowIterator.cells, rowIterator.err
}
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
return rows, err
return rowIterator.cells, rowIterator.err
}
}
}
return rows, err
return rowIterator.cells, rowIterator.err
}
// columnXMLIterator defined runtime use field for the worksheet column SAX parser.
@ -183,6 +164,30 @@ func columnXMLHandler(colIterator *columnXMLIterator, xmlElement *xml.StartEleme
}
}
// rowXMLHandler parse the row XML element of the worksheet.
func (cols *Cols) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.StartElement, decoder *xml.Decoder) {
if rowIterator.inElement == "c" {
rowIterator.cellCol++
for _, attr := range xmlElement.Attr {
if attr.Name.Local == "r" {
if rowIterator.cellCol, rowIterator.cellRow, rowIterator.err = CellNameToCoordinates(attr.Value); rowIterator.err != nil {
return
}
}
}
blank := rowIterator.cellRow - len(rowIterator.cells)
for i := 1; i < blank; i++ {
rowIterator.cells = append(rowIterator.cells, "")
}
if rowIterator.cellCol == cols.curCol {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, xmlElement)
val, _ := colCell.getValueFrom(cols.f, cols.sst, cols.rawCellValue)
rowIterator.cells = append(rowIterator.cells, val)
}
}
}
// Cols returns a columns iterator, used for streaming reading data for a
// worksheet with a large data. This function is concurrency safe. For
// example:
@ -420,7 +425,10 @@ func (f *File) SetColStyle(sheet, columns string, styleID int) error {
if err != nil {
return err
}
s := f.stylesReader()
s, err := f.stylesReader()
if err != nil {
return err
}
s.Lock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
s.Unlock()

View File

@ -56,6 +56,15 @@ func TestCols(t *testing.T) {
})
_, err = f.Rows("Sheet1")
assert.NoError(t, err)
// Test columns iterator with unsupported charset shared strings table.
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
cols, err = f.Cols("Sheet1")
assert.NoError(t, err)
cols.Next()
_, err = cols.Rows()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestColumnsIterator(t *testing.T) {
@ -316,6 +325,10 @@ func TestSetColStyle(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, styleID, cellStyleID)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetColStyle.xlsx")))
// Test set column style with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetColStyle("Sheet1", "C:F", styleID), "XML syntax error on line 1: invalid UTF-8")
}
func TestColWidth(t *testing.T) {

View File

@ -16,7 +16,6 @@ import (
"encoding/xml"
"fmt"
"io"
"log"
"path/filepath"
"strconv"
"strings"
@ -24,8 +23,8 @@ import (
// GetComments retrieves all comments and returns a map of worksheet name to
// the worksheet comments.
func (f *File) GetComments() (comments map[string][]Comment) {
comments = map[string][]Comment{}
func (f *File) GetComments() (map[string][]Comment, error) {
comments := map[string][]Comment{}
for n, path := range f.sheetMap {
target := f.getSheetComments(filepath.Base(path))
if target == "" {
@ -34,12 +33,16 @@ func (f *File) GetComments() (comments map[string][]Comment) {
if !strings.HasPrefix(target, "/") {
target = "xl" + strings.TrimPrefix(target, "..")
}
if d := f.commentsReader(strings.TrimPrefix(target, "/")); d != nil {
cmts, err := f.commentsReader(strings.TrimPrefix(target, "/"))
if err != nil {
return comments, err
}
if cmts != nil {
var sheetComments []Comment
for _, comment := range d.CommentList.Comment {
for _, comment := range cmts.CommentList.Comment {
sheetComment := Comment{}
if comment.AuthorID < len(d.Authors.Author) {
sheetComment.Author = d.Authors.Author[comment.AuthorID]
if comment.AuthorID < len(cmts.Authors.Author) {
sheetComment.Author = cmts.Authors.Author[comment.AuthorID]
}
sheetComment.Cell = comment.Ref
sheetComment.AuthorID = comment.AuthorID
@ -60,7 +63,7 @@ func (f *File) GetComments() (comments map[string][]Comment) {
comments[n] = sheetComments
}
}
return
return comments, nil
}
// getSheetComments provides the method to get the target comment reference by
@ -129,7 +132,9 @@ func (f *File) AddComment(sheet string, comment Comment) error {
if err = f.addDrawingVML(commentID, drawingVML, comment.Cell, rows+1, cols); err != nil {
return err
}
f.addComment(commentsXML, comment)
if err = f.addComment(commentsXML, comment); err != nil {
return err
}
f.addContentTypePart(commentID, "comments")
return err
}
@ -139,34 +144,36 @@ func (f *File) AddComment(sheet string, comment Comment) error {
//
// err := f.DeleteComment("Sheet1", "A30")
func (f *File) DeleteComment(sheet, cell string) error {
var err error
sheetXMLPath, ok := f.getSheetXMLPath(sheet)
if !ok {
err = newNoExistSheetError(sheet)
return err
return newNoExistSheetError(sheet)
}
commentsXML := f.getSheetComments(filepath.Base(sheetXMLPath))
if !strings.HasPrefix(commentsXML, "/") {
commentsXML = "xl" + strings.TrimPrefix(commentsXML, "..")
}
commentsXML = strings.TrimPrefix(commentsXML, "/")
if comments := f.commentsReader(commentsXML); comments != nil {
for i := 0; i < len(comments.CommentList.Comment); i++ {
cmt := comments.CommentList.Comment[i]
cmts, err := f.commentsReader(commentsXML)
if err != nil {
return err
}
if cmts != nil {
for i := 0; i < len(cmts.CommentList.Comment); i++ {
cmt := cmts.CommentList.Comment[i]
if cmt.Ref != cell {
continue
}
if len(comments.CommentList.Comment) > 1 {
comments.CommentList.Comment = append(
comments.CommentList.Comment[:i],
comments.CommentList.Comment[i+1:]...,
if len(cmts.CommentList.Comment) > 1 {
cmts.CommentList.Comment = append(
cmts.CommentList.Comment[:i],
cmts.CommentList.Comment[i+1:]...,
)
i--
continue
}
comments.CommentList.Comment = nil
cmts.CommentList.Comment = nil
}
f.Comments[commentsXML] = comments
f.Comments[commentsXML] = cmts
}
return err
}
@ -209,7 +216,10 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount,
},
}
// load exist comment shapes from xl/drawings/vmlDrawing%d.vml
d := f.decodeVMLDrawingReader(drawingVML)
d, err := f.decodeVMLDrawingReader(drawingVML)
if err != nil {
return err
}
if d != nil {
for _, v := range d.Shape {
s := xlsxShape{
@ -274,22 +284,30 @@ func (f *File) addDrawingVML(commentID int, drawingVML, cell string, lineCount,
// addComment provides a function to create chart as xl/comments%d.xml by
// given cell and format sets.
func (f *File) addComment(commentsXML string, comment Comment) {
func (f *File) addComment(commentsXML string, comment Comment) error {
if comment.Author == "" {
comment.Author = "Author"
}
if len(comment.Author) > MaxFieldLength {
comment.Author = comment.Author[:MaxFieldLength]
}
comments, authorID := f.commentsReader(commentsXML), 0
if comments == nil {
comments = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}}
cmts, err := f.commentsReader(commentsXML)
if err != nil {
return err
}
if inStrSlice(comments.Authors.Author, comment.Author, true) == -1 {
comments.Authors.Author = append(comments.Authors.Author, comment.Author)
authorID = len(comments.Authors.Author) - 1
var authorID int
if cmts == nil {
cmts = &xlsxComments{Authors: xlsxAuthor{Author: []string{comment.Author}}}
}
defaultFont, chars, cmt := f.GetDefaultFont(), 0, xlsxComment{
if inStrSlice(cmts.Authors.Author, comment.Author, true) == -1 {
cmts.Authors.Author = append(cmts.Authors.Author, comment.Author)
authorID = len(cmts.Authors.Author) - 1
}
defaultFont, err := f.GetDefaultFont()
if err != nil {
return err
}
chars, cmt := 0, xlsxComment{
Ref: comment.Cell,
AuthorID: authorID,
Text: xlsxText{R: []xlsxR{}},
@ -328,8 +346,9 @@ func (f *File) addComment(commentsXML string, comment Comment) {
}
cmt.Text.R = append(cmt.Text.R, r)
}
comments.CommentList.Comment = append(comments.CommentList.Comment, cmt)
f.Comments[commentsXML] = comments
cmts.CommentList.Comment = append(cmts.CommentList.Comment, cmt)
f.Comments[commentsXML] = cmts
return err
}
// countComments provides a function to get comments files count storage in
@ -355,20 +374,18 @@ func (f *File) countComments() int {
// decodeVMLDrawingReader provides a function to get the pointer to the
// structure after deserialization of xl/drawings/vmlDrawing%d.xml.
func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
var err error
func (f *File) decodeVMLDrawingReader(path string) (*decodeVmlDrawing, error) {
if f.DecodeVMLDrawing[path] == nil {
c, ok := f.Pkg.Load(path)
if ok && c != nil {
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
return nil, err
}
}
}
return f.DecodeVMLDrawing[path]
return f.DecodeVMLDrawing[path], nil
}
// vmlDrawingWriter provides a function to save xl/drawings/vmlDrawing%d.xml
@ -384,19 +401,18 @@ func (f *File) vmlDrawingWriter() {
// commentsReader provides a function to get the pointer to the structure
// after deserialization of xl/comments%d.xml.
func (f *File) commentsReader(path string) *xlsxComments {
var err error
func (f *File) commentsReader(path string) (*xlsxComments, error) {
if f.Comments[path] == nil {
content, ok := f.Pkg.Load(path)
if ok && content != nil {
f.Comments[path] = new(xlsxComments)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(f.Comments[path]); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
return nil, err
}
}
}
return f.Comments[path]
return f.Comments[path], nil
}
// commentsWriter provides a function to save xl/comments%d.xml after

View File

@ -34,16 +34,37 @@ func TestAddComments(t *testing.T) {
assert.EqualError(t, f.AddComment("SheetN", Comment{Cell: "B7", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), "sheet SheetN does not exist")
// Test add comment on with illegal cell reference
assert.EqualError(t, f.AddComment("Sheet1", Comment{Cell: "A", Author: "Excelize", Runs: []RichTextRun{{Text: "Excelize: ", Font: &Font{Bold: true}}, {Text: "This is a comment."}}}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
comments, err := f.GetComments()
assert.NoError(t, err)
if assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddComments.xlsx"))) {
assert.Len(t, f.GetComments(), 2)
assert.Len(t, comments, 2)
}
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", []byte(xml.Header+`<comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><authors><author>Excelize: </author></authors><commentList><comment ref="B7" authorId="0"><text><t>Excelize: </t></text></comment></commentList></comments>`))
comments := f.GetComments()
comments, err = f.GetComments()
assert.NoError(t, err)
assert.EqualValues(t, 2, len(comments["Sheet1"]))
assert.EqualValues(t, 1, len(comments["Sheet2"]))
assert.EqualValues(t, len(NewFile().GetComments()), 0)
comments, err = NewFile().GetComments()
assert.NoError(t, err)
assert.EqualValues(t, len(comments), 0)
// Test add comments with unsupported charset.
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
_, err = f.GetComments()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test add comments with unsupported charset.
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8")
// Test add comments with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddComment("Sheet2", Comment{Cell: "A30", Text: "Comment"}), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteComment(t *testing.T) {
@ -61,19 +82,30 @@ func TestDeleteComment(t *testing.T) {
assert.NoError(t, f.DeleteComment("Sheet2", "A40"))
assert.EqualValues(t, 5, len(f.GetComments()["Sheet2"]))
assert.EqualValues(t, len(NewFile().GetComments()), 0)
comments, err := f.GetComments()
assert.NoError(t, err)
assert.EqualValues(t, 5, len(comments["Sheet2"]))
comments, err = NewFile().GetComments()
assert.NoError(t, err)
assert.EqualValues(t, len(comments), 0)
// Test delete all comments in a worksheet
assert.NoError(t, f.DeleteComment("Sheet2", "A41"))
assert.NoError(t, f.DeleteComment("Sheet2", "C41"))
assert.NoError(t, f.DeleteComment("Sheet2", "C42"))
assert.EqualValues(t, 0, len(f.GetComments()["Sheet2"]))
comments, err = f.GetComments()
assert.NoError(t, err)
assert.EqualValues(t, 0, len(comments["Sheet2"]))
// Test delete comment on not exists worksheet
assert.EqualError(t, f.DeleteComment("SheetN", "A1"), "sheet SheetN does not exist")
// Test delete comment with worksheet part
f.Pkg.Delete("xl/worksheets/sheet1.xml")
assert.NoError(t, f.DeleteComment("Sheet1", "A22"))
f.Comments["xl/comments2.xml"] = nil
f.Pkg.Store("xl/comments2.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteComment("Sheet2", "A41"), "XML syntax error on line 1: invalid UTF-8")
}
func TestDecodeVMLDrawingReader(t *testing.T) {
@ -85,9 +117,11 @@ func TestDecodeVMLDrawingReader(t *testing.T) {
func TestCommentsReader(t *testing.T) {
f := NewFile()
// Test read comments with unsupported charset.
path := "xl/comments1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
f.commentsReader(path)
_, err := f.commentsReader(path)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestCountComments(t *testing.T) {

View File

@ -76,7 +76,6 @@ func (f *File) SetAppProps(appProperties *AppProperties) error {
app = new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return err
}
fields = []string{"Application", "ScaleCrop", "DocSecurity", "Company", "LinksUpToDate", "HyperlinksChanged", "AppVersion"}
@ -103,7 +102,6 @@ func (f *File) GetAppProps() (ret *AppProperties, err error) {
app := new(xlsxProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsApp)))).
Decode(app); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return
}
ret, err = &AppProperties{
@ -182,7 +180,6 @@ func (f *File) SetDocProps(docProperties *DocProperties) error {
core = new(decodeCoreProperties)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return err
}
newProps = &xlsxCoreProperties{
@ -237,7 +234,6 @@ func (f *File) GetDocProps() (ret *DocProperties, err error) {
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathDocPropsCore)))).
Decode(core); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return
}
ret, err = &DocProperties{

View File

@ -42,7 +42,7 @@ func TestSetAppProps(t *testing.T) {
// Test unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetAppProps(&AppProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.SetAppProps(&AppProperties{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetAppProps(t *testing.T) {
@ -58,11 +58,11 @@ func TestGetAppProps(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test unsupported charset
// Test get application properties with unsupported charset.
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsApp, MacintoshCyrillicCharset)
_, err = f.GetAppProps()
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetDocProps(t *testing.T) {
@ -94,7 +94,7 @@ func TestSetDocProps(t *testing.T) {
// Test unsupported charset
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.SetDocProps(&DocProperties{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetDocProps(t *testing.T) {
@ -110,9 +110,9 @@ func TestGetDocProps(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, f.Close())
// Test unsupported charset
// Test get workbook properties with unsupported charset.
f = NewFile()
f.Pkg.Store(defaultXMLPathDocPropsCore, MacintoshCyrillicCharset)
_, err = f.GetDocProps()
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}

View File

@ -15,7 +15,6 @@ import (
"bytes"
"encoding/xml"
"io"
"log"
"reflect"
"strconv"
"strings"
@ -1194,7 +1193,7 @@ func (f *File) drawPlotAreaTxPr(opts *chartAxisOptions) *cTxPr {
// the problem that the label structure is changed after serialization and
// deserialization, two different structures: decodeWsDr and encodeWsDr are
// defined.
func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
func (f *File) drawingParser(path string) (*xlsxWsDr, int, error) {
var (
err error
ok bool
@ -1208,7 +1207,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
decodeWsDr := decodeWsDr{}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
Decode(&decodeWsDr); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
return nil, 0, err
}
content.R = decodeWsDr.R
for _, v := range decodeWsDr.AlternateContent {
@ -1238,7 +1237,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
}
wsDr.Lock()
defer wsDr.Unlock()
return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2
return wsDr, len(wsDr.OneCellAnchor) + len(wsDr.TwoCellAnchor) + 2, nil
}
// addDrawingChart provides a function to add chart graphic frame by given
@ -1254,7 +1253,10 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
width = int(float64(width) * opts.XScale)
height = int(float64(height) * opts.YScale)
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.OffsetX, opts.OffsetY, width, height)
content, cNvPrID := f.drawingParser(drawingXML)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = opts.Positioning
from := xlsxFrom{}
@ -1302,8 +1304,11 @@ func (f *File) addDrawingChart(sheet, drawingXML, cell string, width, height, rI
// addSheetDrawingChart provides a function to add chart graphic frame for
// chartsheet by given sheet, drawingXML, width, height, relationship index
// and format sets.
func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOptions) {
content, cNvPrID := f.drawingParser(drawingXML)
func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOptions) error {
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
absoluteAnchor := xdrCellAnchor{
EditAs: opts.Positioning,
Pos: &xlsxPoint2D{},
@ -1336,6 +1341,7 @@ func (f *File) addSheetDrawingChart(drawingXML string, rID int, opts *pictureOpt
}
content.AbsoluteAnchor = append(content.AbsoluteAnchor, &absoluteAnchor)
f.Drawings.Store(drawingXML, content)
return err
}
// deleteDrawing provides a function to delete chart graphic frame by given by
@ -1354,7 +1360,9 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error
"Chart": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic == nil },
"Pic": func(anchor *decodeTwoCellAnchor) bool { return anchor.Pic != nil },
}
wsDr, _ = f.drawingParser(drawingXML)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return err
}
for idx := 0; idx < len(wsDr.TwoCellAnchor); idx++ {
if err = nil; wsDr.TwoCellAnchor[idx].From != nil && xdrCellAnchorFuncs[drawingType](wsDr.TwoCellAnchor[idx]) {
if wsDr.TwoCellAnchor[idx].From.Col == col && wsDr.TwoCellAnchor[idx].From.Row == row {
@ -1367,7 +1375,6 @@ func (f *File) deleteDrawing(col, row int, drawingXML, drawingType string) error
deTwoCellAnchor = new(decodeTwoCellAnchor)
if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + wsDr.TwoCellAnchor[idx].GraphicFrame + "</decodeTwoCellAnchor>")).
Decode(deTwoCellAnchor); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return err
}
if err = nil; deTwoCellAnchor.From != nil && decodeTwoCellAnchorFuncs[drawingType](deTwoCellAnchor) {

View File

@ -15,6 +15,8 @@ import (
"encoding/xml"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDrawingParser(t *testing.T) {
@ -24,12 +26,15 @@ func TestDrawingParser(t *testing.T) {
}
f.Pkg.Store("charset", MacintoshCyrillicCharset)
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`))
// Test with one cell anchor
f.drawingParser("wsDr")
// Test with unsupported charset
f.drawingParser("charset")
// Test with alternate content
// Test with one cell anchor.
_, _, err := f.drawingParser("wsDr")
assert.NoError(t, err)
// Test with unsupported charset.
_, _, err = f.drawingParser("charset")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test with alternate content.
f.Drawings = sync.Map{}
f.Pkg.Store("wsDr", []byte(xml.Header+`<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"><mc:Choice xmlns:a14="http://schemas.microsoft.com/office/drawing/2010/main" Requires="a14"><xdr:twoCellAnchor editAs="oneCell"></xdr:twoCellAnchor></mc:Choice><mc:Fallback/></mc:AlternateContent></xdr:wsDr>`))
f.drawingParser("wsDr")
_, _, err = f.drawingParser("wsDr")
assert.NoError(t, err)
}

View File

@ -82,11 +82,6 @@ func newNotWorksheetError(name string) error {
return fmt.Errorf("sheet %s is not a worksheet", name)
}
// newDecodeXMLError defined the error message on decode XML error.
func newDecodeXMLError(err error) error {
return fmt.Errorf("xml decode error: %s", err)
}
// newStreamSetRowError defined the error message on the stream writer
// receiving the non-ascending row number.
func newStreamSetRowError(row int) error {

View File

@ -177,11 +177,13 @@ func OpenReader(r io.Reader, opts ...Options) (*File, error) {
for k, v := range file {
f.Pkg.Store(k, v)
}
f.CalcChain = f.calcChainReader()
if f.CalcChain, err = f.calcChainReader(); err != nil {
return f, err
}
f.sheetMap = f.getSheetMap()
f.Styles = f.stylesReader()
f.Styles, err = f.stylesReader()
f.Theme = f.themeReader()
return f, nil
return f, err
}
// parseOptions provides a function to parse the optional settings for open
@ -250,7 +252,6 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readBytes(name)))).
Decode(ws); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return
}
err = nil

View File

@ -10,6 +10,7 @@ import (
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"math"
"os"
"path/filepath"
@ -217,6 +218,28 @@ func TestOpenReader(t *testing.T) {
_, err = OpenReader(bytes.NewReader(oleIdentifier), Options{Password: "password", UnzipXMLSizeLimit: UnzipSizeLimit + 1})
assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
// Test open workbook with unsupported charset internal calculation chain.
source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx"))
assert.NoError(t, err)
buf := new(bytes.Buffer)
zw := zip.NewWriter(buf)
for _, item := range source.File {
// The following statements can be simplified as zw.Copy(item) in go1.17
writer, err := zw.Create(item.Name)
assert.NoError(t, err)
readerCloser, err := item.Open()
assert.NoError(t, err)
_, err = io.Copy(writer, readerCloser)
assert.NoError(t, err)
}
fi, err := zw.Create(defaultXMLPathCalcChain)
assert.NoError(t, err)
_, err = fi.Write(MacintoshCyrillicCharset)
assert.NoError(t, err)
assert.NoError(t, zw.Close())
_, err = OpenReader(buf)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test open spreadsheet with unzip size limit.
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100})
assert.EqualError(t, err, newUnzipSizeLimitError(100).Error())
@ -338,6 +361,9 @@ func TestAddDrawingVML(t *testing.T) {
// Test addDrawingVML with illegal cell reference.
f := NewFile()
assert.EqualError(t, f.addDrawingVML(0, "", "*", 0, 0), newCellNameToCoordinatesError("*", newInvalidCellNameError("*")).Error())
f.Pkg.Store("xl/drawings/vmlDrawing1.vml", MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingVML(0, "xl/drawings/vmlDrawing1.vml", "A1", 0, 0), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetCellHyperLink(t *testing.T) {
@ -1332,8 +1358,8 @@ func TestWorkSheetReader(t *testing.T) {
f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err := f.workSheetReader("Sheet1")
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.UpdateLinkedValue(), "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8")
// Test on no checked worksheet.
f = NewFile()

View File

@ -37,11 +37,11 @@ func NewFile() *File {
f.Pkg.Store(defaultXMLPathWorkbook, []byte(xml.Header+templateWorkbook))
f.Pkg.Store(defaultXMLPathContentTypes, []byte(xml.Header+templateContentTypes))
f.SheetCount = 1
f.CalcChain = f.calcChainReader()
f.CalcChain, _ = f.calcChainReader()
f.Comments = make(map[string]*xlsxComments)
f.ContentTypes = f.contentTypesReader()
f.Drawings = sync.Map{}
f.Styles = f.stylesReader()
f.Styles, _ = f.stylesReader()
f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing)
f.VMLDrawing = make(map[string]*vmlDrawing)
f.WorkBook = f.workbookReader()

View File

@ -197,3 +197,9 @@ func TestFlatMergedCells(t *testing.T) {
ws := &xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: ""}}}}
assert.EqualError(t, flatMergedCells(ws, [][]*xlsxMergeCell{}), "cannot convert cell \"\" to coordinates: invalid cell name \"\"")
}
func TestMergeCellsParser(t *testing.T) {
f := NewFile()
_, err := f.mergeCellsParser(&xlsxWorksheet{MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{nil}}}, "A1")
assert.NoError(t, err)
}

View File

@ -281,7 +281,10 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file, ext string, rID,
col--
row--
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, col, row, opts.OffsetX, opts.OffsetY, width, height)
content, cNvPrID := f.drawingParser(drawingXML)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = opts.Positioning
from := xlsxFrom{}
@ -559,14 +562,15 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
deTwoCellAnchor *decodeTwoCellAnchor
)
wsDr, _ = f.drawingParser(drawingXML)
if wsDr, _, err = f.drawingParser(drawingXML); err != nil {
return
}
if ret, buf = f.getPictureFromWsDr(row, col, drawingRelationships, wsDr); len(buf) > 0 {
return
}
deWsDr = new(decodeWsDr)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(drawingXML)))).
Decode(deWsDr); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return
}
err = nil
@ -574,7 +578,6 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
deTwoCellAnchor = new(decodeTwoCellAnchor)
if err = f.xmlNewDecoder(strings.NewReader("<decodeTwoCellAnchor>" + anchor.Content + "</decodeTwoCellAnchor>")).
Decode(deTwoCellAnchor); err != nil && err != io.EOF {
err = newDecodeXMLError(err)
return
}
if err = nil; deTwoCellAnchor.From != nil && deTwoCellAnchor.Pic != nil {

View File

@ -169,15 +169,25 @@ func TestGetPicture(t *testing.T) {
assert.Empty(t, raw)
f, err = prepareTestBook1()
assert.NoError(t, err)
f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
_, _, err = f.getPicture(20, 5, "xl/drawings/drawing1.xml", "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
// Test get pictures with unsupported charset.
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
_, _, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
f.Drawings.Delete(path)
_, _, err = f.getPicture(20, 5, path, "xl/drawings/_rels/drawing2.xml.rels")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestAddDrawingPicture(t *testing.T) {
// Test addDrawingPicture with illegal cell reference.
f := NewFile()
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", "", 0, 0, image.Config{}, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingPicture("sheet1", path, "A1", "", "", 0, 0, image.Config{}, &pictureOptions{}), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddPictureFromBytes(t *testing.T) {

39
rows.go
View File

@ -16,7 +16,6 @@ import (
"encoding/xml"
"fmt"
"io"
"log"
"math"
"os"
"strconv"
@ -139,7 +138,10 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) {
}
var rowIterator rowXMLIterator
var token xml.Token
rows.rawCellValue, rows.sst = parseOptions(opts...).RawCellValue, rows.f.sharedStringsReader()
rows.rawCellValue = parseOptions(opts...).RawCellValue
if rows.sst, rowIterator.err = rows.f.sharedStringsReader(); rowIterator.err != nil {
return rowIterator.cells, rowIterator.err
}
for {
if rows.token != nil {
token = rows.token
@ -160,21 +162,21 @@ func (rows *Rows) Columns(opts ...Options) ([]string, error) {
rows.seekRowOpts = extractRowOpts(xmlElement.Attr)
if rows.curRow > rows.seekRow {
rows.token = nil
return rowIterator.columns, rowIterator.err
return rowIterator.cells, rowIterator.err
}
}
if rows.rowXMLHandler(&rowIterator, &xmlElement, rows.rawCellValue); rowIterator.err != nil {
rows.token = nil
return rowIterator.columns, rowIterator.err
return rowIterator.cells, rowIterator.err
}
rows.token = nil
case xml.EndElement:
if xmlElement.Name.Local == "sheetData" {
return rowIterator.columns, rowIterator.err
return rowIterator.cells, rowIterator.err
}
}
}
return rowIterator.columns, rowIterator.err
return rowIterator.cells, rowIterator.err
}
// extractRowOpts extract row element attributes.
@ -211,10 +213,10 @@ func (err ErrSheetNotExist) Error() string {
// rowXMLIterator defined runtime use field for the worksheet row SAX parser.
type rowXMLIterator struct {
err error
inElement string
cellCol int
columns []string
err error
inElement string
cellCol, cellRow int
cells []string
}
// rowXMLHandler parse the row XML element of the worksheet.
@ -228,9 +230,9 @@ func (rows *Rows) rowXMLHandler(rowIterator *rowXMLIterator, xmlElement *xml.Sta
return
}
}
blank := rowIterator.cellCol - len(rowIterator.columns)
blank := rowIterator.cellCol - len(rowIterator.cells)
if val, _ := colCell.getValueFrom(rows.f, rows.sst, raw); val != "" || colCell.F != nil {
rowIterator.columns = append(appendSpace(blank, rowIterator.columns), val)
rowIterator.cells = append(appendSpace(blank, rowIterator.cells), val)
}
}
}
@ -409,7 +411,7 @@ 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 {
func (f *File) sharedStringsReader() (*xlsxSST, error) {
var err error
f.Lock()
defer f.Unlock()
@ -419,7 +421,7 @@ func (f *File) sharedStringsReader() *xlsxSST {
ss := f.readXML(defaultXMLPathSharedStrings)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(ss))).
Decode(&sharedStrings); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
return f.SharedStrings, err
}
if sharedStrings.Count == 0 {
sharedStrings.Count = len(sharedStrings.SI)
@ -437,14 +439,14 @@ func (f *File) sharedStringsReader() *xlsxSST {
rels := f.relsReader(relPath)
for _, rel := range rels.Relationships {
if rel.Target == "/xl/sharedStrings.xml" {
return f.SharedStrings
return f.SharedStrings, nil
}
}
// Update workbook.xml.rels
f.addRels(relPath, SourceRelationshipSharedStrings, "/xl/sharedStrings.xml", "")
}
return f.SharedStrings
return f.SharedStrings, nil
}
// SetRowVisible provides a function to set visible of a single row by given
@ -800,7 +802,10 @@ func (f *File) SetRowStyle(sheet string, start, end, styleID int) error {
if end > TotalRows {
return ErrMaxRows
}
s := f.stylesReader()
s, err := f.stylesReader()
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {

View File

@ -55,7 +55,7 @@ func TestRows(t *testing.T) {
value, err := f.GetCellValue("Sheet1", "A19")
assert.NoError(t, err)
assert.Equal(t, "Total:", value)
// Test load shared string table to memory
// Test load shared string table to memory.
err = f.SetCellValue("Sheet1", "A19", "A19")
assert.NoError(t, err)
value, err = f.GetCellValue("Sheet1", "A19")
@ -63,6 +63,14 @@ func TestRows(t *testing.T) {
assert.Equal(t, "A19", value)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRow.xlsx")))
assert.NoError(t, f.Close())
// Test rows iterator with unsupported charset shared strings table.
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
rows, err = f.Rows(sheet2)
assert.NoError(t, err)
_, err = rows.Columns()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestRowsIterator(t *testing.T) {
@ -225,6 +233,7 @@ func TestColumns(t *testing.T) {
func TestSharedStringsReader(t *testing.T) {
f := NewFile()
// Test read shared string with unsupported charset.
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
f.sharedStringsReader()
si := xlsxSI{}
@ -965,12 +974,16 @@ func TestSetRowStyle(t *testing.T) {
cellStyleID, err := f.GetCellStyle("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, style2, cellStyleID)
// Test cell inheritance rows style
// Test cell inheritance rows style.
assert.NoError(t, f.SetCellValue("Sheet1", "C1", nil))
cellStyleID, err = f.GetCellStyle("Sheet1", "C1")
assert.NoError(t, err)
assert.Equal(t, style2, cellStyleID)
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetRowStyle.xlsx")))
// Test set row style with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetRowStyle("Sheet1", 1, 1, cellStyleID), "XML syntax error on line 1: invalid UTF-8")
}
func TestNumberFormats(t *testing.T) {

View File

@ -305,8 +305,7 @@ func (f *File) AddShape(sheet, cell, opts string) error {
f.addSheetDrawing(sheet, rID)
f.addSheetNameSpace(sheet, SourceRelationship)
}
err = f.addDrawingShape(sheet, drawingXML, cell, options)
if err != nil {
if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil {
return err
}
f.addContentTypePart(drawingID, "drawings")
@ -328,7 +327,10 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
colStart, rowStart, colEnd, rowEnd, x2, y2 := f.positionObjectPixels(sheet, colIdx, rowIdx, opts.Format.OffsetX, opts.Format.OffsetY,
width, height)
content, cNvPrID := f.drawingParser(drawingXML)
content, cNvPrID, err := f.drawingParser(drawingXML)
if err != nil {
return err
}
twoCellAnchor := xdrCellAnchor{}
twoCellAnchor.EditAs = opts.Format.Positioning
from := xlsxFrom{}
@ -385,6 +387,10 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
W: f.ptToEMUs(opts.Line.Width),
}
}
defaultFont, err := f.GetDefaultFont()
if err != nil {
return err
}
if len(opts.Paragraph) < 1 {
opts.Paragraph = []shapeParagraphOptions{
{
@ -392,7 +398,7 @@ func (f *File) addDrawingShape(sheet, drawingXML, cell string, opts *shapeOption
Bold: false,
Italic: false,
Underline: "none",
Family: f.GetDefaultFont(),
Family: defaultFont,
Size: 11,
Color: "#000000",
},

View File

@ -87,4 +87,15 @@ func TestAddShape(t *testing.T) {
}
}`))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
// Test set row style with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddDrawingShape(t *testing.T) {
f := NewFile()
path := "xl/drawings/drawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset)
assert.EqualError(t, f.addDrawingShape("sheet1", path, "A1", &shapeOptions{}), "XML syntax error on line 1: invalid UTF-8")
}

View File

@ -881,10 +881,12 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string,
var (
cellName, inElement string
cellCol, row int
d *xlsxSST
sst *xlsxSST
)
d = f.sharedStringsReader()
if sst, err = f.sharedStringsReader(); err != nil {
return
}
decoder := f.xmlNewDecoder(bytes.NewReader(f.readBytes(name)))
for {
var token xml.Token
@ -907,7 +909,7 @@ func (f *File) searchSheet(name, value string, regSearch bool) (result []string,
if inElement == "c" {
colCell := xlsxC{}
_ = decoder.DecodeElement(&colCell, &xmlElement)
val, _ := colCell.getValueFrom(f, d, false)
val, _ := colCell.getValueFrom(f, sst, false)
if regSearch {
regex := regexp.MustCompile(value)
if !regex.MatchString(val) {

View File

@ -91,6 +91,12 @@ func TestSearchSheet(t *testing.T) {
result, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, "invalid cell reference [1, 0]")
assert.Equal(t, []string(nil), result)
// Test search sheet with unsupported charset shared strings table.
f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
_, err = f.SearchSheet("Sheet1", "A")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetPageLayout(t *testing.T) {

View File

@ -106,12 +106,12 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, streamWriter.rawData.tmp.Close())
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
// Test unsupported charset
// Test create stream writer with unsupported charset.
file = NewFile()
file.Sheet.Delete("xl/worksheets/sheet1.xml")
file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err = file.NewStreamWriter("Sheet1")
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.NoError(t, file.Close())
// Test read cell.

112
styles.go
View File

@ -1037,15 +1037,15 @@ func formatToE(v, format string, date1904 bool) string {
// stylesReader provides a function to get the pointer to the structure after
// deserialization of xl/styles.xml.
func (f *File) stylesReader() *xlsxStyleSheet {
func (f *File) stylesReader() (*xlsxStyleSheet, error) {
if f.Styles == nil {
f.Styles = new(xlsxStyleSheet)
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathStyles)))).
Decode(f.Styles); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err)
return f.Styles, err
}
}
return f.Styles
return f.Styles, nil
}
// styleSheetWriter provides a function to save xl/styles.xml after serialize
@ -1965,9 +1965,12 @@ func parseFormatStyleSet(style interface{}) (*Style, error) {
//
// Cell Sheet1!A6 in the Excel Application: martes, 04 de Julio de 2017
func (f *File) NewStyle(style interface{}) (int, error) {
var fs *Style
var err error
var cellXfsID, fontID, borderID, fillID int
var (
fs *Style
font *xlsxFont
err error
cellXfsID, fontID, borderID, fillID int
)
fs, err = parseFormatStyleSet(style)
if err != nil {
return cellXfsID, err
@ -1975,21 +1978,25 @@ func (f *File) NewStyle(style interface{}) (int, error) {
if fs.DecimalPlaces == 0 {
fs.DecimalPlaces = 2
}
s := f.stylesReader()
s, err := f.stylesReader()
if err != nil {
return cellXfsID, err
}
s.Lock()
defer s.Unlock()
// check given style already exist.
if cellXfsID = f.getStyleID(s, fs); cellXfsID != -1 {
if cellXfsID, err = f.getStyleID(s, fs); err != nil || cellXfsID != -1 {
return cellXfsID, err
}
numFmtID := newNumFmt(s, fs)
if fs.Font != nil {
fontID = f.getFontID(s, fs)
fontID, _ = f.getFontID(s, fs)
if fontID == -1 {
s.Fonts.Count++
s.Fonts.Font = append(s.Fonts.Font, f.newFont(fs))
font, _ = f.newFont(fs)
s.Fonts.Font = append(s.Fonts.Font, font)
fontID = s.Fonts.Count - 1
}
}
@ -2065,12 +2072,19 @@ var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{
// getStyleID provides a function to get styleID by given style. If given
// style does not exist, will return -1.
func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) {
styleID = -1
func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) {
var (
err error
fontID int
styleID = -1
)
if ss.CellXfs == nil {
return
return styleID, err
}
numFmtID, borderID, fillID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style)
if fontID, err = f.getFontID(ss, style); err != nil {
return styleID, err
}
numFmtID, borderID, fillID, fontID := getNumFmtID(ss, style), getBorderID(ss, style), getFillID(ss, style), f.getFontID(ss, style)
if style.CustomNumFmt != nil {
numFmtID = getCustomNumFmtID(ss, style)
}
@ -2082,10 +2096,10 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) {
getXfIDFuncs["alignment"](0, xf, style) &&
getXfIDFuncs["protection"](0, xf, style) {
styleID = xfID
return
return styleID, err
}
}
return
return styleID, err
}
// NewConditionalStyle provides a function to create style for conditional
@ -2093,7 +2107,10 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (styleID int) {
// function. Note that the color field uses RGB color code and only support to
// set font, fills, alignment and borders currently.
func (f *File) NewConditionalStyle(style string) (int, error) {
s := f.stylesReader()
s, err := f.stylesReader()
if err != nil {
return 0, err
}
fs, err := parseFormatStyleSet(style)
if err != nil {
return 0, err
@ -2108,7 +2125,7 @@ func (f *File) NewConditionalStyle(style string) (int, error) {
dxf.Border = newBorders(fs)
}
if fs.Font != nil {
dxf.Font = f.newFont(fs)
dxf.Font, _ = f.newFont(fs)
}
dxfStr, _ := xml.Marshal(dxf)
if s.Dxfs == nil {
@ -2123,41 +2140,56 @@ func (f *File) NewConditionalStyle(style string) (int, error) {
// GetDefaultFont provides the default font name currently set in the
// workbook. The spreadsheet generated by excelize default font is Calibri.
func (f *File) GetDefaultFont() string {
font := f.readDefaultFont()
return *font.Name.Val
func (f *File) GetDefaultFont() (string, error) {
font, err := f.readDefaultFont()
if err != nil {
return "", err
}
return *font.Name.Val, err
}
// SetDefaultFont changes the default font in the workbook.
func (f *File) SetDefaultFont(fontName string) {
font := f.readDefaultFont()
func (f *File) SetDefaultFont(fontName string) error {
font, err := f.readDefaultFont()
if err != nil {
return err
}
font.Name.Val = stringPtr(fontName)
s := f.stylesReader()
s, _ := f.stylesReader()
s.Fonts.Font[0] = font
custom := true
s.CellStyles.CellStyle[0].CustomBuiltIn = &custom
return err
}
// readDefaultFont provides an un-marshalled font value.
func (f *File) readDefaultFont() *xlsxFont {
s := f.stylesReader()
return s.Fonts.Font[0]
func (f *File) readDefaultFont() (*xlsxFont, error) {
s, err := f.stylesReader()
if err != nil {
return nil, err
}
return s.Fonts.Font[0], err
}
// getFontID provides a function to get font ID.
// If given font does not exist, will return -1.
func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (fontID int) {
fontID = -1
func (f *File) getFontID(styleSheet *xlsxStyleSheet, style *Style) (int, error) {
var err error
fontID := -1
if styleSheet.Fonts == nil || style.Font == nil {
return
return fontID, err
}
for idx, fnt := range styleSheet.Fonts.Font {
if reflect.DeepEqual(*fnt, *f.newFont(style)) {
font, err := f.newFont(style)
if err != nil {
return fontID, err
}
if reflect.DeepEqual(*fnt, *font) {
fontID = idx
return
return fontID, err
}
}
return
return fontID, err
}
// newFontColor set font color by given styles.
@ -2190,7 +2222,8 @@ func newFontColor(font *Font) *xlsxColor {
// newFont provides a function to add font style by given cell format
// settings.
func (f *File) newFont(style *Style) *xlsxFont {
func (f *File) newFont(style *Style) (*xlsxFont, error) {
var err error
if style.Font.Size < MinFontSize {
style.Font.Size = 11
}
@ -2207,7 +2240,9 @@ func (f *File) newFont(style *Style) *xlsxFont {
fnt.I = &attrValBool{Val: &style.Font.Italic}
}
if *fnt.Name.Val == "" {
*fnt.Name.Val = f.GetDefaultFont()
if *fnt.Name.Val, err = f.GetDefaultFont(); err != nil {
return &fnt, err
}
}
if style.Font.Strike {
fnt.Strike = &attrValBool{Val: &style.Font.Strike}
@ -2215,7 +2250,7 @@ func (f *File) newFont(style *Style) *xlsxFont {
if idx := inStrSlice(supportedUnderlineTypes, style.Font.Underline, true); idx != -1 {
fnt.U = &attrValString{Val: stringPtr(supportedUnderlineTypes[idx])}
}
return &fnt
return &fnt, err
}
// getNumFmtID provides a function to get number format code ID.
@ -2754,7 +2789,10 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
ws.Lock()
defer ws.Unlock()
s := f.stylesReader()
s, err := f.stylesReader()
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {

View File

@ -30,7 +30,8 @@ func TestStyleFill(t *testing.T) {
styleID, err := xl.NewStyle(testCase.format)
assert.NoError(t, err)
styles := xl.stylesReader()
styles, err := xl.stylesReader()
assert.NoError(t, err)
style := styles.CellXfs.Xf[styleID]
if testCase.expectFill {
assert.NotEqual(t, *style.FillID, 0, testCase.label)
@ -220,7 +221,8 @@ func TestNewStyle(t *testing.T) {
f := NewFile()
styleID, err := f.NewStyle(`{"font":{"bold":true,"italic":true,"family":"Times New Roman","size":36,"color":"#777777"}}`)
assert.NoError(t, err)
styles := f.stylesReader()
styles, err := f.stylesReader()
assert.NoError(t, err)
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")
@ -238,7 +240,7 @@ func TestNewStyle(t *testing.T) {
_, err = f.NewStyle(&Style{Font: &Font{Size: MaxFontSize + 1}})
assert.EqualError(t, err, ErrFontSize.Error())
// new numeric custom style
// Test create numeric custom style.
numFmt := "####;####"
f.Styles.NumFmts = nil
styleID, err = f.NewStyle(&Style{
@ -254,7 +256,7 @@ func TestNewStyle(t *testing.T) {
nf := f.Styles.CellXfs.Xf[styleID]
assert.Equal(t, 164, *nf.NumFmtID)
// new currency custom style
// Test create currency custom style.
f.Styles.NumFmts = nil
styleID, err = f.NewStyle(&Style{
Lang: "ko-kr",
@ -271,7 +273,7 @@ func TestNewStyle(t *testing.T) {
nf = f.Styles.CellXfs.Xf[styleID]
assert.Equal(t, 32, *nf.NumFmtID)
// Test set build-in scientific number format
// Test set build-in scientific number format.
styleID, err = f.NewStyle(&Style{NumFmt: 11})
assert.NoError(t, err)
assert.NoError(t, f.SetCellStyle("Sheet1", "A1", "B1", styleID))
@ -281,7 +283,7 @@ func TestNewStyle(t *testing.T) {
assert.Equal(t, [][]string{{"1.23E+00", "1.23E+00"}}, rows)
f = NewFile()
// Test currency number format
// Test currency number format.
customNumFmt := "[$$-409]#,##0.00"
style1, err := f.NewStyle(&Style{CustomNumFmt: &customNumFmt})
assert.NoError(t, err)
@ -306,21 +308,48 @@ func TestNewStyle(t *testing.T) {
style5, err := f.NewStyle(&Style{NumFmt: 160, Lang: "zh-cn"})
assert.NoError(t, err)
assert.Equal(t, 0, style5)
// Test create style with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.NewStyle(&Style{NumFmt: 165})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestNewConditionalStyle(t *testing.T) {
f := NewFile()
// Test create conditional style with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err := f.NewConditionalStyle(`{"font":{"color":"#9A0511"},"fill":{"type":"pattern","color":["#FEC7CE"],"pattern":1}}`)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestGetDefaultFont(t *testing.T) {
f := NewFile()
s := f.GetDefaultFont()
s, err := f.GetDefaultFont()
assert.NoError(t, err)
assert.Equal(t, s, "Calibri", "Default font should be Calibri")
// Test get default font with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.GetDefaultFont()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestSetDefaultFont(t *testing.T) {
f := NewFile()
f.SetDefaultFont("Arial")
styles := f.stylesReader()
s := f.GetDefaultFont()
assert.NoError(t, f.SetDefaultFont("Arial"))
styles, err := f.stylesReader()
assert.NoError(t, err)
s, err := f.GetDefaultFont()
assert.NoError(t, err)
assert.Equal(t, s, "Arial", "Default font should change to Arial")
assert.Equal(t, *styles.CellStyles.CellStyle[0].CustomBuiltIn, true)
// Test set default font with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDefaultFont("Arial"), "XML syntax error on line 1: invalid UTF-8")
}
func TestStylesReader(t *testing.T) {
@ -328,7 +357,9 @@ func TestStylesReader(t *testing.T) {
// Test read styles with unsupported charset.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader())
styles, err := f.stylesReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualValues(t, new(xlsxStyleSheet), styles)
}
func TestThemeReader(t *testing.T) {
@ -346,14 +377,33 @@ func TestSetCellStyle(t *testing.T) {
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", -1), newInvalidStyleID(-1).Error())
// Test set cell style with not exists style ID.
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 10), newInvalidStyleID(10).Error())
// Test set cell style with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellStyle("Sheet1", "A1", "A2", 1), "XML syntax error on line 1: invalid UTF-8")
}
func TestGetStyleID(t *testing.T) {
assert.Equal(t, -1, NewFile().getStyleID(&xlsxStyleSheet{}, nil))
f := NewFile()
styleID, err := f.getStyleID(&xlsxStyleSheet{}, nil)
assert.NoError(t, err)
assert.Equal(t, -1, styleID)
// Test get style ID with unsupported charset style sheet.
f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
_, err = f.getStyleID(&xlsxStyleSheet{
CellXfs: &xlsxCellXfs{},
Fonts: &xlsxFonts{
Font: []*xlsxFont{{}},
},
}, &Style{NumFmt: 0, Font: &Font{}})
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
func TestGetFillID(t *testing.T) {
assert.Equal(t, -1, getFillID(NewFile().stylesReader(), &Style{Fill: Fill{Type: "unknown"}}))
styles, err := NewFile().stylesReader()
assert.NoError(t, err)
assert.Equal(t, -1, getFillID(styles, &Style{Fill: Fill{Type: "unknown"}}))
}
func TestThemeColor(t *testing.T) {