Skip to content

Commit 544ef18

Browse files
committed
- Support concurrency iterate rows and columns
- Rename exported field `File.XLSX` to `File.Pkg` - Exported error message
1 parent 0e02329 commit 544ef18

38 files changed

+377
-262
lines changed

adjust_test.go

+4-14
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,10 @@ func TestAdjustAutoFilter(t *testing.T) {
7373
func TestAdjustHelper(t *testing.T) {
7474
f := NewFile()
7575
f.NewSheet("Sheet2")
76-
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
77-
MergeCells: &xlsxMergeCells{
78-
Cells: []*xlsxMergeCell{
79-
{
80-
Ref: "A:B1",
81-
},
82-
},
83-
},
84-
}
85-
f.Sheet["xl/worksheets/sheet2.xml"] = &xlsxWorksheet{
86-
AutoFilter: &xlsxAutoFilter{
87-
Ref: "A1:B",
88-
},
89-
}
76+
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
77+
MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}})
78+
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
79+
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}})
9080
// testing adjustHelper with illegal cell coordinates.
9181
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), `cannot convert cell "A" to coordinates: invalid cell name "A"`)
9282
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), `cannot convert cell "B" to coordinates: invalid cell name "B"`)

calcchain.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (f *File) deleteCalcChain(index int, axis string) {
5454
}
5555
if len(calc.C) == 0 {
5656
f.CalcChain = nil
57-
delete(f.XLSX, "xl/calcChain.xml")
57+
f.Pkg.Delete("xl/calcChain.xml")
5858
content := f.contentTypesReader()
5959
for k, v := range content.Overrides {
6060
if v.PartName == "/xl/calcChain.xml" {

calcchain_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "testing"
55
func TestCalcChainReader(t *testing.T) {
66
f := NewFile()
77
f.CalcChain = nil
8-
f.XLSX["xl/calcChain.xml"] = MacintoshCyrillicCharset
8+
f.Pkg.Store("xl/calcChain.xml", MacintoshCyrillicCharset)
99
f.calcChainReader()
1010
}
1111

cell.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ package excelize
1313

1414
import (
1515
"encoding/xml"
16-
"errors"
1716
"fmt"
1817
"reflect"
1918
"strconv"
@@ -187,6 +186,8 @@ func (f *File) SetCellInt(sheet, axis string, value int) error {
187186
if err != nil {
188187
return err
189188
}
189+
ws.Lock()
190+
defer ws.Unlock()
190191
cellData.S = f.prepareCellStyle(ws, col, cellData.S)
191192
cellData.T, cellData.V = setCellInt(value)
192193
return err
@@ -262,6 +263,8 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
262263
if err != nil {
263264
return err
264265
}
266+
ws.Lock()
267+
defer ws.Unlock()
265268
cellData.S = f.prepareCellStyle(ws, col, cellData.S)
266269
cellData.T, cellData.V = f.setCellString(value)
267270
return err
@@ -742,7 +745,7 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
742745
// Make sure 'slice' is a Ptr to Slice
743746
v := reflect.ValueOf(slice)
744747
if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice {
745-
return errors.New("pointer to slice expected")
748+
return ErrParameterInvalid
746749
}
747750
v = v.Elem()
748751

@@ -762,8 +765,6 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
762765

763766
// getCellInfo does common preparation for all SetCell* methods.
764767
func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
765-
ws.Lock()
766-
defer ws.Unlock()
767768
var err error
768769
cell, err = f.mergeCellsParser(ws, cell)
769770
if err != nil {
@@ -775,7 +776,8 @@ func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int,
775776
}
776777

777778
prepareSheetXML(ws, col, row)
778-
779+
ws.Lock()
780+
defer ws.Unlock()
779781
return &ws.SheetData.Row[row-1].C[col-1], col, row, err
780782
}
781783

cell_test.go

+31-11
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ func TestConcurrency(t *testing.T) {
3030
assert.Equal(t, "", name)
3131
assert.Nil(t, raw)
3232
assert.NoError(t, err)
33+
// Concurrency iterate rows
34+
rows, err := f.Rows("Sheet1")
35+
assert.NoError(t, err)
36+
for rows.Next() {
37+
_, err := rows.Columns()
38+
assert.NoError(t, err)
39+
}
40+
// Concurrency iterate columns
41+
cols, err := f.Cols("Sheet1")
42+
assert.NoError(t, err)
43+
for rows.Next() {
44+
_, err := cols.Rows()
45+
assert.NoError(t, err)
46+
}
3347

3448
wg.Done()
3549
}(i, t)
@@ -149,8 +163,8 @@ func TestGetCellValue(t *testing.T) {
149163
// Test get cell value without r attribute of the row.
150164
f := NewFile()
151165
sheetData := `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>%s</sheetData></worksheet>`
152-
delete(f.Sheet, "xl/worksheets/sheet1.xml")
153-
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>`))
166+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
167+
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>`)))
154168
f.checked = nil
155169
cells := []string{"A3", "A4", "B4", "A7", "B7"}
156170
rows, err := f.GetRows("Sheet1")
@@ -164,20 +178,20 @@ func TestGetCellValue(t *testing.T) {
164178
cols, err := f.GetCols("Sheet1")
165179
assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols)
166180
assert.NoError(t, err)
167-
delete(f.Sheet, "xl/worksheets/sheet1.xml")
168-
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>`))
181+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
182+
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>`)))
169183
f.checked = nil
170184
cell, err := f.GetCellValue("Sheet1", "A2")
171185
assert.Equal(t, "A2", cell)
172186
assert.NoError(t, err)
173-
delete(f.Sheet, "xl/worksheets/sheet1.xml")
174-
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>`))
187+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
188+
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>`)))
175189
f.checked = nil
176190
rows, err = f.GetRows("Sheet1")
177191
assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows)
178192
assert.NoError(t, err)
179-
delete(f.Sheet, "xl/worksheets/sheet1.xml")
180-
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>`))
193+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
194+
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>`)))
181195
f.checked = nil
182196
rows, err = f.GetRows("Sheet1")
183197
assert.Equal(t, [][]string{{"A1", "B1"}}, rows)
@@ -264,17 +278,23 @@ func TestGetCellRichText(t *testing.T) {
264278
assert.True(t, reflect.DeepEqual(runsSource[1].Font, runs[1].Font), "should get the same font")
265279

266280
// Test get cell rich text when string item index overflow
267-
f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "2"
281+
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
282+
assert.True(t, ok)
283+
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "2"
268284
runs, err = f.GetCellRichText("Sheet1", "A1")
269285
assert.NoError(t, err)
270286
assert.Equal(t, 0, len(runs))
271287
// Test get cell rich text when string item index is negative
272-
f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "-1"
288+
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
289+
assert.True(t, ok)
290+
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "-1"
273291
runs, err = f.GetCellRichText("Sheet1", "A1")
274292
assert.NoError(t, err)
275293
assert.Equal(t, 0, len(runs))
276294
// Test get cell rich text on invalid string item index
277-
f.Sheet["xl/worksheets/sheet1.xml"].SheetData.Row[0].C[0].V = "x"
295+
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
296+
assert.True(t, ok)
297+
ws.(*xlsxWorksheet).SheetData.Row[0].C[0].V = "x"
278298
_, err = f.GetCellRichText("Sheet1", "A1")
279299
assert.EqualError(t, err, "strconv.Atoi: parsing \"x\": invalid syntax")
280300
// Test set cell rich text on not exists worksheet

chart.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ package excelize
1414
import (
1515
"encoding/json"
1616
"encoding/xml"
17-
"errors"
1817
"fmt"
1918
"strconv"
2019
"strings"
@@ -945,7 +944,7 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error {
945944
sheetID++
946945
path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml"
947946
f.sheetMap[trimSheetName(sheet)] = path
948-
f.Sheet[path] = nil
947+
f.Sheet.Store(path, nil)
949948
drawingID := f.countDrawings() + 1
950949
chartID := f.countCharts() + 1
951950
drawingXML := "xl/drawings/drawing" + strconv.Itoa(drawingID) + ".xml"
@@ -981,12 +980,12 @@ func (f *File) getFormatChart(format string, combo []string) (*formatChart, []*f
981980
return formatSet, comboCharts, err
982981
}
983982
if _, ok := chartValAxNumFmtFormatCode[comboChart.Type]; !ok {
984-
return formatSet, comboCharts, errors.New("unsupported chart type " + comboChart.Type)
983+
return formatSet, comboCharts, newUnsupportChartType(comboChart.Type)
985984
}
986985
comboCharts = append(comboCharts, comboChart)
987986
}
988987
if _, ok := chartValAxNumFmtFormatCode[formatSet.Type]; !ok {
989-
return formatSet, comboCharts, errors.New("unsupported chart type " + formatSet.Type)
988+
return formatSet, comboCharts, newUnsupportChartType(formatSet.Type)
990989
}
991990
return formatSet, comboCharts, err
992991
}
@@ -1015,11 +1014,12 @@ func (f *File) DeleteChart(sheet, cell string) (err error) {
10151014
// folder xl/charts.
10161015
func (f *File) countCharts() int {
10171016
count := 0
1018-
for k := range f.XLSX {
1019-
if strings.Contains(k, "xl/charts/chart") {
1017+
f.Pkg.Range(func(k, v interface{}) bool {
1018+
if strings.Contains(k.(string), "xl/charts/chart") {
10201019
count++
10211020
}
1022-
}
1021+
return true
1022+
})
10231023
return count
10241024
}
10251025

chart_test.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ func TestChartSize(t *testing.T) {
6565
anchor decodeTwoCellAnchor
6666
)
6767

68-
content, ok := newFile.XLSX["xl/drawings/drawing1.xml"]
68+
content, ok := newFile.Pkg.Load("xl/drawings/drawing1.xml")
6969
assert.True(t, ok, "Can't open the chart")
7070

71-
err = xml.Unmarshal([]byte(content), &workdir)
71+
err = xml.Unmarshal(content.([]byte), &workdir)
7272
if !assert.NoError(t, err) {
7373
t.FailNow()
7474
}
@@ -340,11 +340,15 @@ func TestChartWithLogarithmicBase(t *testing.T) {
340340
type xmlChartContent []byte
341341
xmlCharts := make([]xmlChartContent, expectedChartsCount)
342342
expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000}
343-
var ok bool
344-
343+
var (
344+
drawingML interface{}
345+
ok bool
346+
)
345347
for i := 0; i < expectedChartsCount; i++ {
346348
chartPath := fmt.Sprintf("xl/charts/chart%d.xml", i+1)
347-
xmlCharts[i], ok = newFile.XLSX[chartPath]
349+
if drawingML, ok = newFile.Pkg.Load(chartPath); ok {
350+
xmlCharts[i] = drawingML.([]byte)
351+
}
348352
assert.True(t, ok, "Can't open the %s", chartPath)
349353

350354
err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i])

col.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,11 @@ func (f *File) Cols(sheet string) (*Cols, error) {
199199
if !ok {
200200
return nil, ErrSheetNotExist{sheet}
201201
}
202-
if f.Sheet[name] != nil {
203-
output, _ := xml.Marshal(f.Sheet[name])
202+
if ws, ok := f.Sheet.Load(name); ok && ws != nil {
203+
worksheet := ws.(*xlsxWorksheet)
204+
worksheet.Lock()
205+
defer worksheet.Unlock()
206+
output, _ := xml.Marshal(worksheet)
204207
f.saveFileList(name, f.replaceNameSpaceBytes(name, output))
205208
}
206209
var colIterator columnXMLIterator

col_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ func TestCols(t *testing.T) {
4848
_, err = f.Rows("Sheet1")
4949
assert.NoError(t, err)
5050

51-
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
51+
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
5252
Dimension: &xlsxDimension{
5353
Ref: "C2:C4",
5454
},
55-
}
55+
})
5656
_, err = f.Rows("Sheet1")
5757
assert.NoError(t, err)
5858
}
@@ -110,15 +110,15 @@ func TestGetColsError(t *testing.T) {
110110
assert.EqualError(t, err, "sheet SheetN is not exist")
111111

112112
f = NewFile()
113-
delete(f.Sheet, "xl/worksheets/sheet1.xml")
114-
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="A"><c r="2" t="str"><v>B</v></c></row></sheetData></worksheet>`)
113+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
114+
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>`))
115115
f.checked = nil
116116
_, err = f.GetCols("Sheet1")
117117
assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`)
118118

119119
f = NewFile()
120-
delete(f.Sheet, "xl/worksheets/sheet1.xml")
121-
f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`<worksheet><sheetData><row r="2"><c r="A" t="str"><v>B</v></c></row></sheetData></worksheet>`)
120+
f.Sheet.Delete("xl/worksheets/sheet1.xml")
121+
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>`))
122122
f.checked = nil
123123
_, err = f.GetCols("Sheet1")
124124
assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`)
@@ -142,14 +142,14 @@ func TestColsRows(t *testing.T) {
142142
assert.NoError(t, err)
143143

144144
assert.NoError(t, f.SetCellValue("Sheet1", "A1", 1))
145-
f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{
145+
f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{
146146
Dimension: &xlsxDimension{
147147
Ref: "A1:A1",
148148
},
149-
}
149+
})
150150

151151
f = NewFile()
152-
f.XLSX["xl/worksheets/sheet1.xml"] = nil
152+
f.Pkg.Store("xl/worksheets/sheet1.xml", nil)
153153
_, err = f.Cols("Sheet1")
154154
if !assert.NoError(t, err) {
155155
t.FailNow()

comment.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,12 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) {
299299
// the folder xl.
300300
func (f *File) countComments() int {
301301
c1, c2 := 0, 0
302-
for k := range f.XLSX {
303-
if strings.Contains(k, "xl/comments") {
302+
f.Pkg.Range(func(k, v interface{}) bool {
303+
if strings.Contains(k.(string), "xl/comments") {
304304
c1++
305305
}
306-
}
306+
return true
307+
})
307308
for rel := range f.Comments {
308309
if strings.Contains(rel, "xl/comments") {
309310
c2++
@@ -321,10 +322,10 @@ func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing {
321322
var err error
322323

323324
if f.DecodeVMLDrawing[path] == nil {
324-
c, ok := f.XLSX[path]
325-
if ok {
325+
c, ok := f.Pkg.Load(path)
326+
if ok && c != nil {
326327
f.DecodeVMLDrawing[path] = new(decodeVmlDrawing)
327-
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c))).
328+
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(c.([]byte)))).
328329
Decode(f.DecodeVMLDrawing[path]); err != nil && err != io.EOF {
329330
log.Printf("xml decode error: %s", err)
330331
}
@@ -339,7 +340,7 @@ func (f *File) vmlDrawingWriter() {
339340
for path, vml := range f.VMLDrawing {
340341
if vml != nil {
341342
v, _ := xml.Marshal(vml)
342-
f.XLSX[path] = v
343+
f.Pkg.Store(path, v)
343344
}
344345
}
345346
}
@@ -348,12 +349,11 @@ func (f *File) vmlDrawingWriter() {
348349
// after deserialization of xl/comments%d.xml.
349350
func (f *File) commentsReader(path string) *xlsxComments {
350351
var err error
351-
352352
if f.Comments[path] == nil {
353-
content, ok := f.XLSX[path]
354-
if ok {
353+
content, ok := f.Pkg.Load(path)
354+
if ok && content != nil {
355355
f.Comments[path] = new(xlsxComments)
356-
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content))).
356+
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
357357
Decode(f.Comments[path]); err != nil && err != io.EOF {
358358
log.Printf("xml decode error: %s", err)
359359
}

0 commit comments

Comments
 (0)