forked from p30928647/excelize
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:
parent
58b5dae5eb
commit
bd5dd17673
|
@ -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))
|
||||
}
|
||||
|
|
20
calcchain.go
20
calcchain.go
|
@ -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
|
||||
|
|
|
@ -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
92
cell.go
|
@ -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
|
||||
|
|
113
cell_test.go
113
cell_test.go
|
@ -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) {
|
||||
|
|
|
@ -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
78
col.go
|
@ -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()
|
||||
|
|
13
col_test.go
13
col_test.go
|
@ -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) {
|
||||
|
|
102
comment.go
102
comment.go
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
25
drawing.go
25
drawing.go
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
4
file.go
4
file.go
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
11
picture.go
11
picture.go
|
@ -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 {
|
||||
|
|
|
@ -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
39
rows.go
|
@ -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 {
|
||||
|
|
17
rows_test.go
17
rows_test.go
|
@ -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) {
|
||||
|
|
14
shape.go
14
shape.go
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
8
sheet.go
8
sheet.go
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
112
styles.go
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue