- Support concurrency iterate rows and columns

- Rename exported field `File.XLSX` to `File.Pkg`
- Exported error message
This commit is contained in:
xuri 2021-07-05 00:03:56 +08:00
parent 0e02329bed
commit 544ef18a8c
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
38 changed files with 379 additions and 264 deletions

View File

@ -73,20 +73,10 @@ func TestAdjustAutoFilter(t *testing.T) {
func TestAdjustHelper(t *testing.T) { func TestAdjustHelper(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") f.NewSheet("Sheet2")
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{ f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
MergeCells: &xlsxMergeCells{ MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}})
Cells: []*xlsxMergeCell{ f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
{ AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}})
Ref: "A:B1",
},
},
},
}
f.Sheet["xl/worksheets/sheet2.xml"] = &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{
Ref: "A1:B",
},
}
// testing adjustHelper with illegal cell coordinates. // testing adjustHelper with illegal cell coordinates.
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), `cannot convert cell "B" to coordinates: invalid cell name "B"`) assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), `cannot convert cell "B" to coordinates: invalid cell name "B"`)

View File

@ -54,7 +54,7 @@ func (f *File) deleteCalcChain(index int, axis string) {
} }
if len(calc.C) == 0 { if len(calc.C) == 0 {
f.CalcChain = nil f.CalcChain = nil
delete(f.XLSX, "xl/calcChain.xml") f.Pkg.Delete("xl/calcChain.xml")
content := f.contentTypesReader() content := f.contentTypesReader()
for k, v := range content.Overrides { for k, v := range content.Overrides {
if v.PartName == "/xl/calcChain.xml" { if v.PartName == "/xl/calcChain.xml" {

View File

@ -5,7 +5,7 @@ import "testing"
func TestCalcChainReader(t *testing.T) { func TestCalcChainReader(t *testing.T) {
f := NewFile() f := NewFile()
f.CalcChain = nil f.CalcChain = nil
f.XLSX["xl/calcChain.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/calcChain.xml", MacintoshCyrillicCharset)
f.calcChainReader() f.calcChainReader()
} }

12
cell.go
View File

@ -13,7 +13,6 @@ package excelize
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
@ -187,6 +186,8 @@ func (f *File) SetCellInt(sheet, axis string, value int) error {
if err != nil { if err != nil {
return err return err
} }
ws.Lock()
defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, cellData.S) cellData.S = f.prepareCellStyle(ws, col, cellData.S)
cellData.T, cellData.V = setCellInt(value) cellData.T, cellData.V = setCellInt(value)
return err return err
@ -262,6 +263,8 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
if err != nil { if err != nil {
return err return err
} }
ws.Lock()
defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, cellData.S) cellData.S = f.prepareCellStyle(ws, col, cellData.S)
cellData.T, cellData.V = f.setCellString(value) cellData.T, cellData.V = f.setCellString(value)
return err return err
@ -742,7 +745,7 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
// Make sure 'slice' is a Ptr to Slice // Make sure 'slice' is a Ptr to Slice
v := reflect.ValueOf(slice) v := reflect.ValueOf(slice)
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice { if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
return errors.New("pointer to slice expected") return ErrParameterInvalid
} }
v = v.Elem() v = v.Elem()
@ -762,8 +765,6 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
// getCellInfo does common preparation for all SetCell* methods. // getCellInfo does common preparation for all SetCell* methods.
func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) { func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
ws.Lock()
defer ws.Unlock()
var err error var err error
cell, err = f.mergeCellsParser(ws, cell) cell, err = f.mergeCellsParser(ws, cell)
if err != nil { if err != nil {
@ -775,7 +776,8 @@ func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int,
} }
prepareSheetXML(ws, col, row) prepareSheetXML(ws, col, row)
ws.Lock()
defer ws.Unlock()
return &ws.SheetData.Row[row-1].C[col-1], col, row, err return &ws.SheetData.Row[row-1].C[col-1], col, row, err
} }

View File

@ -30,6 +30,20 @@ func TestConcurrency(t *testing.T) {
assert.Equal(t, "", name) assert.Equal(t, "", name)
assert.Nil(t, raw) assert.Nil(t, raw)
assert.NoError(t, err) assert.NoError(t, err)
// Concurrency iterate rows
rows, err := f.Rows("Sheet1")
assert.NoError(t, err)
for rows.Next() {
_, err := rows.Columns()
assert.NoError(t, err)
}
// Concurrency iterate columns
cols, err := f.Cols("Sheet1")
assert.NoError(t, err)
for rows.Next() {
_, err := cols.Rows()
assert.NoError(t, err)
}
wg.Done() wg.Done()
}(i, t) }(i, t)
@ -149,8 +163,8 @@ func TestGetCellValue(t *testing.T) {
// Test get cell value without r attribute of the row. // Test get cell value without r attribute of the row.
f := NewFile() f := NewFile()
sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>` sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="3"><c t="str"><v>A3</v></c></row><row><c t="str"><v>A4</v></c><c t="str"><v>B4</v></c></row><row r="7"><c t="str"><v>A7</v></c><c t="str"><v>B7</v></c></row><row><c t="str"><v>A8</v></c><c t="str"><v>B8</v></c></row>`)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="3"><c t="str"><v>A3</v></c></row><row><c t="str"><v>A4</v></c><c t="str"><v>B4</v></c></row><row r="7"><c t="str"><v>A7</v></c><c t="str"><v>B7</v></c></row><row><c t="str"><v>A8</v></c><c t="str"><v>B8</v></c></row>`)))
f.checked = nil f.checked = nil
cells := []string{"A3", "A4", "B4", "A7", "B7"} cells := []string{"A3", "A4", "B4", "A7", "B7"}
rows, err := f.GetRows("Sheet1") rows, err := f.GetRows("Sheet1")
@ -164,20 +178,20 @@ func TestGetCellValue(t *testing.T) {
cols, err := f.GetCols("Sheet1") cols, err := f.GetCols("Sheet1")
assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols) assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols)
assert.NoError(t, err) assert.NoError(t, err)
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`)))
f.checked = nil f.checked = nil
cell, err := f.GetCellValue("Sheet1", "A2") cell, err := f.GetCellValue("Sheet1", "A2")
assert.Equal(t, "A2", cell) assert.Equal(t, "A2", cell)
assert.NoError(t, err) assert.NoError(t, err)
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="2"><c r="A2" t="str"><v>A2</v></c></row><row r="2"><c r="B2" t="str"><v>B2</v></c></row>`)))
f.checked = nil f.checked = nil
rows, err = f.GetRows("Sheet1") rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows) assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
assert.NoError(t, err) assert.NoError(t, err)
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `<row r="1"><c r="A1" t="str"><v>A1</v></c></row><row r="1"><c r="B1" t="str"><v>B1</v></c></row>`)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `<row r="1"><c r="A1" t="str"><v>A1</v></c></row><row r="1"><c r="B1" t="str"><v>B1</v></c></row>`)))
f.checked = nil f.checked = nil
rows, err = f.GetRows("Sheet1") rows, err = f.GetRows("Sheet1")
assert.Equal(t, [][]string{{"A1", "B1"}}, rows) assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
@ -264,17 +278,23 @@ func TestGetCellRichText(t *testing.T) {
assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font") 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
f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "2" 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") runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, len(runs)) 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
f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "-1" 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") runs, err = f.GetCellRichText("Sheet1", "A1")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 0, len(runs)) 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
f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "x" 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") _, err = f.GetCellRichText("Sheet1", "A1")
assert.EqualError(t, err, "strconv.Atoi: parsing \"x\": invalid syntax") 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

View File

@ -14,7 +14,6 @@ package excelize
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -945,7 +944,7 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
sheetID++ sheetID++
path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml" path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml"
f.sheetMap[trimSheetName(sheet)] = path f.sheetMap[trimSheetName(sheet)] = path
f.Sheet[path] = nil f.Sheet.Store(path, nil)
drawingID := f.countDrawings() + 1 drawingID := f.countDrawings() + 1
chartID := f.countCharts() + 1 chartID := f.countCharts() + 1
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml" drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
@ -981,12 +980,12 @@ func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*f
return formatSet, comboCharts, err return formatSet, comboCharts, err
} }
if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok { if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
return formatSet, comboCharts, errors.New("unsupported chart type " + comboChart.Type) return formatSet, comboCharts, newUnsupportChartType(comboChart.Type)
} }
comboCharts = append(comboCharts, comboChart) comboCharts = append(comboCharts, comboChart)
} }
if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok { if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
return formatSet, comboCharts, errors.New("unsupported chart type " + formatSet.Type) return formatSet, comboCharts, newUnsupportChartType(formatSet.Type)
} }
return formatSet, comboCharts, err return formatSet, comboCharts, err
} }
@ -1015,11 +1014,12 @@ func (f *File) DeleteChart(sheet, cell string) (err error) {
// folder xl/charts. // folder xl/charts.
func (f *File) countCharts() int { func (f *File) countCharts() int {
count := 0 count := 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/charts/chart") { if strings.Contains(k.(string), "xl/charts/chart") {
count++ count++
} }
} return true
})
return count return count
} }

View File

@ -65,10 +65,10 @@ func TestChartSize(t *testing.T) {
anchor decodeTwoCellAnchor anchor decodeTwoCellAnchor
) )
content, ok := newFile.XLSX["xl/drawings/drawing1.xml"] content, ok := newFile.Pkg.Load("xl/drawings/drawing1.xml")
assert.True(t, ok, "Can't open the chart") assert.True(t, ok, "Can't open the chart")
err = xml.Unmarshal([]byte(content), &workdir) err = xml.Unmarshal(content.([]byte), &workdir)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()
} }
@ -340,11 +340,15 @@ func TestChartWithLogarithmicBase(t *testing.T) {
type xmlChartContent []byte type xmlChartContent []byte
xmlCharts := make([]xmlChartContent, expectedChartsCount) xmlCharts := make([]xmlChartContent, expectedChartsCount)
expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000} expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000}
var ok bool var (
drawingML interface{}
ok bool
)
for i := 0; i < expectedChartsCount; i++ { for i := 0; i < expectedChartsCount; i++ {
chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1) chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1)
xmlCharts[i], ok = newFile.XLSX[chartPath] if drawingML, ok = newFile.Pkg.Load(chartPath); ok {
xmlCharts[i] = drawingML.([]byte)
}
assert.True(t, ok, "Can't open the %s", chartPath) assert.True(t, ok, "Can't open the %s", chartPath)
err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i]) err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i])

7
col.go
View File

@ -199,8 +199,11 @@ func (f *File) Cols(sheet string) (*Cols, error) {
if !ok { if !ok {
return nil, ErrSheetNotExist{sheet} return nil, ErrSheetNotExist{sheet}
} }
if f.Sheet[name] != nil { if ws, ok := f.Sheet.Load(name); ok && ws != nil {
output, _ := xml.Marshal(f.Sheet[name]) worksheet := ws.(*xlsxWorksheet)
worksheet.Lock()
defer worksheet.Unlock()
output, _ := xml.Marshal(worksheet)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
} }
var colIterator columnXMLIterator var colIterator columnXMLIterator

View File

@ -48,11 +48,11 @@ func TestCols(t *testing.T) {
_, err = f.Rows("Sheet1") _, err = f.Rows("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{ f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
Dimension: &xlsxDimension{ Dimension: &xlsxDimension{
Ref: "C2:C4", Ref: "C2:C4",
}, },
} })
_, err = f.Rows("Sheet1") _, err = f.Rows("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -110,15 +110,15 @@ func TestGetColsError(t *testing.T) {
assert.EqualError(t, err, "sheet SheetN is not exist") assert.EqualError(t, err, "sheet SheetN is not exist")
f = NewFile() f = NewFile()
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`))
f.checked = nil f.checked = nil
_, err = f.GetCols("Sheet1") _, err = f.GetCols("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
f = NewFile() f = NewFile()
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>B</v></c></row></sheetData></worksheet>`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>B</v></c></row></sheetData></worksheet>`))
f.checked = nil f.checked = nil
_, err = f.GetCols("Sheet1") _, err = f.GetCols("Sheet1")
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
@ -142,14 +142,14 @@ func TestColsRows(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1)) assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{ f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
Dimension: &xlsxDimension{ Dimension: &xlsxDimension{
Ref: "A1:A1", Ref: "A1:A1",
}, },
} })
f = NewFile() f = NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = nil f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
_, err = f.Cols("Sheet1") _, err = f.Cols("Sheet1")
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()

