summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--cell.go20
-rw-r--r--cell_test.go21
-rw-r--r--excelize.go6
-rw-r--r--lib.go2
-rw-r--r--rows.go4
-rw-r--r--stream_test.go1
-rw-r--r--xmlWorksheet.go6
8 files changed, 45 insertions, 17 deletions
diff --git a/.travis.yml b/.travis.yml
index 03825a8..a5c55f3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,7 +21,7 @@ env:
script:
- env GO111MODULE=on go vet ./...
- - env GO111MODULE=on go test ./... -v -coverprofile=coverage.txt -covermode=atomic
+ - env GO111MODULE=on go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
diff --git a/cell.go b/cell.go
index 3293d19..383c02c 100644
--- a/cell.go
+++ b/cell.go
@@ -18,7 +18,6 @@ import (
"reflect"
"strconv"
"strings"
- "sync"
"time"
)
@@ -33,8 +32,6 @@ const (
STCellFormulaTypeShared = "shared"
)
-var rwMutex sync.RWMutex
-
// GetCellValue provides a function to get formatted value from cell by given
// worksheet name and axis in XLSX file. If it is possible to apply a format
// to the cell value, it will do so, if not then an error will be returned,
@@ -181,8 +178,6 @@ func setCellDuration(value time.Duration) (t string, v string) {
// SetCellInt provides a function to set int type value of a cell by given
// worksheet name, cell coordinates and cell value.
func (f *File) SetCellInt(sheet, axis string, value int) error {
- rwMutex.Lock()
- defer rwMutex.Unlock()
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
@@ -204,8 +199,6 @@ func setCellInt(value int) (t string, v string) {
// SetCellBool provides a function to set bool type value of a cell by given
// worksheet name, cell name and cell value.
func (f *File) SetCellBool(sheet, axis string, value bool) error {
- rwMutex.Lock()
- defer rwMutex.Unlock()
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
@@ -239,8 +232,6 @@ func setCellBool(value bool) (t string, v string) {
// f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
//
func (f *File) SetCellFloat(sheet, axis string, value float64, prec, bitSize int) error {
- rwMutex.Lock()
- defer rwMutex.Unlock()
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
@@ -262,8 +253,6 @@ func setCellFloat(value float64, prec, bitSize int) (t string, v string) {
// SetCellStr provides a function to set string type value of a cell. Total
// number of characters that a cell can contain 32767 characters.
func (f *File) SetCellStr(sheet, axis, value string) error {
- rwMutex.Lock()
- defer rwMutex.Unlock()
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
@@ -291,6 +280,8 @@ func (f *File) setCellString(value string) (t string, v string) {
// setSharedString provides a function to add string to the share string table.
func (f *File) setSharedString(val string) int {
sst := f.sharedStringsReader()
+ f.Lock()
+ defer f.Unlock()
if i, ok := f.sharedStringsMap[val]; ok {
return i
}
@@ -371,8 +362,6 @@ type FormulaOpts struct {
// SetCellFormula provides a function to set cell formula by given string and
// worksheet name.
func (f *File) SetCellFormula(sheet, axis, formula string, opts ...FormulaOpts) error {
- rwMutex.Lock()
- defer rwMutex.Unlock()
xlsx, err := f.workSheetReader(sheet)
if err != nil {
return err
@@ -697,6 +686,8 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error {
// getCellInfo does common preparation for all SetCell* methods.
func (f *File) prepareCell(xlsx *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) {
+ xlsx.Lock()
+ defer xlsx.Unlock()
var err error
cell, err = f.mergeCellsParser(xlsx, cell)
if err != nil {
@@ -728,6 +719,9 @@ func (f *File) getCellStringFunc(sheet, axis string, fn func(x *xlsxWorksheet, c
return "", err
}
+ xlsx.Lock()
+ defer xlsx.Unlock()
+
lastRowNum := 0
if l := len(xlsx.SheetData.Row); l > 0 {
lastRowNum = xlsx.SheetData.Row[l-1].R
diff --git a/cell_test.go b/cell_test.go
index fb30596..ba4cd83 100644
--- a/cell_test.go
+++ b/cell_test.go
@@ -4,12 +4,33 @@ import (
"fmt"
"path/filepath"
"strconv"
+ "sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
+func TestConcurrency(t *testing.T) {
+ f := NewFile()
+ wg := new(sync.WaitGroup)
+ for i := 1; i <= 5; i++ {
+ wg.Add(1)
+ go func(val int) {
+ f.SetCellValue("Sheet1", fmt.Sprintf("A%d", val), val)
+ f.SetCellValue("Sheet1", fmt.Sprintf("B%d", val), strconv.Itoa(val))
+ f.GetCellValue("Sheet1", fmt.Sprintf("A%d", val))
+ wg.Done()
+ }(i)
+ }
+ wg.Wait()
+ val, err := f.GetCellValue("Sheet1", "A1")
+ if err != nil {
+ t.Error(err)
+ }
+ assert.Equal(t, "1", val)
+}
+
func TestCheckCellInArea(t *testing.T) {
f := NewFile()
expectedTrueCellInAreaList := [][2]string{
diff --git a/excelize.go b/excelize.go
index 5cc88e9..bfb3aba 100644
--- a/excelize.go
+++ b/excelize.go
@@ -24,12 +24,14 @@ import (
"path"
"strconv"
"strings"
+ "sync"
"golang.org/x/net/html/charset"
)
// File define a populated spreadsheet file struct.
type File struct {
+ sync.RWMutex
xmlAttr map[string][]xml.Attr
checked map[string]bool
sheetMap map[string]string
@@ -153,6 +155,8 @@ func (f *File) setDefaultTimeStyle(sheet, axis string, format int) error {
// workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name.
func (f *File) workSheetReader(sheet string) (xlsx *xlsxWorksheet, err error) {
+ f.Lock()
+ defer f.Unlock()
var (
name string
ok bool
@@ -323,7 +327,7 @@ func (f *File) AddVBAProject(bin string) error {
var err error
// Check vbaProject.bin exists first.
if _, err = os.Stat(bin); os.IsNotExist(err) {
- return err
+ return fmt.Errorf("stat %s: no such file or directory", bin)
}
if path.Ext(bin) != ".bin" {
return errors.New("unsupported VBA project extension")
diff --git a/lib.go b/lib.go
index acb4590..88aa3a1 100644
--- a/lib.go
+++ b/lib.go
@@ -167,7 +167,7 @@ func ColumnNumberToName(num int) (string, error) {
}
var col string
for num > 0 {
- col = string((num-1)%26+65) + col
+ col = string(rune((num-1)%26+65)) + col
num = (num - 1) / 26
}
return col, nil
diff --git a/rows.go b/rows.go
index 320ba2f..66dd16b 100644
--- a/rows.go
+++ b/rows.go
@@ -284,6 +284,8 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) {
func (f *File) sharedStringsReader() *xlsxSST {
var err error
+ f.Lock()
+ defer f.Unlock()
if f.SharedStrings == nil {
var sharedStrings xlsxSST
ss := f.readXML("xl/sharedStrings.xml")
@@ -318,6 +320,8 @@ func (f *File) sharedStringsReader() *xlsxSST {
// inteded to be used with for range on rows an argument with the xlsx opened
// file.
func (xlsx *xlsxC) getValueFrom(f *File, d *xlsxSST) (string, error) {
+ f.Lock()
+ defer f.Unlock()
switch xlsx.T {
case "s":
if xlsx.V != "" {
diff --git a/stream_test.go b/stream_test.go
index d89dad8..d81b1d4 100644
--- a/stream_test.go
+++ b/stream_test.go
@@ -91,6 +91,7 @@ func TestStreamWriter(t *testing.T) {
assert.NoError(t, err)
_, err = streamWriter.rawData.Reader()
assert.NoError(t, err)
+ assert.NoError(t, streamWriter.rawData.tmp.Close())
assert.NoError(t, os.Remove(streamWriter.rawData.tmp.Name()))
// Test unsupport charset
diff --git a/xmlWorksheet.go b/xmlWorksheet.go
index 7cd73c4..2b39e64 100644
--- a/xmlWorksheet.go
+++ b/xmlWorksheet.go
@@ -11,11 +11,15 @@
package excelize
-import "encoding/xml"
+import (
+ "encoding/xml"
+ "sync"
+)
// xlsxWorksheet directly maps the worksheet element in the namespace
// http://schemas.openxmlformats.org/spreadsheetml/2006/main.
type xlsxWorksheet struct {
+ sync.RWMutex
XMLName xml.Name `xml:"http://schemas.openxmlformats.org/spreadsheetml/2006/main worksheet"`
SheetPr *xlsxSheetPr `xml:"sheetPr"`
Dimension *xlsxDimension `xml:"dimension"`