This closes #1528, closes #1533

- Avoid format text cell value as a numeric
- Fix race conditions for concurrency safety functions
This commit is contained in:
xuri 2023-04-25 08:44:41 +08:00
parent 93c72b4d55
commit 612f6f104c
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
9 changed files with 87 additions and 36 deletions

39
cell.go
View File

@ -288,16 +288,19 @@ func setCellDuration(value time.Duration) (t string, v string) {
// SetCellInt provides a function to set int type value of a cell by given // SetCellInt provides a function to set int type value of a cell by given
// worksheet name, cell reference and cell value. // worksheet name, cell reference and cell value.
func (f *File) SetCellInt(sheet, cell string, value int) error { func (f *File) SetCellInt(sheet, cell string, value int) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell) c, col, row, err := ws.prepareCell(cell)
if err != nil { if err != nil {
return err return err
} }
ws.mu.Lock()
defer ws.mu.Unlock()
c.S = ws.prepareCellStyle(col, row, c.S) c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellInt(value) c.T, c.V = setCellInt(value)
c.IS = nil c.IS = nil
@ -314,16 +317,19 @@ func setCellInt(value int) (t string, v string) {
// SetCellBool provides a function to set bool type value of a cell by given // SetCellBool provides a function to set bool type value of a cell by given
// worksheet name, cell reference and cell value. // worksheet name, cell reference and cell value.
func (f *File) SetCellBool(sheet, cell string, value bool) error { func (f *File) SetCellBool(sheet, cell string, value bool) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell) c, col, row, err := ws.prepareCell(cell)
if err != nil { if err != nil {
return err return err
} }
ws.mu.Lock()
defer ws.mu.Unlock()
c.S = ws.prepareCellStyle(col, row, c.S) c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellBool(value) c.T, c.V = setCellBool(value)
c.IS = nil c.IS = nil
@ -351,16 +357,19 @@ func setCellBool(value bool) (t string, v string) {
// var x float32 = 1.325 // var x float32 = 1.325
// f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32) // f.SetCellFloat("Sheet1", "A1", float64(x), 2, 32)
func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSize int) error { func (f *File) SetCellFloat(sheet, cell string, value float64, precision, bitSize int) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell) c, col, row, err := ws.prepareCell(cell)
if err != nil { if err != nil {
return err return err
} }
ws.mu.Lock()
defer ws.mu.Unlock()
c.S = ws.prepareCellStyle(col, row, c.S) c.S = ws.prepareCellStyle(col, row, c.S)
c.T, c.V = setCellFloat(value, precision, bitSize) c.T, c.V = setCellFloat(value, precision, bitSize)
c.IS = nil c.IS = nil
@ -377,16 +386,19 @@ func setCellFloat(value float64, precision, bitSize int) (t string, v string) {
// SetCellStr provides a function to set string type value of a cell. Total // SetCellStr provides a function to set string type value of a cell. Total
// number of characters that a cell can contain 32767 characters. // number of characters that a cell can contain 32767 characters.
func (f *File) SetCellStr(sheet, cell, value string) error { func (f *File) SetCellStr(sheet, cell, value string) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell) c, col, row, err := ws.prepareCell(cell)
if err != nil { if err != nil {
return err return err
} }
ws.mu.Lock()
defer ws.mu.Unlock()
c.S = ws.prepareCellStyle(col, row, c.S) c.S = ws.prepareCellStyle(col, row, c.S)
if c.T, c.V, err = f.setCellString(value); err != nil { if c.T, c.V, err = f.setCellString(value); err != nil {
return err return err
@ -558,8 +570,6 @@ func (c *xlsxC) getCellDate(f *File, raw bool) (string, error) {
// intended to be used with for range on rows an argument with the spreadsheet // intended to be used with for range on rows an argument with the spreadsheet
// opened file. // opened file.
func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) { func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
f.mu.Lock()
defer f.mu.Unlock()
switch c.T { switch c.T {
case "b": case "b":
return c.getCellBool(f, raw) return c.getCellBool(f, raw)
@ -596,16 +606,19 @@ func (c *xlsxC) getValueFrom(f *File, d *xlsxSST, raw bool) (string, error) {
// SetCellDefault provides a function to set string type value of a cell as // SetCellDefault provides a function to set string type value of a cell as
// default format without escaping the cell. // default format without escaping the cell.
func (f *File) SetCellDefault(sheet, cell, value string) error { func (f *File) SetCellDefault(sheet, cell, value string) error {
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock()
defer ws.mu.Unlock()
c, col, row, err := ws.prepareCell(cell) c, col, row, err := ws.prepareCell(cell)
if err != nil { if err != nil {
return err return err
} }
ws.mu.Lock()
defer ws.mu.Unlock()
c.S = ws.prepareCellStyle(col, row, c.S) c.S = ws.prepareCellStyle(col, row, c.S)
c.setCellDefault(value) c.setCellDefault(value)
return f.removeFormula(c, ws, sheet) return f.removeFormula(c, ws, sheet)
@ -1264,8 +1277,6 @@ func (ws *xlsxWorksheet) prepareCell(cell string) (*xlsxC, int, int, error) {
} }
ws.prepareSheetXML(col, row) ws.prepareSheetXML(col, row)
ws.mu.Lock()
defer ws.mu.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

@ -65,7 +65,7 @@ func TestConcurrency(t *testing.T) {
// Concurrency iterate columns // Concurrency iterate columns
cols, err := f.Cols("Sheet1") cols, err := f.Cols("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
for rows.Next() { for cols.Next() {
_, err := cols.Rows() _, err := cols.Rows()
assert.NoError(t, err) assert.NoError(t, err)
} }

24
col.go
View File

@ -257,10 +257,13 @@ func (f *File) GetColVisible(sheet, col string) (bool, error) {
if err != nil { if err != nil {
return true, err return true, err
} }
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return false, err return false, err
} }
f.mu.Unlock()
ws.mu.Lock() ws.mu.Lock()
defer ws.mu.Unlock() defer ws.mu.Unlock()
if ws.Cols == nil { if ws.Cols == nil {
@ -428,20 +431,24 @@ func (f *File) SetColStyle(sheet, columns string, styleID int) error {
if err != nil { if err != nil {
return err return err
} }
f.mu.Lock()
s, err := f.stylesReader() s, err := f.stylesReader()
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
ws, err := f.workSheetReader(sheet)
if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
s.mu.Lock() s.mu.Lock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
s.mu.Unlock() s.mu.Unlock()
return newInvalidStyleID(styleID) return newInvalidStyleID(styleID)
} }
s.mu.Unlock() s.mu.Unlock()
ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.mu.Lock() ws.mu.Lock()
if ws.Cols == nil { if ws.Cols == nil {
ws.Cols = &xlsxCols{} ws.Cols = &xlsxCols{}
@ -484,10 +491,13 @@ func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error
if width > MaxColumnWidth { if width > MaxColumnWidth {
return ErrColumnWidth return ErrColumnWidth
} }
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock() ws.mu.Lock()
defer ws.mu.Unlock() defer ws.mu.Unlock()
col := xlsxCol{ col := xlsxCol{
@ -659,10 +669,13 @@ func (f *File) GetColStyle(sheet, col string) (int, error) {
if err != nil { if err != nil {
return styleID, err return styleID, err
} }
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return styleID, err return styleID, err
} }
f.mu.Unlock()
ws.mu.Lock() ws.mu.Lock()
defer ws.mu.Unlock() defer ws.mu.Unlock()
if ws.Cols != nil { if ws.Cols != nil {
@ -682,10 +695,13 @@ func (f *File) GetColWidth(sheet, col string) (float64, error) {
if err != nil { if err != nil {
return defaultColWidth, err return defaultColWidth, err
} }
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return defaultColWidth, err return defaultColWidth, err
} }
f.mu.Unlock()
ws.mu.Lock() ws.mu.Lock()
defer ws.mu.Unlock() defer ws.mu.Unlock()
if ws.Cols != nil { if ws.Cols != nil {

View File

@ -234,8 +234,6 @@ func (f *File) setDefaultTimeStyle(sheet, cell string, format int) error {
// workSheetReader provides a function to get the pointer to the structure // workSheetReader provides a function to get the pointer to the structure
// after deserialization by given worksheet name. // after deserialization by given worksheet name.
func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) { func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) {
f.mu.Lock()
defer f.mu.Unlock()
var ( var (
name string name string
ok bool ok bool

View File

@ -948,10 +948,13 @@ func (nf *numberFormat) zeroHandler() string {
// textHandler will be handling text selection for a number format expression. // textHandler will be handling text selection for a number format expression.
func (nf *numberFormat) textHandler() (result string) { func (nf *numberFormat) textHandler() (result string) {
for _, token := range nf.section[nf.sectionIdx].Items { for _, token := range nf.section[nf.sectionIdx].Items {
if inStrSlice([]string{nfp.TokenTypeDateTimes, nfp.TokenTypeElapsedDateTimes}, token.TType, false) != -1 {
return nf.value
}
if token.TType == nfp.TokenTypeLiteral { if token.TType == nfp.TokenTypeLiteral {
result += token.TValue result += token.TValue
} }
if token.TType == nfp.TokenTypeTextPlaceHolder || token.TType == nfp.TokenTypeZeroPlaceHolder { if token.TType == nfp.TokenTypeGeneral || token.TType == nfp.TokenTypeTextPlaceHolder || token.TType == nfp.TokenTypeZeroPlaceHolder {
result += nf.value result += nf.value
} }
} }

View File

@ -1008,4 +1008,12 @@ func TestNumFmt(t *testing.T) {
result := format(item[0], item[1], false, CellTypeNumber) result := format(item[0], item[1], false, CellTypeNumber)
assert.Equal(t, item[2], result, item) assert.Equal(t, item[2], result, item)
} }
for _, item := range [][]string{
{"1234.5678", "General", "1234.5678"},
{"1234.5678", "yyyy\"年\"m\"月\"d\"日\";@", "1234.5678"},
{"1234.5678", "0_);[Red]\\(0\\)", "1234.5678"},
} {
result := format(item[0], item[1], false, CellTypeSharedString)
assert.Equal(t, item[2], result, item)
}
} }

View File

@ -212,10 +212,13 @@ func (f *File) AddPictureFromBytes(sheet, cell string, pic *Picture) error {
return err return err
} }
// Read sheet data. // Read sheet data.
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
f.mu.Unlock()
ws.mu.Lock() ws.mu.Lock()
// Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder. // Add first picture for given sheet, create xl/drawings/ and xl/drawings/_rels/ folder.
drawingID := f.countDrawings() + 1 drawingID := f.countDrawings() + 1
@ -591,10 +594,13 @@ func (f *File) GetPictures(sheet, cell string) ([]Picture, error) {
} }
col-- col--
row-- row--
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return nil, err return nil, err
} }
f.mu.Unlock()
if ws.Drawing == nil { if ws.Drawing == nil {
return nil, err return nil, err
} }

View File

@ -1843,8 +1843,6 @@ func (f *File) relsReader(path string) (*xlsxRelationships, error) {
// 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 (ws *xlsxWorksheet) prepareSheetXML(col int, row int) { func (ws *xlsxWorksheet) prepareSheetXML(col int, row int) {
ws.mu.Lock()
defer ws.mu.Unlock()
rowCount := len(ws.SheetData.Row) rowCount := len(ws.SheetData.Row)
sizeHint := 0 sizeHint := 0
var ht *float64 var ht *float64
@ -1878,9 +1876,7 @@ func fillColumns(rowData *xlsxRow, col, row int) {
} }
// makeContiguousColumns make columns in specific rows as contiguous. // makeContiguousColumns make columns in specific rows as contiguous.
func makeContiguousColumns(ws *xlsxWorksheet, fromRow, toRow, colCount int) { func (ws *xlsxWorksheet) makeContiguousColumns(fromRow, toRow, colCount int) {
ws.mu.Lock()
defer ws.mu.Unlock()
for ; fromRow < toRow; fromRow++ { for ; fromRow < toRow; fromRow++ {
rowData := &ws.SheetData.Row[fromRow-1] rowData := &ws.SheetData.Row[fromRow-1]
fillColumns(rowData, colCount, fromRow) fillColumns(rowData, colCount, fromRow)

View File

@ -2004,10 +2004,13 @@ func (f *File) NewStyle(style *Style) (int, error) {
if fs.DecimalPlaces == 0 { if fs.DecimalPlaces == 0 {
fs.DecimalPlaces = 2 fs.DecimalPlaces = 2
} }
f.mu.Lock()
s, err := f.stylesReader() s, err := f.stylesReader()
if err != nil { if err != nil {
f.mu.Unlock()
return cellXfsID, err return cellXfsID, err
} }
f.mu.Unlock()
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
// check given style already exist. // check given style already exist.
@ -2131,10 +2134,13 @@ func (f *File) getStyleID(ss *xlsxStyleSheet, style *Style) (int, error) {
// format by given style format. The parameters are the same with the NewStyle // format by given style format. The parameters are the same with the NewStyle
// function. // function.
func (f *File) NewConditionalStyle(style *Style) (int, error) { func (f *File) NewConditionalStyle(style *Style) (int, error) {
f.mu.Lock()
s, err := f.stylesReader() s, err := f.stylesReader()
if err != nil { if err != nil {
f.mu.Unlock()
return 0, err return 0, err
} }
f.mu.Unlock()
fs, err := parseFormatStyleSet(style) fs, err := parseFormatStyleSet(style)
if err != nil { if err != nil {
return 0, err return 0, err
@ -2179,7 +2185,9 @@ func (f *File) SetDefaultFont(fontName string) error {
return err return err
} }
font.Name.Val = stringPtr(fontName) font.Name.Val = stringPtr(fontName)
f.mu.Lock()
s, _ := f.stylesReader() s, _ := f.stylesReader()
f.mu.Unlock()
s.Fonts.Font[0] = font s.Fonts.Font[0] = font
custom := true custom := true
s.CellStyles.CellStyle[0].CustomBuiltIn = &custom s.CellStyles.CellStyle[0].CustomBuiltIn = &custom
@ -2188,6 +2196,8 @@ func (f *File) SetDefaultFont(fontName string) error {
// readDefaultFont provides an un-marshalled font value. // readDefaultFont provides an un-marshalled font value.
func (f *File) readDefaultFont() (*xlsxFont, error) { func (f *File) readDefaultFont() (*xlsxFont, error) {
f.mu.Lock()
defer f.mu.Unlock()
s, err := f.stylesReader() s, err := f.stylesReader()
if err != nil { if err != nil {
return nil, err return nil, err
@ -2803,22 +2813,25 @@ func (f *File) SetCellStyle(sheet, hCell, vCell string, styleID int) error {
vColIdx := vCol - 1 vColIdx := vCol - 1
vRowIdx := vRow - 1 vRowIdx := vRow - 1
f.mu.Lock()
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
f.mu.Unlock()
return err return err
} }
ws.prepareSheetXML(vCol, vRow) s, err := f.stylesReader()
makeContiguousColumns(ws, hRow, vRow, vCol) if err != nil {
f.mu.Unlock()
return err
}
f.mu.Unlock()
ws.mu.Lock() ws.mu.Lock()
defer ws.mu.Unlock() defer ws.mu.Unlock()
s, err := f.stylesReader() ws.prepareSheetXML(vCol, vRow)
if err != nil { ws.makeContiguousColumns(hRow, vRow, vCol)
return err
}
s.mu.Lock()
defer s.mu.Unlock()
if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID { if styleID < 0 || s.CellXfs == nil || len(s.CellXfs.Xf) <= styleID {
return newInvalidStyleID(styleID) return newInvalidStyleID(styleID)
} }