View File

@ -299,11 +299,12 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
// the folder xl. // the folder xl.
func (f *File) countComments() int { func (f *File) countComments() int {
c1, c2 := 0, 0 c1, c2 := 0, 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/comments") { if strings.Contains(k.(string), "xl/comments") {
c1++ c1++
} }
} return true
})
for rel := range f.Comments { for rel := range f.Comments {
if strings.Contains(rel, "xl/comments") { if strings.Contains(rel, "xl/comments") {
c2++ c2++
@ -321,10 +322,10 @@ func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
var err error var err error
if f.DecodeVMLDrawing[path] == nil { if f.DecodeVMLDrawing[path] == nil {
c, ok := f.XLSX[path] c, ok := f.Pkg.Load(path)
if ok { if ok && c != nil {
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing) f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c))). if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF { Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err) log.Printf("xml decode error: %s", err)
} }
@ -339,7 +340,7 @@ func (f *File) vmlDrawingWriter() {
for path, vml := range f.VMLDrawing { for path, vml := range f.VMLDrawing {
if vml != nil { if vml != nil {
v, _ := xml.Marshal(vml) v, _ := xml.Marshal(vml)
f.XLSX[path] = v f.Pkg.Store(path, v)
} }
} }
} }
@ -348,12 +349,11 @@ func (f *File) vmlDrawingWriter() {
// after deserialization of xl/comments%d.xml. // after deserialization of xl/comments%d.xml.
func (f *File) commentsReader(path string) *xlsxComments { func (f *File) commentsReader(path string) *xlsxComments {
var err error var err error
if f.Comments[path] == nil { if f.Comments[path] == nil {
content, ok := f.XLSX[path] content, ok := f.Pkg.Load(path)
if ok { if ok && content != nil {
f.Comments[path] = new(xlsxComments) f.Comments[path] = new(xlsxComments)
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content))). if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(f.Comments[path]); err != nil && err != io.EOF { Decode(f.Comments[path]); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err) log.Printf("xml decode error: %s", err)
} }

View File

@ -36,7 +36,7 @@ func TestAddComments(t *testing.T) {
} }
f.Comments["xl/comments2.xml"] = nil f.Comments["xl/comments2.xml"] = nil
f.XLSX["xl/comments2.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><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>`) f.Pkg.Store("xl/comments2.xml", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><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 := f.GetComments()
assert.EqualValues(t, 2, len(comments["Sheet1"])) assert.EqualValues(t, 2, len(comments["Sheet1"]))
assert.EqualValues(t, 1, len(comments["Sheet2"])) assert.EqualValues(t, 1, len(comments["Sheet2"]))
@ -46,14 +46,14 @@ func TestAddComments(t *testing.T) {
func TestDecodeVMLDrawingReader(t *testing.T) { func TestDecodeVMLDrawingReader(t *testing.T) {
f := NewFile() f := NewFile()
path := "xl/drawings/vmlDrawing1.xml" path := "xl/drawings/vmlDrawing1.xml"
f.XLSX[path] = MacintoshCyrillicCharset f.Pkg.Store(path, MacintoshCyrillicCharset)
f.decodeVMLDrawingReader(path) f.decodeVMLDrawingReader(path)
} }
func TestCommentsReader(t *testing.T) { func TestCommentsReader(t *testing.T) {
f := NewFile() f := NewFile()
path := "xl/comments1.xml" path := "xl/comments1.xml"
f.XLSX[path] = MacintoshCyrillicCharset f.Pkg.Store(path, MacintoshCyrillicCharset)
f.commentsReader(path) f.commentsReader(path)
} }

View File

@ -21,7 +21,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/xml" "encoding/xml"
"errors"
"hash" "hash"
"math/rand" "math/rand"
"reflect" "reflect"
@ -145,7 +144,7 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
case "standard": case "standard":
return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt)
default: default:
err = errors.New("unsupport encryption mechanism") err = ErrUnsupportEncryptMechanism
} }
return return
} }
@ -265,7 +264,7 @@ func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
} }
// TODO: Create a new CFB. // TODO: Create a new CFB.
_, _ = encryptedPackage, encryptionInfoBuffer _, _ = encryptedPackage, encryptionInfoBuffer
err = errors.New("not support encryption currently") err = ErrEncrypt
return return
} }
@ -293,7 +292,7 @@ func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []by
// encryptionMechanism parse password-protected documents created mechanism. // encryptionMechanism parse password-protected documents created mechanism.
func encryptionMechanism(buffer []byte) (mechanism string, err error) { func encryptionMechanism(buffer []byte) (mechanism string, err error) {
if len(buffer) < 4 { if len(buffer) < 4 {
err = errors.New("unknown encryption mechanism") err = ErrUnknownEncryptMechanism
return return
} }
versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[0:2]), binary.LittleEndian.Uint16(buffer[2:4]) versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[0:2]), binary.LittleEndian.Uint16(buffer[2:4])
@ -306,7 +305,7 @@ func encryptionMechanism(buffer []byte) (mechanism string, err error) {
} else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 { } else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 {
mechanism = "extensible" mechanism = "extensible"
} }
err = errors.New("unsupport encryption mechanism") err = ErrUnsupportEncryptMechanism
return return
} }
@ -470,7 +469,6 @@ func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (
if len(key) < keyBytes { if len(key) < keyBytes {
tmp := make([]byte, 0x36) tmp := make([]byte, 0x36)
key = append(key, tmp...) key = append(key, tmp...)
key = tmp
} else if len(key) > keyBytes { } else if len(key) > keyBytes {
key = key[:keyBytes] key = key[:keyBytes]
} }
@ -599,7 +597,6 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
if len(iv) < encryptedKey.BlockSize { if len(iv) < encryptedKey.BlockSize {
tmp := make([]byte, 0x36) tmp := make([]byte, 0x36)
iv = append(iv, tmp...) iv = append(iv, tmp...)
iv = tmp
} else if len(iv) > encryptedKey.BlockSize { } else if len(iv) > encryptedKey.BlockSize {
iv = iv[0:encryptedKey.BlockSize] iv = iv[0:encryptedKey.BlockSize]
} }

View File

@ -42,12 +42,12 @@ func TestSetDocProps(t *testing.T) {
Version: "1.0.0", Version: "1.0.0",
})) }))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetDocProps.xlsx")))
f.XLSX["docProps/core.xml"] = nil f.Pkg.Store("docProps/core.xml", nil)
assert.NoError(t, f.SetDocProps(&DocProperties{})) assert.NoError(t, f.SetDocProps(&DocProperties{}))
// Test unsupported charset // Test unsupported charset
f = NewFile() f = NewFile()
f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset f.Pkg.Store("docProps/core.xml", 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 decode error: XML syntax error on line 1: invalid UTF-8")
} }
@ -59,13 +59,13 @@ func TestGetDocProps(t *testing.T) {
props, err := f.GetDocProps() props, err := f.GetDocProps()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, props.Creator, "Microsoft Office User") assert.Equal(t, props.Creator, "Microsoft Office User")
f.XLSX["docProps/core.xml"] = nil f.Pkg.Store("docProps/core.xml", nil)
_, err = f.GetDocProps() _, err = f.GetDocProps()
assert.NoError(t, err) assert.NoError(t, err)
// Test unsupported charset // Test unsupported charset
f = NewFile() f = NewFile()
f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset f.Pkg.Store("docProps/core.xml", MacintoshCyrillicCharset)
_, err = f.GetDocProps() _, err = f.GetDocProps()
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
} }

