From 544ef18a8cb9949fcb8833c6d2816783c90f3318 Mon Sep 17 00:00:00 2001 From: xuri Date: Mon, 5 Jul 2021 00:03:56 +0800 Subject: [PATCH] - Support concurrency iterate rows and columns - Rename exported field `File.XLSX` to `File.Pkg` - Exported error message --- adjust_test.go | 18 +++--------- calcchain.go | 2 +- calcchain_test.go | 2 +- cell.go | 12 ++++---- cell_test.go | 42 ++++++++++++++++++++------- chart.go | 14 ++++----- chart_test.go | 14 +++++---- col.go | 7 +++-- col_test.go | 18 ++++++------ comment.go | 22 +++++++------- comment_test.go | 6 ++-- crypt.go | 11 +++---- docProps_test.go | 8 +++--- drawing.go | 2 +- drawing_test.go | 6 ++-- errors.go | 34 ++++++++++++++++++++++ excelize.go | 71 ++++++++++++++++++++++++---------------------- excelize_test.go | 34 +++++++++++++--------- file.go | 48 ++++++++++++++++--------------- file_test.go | 15 +++++----- lib.go | 6 ++-- merge_test.go | 28 +++++++++++++----- picture.go | 45 ++++++++++++++++++----------- picture_test.go | 13 +++++---- pivotTable.go | 23 ++++++++------- pivotTable_test.go | 4 +-- rows.go | 7 +++-- rows_test.go | 14 +++++---- sheet.go | 55 +++++++++++++++++------------------ sheet_test.go | 14 +++++---- sheetpr_test.go | 8 ++++-- sparkline.go | 2 +- sparkline_test.go | 4 ++- stream.go | 6 ++-- stream_test.go | 8 ++++-- styles.go | 7 ++--- styles_test.go | 6 ++-- table.go | 7 +++-- 38 files changed, 379 insertions(+), 264 deletions(-) diff --git a/adjust_test.go b/adjust_test.go index 0d63ed62..c4af38b1 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -73,20 +73,10 @@ func TestAdjustAutoFilter(t *testing.T) { func TestAdjustHelper(t *testing.T) { f := NewFile() f.NewSheet("Sheet2") - f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{ - MergeCells: &xlsxMergeCells{ - Cells: []*xlsxMergeCell{ - { - Ref: "A:B1", - }, - }, - }, - } - f.Sheet["xl/worksheets/sheet2.xml"] = &xlsxWorksheet{ - AutoFilter: &xlsxAutoFilter{ - Ref: "A1:B", - }, - } + f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ + MergeCells: &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:B1"}}}}) + f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{ + AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}}) // 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("Sheet2", rows, 0, 0), `cannot convert cell "B" to coordinates: invalid cell name "B"`) diff --git a/calcchain.go b/calcchain.go index ea3080f2..1b99a04b 100644 --- a/calcchain.go +++ b/calcchain.go @@ -54,7 +54,7 @@ func (f *File) deleteCalcChain(index int, axis string) { } if len(calc.C) == 0 { f.CalcChain = nil - delete(f.XLSX, "xl/calcChain.xml") + f.Pkg.Delete("xl/calcChain.xml") content := f.contentTypesReader() for k, v := range content.Overrides { if v.PartName == "/xl/calcChain.xml" { diff --git a/calcchain_test.go b/calcchain_test.go index 842dde16..4956f60d 100644 --- a/calcchain_test.go +++ b/calcchain_test.go @@ -5,7 +5,7 @@ import "testing" func TestCalcChainReader(t *testing.T) { f := NewFile() f.CalcChain = nil - f.XLSX["xl/calcChain.xml"] = MacintoshCyrillicCharset + f.Pkg.Store("xl/calcChain.xml", MacintoshCyrillicCharset) f.calcChainReader() } diff --git a/cell.go b/cell.go index 4dec0935..1d08c8ae 100644 --- a/cell.go +++ b/cell.go @@ -13,7 +13,6 @@ package excelize import ( "encoding/xml" - "errors" "fmt" "reflect" "strconv" @@ -187,6 +186,8 @@ func (f *File) SetCellInt(sheet, axis string, value int) error { if err != nil { return err } + ws.Lock() + defer ws.Unlock() cellData.S = f.prepareCellStyle(ws, col, cellData.S) cellData.T, cellData.V = setCellInt(value) return err @@ -262,6 +263,8 @@ func (f *File) SetCellStr(sheet, axis, value string) error { if err != nil { return err } + ws.Lock() + defer ws.Unlock() cellData.S = f.prepareCellStyle(ws, col, cellData.S) cellData.T, cellData.V = f.setCellString(value) return err @@ -742,7 +745,7 @@ func (f *File) SetSheetRow(sheet, axis string, slice interface{}) error { // Make sure 'slice' is a Ptr to Slice v := reflect.ValueOf(slice) if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Slice { - return errors.New("pointer to slice expected") + return ErrParameterInvalid } 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. func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int, int, error) { - ws.Lock() - defer ws.Unlock() var err error cell, err = f.mergeCellsParser(ws, cell) if err != nil { @@ -775,7 +776,8 @@ func (f *File) prepareCell(ws *xlsxWorksheet, sheet, cell string) (*xlsxC, int, } prepareSheetXML(ws, col, row) - + ws.Lock() + defer ws.Unlock() return &ws.SheetData.Row[row-1].C[col-1], col, row, err } diff --git a/cell_test.go b/cell_test.go index 2282e55a..e289983f 100644 --- a/cell_test.go +++ b/cell_test.go @@ -30,6 +30,20 @@ func TestConcurrency(t *testing.T) { assert.Equal(t, "", name) assert.Nil(t, raw) 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() }(i, t) @@ -149,8 +163,8 @@ func TestGetCellValue(t *testing.T) { // Test get cell value without r attribute of the row. f := NewFile() sheetData := `%s` - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `A3A4B4A7B7A8B8`)) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A3A4B4A7B7A8B8`))) f.checked = nil cells := []string{"A3", "A4", "B4", "A7", "B7"} rows, err := f.GetRows("Sheet1") @@ -164,20 +178,20 @@ func TestGetCellValue(t *testing.T) { cols, err := f.GetCols("Sheet1") assert.Equal(t, [][]string{{"", "", "A3", "A4", "", "", "A7", "A8"}, {"", "", "", "B4", "", "", "B7", "B8"}}, cols) assert.NoError(t, err) - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `A2B2`)) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2B2`))) f.checked = nil cell, err := f.GetCellValue("Sheet1", "A2") assert.Equal(t, "A2", cell) assert.NoError(t, err) - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `A2B2`)) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A2B2`))) f.checked = nil rows, err = f.GetRows("Sheet1") assert.Equal(t, [][]string{nil, {"A2", "B2"}}, rows) assert.NoError(t, err) - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(fmt.Sprintf(sheetData, `A1B1`)) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(fmt.Sprintf(sheetData, `A1B1`))) f.checked = nil rows, err = f.GetRows("Sheet1") 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") // 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") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) // 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") assert.NoError(t, err) assert.Equal(t, 0, len(runs)) // 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") assert.EqualError(t, err, "strconv.Atoi: parsing \"x\": invalid syntax") // Test set cell rich text on not exists worksheet diff --git a/chart.go b/chart.go index 9bc4b20d..52fd5431 100644 --- a/chart.go +++ b/chart.go @@ -14,7 +14,6 @@ package excelize import ( "encoding/json" "encoding/xml" - "errors" "fmt" "strconv" "strings" @@ -945,7 +944,7 @@ func (f *File) AddChartSheet(sheet, format string, combo ...string) error { sheetID++ path := "xl/chartsheets/sheet" + strconv.Itoa(sheetID) + ".xml" f.sheetMap[trimSheetName(sheet)] = path - f.Sheet[path] = nil + f.Sheet.Store(path, nil) drawingID := f.countDrawings() + 1 chartID := f.countCharts() + 1 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 } 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) } 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 } @@ -1015,11 +1014,12 @@ func (f *File) DeleteChart(sheet, cell string) (err error) { // folder xl/charts. func (f *File) countCharts() int { count := 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/charts/chart") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/charts/chart") { count++ } - } + return true + }) return count } diff --git a/chart_test.go b/chart_test.go index 657230b1..8957b93f 100644 --- a/chart_test.go +++ b/chart_test.go @@ -65,10 +65,10 @@ func TestChartSize(t *testing.T) { 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") - err = xml.Unmarshal([]byte(content), &workdir) + err = xml.Unmarshal(content.([]byte), &workdir) if !assert.NoError(t, err) { t.FailNow() } @@ -340,11 +340,15 @@ func TestChartWithLogarithmicBase(t *testing.T) { type xmlChartContent []byte xmlCharts := make([]xmlChartContent, expectedChartsCount) expectedChartsLogBase := []float64{0, 10.5, 0, 2, 0, 1000} - var ok bool - + var ( + drawingML interface{} + ok bool + ) for i := 0; i < expectedChartsCount; i++ { 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) err = xml.Unmarshal([]byte(xmlCharts[i]), &chartSpaces[i]) diff --git a/col.go b/col.go index 2fd90b2f..91ca3da1 100644 --- a/col.go +++ b/col.go @@ -199,8 +199,11 @@ func (f *File) Cols(sheet string) (*Cols, error) { if !ok { return nil, ErrSheetNotExist{sheet} } - if f.Sheet[name] != nil { - output, _ := xml.Marshal(f.Sheet[name]) + if ws, ok := f.Sheet.Load(name); ok && ws != nil { + worksheet := ws.(*xlsxWorksheet) + worksheet.Lock() + defer worksheet.Unlock() + output, _ := xml.Marshal(worksheet) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var colIterator columnXMLIterator diff --git a/col_test.go b/col_test.go index 6ab5e576..8159a11c 100644 --- a/col_test.go +++ b/col_test.go @@ -48,11 +48,11 @@ func TestCols(t *testing.T) { _, err = f.Rows("Sheet1") assert.NoError(t, err) - f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{ + f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ Dimension: &xlsxDimension{ Ref: "C2:C4", }, - } + }) _, err = f.Rows("Sheet1") assert.NoError(t, err) } @@ -110,15 +110,15 @@ func TestGetColsError(t *testing.T) { assert.EqualError(t, err, "sheet SheetN is not exist") f = NewFile() - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`B`) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B`)) f.checked = nil _, err = f.GetCols("Sheet1") assert.EqualError(t, err, `strconv.Atoi: parsing "A": invalid syntax`) f = NewFile() - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`B`) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`B`)) f.checked = nil _, err = f.GetCols("Sheet1") 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, f.SetCellValue("Sheet1", "A1", 1)) - f.Sheet["xl/worksheets/sheet1.xml"] = &xlsxWorksheet{ + f.Sheet.Store("xl/worksheets/sheet1.xml", &xlsxWorksheet{ Dimension: &xlsxDimension{ Ref: "A1:A1", }, - } + }) f = NewFile() - f.XLSX["xl/worksheets/sheet1.xml"] = nil + f.Pkg.Store("xl/worksheets/sheet1.xml", nil) _, err = f.Cols("Sheet1") if !assert.NoError(t, err) { t.FailNow() diff --git a/comment.go b/comment.go index b05f3080..306826dc 100644 --- a/comment.go +++ b/comment.go @@ -299,11 +299,12 @@ func (f *File) addComment(commentsXML, cell string, formatSet *formatComment) { // the folder xl. func (f *File) countComments() int { c1, c2 := 0, 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/comments") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/comments") { c1++ } - } + return true + }) for rel := range f.Comments { if strings.Contains(rel, "xl/comments") { c2++ @@ -321,10 +322,10 @@ func (f *File) decodeVMLDrawingReader(path string) *decodeVmlDrawing { var err error if f.DecodeVMLDrawing[path] == nil { - c, ok := f.XLSX[path] - if ok { + c, ok := f.Pkg.Load(path) + if ok && c != nil { 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 { log.Printf("xml decode error: %s", err) } @@ -339,7 +340,7 @@ func (f *File) vmlDrawingWriter() { for path, vml := range f.VMLDrawing { if vml != nil { 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. func (f *File) commentsReader(path string) *xlsxComments { var err error - if f.Comments[path] == nil { - content, ok := f.XLSX[path] - if ok { + content, ok := f.Pkg.Load(path) + if ok && content != nil { 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 { log.Printf("xml decode error: %s", err) } diff --git a/comment_test.go b/comment_test.go index 19b705f4..f1b60dc6 100644 --- a/comment_test.go +++ b/comment_test.go @@ -36,7 +36,7 @@ func TestAddComments(t *testing.T) { } f.Comments["xl/comments2.xml"] = nil - f.XLSX["xl/comments2.xml"] = []byte(`Excelize: Excelize: `) + f.Pkg.Store("xl/comments2.xml", []byte(`Excelize: Excelize: `)) comments := f.GetComments() assert.EqualValues(t, 2, len(comments["Sheet1"])) assert.EqualValues(t, 1, len(comments["Sheet2"])) @@ -46,14 +46,14 @@ func TestAddComments(t *testing.T) { func TestDecodeVMLDrawingReader(t *testing.T) { f := NewFile() path := "xl/drawings/vmlDrawing1.xml" - f.XLSX[path] = MacintoshCyrillicCharset + f.Pkg.Store(path, MacintoshCyrillicCharset) f.decodeVMLDrawingReader(path) } func TestCommentsReader(t *testing.T) { f := NewFile() path := "xl/comments1.xml" - f.XLSX[path] = MacintoshCyrillicCharset + f.Pkg.Store(path, MacintoshCyrillicCharset) f.commentsReader(path) } diff --git a/crypt.go b/crypt.go index 88abd0e3..a0096c92 100644 --- a/crypt.go +++ b/crypt.go @@ -21,7 +21,6 @@ import ( "encoding/base64" "encoding/binary" "encoding/xml" - "errors" "hash" "math/rand" "reflect" @@ -145,7 +144,7 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { case "standard": return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) default: - err = errors.New("unsupport encryption mechanism") + err = ErrUnsupportEncryptMechanism } return } @@ -265,7 +264,7 @@ func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { } // TODO: Create a new CFB. _, _ = encryptedPackage, encryptionInfoBuffer - err = errors.New("not support encryption currently") + err = ErrEncrypt return } @@ -293,7 +292,7 @@ func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []by // encryptionMechanism parse password-protected documents created mechanism. func encryptionMechanism(buffer []byte) (mechanism string, err error) { if len(buffer) < 4 { - err = errors.New("unknown encryption mechanism") + err = ErrUnknownEncryptMechanism return } 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 { mechanism = "extensible" } - err = errors.New("unsupport encryption mechanism") + err = ErrUnsupportEncryptMechanism return } @@ -470,7 +469,6 @@ func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) ( if len(key) < keyBytes { tmp := make([]byte, 0x36) key = append(key, tmp...) - key = tmp } else if len(key) > keyBytes { key = key[:keyBytes] } @@ -599,7 +597,6 @@ func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) { if len(iv) < encryptedKey.BlockSize { tmp := make([]byte, 0x36) iv = append(iv, tmp...) - iv = tmp } else if len(iv) > encryptedKey.BlockSize { iv = iv[0:encryptedKey.BlockSize] } diff --git a/docProps_test.go b/docProps_test.go index 0cb6f71f..40ae2dc9 100644 --- a/docProps_test.go +++ b/docProps_test.go @@ -42,12 +42,12 @@ func TestSetDocProps(t *testing.T) { Version: "1.0.0", })) 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{})) // Test unsupported charset 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") } @@ -59,13 +59,13 @@ func TestGetDocProps(t *testing.T) { props, err := f.GetDocProps() assert.NoError(t, err) assert.Equal(t, props.Creator, "Microsoft Office User") - f.XLSX["docProps/core.xml"] = nil + f.Pkg.Store("docProps/core.xml", nil) _, err = f.GetDocProps() assert.NoError(t, err) // Test unsupported charset f = NewFile() - f.XLSX["docProps/core.xml"] = MacintoshCyrillicCharset + f.Pkg.Store("docProps/core.xml", MacintoshCyrillicCharset) _, err = f.GetDocProps() assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") } diff --git a/drawing.go b/drawing.go index 58e26693..181bb433 100644 --- a/drawing.go +++ b/drawing.go @@ -1151,7 +1151,7 @@ func (f *File) drawingParser(path string) (*xlsxWsDr, int) { content := xlsxWsDr{} content.A = NameSpaceDrawingML.Value content.Xdr = NameSpaceDrawingMLSpreadSheet.Value - if _, ok = f.XLSX[path]; ok { // Append Model + if _, ok = f.Pkg.Load(path); ok { // Append Model decodeWsDr := decodeWsDr{} if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). Decode(&decodeWsDr); err != nil && err != io.EOF { diff --git a/drawing_test.go b/drawing_test.go index 3c0b619e..f2413cfe 100644 --- a/drawing_test.go +++ b/drawing_test.go @@ -19,10 +19,10 @@ import ( func TestDrawingParser(t *testing.T) { f := File{ Drawings: sync.Map{}, - XLSX: map[string][]byte{ - "charset": MacintoshCyrillicCharset, - "wsDr": []byte(``)}, + Pkg: sync.Map{}, } + f.Pkg.Store("charset", MacintoshCyrillicCharset) + f.Pkg.Store("wsDr", []byte(``)) // Test with one cell anchor f.drawingParser("wsDr") // Test with unsupported charset diff --git a/errors.go b/errors.go index a0c61c8d..4931198e 100644 --- a/errors.go +++ b/errors.go @@ -32,6 +32,10 @@ func newInvalidExcelDateError(dateValue float64) error { 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 ( // ErrStreamSetColWidth defined the error message on set column width in // stream writing mode. @@ -71,4 +75,34 @@ var ( // ErrMaxFileNameLength defined the error message on receive the file name // length overflow. 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") ) diff --git a/excelize.go b/excelize.go index 66cfd00f..bdb71202 100644 --- a/excelize.go +++ b/excelize.go @@ -43,7 +43,7 @@ type File struct { Path string SharedStrings *xlsxSST sharedStringsMap map[string]int - Sheet map[string]*xlsxWorksheet + Sheet sync.Map // map[string]*xlsxWorksheet SheetCount int Styles *xlsxStyleSheet Theme *xlsxTheme @@ -51,7 +51,7 @@ type File struct { VMLDrawing map[string]*vmlDrawing WorkBook *xlsxWorkbook Relationships sync.Map - XLSX map[string][]byte + Pkg sync.Map CharsetReader charsetTranscoderFn } @@ -95,7 +95,7 @@ func newFile() *File { Comments: make(map[string]*xlsxComments), Drawings: sync.Map{}, sharedStringsMap: make(map[string]int), - Sheet: make(map[string]*xlsxWorksheet), + Sheet: sync.Map{}, DecodeVMLDrawing: make(map[string]*decodeVmlDrawing), VMLDrawing: make(map[string]*vmlDrawing), Relationships: sync.Map{}, @@ -129,7 +129,10 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) { if err != nil { 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.sheetMap = f.getSheetMap() f.Styles = f.stylesReader() @@ -172,40 +175,40 @@ func (f *File) workSheetReader(sheet string) (ws *xlsxWorksheet, err error) { name string ok bool ) - if name, ok = f.sheetMap[trimSheetName(sheet)]; !ok { err = fmt.Errorf("sheet %s is not exist", sheet) return } - if ws = f.Sheet[name]; f.Sheet[name] == nil { - if strings.HasPrefix(name, "xl/chartsheets") { - err = fmt.Errorf("sheet %s is chart sheet", sheet) - return - } - ws = new(xlsxWorksheet) - if _, ok := f.xmlAttr[name]; !ok { - d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))) - f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...) - } - if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))). - Decode(ws); err != nil && err != io.EOF { - err = fmt.Errorf("xml decode error: %s", err) - return - } - err = nil - if f.checked == nil { - f.checked = make(map[string]bool) - } - if ok = f.checked[name]; !ok { - checkSheet(ws) - if err = checkRow(ws); err != nil { - return - } - f.checked[name] = true - } - f.Sheet[name] = ws + if worksheet, ok := f.Sheet.Load(name); ok && worksheet != nil { + ws = worksheet.(*xlsxWorksheet) + return } - + if strings.HasPrefix(name, "xl/chartsheets") { + err = fmt.Errorf("sheet %s is chart sheet", sheet) + return + } + ws = new(xlsxWorksheet) + if _, ok := f.xmlAttr[name]; !ok { + d := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))) + f.xmlAttr[name] = append(f.xmlAttr[name], getRootElement(d)...) + } + if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(name)))). + Decode(ws); err != nil && err != io.EOF { + err = fmt.Errorf("xml decode error: %s", err) + return + } + err = nil + if f.checked == nil { + f.checked = make(map[string]bool) + } + if ok = f.checked[name]; !ok { + checkSheet(ws) + if err = checkRow(ws); err != nil { + return + } + f.checked[name] = true + } + f.Sheet.Store(name, ws) return } @@ -375,7 +378,7 @@ func (f *File) AddVBAProject(bin string) error { }) } file, _ := ioutil.ReadFile(bin) - f.XLSX["xl/vbaProject.bin"] = file + f.Pkg.Store("xl/vbaProject.bin", file) return err } diff --git a/excelize_test.go b/excelize_test.go index e3cfa533..22e39d12 100644 --- a/excelize_test.go +++ b/excelize_test.go @@ -343,13 +343,17 @@ func TestSetCellHyperLink(t *testing.T) { f = NewFile() _, err = f.workSheetReader("Sheet1") 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()) f = NewFile() _, err = f.workSheetReader("Sheet1") 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") 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() _, err = f.workSheetReader("Sheet1") 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"}}, } link, target, err = f.GetCellHyperLink("Sheet1", "A1") @@ -384,7 +390,9 @@ func TestGetCellHyperLink(t *testing.T) { assert.Equal(t, link, true) 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") assert.EqualError(t, err, `cannot convert cell "A" to coordinates: invalid cell name "A"`) assert.Equal(t, link, false) @@ -1112,8 +1120,8 @@ func TestSetSheetRow(t *testing.T) { assert.EqualError(t, f.SetSheetRow("Sheet1", "", &[]interface{}{"cell", nil, 2}), `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", &f), `pointer to slice expected`) + assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", []interface{}{}), ErrParameterInvalid.Error()) + assert.EqualError(t, f.SetSheetRow("Sheet1", "B27", &f), ErrParameterInvalid.Error()) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetRow.xlsx"))) } @@ -1198,7 +1206,7 @@ func TestContentTypesReader(t *testing.T) { // Test unsupported charset. f := NewFile() f.ContentTypes = nil - f.XLSX["[Content_Types].xml"] = MacintoshCyrillicCharset + f.Pkg.Store("[Content_Types].xml", MacintoshCyrillicCharset) f.contentTypesReader() } @@ -1206,22 +1214,22 @@ func TestWorkbookReader(t *testing.T) { // Test unsupported charset. f := NewFile() f.WorkBook = nil - f.XLSX["xl/workbook.xml"] = MacintoshCyrillicCharset + f.Pkg.Store("xl/workbook.xml", MacintoshCyrillicCharset) f.workbookReader() } func TestWorkSheetReader(t *testing.T) { // Test unsupported charset. f := NewFile() - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err := f.workSheetReader("Sheet1") assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") // Test on no checked worksheet. f = NewFile() - delete(f.Sheet, "xl/worksheets/sheet1.xml") - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(``) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(``)) f.checked = nil _, err = f.workSheetReader("Sheet1") assert.NoError(t, err) @@ -1232,7 +1240,7 @@ func TestRelsReader(t *testing.T) { f := NewFile() rels := "xl/_rels/workbook.xml.rels" f.Relationships.Store(rels, nil) - f.XLSX[rels] = MacintoshCyrillicCharset + f.Pkg.Store(rels, MacintoshCyrillicCharset) f.relsReader(rels) } diff --git a/file.go b/file.go index fa73ec83..1786e27d 100644 --- a/file.go +++ b/file.go @@ -26,18 +26,17 @@ import ( // f := NewFile() // 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.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.Comments = make(map[string]*xlsxComments) f.ContentTypes = f.contentTypesReader() @@ -48,8 +47,9 @@ func NewFile() *File { f.WorkBook = f.workbookReader() f.Relationships = sync.Map{} 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" + ws, _ := f.workSheetReader("Sheet1") + f.Sheet.Store("xl/worksheets/sheet1.xml", ws) f.Theme = f.themeReader() return f } @@ -165,20 +165,22 @@ func (f *File) writeToZip(zw *zip.Writer) error { } stream.rawData.Close() } - - for path, content := range f.XLSX { - if _, ok := f.streams[path]; ok { - continue - } - fi, err := zw.Create(path) + var err error + f.Pkg.Range(func(path, content interface{}) bool { if err != nil { - return err + return false } - _, err = fi.Write(content) + if _, ok := f.streams[path.(string)]; ok { + return true + } + var fi io.Writer + fi, err = zw.Create(path.(string)) if err != nil { - return err + return false } - } + _, err = fi.Write(content.([]byte)) + return true + }) - return nil + return err } diff --git a/file_test.go b/file_test.go index dbbf75a8..d86ce535 100644 --- a/file_test.go +++ b/file_test.go @@ -5,6 +5,7 @@ import ( "bytes" "os" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -38,19 +39,19 @@ func TestWriteTo(t *testing.T) { { f := File{} buf := bytes.Buffer{} - f.XLSX = make(map[string][]byte) - f.XLSX["/d/"] = []byte("s") + f.Pkg = sync.Map{} + f.Pkg.Store("/d/", []byte("s")) _, err := f.WriteTo(bufio.NewWriter(&buf)) assert.EqualError(t, err, "zip: write to directory") - delete(f.XLSX, "/d/") + f.Pkg.Delete("/d/") } // Test file path overflow { f := File{} buf := bytes.Buffer{} - f.XLSX = make(map[string][]byte) + f.Pkg = sync.Map{} 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)) assert.EqualError(t, err, "zip: FileHeader.Name too long") } @@ -58,8 +59,8 @@ func TestWriteTo(t *testing.T) { { f := File{} buf := bytes.Buffer{} - f.XLSX = make(map[string][]byte) - f.XLSX["s"] = nil + f.Pkg = sync.Map{} + f.Pkg.Store("s", nil) f.streams = make(map[string]*StreamWriter) file, _ := os.Open("123") f.streams["s"] = &StreamWriter{rawData: bufferedWriter{tmp: file}} diff --git a/lib.go b/lib.go index 5c9bbf6e..00a67d96 100644 --- a/lib.go +++ b/lib.go @@ -51,8 +51,8 @@ func ReadZipReader(r *zip.Reader) (map[string][]byte, int, error) { // readXML provides a function to read XML content as string. func (f *File) readXML(name string) []byte { - if content, ok := f.XLSX[name]; ok { - return content + if content, _ := f.Pkg.Load(name); content != nil { + return content.([]byte) } if content, ok := f.streams[name]; ok { 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 = append(newContent, []byte(XMLHeader)...) newContent = append(newContent, content...) - f.XLSX[name] = newContent + f.Pkg.Store(name, newContent) } // Read file content as string in a archive file. diff --git a/merge_test.go b/merge_test.go index afe75aac..cf460dd9 100644 --- a/merge_test.go +++ b/merge_test.go @@ -71,13 +71,19 @@ func TestMergeCell(t *testing.T) { f = NewFile() 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")) - 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"`) - 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"`) } @@ -154,16 +160,24 @@ func TestUnmergeCell(t *testing.T) { // Test unmerged area on not exists worksheet. 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")) - 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")) - 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"`) - 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"`) } diff --git a/picture.go b/picture.go index 09283c81..58fa9097 100644 --- a/picture.go +++ b/picture.go @@ -223,11 +223,12 @@ func (f *File) addSheetPicture(sheet string, rID int) { // folder xl/drawings. func (f *File) countDrawings() int { c1, c2 := 0, 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/drawings/drawing") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/drawings/drawing") { c1++ } - } + return true + }) f.Drawings.Range(func(rel, value interface{}) bool { if strings.Contains(rel.(string), "xl/drawings/drawing") { c2++ @@ -305,11 +306,12 @@ func (f *File) addDrawingPicture(sheet, drawingXML, cell, file string, width, he // folder xl/media/image. func (f *File) countMedia() int { count := 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/media/image") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/media/image") { count++ } - } + return true + }) return count } @@ -318,16 +320,22 @@ func (f *File) countMedia() int { // and drawings that use it will reference the same image. func (f *File) addMedia(file []byte, ext string) string { count := f.countMedia() - for name, existing := range f.XLSX { - if !strings.HasPrefix(name, "xl/media/image") { - continue + var name string + f.Pkg.Range(func(k, existing interface{}) bool { + if !strings.HasPrefix(k.(string), "xl/media/image") { + return true } - if bytes.Equal(file, existing) { - return name + if bytes.Equal(file, existing.([]byte)) { + name = k.(string) + return false } + return true + }) + if name != "" { + return name } media := "xl/media/image" + strconv.Itoa(count+1) + ext - f.XLSX[media] = file + f.Pkg.Store(media, file) return media } @@ -468,8 +476,7 @@ func (f *File) GetPicture(sheet, cell string) (string, []byte, error) { } target := f.getSheetRelationshipsTargetByID(sheet, ws.Drawing.RID) drawingXML := strings.Replace(target, "..", "xl", -1) - _, ok := f.XLSX[drawingXML] - if !ok { + if _, ok := f.Pkg.Load(drawingXML); !ok { return "", nil, err } 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 { drawRel = f.getDrawingRelationships(drawingRelationships, deTwoCellAnchor.Pic.BlipFill.Blip.Embed) 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 } } @@ -556,7 +566,10 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD if drawRel = f.getDrawingRelationships(drawingRelationships, anchor.Pic.BlipFill.Blip.Embed); drawRel != nil { 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 } } diff --git a/picture_test.go b/picture_test.go index be917b84..69873eb0 100644 --- a/picture_test.go +++ b/picture_test.go @@ -155,7 +155,7 @@ func TestGetPicture(t *testing.T) { assert.Empty(t, raw) f, err = prepareTestBook1() 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") 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", 50), "", "logo", ".png", imgFile)) imageCount := 0 - for fileName := range f.XLSX { - if strings.Contains(fileName, "media/image") { + f.Pkg.Range(func(fileName, v interface{}) bool { + if strings.Contains(fileName.(string), "media/image") { imageCount++ } - } + return true + }) 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") } @@ -205,6 +206,8 @@ func TestDrawingResize(t *testing.T) { // Test calculate drawing resize with invalid coordinates. _, _, _, _, err = f.drawingResize("Sheet1", "", 1, 1, nil) 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"`) } diff --git a/pivotTable.go b/pivotTable.go index 3d93260a..05ac7832 100644 --- a/pivotTable.go +++ b/pivotTable.go @@ -13,7 +13,6 @@ package excelize import ( "encoding/xml" - "errors" "fmt" "strconv" "strings" @@ -163,7 +162,7 @@ func (f *File) AddPivotTable(opt *PivotTableOption) error { // properties. func (f *File) parseFormatPivotTableSet(opt *PivotTableOption) (*xlsxWorksheet, string, error) { if opt == nil { - return nil, "", errors.New("parameter is required") + return nil, "", ErrParameterRequired } pivotTableSheetName, _, err := f.adjustRange(opt.PivotTableRange) 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 func (f *File) adjustRange(rangeStr string) (string, []int, error) { if len(rangeStr) < 1 { - return "", []int{}, errors.New("parameter is required") + return "", []int{}, ErrParameterRequired } rng := strings.Split(rangeStr, "!") if len(rng) != 2 { - return "", []int{}, errors.New("parameter is invalid") + return "", []int{}, ErrParameterInvalid } trimRng := strings.Replace(rng[1], "$", "", -1) 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] 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. @@ -600,11 +599,12 @@ func (f *File) addPivotFields(pt *xlsxPivotTableDefinition, opt *PivotTableOptio // the folder xl/pivotTables. func (f *File) countPivotTables() int { count := 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/pivotTables/pivotTable") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/pivotTables/pivotTable") { count++ } - } + return true + }) return count } @@ -612,11 +612,12 @@ func (f *File) countPivotTables() int { // the folder xl/pivotCache. func (f *File) countPivotCache() int { count := 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/pivotCache/pivotCacheDefinition") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/pivotCache/pivotCacheDefinition") { count++ } - } + return true + }) return count } diff --git a/pivotTable_test.go b/pivotTable_test.go index 7098b3a1..e746d8df 100644 --- a/pivotTable_test.go +++ b/pivotTable_test.go @@ -137,12 +137,12 @@ func TestAddPivotTable(t *testing.T) { ShowLastColumn: true, })) // Create pivot table with many data, many rows, many cols and defined name - f.SetDefinedName(&DefinedName{ + assert.NoError(t, f.SetDefinedName(&DefinedName{ Name: "dataRange", RefersTo: "Sheet1!$A$1:$E$31", Comment: "Pivot Table Data Range", Scope: "Sheet2", - }) + })) assert.NoError(t, f.AddPivotTable(&PivotTableOption{ DataRange: "dataRange", PivotTableRange: "Sheet2!$A$57:$AJ$91", diff --git a/rows.go b/rows.go index 6360f4e7..229b12d6 100644 --- a/rows.go +++ b/rows.go @@ -195,9 +195,12 @@ func (f *File) Rows(sheet string) (*Rows, error) { if !ok { 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 - output, _ := xml.Marshal(f.Sheet[name]) + output, _ := xml.Marshal(worksheet) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } var ( diff --git a/rows_test.go b/rows_test.go index 585fe59a..069b6680 100644 --- a/rows_test.go +++ b/rows_test.go @@ -43,11 +43,13 @@ func TestRows(t *testing.T) { } f = NewFile() - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`1B`) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`1B`)) + f.Sheet.Delete("xl/worksheets/sheet1.xml") + delete(f.checked, "xl/worksheets/sheet1.xml") _, err = f.Rows("Sheet1") 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") assert.NoError(t, err) } @@ -187,7 +189,7 @@ func TestColumns(t *testing.T) { func TestSharedStringsReader(t *testing.T) { f := NewFile() - f.XLSX["xl/sharedStrings.xml"] = MacintoshCyrillicCharset + f.Pkg.Store("xl/sharedStrings.xml", MacintoshCyrillicCharset) f.sharedStringsReader() si := xlsxSI{} assert.EqualValues(t, "", si.String()) @@ -874,12 +876,14 @@ func TestErrSheetNotExistError(t *testing.T) { func TestCheckRow(t *testing.T) { f := NewFile() - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`12345`) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`12345`)) _, err := f.GetRows("Sheet1") assert.NoError(t, err) assert.NoError(t, f.SetCellValue("Sheet1", "A1", false)) f = NewFile() - f.XLSX["xl/worksheets/sheet1.xml"] = []byte(`12345`) + f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(`12345`)) + 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 "-"`) } diff --git a/sheet.go b/sheet.go index 420235c0..2b914950 100644 --- a/sheet.go +++ b/sheet.go @@ -15,7 +15,6 @@ import ( "bytes" "encoding/json" "encoding/xml" - "errors" "fmt" "io" "io/ioutil" @@ -152,25 +151,27 @@ func (f *File) workSheetWriter() { var arr []byte buffer := bytes.NewBuffer(arr) encoder := xml.NewEncoder(buffer) - for p, sheet := range f.Sheet { - if sheet != nil { + f.Sheet.Range(func(p, ws interface{}) bool { + if ws != nil { + sheet := ws.(*xlsxWorksheet) 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 { - f.addNameSpaces(p, SourceRelationship) + f.addNameSpaces(p.(string), SourceRelationship) } // reusing buffer _ = encoder.Encode(sheet) - f.saveFileList(p, replaceRelationshipsBytes(f.replaceNameSpaceBytes(p, buffer.Bytes()))) - ok := f.checked[p] + f.saveFileList(p.(string), replaceRelationshipsBytes(f.replaceNameSpaceBytes(p.(string), buffer.Bytes()))) + ok := f.checked[p.(string)] if ok { - delete(f.Sheet, p) - f.checked[p] = false + f.Sheet.Delete(p.(string)) + f.checked[p.(string)] = false } buffer.Reset() } - } + return true + }) } // 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" f.sheetMap[trimSheetName(name)] = path - f.Sheet[path] = &ws + f.Sheet.Store(path, &ws) f.xmlAttr[path] = []xml.Attr{NameSpaceSpreadSheet} } @@ -448,7 +449,7 @@ func (f *File) getSheetMap() map[string]string { if strings.HasPrefix(rel.Target, "/") { 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 } } @@ -524,10 +525,10 @@ func (f *File) DeleteSheet(name string) { f.deleteSheetFromContentTypes(target) f.deleteCalcChain(sheet.SheetID, "") delete(f.sheetMap, sheetName) - delete(f.XLSX, sheetXML) - delete(f.XLSX, rels) + f.Pkg.Delete(sheetXML) + f.Pkg.Delete(rels) f.Relationships.Delete(rels) - delete(f.Sheet, sheetXML) + f.Sheet.Delete(sheetXML) delete(f.xmlAttr, sheetXML) f.SheetCount-- } @@ -573,7 +574,7 @@ func (f *File) deleteSheetFromContentTypes(target string) { // func (f *File) CopySheet(from, to int) error { 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) } @@ -595,12 +596,11 @@ func (f *File) copySheet(from, to int) error { worksheet.Drawing = nil worksheet.TableParts = nil worksheet.PageSetUp = nil - f.Sheet[path] = worksheet + f.Sheet.Store(path, worksheet) toRels := "xl/worksheets/_rels/sheet" + toSheetID + ".xml.rels" fromRels := "xl/worksheets/_rels/sheet" + strconv.Itoa(f.getSheetID(fromSheet)) + ".xml.rels" - _, ok := f.XLSX[fromRels] - if ok { - f.XLSX[toRels] = f.XLSX[fromRels] + if rels, ok := f.Pkg.Load(fromRels); ok && rels != nil { + f.Pkg.Store(toRels, rels.([]byte)) } fromSheetXMLPath := f.sheetMap[trimSheetName(fromSheet)] fromSheetAttr := f.xmlAttr[fromSheetXMLPath] @@ -824,9 +824,9 @@ func (f *File) SearchSheet(sheet, value string, reg ...bool) ([]string, error) { if !ok { return result, ErrSheetNotExist{sheet} } - if f.Sheet[name] != nil { + if ws, ok := f.Sheet.Load(name); ok && ws != nil { // flush data - output, _ := xml.Marshal(f.Sheet[name]) + output, _ := xml.Marshal(ws.(*xlsxWorksheet)) f.saveFileList(name, f.replaceNameSpaceBytes(name, output)) } return f.searchSheet(name, value, regSearch) @@ -1483,7 +1483,7 @@ func (f *File) SetDefinedName(definedName *DefinedName) error { scope = f.GetSheetName(*dn.LocalSheetID) } 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) @@ -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 @@ -1558,7 +1558,7 @@ func (f *File) GroupSheets(sheets []string) error { } } if !inActiveSheet { - return errors.New("group worksheet must contain an active worksheet") + return ErrGroupSheets } // check worksheet exists wss := []*xlsxWorksheet{} @@ -1714,8 +1714,7 @@ func (f *File) relsReader(path string) *xlsxRelationships { var err error rels, _ := f.Relationships.Load(path) if rels == nil { - _, ok := f.XLSX[path] - if ok { + if _, ok := f.Pkg.Load(path); ok { c := xlsxRelationships{} if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). 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 // 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) { + ws.Lock() + defer ws.Unlock() rowCount := len(ws.SheetData.Row) sizeHint := 0 var ht float64 diff --git a/sheet_test.go b/sheet_test.go index a7214720..268abdca 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -347,9 +347,13 @@ func TestSetActiveSheet(t *testing.T) { f.WorkBook.BookViews = nil f.SetActiveSheet(1) 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.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 = NewFile() f.SetActiveSheet(-1) @@ -365,14 +369,14 @@ func TestSetSheetName(t *testing.T) { func TestGetWorkbookPath(t *testing.T) { f := NewFile() - delete(f.XLSX, "_rels/.rels") + f.Pkg.Delete("_rels/.rels") assert.Equal(t, "", f.getWorkbookPath()) } func TestGetWorkbookRelsPath(t *testing.T) { f := NewFile() - delete(f.XLSX, "xl/_rels/.rels") - f.XLSX["_rels/.rels"] = []byte(``) + f.Pkg.Delete("xl/_rels/.rels") + f.Pkg.Store("_rels/.rels", []byte(``)) assert.Equal(t, "_rels/workbook.xml.rels", f.getWorkbookRelsPath()) } diff --git a/sheetpr_test.go b/sheetpr_test.go index 42e2e0d1..53532e95 100644 --- a/sheetpr_test.go +++ b/sheetpr_test.go @@ -443,7 +443,9 @@ func TestSheetFormatPrOptions(t *testing.T) { func TestSetSheetFormatPr(t *testing.T) { f := NewFile() 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))) // Test set formatting properties on not exists worksheet. 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) { f := NewFile() 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 ( baseColWidth BaseColWidth defaultColWidth DefaultColWidth diff --git a/sparkline.go b/sparkline.go index 150c0eae..5326c60c 100644 --- a/sparkline.go +++ b/sparkline.go @@ -467,7 +467,7 @@ func (f *File) parseFormatAddSparklineSet(sheet string, opt *SparklineOption) (* return ws, err } if opt == nil { - return ws, errors.New("parameter is required") + return ws, ErrParameterRequired } if len(opt.Location) < 1 { return ws, errors.New("parameter 'Location' is required") diff --git a/sparkline_test.go b/sparkline_test.go index eac98246..0777ee16 100644 --- a/sparkline_test.go +++ b/sparkline_test.go @@ -253,7 +253,9 @@ func TestAddSparkline(t *testing.T) { Style: -1, }), `parameter 'Style' must betweent 0-35`) - f.Sheet["xl/worksheets/sheet1.xml"].ExtLst.Ext = ` + ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml") + assert.True(t, ok) + ws.(*xlsxWorksheet).ExtLst.Ext = ` diff --git a/stream.go b/stream.go index 054dd8d1..4a77b563 100644 --- a/stream.go +++ b/stream.go @@ -301,7 +301,7 @@ func (sw *StreamWriter) SetRow(axis string, values []interface{}) error { } if !sw.sheetWritten { if len(sw.cols) > 0 { - sw.rawData.WriteString("" + sw.cols + "") + _, _ = sw.rawData.WriteString("" + sw.cols + "") } _, _ = sw.rawData.WriteString(``) sw.sheetWritten = true @@ -481,9 +481,9 @@ func (sw *StreamWriter) Flush() error { } 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.XLSX, sheetPath) + sw.File.Pkg.Delete(sheetPath) return nil } diff --git a/stream_test.go b/stream_test.go index cf133f14..f911ccc8 100644 --- a/stream_test.go +++ b/stream_test.go @@ -99,8 +99,8 @@ func TestStreamWriter(t *testing.T) { // Test unsupported charset file = NewFile() - delete(file.Sheet, "xl/worksheets/sheet1.xml") - file.XLSX["xl/worksheets/sheet1.xml"] = MacintoshCyrillicCharset + file.Sheet.Delete("xl/worksheets/sheet1.xml") + file.Pkg.Store("xl/worksheets/sheet1.xml", MacintoshCyrillicCharset) _, err = file.NewStreamWriter("Sheet1") assert.EqualError(t, err, "xml decode error: XML syntax error on line 1: invalid UTF-8") @@ -145,7 +145,9 @@ func TestStreamTable(t *testing.T) { // Verify the table has names. 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, "B", table.TableColumns.TableColumn[1].Name) assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name) diff --git a/styles.go b/styles.go index 1ba9f08b..235746c5 100644 --- a/styles.go +++ b/styles.go @@ -15,7 +15,6 @@ import ( "bytes" "encoding/json" "encoding/xml" - "errors" "fmt" "io" "log" @@ -1104,14 +1103,14 @@ func parseFormatStyleSet(style interface{}) (*Style, error) { case *Style: fs = *v default: - err = errors.New("invalid parameter type") + err = ErrParameterInvalid } if fs.Font != nil { 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 { - return &fs, errors.New("font size must be between 1 and 409 points") + return &fs, ErrFontSize } } return &fs, err diff --git a/styles_test.go b/styles_test.go index e2eed1d7..5d452f6f 100644 --- a/styles_test.go +++ b/styles_test.go @@ -201,7 +201,7 @@ func TestNewStyle(t *testing.T) { _, err = f.NewStyle(&Style{}) assert.NoError(t, err) _, 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)}}) 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() // Test read styles with unsupported charset. f.Styles = nil - f.XLSX["xl/styles.xml"] = MacintoshCyrillicCharset + f.Pkg.Store("xl/styles.xml", MacintoshCyrillicCharset) assert.EqualValues(t, new(xlsxStyleSheet), f.stylesReader()) } func TestThemeReader(t *testing.T) { f := NewFile() // 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()) } diff --git a/table.go b/table.go index 12ef41a8..620cf20b 100644 --- a/table.go +++ b/table.go @@ -105,11 +105,12 @@ func (f *File) AddTable(sheet, hcell, vcell, format string) error { // folder xl/tables. func (f *File) countTables() int { count := 0 - for k := range f.XLSX { - if strings.Contains(k, "xl/tables/table") { + f.Pkg.Range(func(k, v interface{}) bool { + if strings.Contains(k.(string), "xl/tables/table") { count++ } - } + return true + }) return count }