View File

@ -1151,7 +1151,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) {
content := xlsxWsDr{} content := xlsxWsDr{}
content.A = NameSpaceDrawingML.Value content.A = NameSpaceDrawingML.Value
content.Xdr = NameSpaceDrawingMLSpreadSheet.Value content.Xdr = NameSpaceDrawingMLSpreadSheet.Value
if _, ok = f.XLSX[path]; ok { // Append Model if _, ok = f.Pkg.Load(path); ok { // Append Model
decodeWsDr := decodeWsDr{} decodeWsDr := decodeWsDr{}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
Decode(&decodeWsDr); err != nil && err != io.EOF { Decode(&decodeWsDr); err != nil && err != io.EOF {

View File

@ -19,10 +19,10 @@ import (
func TestDrawingParser(t *testing.T) { func TestDrawingParser(t *testing.T) {
f := File{ f := File{
Drawings: sync.Map{}, Drawings: sync.Map{},
XLSX: map[string][]byte{ Pkg: sync.Map{},
"charset": MacintoshCyrillicCharset,
"wsDr": []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`)},
} }
f.Pkg.Store("charset", MacintoshCyrillicCharset)
f.Pkg.Store("wsDr", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><xdr:oneCellAnchor><xdr:graphicFrame/></xdr:oneCellAnchor></xdr:wsDr>`))
// Test with one cell anchor // Test with one cell anchor
f.drawingParser("wsDr") f.drawingParser("wsDr")
// Test with unsupported charset // Test with unsupported charset

View File

@ -32,6 +32,10 @@ func newInvalidExcelDateError(dateValue float64) error {
return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue) return fmt.Errorf("invalid date value %f, negative values are not supported supported", dateValue)
} }
func newUnsupportChartType(chartType string) error {
return fmt.Errorf("unsupported chart type %s", chartType)
}
var ( var (
// ErrStreamSetColWidth defined the error message on set column width in // ErrStreamSetColWidth defined the error message on set column width in
// stream writing mode. // stream writing mode.
@ -71,4 +75,34 @@ var (
// ErrMaxFileNameLength defined the error message on receive the file name // ErrMaxFileNameLength defined the error message on receive the file name
// length overflow. // length overflow.
ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit") ErrMaxFileNameLength = errors.New("file name length exceeds maximum limit")
// ErrEncrypt defined the error message on encryption spreadsheet.
ErrEncrypt = errors.New("not support encryption currently")
// ErrUnknownEncryptMechanism defined the error message on unsupport
// encryption mechanism.
ErrUnknownEncryptMechanism = errors.New("unknown encryption mechanism")
// ErrUnsupportEncryptMechanism defined the error message on unsupport
// encryption mechanism.
ErrUnsupportEncryptMechanism = errors.New("unsupport encryption mechanism")
// ErrParameterRequired defined the error message on receive the empty
// parameter.
ErrParameterRequired = errors.New("parameter is required")
// ErrParameterInvalid defined the error message on receive the invalid
// parameter.
ErrParameterInvalid = errors.New("parameter is invalid")
// ErrDefinedNameScope defined the error message on not found defined name
// in the given scope.
ErrDefinedNameScope = errors.New("no defined name on the scope")
// ErrDefinedNameduplicate defined the error message on the same name
// already exists on the scope.
ErrDefinedNameduplicate = errors.New("the same name already exists on the scope")
// ErrFontLength defined the error message on the length of the font
// family name overflow.
ErrFontLength = errors.New("the length of the font family name must be smaller than or equal to 31")
// ErrFontSize defined the error message on the size of the font is invalid.
ErrFontSize = errors.New("font size must be between 1 and 409 points")
// ErrSheetIdx defined the error message on receive the invalid worksheet
// index.
ErrSheetIdx = errors.New("invalid worksheet index")
// ErrGroupSheets defined the error message on group sheets.
ErrGroupSheets = errors.New("group worksheet must contain an active worksheet")
) )

View File

@ -43,7 +43,7 @@ type File struct {
Path string Path string
SharedStrings *xlsxSST SharedStrings *xlsxSST
sharedStringsMap map[string]int sharedStringsMap map[string]int
Sheet map[string]*xlsxWorksheet Sheet sync.Map // map[string]*xlsxWorksheet
SheetCount int SheetCount int
Styles *xlsxStyleSheet Styles *xlsxStyleSheet
Theme *xlsxTheme Theme *xlsxTheme
@ -51,7 +51,7 @@ type File struct {
VMLDrawing map[string]*vmlDrawing VMLDrawing map[string]*vmlDrawing
WorkBook *xlsxWorkbook WorkBook *xlsxWorkbook
Relationships sync.Map Relationships sync.Map
XLSX map[string][]byte Pkg sync.Map
CharsetReader charsetTranscoderFn CharsetReader charsetTranscoderFn
} }
@ -95,7 +95,7 @@ func newFile() *File {
Comments: make(map[string]*xlsxComments), Comments: make(map[string]*xlsxComments),
Drawings: sync.Map{}, Drawings: sync.Map{},
sharedStringsMap: make(map[string]int), sharedStringsMap: make(map[string]int),
Sheet: make(map[string]*xlsxWorksheet), Sheet: sync.Map{},
DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), DecodeVMLDrawing: make(map[string]*decodeVmlDrawing),
VMLDrawing: make(map[string]*vmlDrawing), VMLDrawing: make(map[string]*vmlDrawing),
Relationships: sync.Map{}, Relationships: sync.Map{},
@ -129,7 +129,10 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
f.SheetCount, f.XLSX = sheetCount, file f.SheetCount = sheetCount
for k, v := range file {
f.Pkg.Store(k, v)
}
f.CalcChain = f.calcChainReader() f.CalcChain = f.calcChainReader()
f.sheetMap = f.getSheetMap() f.sheetMap = f.getSheetMap()
f.Styles = f.stylesReader() f.Styles = f.stylesReader()
@ -172,12 +175,14 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
name string name string
ok bool ok bool
) )
if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok { if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok {
err = fmt.Errorf("sheet %s is not exist", sheet) err = fmt.Errorf("sheet %s is not exist", sheet)
return return
} }
if ws = f.Sheet[name]; f.Sheet[name] == nil { if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil {
ws = worksheet.(*xlsxWorksheet)
return
}
if strings.HasPrefix(name, "xl/chartsheets") { if strings.HasPrefix(name, "xl/chartsheets") {
err = fmt.Errorf("sheet %s is chart sheet", sheet) err = fmt.Errorf("sheet %s is chart sheet", sheet)
return return
@ -203,9 +208,7 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
} }
f.checked[name] = true f.checked[name] = true
} }
f.Sheet[name] = ws f.Sheet.Store(name, ws)
}
return return
} }
@ -375,7 +378,7 @@ func (f *File) AddVBAProject(bin string) error {
}) })
} }
file, _ := ioutil.ReadFile(bin) file, _ := ioutil.ReadFile(bin)
f.XLSX["xl/vbaProject.bin"] = file f.Pkg.Store("xl/vbaProject.bin", file)
return err return err
} }

View File

@ -343,13 +343,17 @@ func TestSetCellHyperLink(t *testing.T) {
f = NewFile() f = NewFile()
_, err = f.workSheetReader("Sheet1") _, err = f.workSheetReader("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
f.Sheet["xl/worksheets/sheet1.xml"].Hyperlinks = &xlsxHyperlinks{Hyperlink: make([]xlsxHyperlink, 65530)} ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{Hyperlink: make([]xlsxHyperlink, 65530)}
assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/360EntSecGroup-Skylar/excelize", "External"), ErrTotalSheetHyperlinks.Error()) assert.EqualError(t, f.SetCellHyperLink("Sheet1", "A65531", "https://github.com/360EntSecGroup-Skylar/excelize", "External"), ErrTotalSheetHyperlinks.Error())
f = NewFile() f = NewFile()
_, err = f.workSheetReader("Sheet1") _, err = f.workSheetReader("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
err = f.SetCellHyperLink("Sheet1", "A1", "https://github.com/360EntSecGroup-Skylar/excelize", "External") err = f.SetCellHyperLink("Sheet1", "A1", "https://github.com/360EntSecGroup-Skylar/excelize", "External")
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
} }
@ -376,7 +380,9 @@ func TestGetCellHyperLink(t *testing.T) {
f = NewFile() f = NewFile()
_, err = f.workSheetReader("Sheet1") _, err = f.workSheetReader("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
f.Sheet["xl/worksheets/sheet1.xml"].Hyperlinks = &xlsxHyperlinks{ ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).Hyperlinks = &xlsxHyperlinks{
Hyperlink: []xlsxHyperlink{{Ref: "A1"}}, Hyperlink: []xlsxHyperlink{{Ref: "A1"}},
} }
link, target, err = f.GetCellHyperLink("Sheet1", "A1") link, target, err = f.GetCellHyperLink("Sheet1", "A1")
@ -384,7 +390,9 @@ func TestGetCellHyperLink(t *testing.T) {
assert.Equal(t, link, true) assert.Equal(t, link, true)
assert.Equal(t, target, "") assert.Equal(t, target, "")
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
link, target, err = f.GetCellHyperLink("Sheet1", "A1") link, target, err = f.GetCellHyperLink("Sheet1", "A1")
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
assert.Equal(t, link, false) assert.Equal(t, link, false)
@ -1112,8 +1120,8 @@ func TestSetSheetRow(t *testing.T) {
assert.EqualError(t, f.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}), assert.EqualError(t, f.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}),
`cannot convert cell "" to coordinates: invalid cell name ""`) `cannot convert cell "" to coordinates: invalid cell name ""`)
assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", []interface{}{}), `pointer to slice expected`) assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", []interface{}{}), ErrParameterInvalid.Error())
assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", &f), `pointer to slice expected`) assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", &f), ErrParameterInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx")))
} }
@ -1198,7 +1206,7 @@ func TestContentTypesReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset.
f := NewFile() f := NewFile()
f.ContentTypes = nil f.ContentTypes = nil
f.XLSX["[Content_Types].xml"] = MacintoshCyrillicCharset f.Pkg.Store("[Content_Types].xml", MacintoshCyrillicCharset)
f.contentTypesReader() f.contentTypesReader()
} }
@ -1206,22 +1214,22 @@ func TestWorkbookReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset.
f := NewFile() f := NewFile()
f.WorkBook = nil f.WorkBook = nil
f.XLSX["xl/workbook.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/workbook.xml", MacintoshCyrillicCharset)
f.workbookReader() f.workbookReader()
} }
func TestWorkSheetReader(t *testing.T) { func TestWorkSheetReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset.
f := NewFile() f := NewFile()
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err := f.workSheetReader("Sheet1") _, err := f.workSheetReader("Sheet1")
assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
// Test on no checked worksheet. // Test on no checked worksheet.
f = NewFile() f = NewFile()
delete(f.Sheet, "xl/worksheets/sheet1.xml") f.Sheet.Delete("xl/worksheets/sheet1.xml")
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData/></worksheet>`))
f.checked = nil f.checked = nil
_, err = f.workSheetReader("Sheet1") _, err = f.workSheetReader("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
@ -1232,7 +1240,7 @@ func TestRelsReader(t *testing.T) {
f := NewFile() f := NewFile()
rels := "xl/_rels/workbook.xml.rels" rels := "xl/_rels/workbook.xml.rels"
f.Relationships.Store(rels, nil) f.Relationships.Store(rels, nil)
f.XLSX[rels] = MacintoshCyrillicCharset f.Pkg.Store(rels, MacintoshCyrillicCharset)
f.relsReader(rels) f.relsReader(rels)
} }

54
file.go
View File

@ -26,18 +26,17 @@ import (
// f := NewFile() // f := NewFile()
// //
func NewFile() *File { func NewFile() *File {
file := make(map[string][]byte)
file["_rels/.rels"] = []byte(XMLHeader + templateRels)
file["docProps/app.xml"] = []byte(XMLHeader + templateDocpropsApp)
file["docProps/core.xml"] = []byte(XMLHeader + templateDocpropsCore)
file["xl/_rels/workbook.xml.rels"] = []byte(XMLHeader + templateWorkbookRels)
file["xl/theme/theme1.xml"] = []byte(XMLHeader + templateTheme)
file["xl/worksheets/sheet1.xml"] = []byte(XMLHeader + templateSheet)
file["xl/styles.xml"] = []byte(XMLHeader + templateStyles)
file["xl/workbook.xml"] = []byte(XMLHeader + templateWorkbook)
file["[Content_Types].xml"] = []byte(XMLHeader + templateContentTypes)
f := newFile() f := newFile()
f.SheetCount, f.XLSX = 1, file f.Pkg.Store("_rels/.rels", []byte(XMLHeader+templateRels))
f.Pkg.Store("docProps/app.xml", []byte(XMLHeader+templateDocpropsApp))
f.Pkg.Store("docProps/core.xml", []byte(XMLHeader+templateDocpropsCore))
f.Pkg.Store("xl/_rels/workbook.xml.rels", []byte(XMLHeader+templateWorkbookRels))
f.Pkg.Store("xl/theme/theme1.xml", []byte(XMLHeader+templateTheme))
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(XMLHeader+templateSheet))
f.Pkg.Store("xl/styles.xml", []byte(XMLHeader+templateStyles))
f.Pkg.Store("xl/workbook.xml", []byte(XMLHeader+templateWorkbook))
f.Pkg.Store("[Content_Types].xml", []byte(XMLHeader+templateContentTypes))
f.SheetCount = 1
f.CalcChain = f.calcChainReader() f.CalcChain = f.calcChainReader()
f.Comments = make(map[string]*xlsxComments) f.Comments = make(map[string]*xlsxComments)
f.ContentTypes = f.contentTypesReader() f.ContentTypes = f.contentTypesReader()
@ -48,8 +47,9 @@ func NewFile() *File {
f.WorkBook = f.workbookReader() f.WorkBook = f.workbookReader()
f.Relationships = sync.Map{} f.Relationships = sync.Map{}
f.Relationships.Store("xl/_rels/workbook.xml.rels", f.relsReader("xl/_rels/workbook.xml.rels")) f.Relationships.Store("xl/_rels/workbook.xml.rels", f.relsReader("xl/_rels/workbook.xml.rels"))
f.Sheet["xl/worksheets/sheet1.xml"], _ = f.workSheetReader("Sheet1")
f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml" f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml"
ws, _ := f.workSheetReader("Sheet1")
f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
f.Theme = f.themeReader() f.Theme = f.themeReader()
return f return f
} }
@ -165,20 +165,22 @@ func (f *File) writeToZip(zw *zip.Writer) error {
} }
stream.rawData.Close() stream.rawData.Close()
} }
var err error
for path, content := range f.XLSX { f.Pkg.Range(func(path, content interface{}) bool {
if _, ok := f.streams[path]; ok {
continue
}
fi, err := zw.Create(path)
if err != nil { if err != nil {
return false
}
if _, ok := f.streams[path.(string)]; ok {
return true
}
var fi io.Writer
fi, err = zw.Create(path.(string))
if err != nil {
return false
}
_, err = fi.Write(content.([]byte))
return true
})
return err return err
} }
_, err = fi.Write(content)
if err != nil {
return err
}
}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"os" "os"
"strings" "strings"
"sync"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -38,19 +39,19 @@ func TestWriteTo(t *testing.T) {
{ {
f := File{} f := File{}
buf := bytes.Buffer{} buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte) f.Pkg = sync.Map{}
f.XLSX["/d/"] = []byte("s") f.Pkg.Store("/d/", []byte("s"))
_, err := f.WriteTo(bufio.NewWriter(&buf)) _, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: write to directory") assert.EqualError(t, err, "zip: write to directory")
delete(f.XLSX, "/d/") f.Pkg.Delete("/d/")
} }
// Test file path overflow // Test file path overflow
{ {
f := File{} f := File{}
buf := bytes.Buffer{} buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte) f.Pkg = sync.Map{}
const maxUint16 = 1<<16 - 1 const maxUint16 = 1<<16 - 1
f.XLSX[strings.Repeat("s", maxUint16+1)] = nil f.Pkg.Store(strings.Repeat("s", maxUint16+1), nil)
_, err := f.WriteTo(bufio.NewWriter(&buf)) _, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "zip: FileHeader.Name too long") assert.EqualError(t, err, "zip: FileHeader.Name too long")
} }
@ -58,8 +59,8 @@ func TestWriteTo(t *testing.T) {
{ {
f := File{} f := File{}
buf := bytes.Buffer{} buf := bytes.Buffer{}
f.XLSX = make(map[string][]byte) f.Pkg = sync.Map{}
f.XLSX["s"] = nil f.Pkg.Store("s", nil)
f.streams = make(map[string]*StreamWriter) f.streams = make(map[string]*StreamWriter)
file, _ := os.Open("123") file, _ := os.Open("123")
f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}} f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}}

6
lib.go
View File

@ -51,8 +51,8 @@ func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) {
// readXML provides a function to read XML content as string. // readXML provides a function to read XML content as string.
func (f *File) readXML(name string) []byte { func (f *File) readXML(name string) []byte {
if content, ok := f.XLSX[name]; ok { if content, _ := f.Pkg.Load(name); content != nil {
return content return content.([]byte)
} }
if content, ok := f.streams[name]; ok { if content, ok := f.streams[name]; ok {
return content.rawData.buf.Bytes() return content.rawData.buf.Bytes()
@ -66,7 +66,7 @@ func (f *File) saveFileList(name string, content []byte) {
newContent := make([]byte, 0, len(XMLHeader)+len(content)) newContent := make([]byte, 0, len(XMLHeader)+len(content))
newContent = append(newContent, []byte(XMLHeader)...) newContent = append(newContent, []byte(XMLHeader)...)
newContent = append(newContent, content...) newContent = append(newContent, content...)
f.XLSX[name] = newContent f.Pkg.Store(name, newContent)
} }
// Read file content as string in a archive file. // Read file content as string in a archive file.

View File

@ -71,13 +71,19 @@ func TestMergeCell(t *testing.T) {
f = NewFile() f = NewFile()
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}} ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3")) assert.NoError(t, f.MergeCell("Sheet1", "A2", "B3"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`) assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, f.MergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
} }
@ -154,16 +160,24 @@ func TestUnmergeCell(t *testing.T) {
// Test unmerged area on not exists worksheet. // Test unmerged area on not exists worksheet.
assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN is not exist") assert.EqualError(t, f.UnmergeCell("SheetN", "A1", "A1"), "sheet SheetN is not exist")
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = nil ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = nil
assert.NoError(t, f.UnmergeCell("Sheet1", "H7", "B15")) assert.NoError(t, f.UnmergeCell("Sheet1", "H7", "B15"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{nil, nil}}
assert.NoError(t, f.UnmergeCell("Sheet1", "H15", "B7")) assert.NoError(t, f.UnmergeCell("Sheet1", "H15", "B7"))
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A1"}}}
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`) assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `invalid area "A1"`)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, f.UnmergeCell("Sheet1", "A2", "B3"), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
} }

View File

@ -223,11 +223,12 @@ func (f *File) addSheetPicture(sheet string, rID int) {
// folder xl/drawings. // folder xl/drawings.
func (f *File) countDrawings() int { func (f *File) countDrawings() int {
c1, c2 := 0, 0 c1, c2 := 0, 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/drawings/drawing") { if strings.Contains(k.(string), "xl/drawings/drawing") {
c1++ c1++
} }
} return true
})
f.Drawings.Range(func(rel, value interface{}) bool { f.Drawings.Range(func(rel, value interface{}) bool {
if strings.Contains(rel.(string), "xl/drawings/drawing") { if strings.Contains(rel.(string), "xl/drawings/drawing") {
c2++ c2++
@ -305,11 +306,12 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he
// folder xl/media/image. // folder xl/media/image.
func (f *File) countMedia() int { func (f *File) countMedia() int {
count := 0 count := 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/media/image") { if strings.Contains(k.(string), "xl/media/image") {
count++ count++
} }
} return true
})
return count return count
} }
@ -318,16 +320,22 @@ func (f *File) countMedia() int {
// and drawings that use it will reference the same image. // and drawings that use it will reference the same image.
func (f *File) addMedia(file []byte, ext string) string { func (f *File) addMedia(file []byte, ext string) string {
count := f.countMedia() count := f.countMedia()
for name, existing := range f.XLSX { var name string
if !strings.HasPrefix(name, "xl/media/image") { f.Pkg.Range(func(k, existing interface{}) bool {
continue if !strings.HasPrefix(k.(string), "xl/media/image") {
return true
} }
if bytes.Equal(file, existing) { if bytes.Equal(file, existing.([]byte)) {
name = k.(string)
return false
}
return true
})
if name != "" {
return name return name
} }
}
media := "xl/media/image" + strconv.Itoa(count+1) + ext media := "xl/media/image" + strconv.Itoa(count+1) + ext
f.XLSX[media] = file f.Pkg.Store(media, file)
return media return media
} }
@ -468,8 +476,7 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte, error) {
} }
target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID)
drawingXML := strings.Replace(target, "..", "xl", -1) drawingXML := strings.Replace(target, "..", "xl", -1)
_, ok := f.XLSX[drawingXML] if _, ok := f.Pkg.Load(drawingXML); !ok {
if !ok {
return "", nil, err return "", nil, err
} }
drawingRelationships := strings.Replace( drawingRelationships := strings.Replace(
@ -532,7 +539,10 @@ func (f *File) getPicture(row, col int, drawingXML, drawingRelationships string)
if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row { if deTwoCellAnchor.From.Col == col && deTwoCellAnchor.From.Row == row {
drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed)
if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)] ret = filepath.Base(drawRel.Target)
if buffer, _ := f.Pkg.Load(strings.Replace(drawRel.Target, "..", "xl", -1)); buffer != nil {
buf = buffer.([]byte)
}
return return
} }
} }
@ -556,7 +566,10 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD
if drawRel = f.getDrawingRelationships(drawingRelationships, if drawRel = f.getDrawingRelationships(drawingRelationships,
anchor.Pic.BlipFill.Blip.Embed); drawRel != nil { anchor.Pic.BlipFill.Blip.Embed); drawRel != nil {
if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok { if _, ok = supportImageTypes[filepath.Ext(drawRel.Target)]; ok {
ret, buf = filepath.Base(drawRel.Target), f.XLSX[strings.Replace(drawRel.Target, "..", "xl", -1)] ret = filepath.Base(drawRel.Target)
if buffer, _ := f.Pkg.Load(strings.Replace(drawRel.Target, "..", "xl", -1)); buffer != nil {
buf = buffer.([]byte)
}
return return
} }
} }

View File

@ -155,7 +155,7 @@ func TestGetPicture(t *testing.T) {
assert.Empty(t, raw) assert.Empty(t, raw)
f, err = prepareTestBook1() f, err = prepareTestBook1()
assert.NoError(t, err) assert.NoError(t, err)
f.XLSX["xl/drawings/drawing1.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/drawings/drawing1.xml", MacintoshCyrillicCharset)
_, _, err = f.getPicture(20, 5, "xl/drawings/drawing1.xml", "xl/drawings/_rels/drawing2.xml.rels") _, _, 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") assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8")
} }
@ -173,11 +173,12 @@ func TestAddPictureFromBytes(t *testing.T) {
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile)) assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 1), "", "logo", ".png", imgFile))
assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile)) assert.NoError(t, f.AddPictureFromBytes("Sheet1", fmt.Sprint("A", 50), "", "logo", ".png", imgFile))
imageCount := 0 imageCount := 0
for fileName := range f.XLSX { f.Pkg.Range(func(fileName, v interface{}) bool {
if strings.Contains(fileName, "media/image") { if strings.Contains(fileName.(string), "media/image") {
imageCount++ imageCount++
} }
} return true
})
assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.") assert.Equal(t, 1, imageCount, "Duplicate image should only be stored once.")
assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), "sheet SheetN is not exist") assert.EqualError(t, f.AddPictureFromBytes("SheetN", fmt.Sprint("A", 1), "", "logo", ".png", imgFile), "sheet SheetN is not exist")
} }
@ -205,6 +206,8 @@ func TestDrawingResize(t *testing.T) {
// Test calculate drawing resize with invalid coordinates. // Test calculate drawing resize with invalid coordinates.
_, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil) _, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil)
assert.EqualError(t, err, `cannot convert cell "" to coordinates: invalid cell name ""`) assert.EqualError(t, err, `cannot convert cell "" to coordinates: invalid cell name ""`)
f.Sheet["xl/worksheets/sheet1.xml"].MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
} }

View File

@ -13,7 +13,6 @@ package excelize
import ( import (
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@ -163,7 +162,7 @@ func (f *File) AddPivotTable(opt *PivotTableOption) error {
// properties. // properties.
func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet, string, error) { func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet, string, error) {
if opt == nil { if opt == nil {
return nil, "", errors.New("parameter is required") return nil, "", ErrParameterRequired
} }
pivotTableSheetName, _, err := f.adjustRange(opt.PivotTableRange) pivotTableSheetName, _, err := f.adjustRange(opt.PivotTableRange)
if err != nil { if err != nil {
@ -192,11 +191,11 @@ func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet,
// adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31 // adjustRange adjust range, for example: adjust Sheet1!$E$31:$A$1 to Sheet1!$A$1:$E$31
func (f *File) adjustRange(rangeStr string) (string, []int, error) { func (f *File) adjustRange(rangeStr string) (string, []int, error) {
if len(rangeStr) < 1 { if len(rangeStr) < 1 {
return "", []int{}, errors.New("parameter is required") return "", []int{}, ErrParameterRequired
} }
rng := strings.Split(rangeStr, "!") rng := strings.Split(rangeStr, "!")
if len(rng) != 2 { if len(rng) != 2 {
return "", []int{}, errors.New("parameter is invalid") return "", []int{}, ErrParameterInvalid
} }
trimRng := strings.Replace(rng[1], "$", "", -1) trimRng := strings.Replace(rng[1], "$", "", -1)
coordinates, err := f.areaRefToCoordinates(trimRng) coordinates, err := f.areaRefToCoordinates(trimRng)
@ -205,7 +204,7 @@ func (f *File) adjustRange(rangeStr string) (string, []int, error) {
} }
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3] x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if x1 == x2 && y1 == y2 { if x1 == x2 && y1 == y2 {
return rng[0], []int{}, errors.New("parameter is invalid") return rng[0], []int{}, ErrParameterInvalid
} }
// Correct the coordinate area, such correct C1:B3 to B1:C3. // Correct the coordinate area, such correct C1:B3 to B1:C3.
@ -600,11 +599,12 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio
// the folder xl/pivotTables. // the folder xl/pivotTables.
func (f *File) countPivotTables() int { func (f *File) countPivotTables() int {
count := 0 count := 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/pivotTables/pivotTable") { if strings.Contains(k.(string), "xl/pivotTables/pivotTable") {
count++ count++
} }
} return true
})
return count return count
} }
@ -612,11 +612,12 @@ func (f *File) countPivotTables() int {
// the folder xl/pivotCache. // the folder xl/pivotCache.
func (f *File) countPivotCache() int { func (f *File) countPivotCache() int {
count := 0 count := 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/pivotCache/pivotCacheDefinition") { if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") {
count++ count++
} }
} return true
})
return count return count
} }

View File

@ -137,12 +137,12 @@ func TestAddPivotTable(t *testing.T) {
ShowLastColumn: true, ShowLastColumn: true,
})) }))
// Create pivot table with many data, many rows, many cols and defined name // Create pivot table with many data, many rows, many cols and defined name
f.SetDefinedName(&DefinedName{ assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "dataRange", Name: "dataRange",
RefersTo: "Sheet1!$A$1:$E$31", RefersTo: "Sheet1!$A$1:$E$31",
Comment: "Pivot Table Data Range", Comment: "Pivot Table Data Range",
Scope: "Sheet2", Scope: "Sheet2",
}) }))
assert.NoError(t, f.AddPivotTable(&PivotTableOption{ assert.NoError(t, f.AddPivotTable(&PivotTableOption{
DataRange: "dataRange", DataRange: "dataRange",
PivotTableRange: "Sheet2!$A$57:$AJ$91", PivotTableRange: "Sheet2!$A$57:$AJ$91",

View File

@ -195,9 +195,12 @@ func (f *File) Rows(sheet string) (*Rows, error) {
if !ok { if !ok {
return nil, ErrSheetNotExist{sheet} return nil, ErrSheetNotExist{sheet}
} }
if f.Sheet[name] != nil { if ws, ok := f.Sheet.Load(name); ok && ws != nil {
worksheet := ws.(*xlsxWorksheet)
worksheet.Lock()
defer worksheet.Unlock()
// flush data // flush data
output, _ := xml.Marshal(f.Sheet[name]) output, _ := xml.Marshal(worksheet)
f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
} }
var ( var (

View File

@ -43,11 +43,13 @@ func TestRows(t *testing.T) {
} }
f = NewFile() f = NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<worksheet><sheetData><row r="1"><c r="A1" t="s"><v>1</v></c></row><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`))
f.Sheet.Delete("xl/worksheets/sheet1.xml")
delete(f.checked, "xl/worksheets/sheet1.xml")
_, err = f.Rows("Sheet1") _, err = f.Rows("Sheet1")
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
f.XLSX["xl/worksheets/sheet1.xml"] = nil f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
_, err = f.Rows("Sheet1") _, err = f.Rows("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -187,7 +189,7 @@ func TestColumns(t *testing.T) {
func TestSharedStringsReader(t *testing.T) { func TestSharedStringsReader(t *testing.T) {
f := NewFile() f := NewFile()
f.XLSX["xl/sharedStrings.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/sharedStrings.xml", MacintoshCyrillicCharset)
f.sharedStringsReader() f.sharedStringsReader()
si := xlsxSI{} si := xlsxSI{}
assert.EqualValues(t, "", si.String()) assert.EqualValues(t, "", si.String())
@ -874,12 +876,14 @@ func TestErrSheetNotExistError(t *testing.T) {
func TestCheckRow(t *testing.T) { func TestCheckRow(t *testing.T) {
f := NewFile() f := NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="F2"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="F2"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`))
_, err := f.GetRows("Sheet1") _, err := f.GetRows("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, f.SetCellValue("Sheet1", "A1", false)) assert.NoError(t, f.SetCellValue("Sheet1", "A1", false))
f = NewFile() f = NewFile()
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="-"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ><sheetData><row r="2"><c><v>1</v></c><c r="-"><v>2</v></c><c><v>3</v></c><c><v>4</v></c><c r="M2"><v>5</v></c></row></sheetData></worksheet>`))
f.Sheet.Delete("xl/worksheets/sheet1.xml")
delete(f.checked, "xl/worksheets/sheet1.xml")
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), `cannot convert cell "-" to coordinates: invalid cell name "-"`) assert.EqualError(t, f.SetCellValue("Sheet1", "A1", false), `cannot convert cell "-" to coordinates: invalid cell name "-"`)
} }

View File

@ -15,7 +15,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -152,25 +151,27 @@ func (f *File) workSheetWriter() {
var arr []byte var arr []byte
buffer := bytes.NewBuffer(arr) buffer := bytes.NewBuffer(arr)
encoder := xml.NewEncoder(buffer) encoder := xml.NewEncoder(buffer)
for p, sheet := range f.Sheet { f.Sheet.Range(func(p, ws interface{}) bool {
if sheet != nil { if ws != nil {
sheet := ws.(*xlsxWorksheet)
for k, v := range sheet.SheetData.Row { for k, v := range sheet.SheetData.Row {
f.Sheet[p].SheetData.Row[k].C = trimCell(v.C) sheet.SheetData.Row[k].C = trimCell(v.C)
} }
if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil { if sheet.SheetPr != nil || sheet.Drawing != nil || sheet.Hyperlinks != nil || sheet.Picture != nil || sheet.TableParts != nil {
f.addNameSpaces(p, SourceRelationship) f.addNameSpaces(p.(string), SourceRelationship)
} }
// reusing buffer // reusing buffer
_ = encoder.Encode(sheet) _ = encoder.Encode(sheet)
f.saveFileList(p, replaceRelationshipsBytes(f.replaceNameSpaceBytes(p, buffer.Bytes()))) f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes())))
ok := f.checked[p] ok := f.checked[p.(string)]
if ok { if ok {
delete(f.Sheet, p) f.Sheet.Delete(p.(string))
f.checked[p] = false f.checked[p.(string)] = false
} }
buffer.Reset() buffer.Reset()
} }
} return true
})
} }
// trimCell provides a function to trim blank cells which created by fillColumns. // trimCell provides a function to trim blank cells which created by fillColumns.
@ -213,7 +214,7 @@ func (f *File) setSheet(index int, name string) {
} }
path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml" path := "xl/worksheets/sheet" + strconv.Itoa(index) + ".xml"
f.sheetMap[trimSheetName(name)] = path f.sheetMap[trimSheetName(name)] = path
f.Sheet[path] = &ws f.Sheet.Store(path, &ws)
f.xmlAttr[path] = []xml.Attr{NameSpaceSpreadSheet} f.xmlAttr[path] = []xml.Attr{NameSpaceSpreadSheet}
} }
@ -448,7 +449,7 @@ func (f *File) getSheetMap() map[string]string {
if strings.HasPrefix(rel.Target, "/") { if strings.HasPrefix(rel.Target, "/") {
path = filepath.ToSlash(strings.TrimPrefix(strings.Replace(filepath.Clean(rel.Target), "\\", "/", -1), "/")) path = filepath.ToSlash(strings.TrimPrefix(strings.Replace(filepath.Clean(rel.Target), "\\", "/", -1), "/"))
} }
if _, ok := f.XLSX[path]; ok { if _, ok := f.Pkg.Load(path); ok {
maps[v.Name] = path maps[v.Name] = path
} }
} }
@ -524,10 +525,10 @@ func (f *File) DeleteSheet(name string) {
f.deleteSheetFromContentTypes(target) f.deleteSheetFromContentTypes(target)
f.deleteCalcChain(sheet.SheetID, "") f.deleteCalcChain(sheet.SheetID, "")
delete(f.sheetMap, sheetName) delete(f.sheetMap, sheetName)
delete(f.XLSX, sheetXML) f.Pkg.Delete(sheetXML)
delete(f.XLSX, rels) f.Pkg.Delete(rels)
f.Relationships.Delete(rels) f.Relationships.Delete(rels)
delete(f.Sheet, sheetXML) f.Sheet.Delete(sheetXML)
delete(f.xmlAttr, sheetXML) delete(f.xmlAttr, sheetXML)
f.SheetCount-- f.SheetCount--
} }
@ -573,7 +574,7 @@ func (f *File) deleteSheetFromContentTypes(target string) {
// //
func (f *File) CopySheet(from, to int) error { func (f *File) CopySheet(from, to int) error {
if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" { if from < 0 || to < 0 || from == to || f.GetSheetName(from) == "" || f.GetSheetName(to) == "" {
return errors.New("invalid worksheet index") return ErrSheetIdx
} }
return f.copySheet(from, to) return f.copySheet(from, to)
} }
@ -595,12 +596,11 @@ func (f *File) copySheet(from, to int) error {
worksheet.Drawing = nil worksheet.Drawing = nil
worksheet.TableParts = nil worksheet.TableParts = nil
worksheet.PageSetUp = nil worksheet.PageSetUp = nil
f.Sheet[path] = worksheet f.Sheet.Store(path, worksheet)
toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels" toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels"
fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels" fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels"
_, ok := f.XLSX[fromRels] if rels, ok := f.Pkg.Load(fromRels); ok && rels != nil {
if ok { f.Pkg.Store(toRels, rels.([]byte))
f.XLSX[toRels] = f.XLSX[fromRels]
} }
fromSheetXMLPath := f.sheetMap[trimSheetName(fromSheet)] fromSheetXMLPath := f.sheetMap[trimSheetName(fromSheet)]
fromSheetAttr := f.xmlAttr[fromSheetXMLPath] fromSheetAttr := f.xmlAttr[fromSheetXMLPath]
@ -824,9 +824,9 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) {
if !ok { if !ok {
return result, ErrSheetNotExist{sheet} return result, ErrSheetNotExist{sheet}
} }
if f.Sheet[name] != nil { if ws, ok := f.Sheet.Load(name); ok && ws != nil {
// flush data // flush data
output, _ := xml.Marshal(f.Sheet[name]) output, _ := xml.Marshal(ws.(*xlsxWorksheet))
f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
} }
return f.searchSheet(name, value, regSearch) return f.searchSheet(name, value, regSearch)
@ -1483,7 +1483,7 @@ func (f *File) SetDefinedName(definedName *DefinedName) error {
scope = f.GetSheetName(*dn.LocalSheetID) scope = f.GetSheetName(*dn.LocalSheetID)
} }
if scope == definedName.Scope && dn.Name == definedName.Name { if scope == definedName.Scope && dn.Name == definedName.Name {
return errors.New("the same name already exists on the scope") return ErrDefinedNameduplicate
} }
} }
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d) wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName, d)
@ -1518,7 +1518,7 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error {
} }
} }
} }
return errors.New("no defined name on the scope") return ErrDefinedNameScope
} }
// GetDefinedName provides a function to get the defined names of the workbook // GetDefinedName provides a function to get the defined names of the workbook
@ -1558,7 +1558,7 @@ func (f *File) GroupSheets(sheets []string) error {
} }
} }
if !inActiveSheet { if !inActiveSheet {
return errors.New("group worksheet must contain an active worksheet") return ErrGroupSheets
} }
// check worksheet exists // check worksheet exists
wss := []*xlsxWorksheet{} wss := []*xlsxWorksheet{}
@ -1714,8 +1714,7 @@ func (f *File) relsReader(path string) *xlsxRelationships {
var err error var err error
rels, _ := f.Relationships.Load(path) rels, _ := f.Relationships.Load(path)
if rels == nil { if rels == nil {
_, ok := f.XLSX[path] if _, ok := f.Pkg.Load(path); ok {
if ok {
c := xlsxRelationships{} c := xlsxRelationships{}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
Decode(&c); err != nil && err != io.EOF { Decode(&c); err != nil && err != io.EOF {
@ -1734,6 +1733,8 @@ func (f *File) relsReader(path string) *xlsxRelationships {
// row to accept data. Missing rows are backfilled and given their row number // row to accept data. Missing rows are backfilled and given their row number
// Uses the last populated row as a hint for the size of the next row to add // Uses the last populated row as a hint for the size of the next row to add
func prepareSheetXML(ws *xlsxWorksheet, col int, row int) { func prepareSheetXML(ws *xlsxWorksheet, col int, row int) {
ws.Lock()
defer ws.Unlock()
rowCount := len(ws.SheetData.Row) rowCount := len(ws.SheetData.Row)
sizeHint := 0 sizeHint := 0
var ht float64 var ht float64

View File

@ -347,9 +347,13 @@ func TestSetActiveSheet(t *testing.T) {
f.WorkBook.BookViews = nil f.WorkBook.BookViews = nil
f.SetActiveSheet(1) f.SetActiveSheet(1)
f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}} f.WorkBook.BookViews = &xlsxBookViews{WorkBookView: []xlsxWorkBookView{}}
f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}} ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{}}
f.SetActiveSheet(1) f.SetActiveSheet(1)
f.Sheet["xl/worksheets/sheet1.xml"].SheetViews = nil ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = nil
f.SetActiveSheet(1) f.SetActiveSheet(1)
f = NewFile() f = NewFile()
f.SetActiveSheet(-1) f.SetActiveSheet(-1)
@ -365,14 +369,14 @@ func TestSetSheetName(t *testing.T) {
func TestGetWorkbookPath(t *testing.T) { func TestGetWorkbookPath(t *testing.T) {
f := NewFile() f := NewFile()
delete(f.XLSX, "_rels/.rels") f.Pkg.Delete("_rels/.rels")
assert.Equal(t, "", f.getWorkbookPath()) assert.Equal(t, "", f.getWorkbookPath())
} }
func TestGetWorkbookRelsPath(t *testing.T) { func TestGetWorkbookRelsPath(t *testing.T) {
f := NewFile() f := NewFile()
delete(f.XLSX, "xl/_rels/.rels") f.Pkg.Delete("xl/_rels/.rels")
f.XLSX["_rels/.rels"] = []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" Target="/workbook.xml"/></Relationships>`) f.Pkg.Store("_rels/.rels", []byte(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://purl.oclc.org/ooxml/officeDocument/relationships/officeDocument" Target="/workbook.xml"/></Relationships>`))
assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath()) assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath())
} }

View File

@ -443,7 +443,9 @@ func TestSheetFormatPrOptions(t *testing.T) {
func TestSetSheetFormatPr(t *testing.T) { func TestSetSheetFormatPr(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.GetSheetFormatPr("Sheet1")) assert.NoError(t, f.GetSheetFormatPr("Sheet1"))
f.Sheet["xl/worksheets/sheet1.xml"].SheetFormatPr = nil ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetFormatPr = nil
assert.NoError(t, f.SetSheetFormatPr("Sheet1", BaseColWidth(1.0))) assert.NoError(t, f.SetSheetFormatPr("Sheet1", BaseColWidth(1.0)))
// Test set formatting properties on not exists worksheet. // Test set formatting properties on not exists worksheet.
assert.EqualError(t, f.SetSheetFormatPr("SheetN"), "sheet SheetN is not exist") assert.EqualError(t, f.SetSheetFormatPr("SheetN"), "sheet SheetN is not exist")
@ -452,7 +454,9 @@ func TestSetSheetFormatPr(t *testing.T) {
func TestGetSheetFormatPr(t *testing.T) { func TestGetSheetFormatPr(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.GetSheetFormatPr("Sheet1")) assert.NoError(t, f.GetSheetFormatPr("Sheet1"))
f.Sheet["xl/worksheets/sheet1.xml"].SheetFormatPr = nil ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetFormatPr = nil
var ( var (
baseColWidth BaseColWidth baseColWidth BaseColWidth
defaultColWidth DefaultColWidth defaultColWidth DefaultColWidth

View File

@ -467,7 +467,7 @@ func (f *File) parseFormatAddSparklineSet(sheet string, opt *SparklineOption) (*
return ws, err return ws, err
} }
if opt == nil { if opt == nil {
return ws, errors.New("parameter is required") return ws, ErrParameterRequired
} }
if len(opt.Location) < 1 { if len(opt.Location) < 1 {
return ws, errors.New("parameter 'Location' is required") return ws, errors.New("parameter 'Location' is required")

View File

@ -253,7 +253,9 @@ func TestAddSparkline(t *testing.T) {
Style: -1, Style: -1,
}), `parameter 'Style' must betweent 0-35`) }), `parameter 'Style' must betweent 0-35`)
f.Sheet["xl/worksheets/sheet1.xml"].ExtLst.Ext = `<extLst> ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).ExtLst.Ext = `<extLst>
<ext x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"> <ext x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" uri="{05C60535-1F16-4fd2-B633-F4F36F0B64E0}">
<x14:sparklineGroups <x14:sparklineGroups
xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main"> xmlns:xm="http://schemas.microsoft.com/office/excel/2006/main">

View File

@ -301,7 +301,7 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}) error {
} }
if !sw.sheetWritten { if !sw.sheetWritten {
if len(sw.cols) > 0 { if len(sw.cols) > 0 {
sw.rawData.WriteString("<cols>" + sw.cols + "</cols>") _, _ = sw.rawData.WriteString("<cols>" + sw.cols + "</cols>")
} }
_, _ = sw.rawData.WriteString(`<sheetData>`) _, _ = sw.rawData.WriteString(`<sheetData>`)
sw.sheetWritten = true sw.sheetWritten = true
@ -481,9 +481,9 @@ func (sw *StreamWriter) Flush() error {
} }
sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)] sheetPath := sw.File.sheetMap[trimSheetName(sw.Sheet)]
delete(sw.File.Sheet, sheetPath) sw.File.Sheet.Delete(sheetPath)
delete(sw.File.checked, sheetPath) delete(sw.File.checked, sheetPath)
delete(sw.File.XLSX, sheetPath) sw.File.Pkg.Delete(sheetPath)
return nil return nil
} }

View File

@ -99,8 +99,8 @@ func TestStreamWriter(t *testing.T) {
// Test unsupported charset // Test unsupported charset
file = NewFile() file = NewFile()
delete(file.Sheet, "xl/worksheets/sheet1.xml") file.Sheet.Delete("xl/worksheets/sheet1.xml")
file.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset)
_, err = file.NewStreamWriter("Sheet1") _, 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 decode error: XML syntax error on line 1: invalid UTF-8")
@ -145,7 +145,9 @@ func TestStreamTable(t *testing.T) {
// Verify the table has names. // Verify the table has names.
var table xlsxTable var table xlsxTable
assert.NoError(t, xml.Unmarshal(file.XLSX["xl/tables/table1.xml"], &table)) val, ok := file.Pkg.Load("xl/tables/table1.xml")
assert.True(t, ok)
assert.NoError(t, xml.Unmarshal(val.([]byte), &table))
assert.Equal(t, "A", table.TableColumns.TableColumn[0].Name) assert.Equal(t, "A", table.TableColumns.TableColumn[0].Name)
assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name) assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name) assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name)

View File

@ -15,7 +15,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"errors"
"fmt" "fmt"
"io" "io"
"log" "log"
@ -1104,14 +1103,14 @@ func parseFormatStyleSet(style interface{}) (*Style, error) {
case *Style: case *Style:
fs = *v fs = *v
default: default:
err = errors.New("invalid parameter type") err = ErrParameterInvalid
} }
if fs.Font != nil { if fs.Font != nil {
if len(fs.Font.Family) > MaxFontFamilyLength { if len(fs.Font.Family) > MaxFontFamilyLength {
return &fs, errors.New("the length of the font family name must be smaller than or equal to 31") return &fs, ErrFontLength
} }
if fs.Font.Size > MaxFontSize { if fs.Font.Size > MaxFontSize {
return &fs, errors.New("font size must be between 1 and 409 points") return &fs, ErrFontSize
} }
} }
return &fs, err return &fs, err

View File

@ -201,7 +201,7 @@ func TestNewStyle(t *testing.T) {
_, err = f.NewStyle(&Style{}) _, err = f.NewStyle(&Style{})
assert.NoError(t, err) assert.NoError(t, err)
_, err = f.NewStyle(Style{}) _, err = f.NewStyle(Style{})
assert.EqualError(t, err, "invalid parameter type") assert.EqualError(t, err, ErrParameterInvalid.Error())
_, err = f.NewStyle(&Style{Font: &Font{Family: strings.Repeat("s", MaxFontFamilyLength+1)}}) _, err = f.NewStyle(&Style{Font: &Font{Family: strings.Repeat("s", MaxFontFamilyLength+1)}})
assert.EqualError(t, err, "the length of the font family name must be smaller than or equal to 31") assert.EqualError(t, err, "the length of the font family name must be smaller than or equal to 31")
@ -261,14 +261,14 @@ func TestStylesReader(t *testing.T) {
f := NewFile() f := NewFile()
// Test read styles with unsupported charset. // Test read styles with unsupported charset.
f.Styles = nil f.Styles = nil
f.XLSX["xl/styles.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/styles.xml", MacintoshCyrillicCharset)
assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader()) assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader())
} }
func TestThemeReader(t *testing.T) { func TestThemeReader(t *testing.T) {
f := NewFile() f := NewFile()
// Test read theme with unsupported charset. // Test read theme with unsupported charset.
f.XLSX["xl/theme/theme1.xml"] = MacintoshCyrillicCharset f.Pkg.Store("xl/theme/theme1.xml", MacintoshCyrillicCharset)
assert.EqualValues(t, new(xlsxTheme), f.themeReader()) assert.EqualValues(t, new(xlsxTheme), f.themeReader())
} }

View File

@ -105,11 +105,12 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error {
// folder xl/tables. // folder xl/tables.
func (f *File) countTables() int { func (f *File) countTables() int {
count := 0 count := 0
for k := range f.XLSX { f.Pkg.Range(func(k, v interface{}) bool {
if strings.Contains(k, "xl/tables/table") { if strings.Contains(k.(string), "xl/tables/table") {
count++ count++
} }
} return true
})
return count return count
} }