Remove internal error log print, throw XML deserialize error

This commit is contained in:
xuri 2022-11-13 00:40:04 +08:00
parent bd5dd17673
commit ac564afa56
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
31 changed files with 458 additions and 203 deletions

View File

@ -54,7 +54,10 @@ func (f *File) deleteCalcChain(index int, cell string) error {
if len(calc.C) == 0 { if len(calc.C) == 0 {
f.CalcChain = nil f.CalcChain = nil
f.Pkg.Delete(defaultXMLPathCalcChain) f.Pkg.Delete(defaultXMLPathCalcChain)
content := f.contentTypesReader() content, err := f.contentTypesReader()
if err != nil {
return err
}
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
for k, v := range content.Overrides { for k, v := range content.Overrides {

View File

@ -33,7 +33,14 @@ func TestDeleteCalcChain(t *testing.T) {
formulaType, ref := STCellFormulaTypeShared, "C1:C5" formulaType, ref := STCellFormulaTypeShared, "C1:C5"
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType})) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", "=A1+B1", FormulaOpts{Ref: &ref, Type: &formulaType}))
// Test delete calculation chain with unsupported charset calculation chain.
f.CalcChain = nil f.CalcChain = nil
f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathCalcChain, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.SetCellValue("Sheet1", "C1", true), "XML syntax error on line 1: invalid UTF-8")
// Test delete calculation chain with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteCalcChain(1, "A1"), "XML syntax error on line 1: invalid UTF-8")
} }

13
cell.go
View File

@ -241,11 +241,14 @@ func (f *File) setCellTimeFunc(sheet, cell string, value time.Time) error {
ws.Lock() ws.Lock()
c.S = f.prepareCellStyle(ws, col, row, c.S) c.S = f.prepareCellStyle(ws, col, row, c.S)
ws.Unlock() ws.Unlock()
date1904, wb := false, f.workbookReader() var date1904, isNum bool
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb != nil && wb.WorkbookPr != nil { if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904 date1904 = wb.WorkbookPr.Date1904
} }
var isNum bool
if isNum, err = c.setCellTime(value, date1904); err != nil { if isNum, err = c.setCellTime(value, date1904); err != nil {
return err return err
} }
@ -1320,7 +1323,11 @@ func (f *File) formattedValue(s int, v string, raw bool) (string, error) {
if styleSheet.CellXfs.Xf[s].NumFmtID != nil { if styleSheet.CellXfs.Xf[s].NumFmtID != nil {
numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID numFmtID = *styleSheet.CellXfs.Xf[s].NumFmtID
} }
date1904, wb := false, f.workbookReader() date1904 := false
wb, err := f.workbookReader()
if err != nil {
return v, err
}
if wb != nil && wb.WorkbookPr != nil { if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904 date1904 = wb.WorkbookPr.Date1904
} }

View File

@ -173,7 +173,7 @@ func TestSetCellValue(t *testing.T) {
f := NewFile() f := NewFile()
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Now().UTC()), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Now().UTC()), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.SetCellValue("Sheet1", "A", time.Duration(1e13)), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set cell value with column and row style inherit // Test set cell value with column and row style inherit.
style1, err := f.NewStyle(&Style{NumFmt: 2}) style1, err := f.NewStyle(&Style{NumFmt: 2})
assert.NoError(t, err) assert.NoError(t, err)
style2, err := f.NewStyle(&Style{NumFmt: 9}) style2, err := f.NewStyle(&Style{NumFmt: 9})
@ -189,10 +189,14 @@ func TestSetCellValue(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "0.50", B2) assert.Equal(t, "0.50", B2)
// Test set cell value with unsupported charset shared strings table // Test set cell value with unsupported charset shared strings table.
f.SharedStrings = nil f.SharedStrings = nil
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.SetCellValue("Sheet1", "A1", "A1"), "XML syntax error on line 1: invalid UTF-8")
// Test set cell value with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetCellValue("Sheet1", "A1", time.Now().UTC()), "XML syntax error on line 1: invalid UTF-8")
} }
func TestSetCellValues(t *testing.T) { func TestSetCellValues(t *testing.T) {
@ -204,7 +208,7 @@ func TestSetCellValues(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, v, "12/31/10 00:00") assert.Equal(t, v, "12/31/10 00:00")
// Test date value lower than min date supported by Excel // Test date value lower than min date supported by Excel.
err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC)) err = f.SetCellValue("Sheet1", "A1", time.Date(1600, time.December, 31, 0, 0, 0, 0, time.UTC))
assert.NoError(t, err) assert.NoError(t, err)
@ -782,6 +786,12 @@ func TestFormattedValue(t *testing.T) {
assert.Equal(t, "0_0", fn("0_0", "", false)) assert.Equal(t, "0_0", fn("0_0", "", false))
} }
// Test format value with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.formattedValue(1, "43528", false)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test format value with unsupported charset style sheet. // Test format value with unsupported charset style sheet.
f.Styles = nil f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)

View File

@ -927,8 +927,10 @@ func (f *File) AddChart(sheet, cell, opts string, combo ...string) error {
return err return err
} }
f.addChart(options, comboCharts) f.addChart(options, comboCharts)
f.addContentTypePart(chartID, "chart") if err = f.addContentTypePart(chartID, "chart"); err != nil {
f.addContentTypePart(drawingID, "drawings") return err
}
_ = f.addContentTypePart(drawingID, "drawings")
f.addSheetNameSpace(sheet, SourceRelationship) f.addSheetNameSpace(sheet, SourceRelationship)
return err return err
} }
@ -952,7 +954,7 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
}, },
} }
f.SheetCount++ f.SheetCount++
wb := f.workbookReader() wb, _ := f.workbookReader()
sheetID := 0 sheetID := 0
for _, v := range wb.Sheets.Sheet { for _, v := range wb.Sheets.Sheet {
if v.SheetID > sheetID { if v.SheetID > sheetID {
@ -969,11 +971,15 @@ func (f *File) AddChartSheet(sheet, opts string, combo ...string) error {
f.prepareChartSheetDrawing(&cs, drawingID, sheet) f.prepareChartSheetDrawing(&cs, drawingID, sheet)
drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels" drawingRels := "xl/drawings/_rels/drawing" + strconv.Itoa(drawingID) + ".xml.rels"
drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "") drawingRID := f.addRels(drawingRels, SourceRelationshipChart, "../charts/chart"+strconv.Itoa(chartID)+".xml", "")
f.addSheetDrawingChart(drawingXML, drawingRID, &options.Format) if err = f.addSheetDrawingChart(drawingXML, drawingRID, &options.Format); err != nil {
return err
}
f.addChart(options, comboCharts) f.addChart(options, comboCharts)
f.addContentTypePart(chartID, "chart") if err = f.addContentTypePart(chartID, "chart"); err != nil {
f.addContentTypePart(sheetID, "chartsheet") return err
f.addContentTypePart(drawingID, "drawings") }
_ = f.addContentTypePart(sheetID, "chartsheet")
_ = f.addContentTypePart(drawingID, "drawings")
// Update workbook.xml.rels // Update workbook.xml.rels
rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "") rID := f.addRels(f.getWorkbookRelsPath(), SourceRelationshipChartsheet, fmt.Sprintf("/xl/chartsheets/sheet%d.xml", sheetID), "")
// Update workbook.xml // Update workbook.xml

View File

@ -226,6 +226,11 @@ func TestAddChart(t *testing.T) {
// Test add combo chart with unsupported chart type // Test add combo chart with unsupported chart type
assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown") assert.EqualError(t, f.AddChart("Sheet2", "BD64", `{"type":"barOfPie","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`, `{"type":"unknown","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$A$30:$D$37","values":"Sheet1!$B$30:$B$37"}],"format":{"x_scale":1.0,"y_scale":1.0,"x_offset":15,"y_offset":10,"print_obj":true,"lock_aspect_ratio":false,"locked":false},"legend":{"position":"left","show_legend_key":false},"title":{"name":"Bar of Pie Chart"},"plotarea":{"show_bubble_size":true,"show_cat_name":false,"show_leader_lines":false,"show_percent":true,"show_series_name":true,"show_val":true},"show_blanks_as":"zero","x_axis":{"major_grid_lines":true},"y_axis":{"major_grid_lines":true}}`), "unsupported chart type unknown")
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
// Test add chart with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChart("Sheet1", "P1", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8")
} }
func TestAddChartSheet(t *testing.T) { func TestAddChartSheet(t *testing.T) {
@ -259,6 +264,14 @@ func TestAddChartSheet(t *testing.T) {
assert.NoError(t, f.UpdateLinkedValue()) assert.NoError(t, f.UpdateLinkedValue())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddChartSheet.xlsx")))
// Test add chart sheet with unsupported charset drawing XML.
f.Pkg.Store("xl/drawings/drawing4.xml", MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChartSheet("Chart3", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8")
// Test add chart sheet with unsupported charset content types.
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddChartSheet("Chart4", `{"type":"col","series":[{"name":"Sheet1!$A$30","categories":"Sheet1!$B$29:$D$29","values":"Sheet1!$B$30:$D$30"}],"title":{"name":"2D Column Chart"}}`), "XML syntax error on line 1: invalid UTF-8")
} }
func TestDeleteChart(t *testing.T) { func TestDeleteChart(t *testing.T) {

View File

@ -69,8 +69,8 @@ func (f *File) GetComments() (map[string][]Comment, error) {
// getSheetComments provides the method to get the target comment reference by // getSheetComments provides the method to get the target comment reference by
// given worksheet file path. // given worksheet file path.
func (f *File) getSheetComments(sheetFile string) string { func (f *File) getSheetComments(sheetFile string) string {
rels := "xl/worksheets/_rels/" + sheetFile + ".rels" rels, _ := f.relsReader("xl/worksheets/_rels/" + sheetFile + ".rels")
if sheetRels := f.relsReader(rels); sheetRels != nil { if sheetRels := rels; sheetRels != nil {
sheetRels.Lock() sheetRels.Lock()
defer sheetRels.Unlock() defer sheetRels.Unlock()
for _, v := range sheetRels.Relationships { for _, v := range sheetRels.Relationships {
@ -135,8 +135,7 @@ func (f *File) AddComment(sheet string, comment Comment) error {
if err = f.addComment(commentsXML, comment); err != nil { if err = f.addComment(commentsXML, comment); err != nil {
return err return err
} }
f.addContentTypePart(commentID, "comments") return f.addContentTypePart(commentID, "comments")
return err
} }
// DeleteComment provides the method to delete comment in a sheet by given // DeleteComment provides the method to delete comment in a sheet by given

View File

@ -112,7 +112,8 @@ func TestDecodeVMLDrawingReader(t *testing.T) {
f := NewFile() f := NewFile()
path := "xl/drawings/vmlDrawing1.xml" path := "xl/drawings/vmlDrawing1.xml"
f.Pkg.Store(path, MacintoshCyrillicCharset) f.Pkg.Store(path, MacintoshCyrillicCharset)
f.decodeVMLDrawingReader(path) _, err := f.decodeVMLDrawingReader(path)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }
func TestCommentsReader(t *testing.T) { func TestCommentsReader(t *testing.T) {

View File

@ -181,8 +181,10 @@ func OpenReader(r io.Reader, opts ...Options) (*File, error) {
return f, err return f, err
} }
f.sheetMap = f.getSheetMap() f.sheetMap = f.getSheetMap()
f.Styles, err = f.stylesReader() if f.Styles, err = f.stylesReader(); err != nil {
f.Theme = f.themeReader() return f, err
}
f.Theme, err = f.themeReader()
return f, err return f, err
} }
@ -335,7 +337,7 @@ func checkSheetR0(ws *xlsxWorksheet, sheetData *xlsxSheetData, r0 *xlsxRow) {
// setRels provides a function to set relationships by given relationship ID, // setRels provides a function to set relationships by given relationship ID,
// XML path, relationship type, target and target mode. // XML path, relationship type, target and target mode.
func (f *File) setRels(rID, relPath, relType, target, targetMode string) int { func (f *File) setRels(rID, relPath, relType, target, targetMode string) int {
rels := f.relsReader(relPath) rels, _ := f.relsReader(relPath)
if rels == nil || rID == "" { if rels == nil || rID == "" {
return f.addRels(relPath, relType, target, targetMode) return f.addRels(relPath, relType, target, targetMode)
} }
@ -360,7 +362,7 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
uniqPart := map[string]string{ uniqPart := map[string]string{
SourceRelationshipSharedStrings: "/xl/sharedStrings.xml", SourceRelationshipSharedStrings: "/xl/sharedStrings.xml",
} }
rels := f.relsReader(relPath) rels, _ := f.relsReader(relPath)
if rels == nil { if rels == nil {
rels = &xlsxRelationships{} rels = &xlsxRelationships{}
} }
@ -418,7 +420,10 @@ func (f *File) addRels(relPath, relType, target, targetMode string) int {
// </c> // </c>
// </row> // </row>
func (f *File) UpdateLinkedValue() error { func (f *File) UpdateLinkedValue() error {
wb := f.workbookReader() wb, err := f.workbookReader()
if err != nil {
return err
}
// recalculate formulas // recalculate formulas
wb.CalcPr = nil wb.CalcPr = nil
for _, name := range f.GetSheetList() { for _, name := range f.GetSheetList() {
@ -465,12 +470,15 @@ func (f *File) AddVBAProject(bin string) error {
if path.Ext(bin) != ".bin" { if path.Ext(bin) != ".bin" {
return ErrAddVBAProject return ErrAddVBAProject
} }
wb := f.relsReader(f.getWorkbookRelsPath()) rels, err := f.relsReader(f.getWorkbookRelsPath())
wb.Lock() if err != nil {
defer wb.Unlock() return err
}
rels.Lock()
defer rels.Unlock()
var rID int var rID int
var ok bool var ok bool
for _, rel := range wb.Relationships { for _, rel := range rels.Relationships {
if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject { if rel.Target == "vbaProject.bin" && rel.Type == SourceRelationshipVBAProject {
ok = true ok = true
continue continue
@ -482,7 +490,7 @@ func (f *File) AddVBAProject(bin string) error {
} }
rID++ rID++
if !ok { if !ok {
wb.Relationships = append(wb.Relationships, xlsxRelationship{ rels.Relationships = append(rels.Relationships, xlsxRelationship{
ID: "rId" + strconv.Itoa(rID), ID: "rId" + strconv.Itoa(rID),
Target: "vbaProject.bin", Target: "vbaProject.bin",
Type: SourceRelationshipVBAProject, Type: SourceRelationshipVBAProject,
@ -495,9 +503,12 @@ func (f *File) AddVBAProject(bin string) error {
// setContentTypePartProjectExtensions provides a function to set the content // setContentTypePartProjectExtensions provides a function to set the content
// type for relationship parts and the main document part. // type for relationship parts and the main document part.
func (f *File) setContentTypePartProjectExtensions(contentType string) { func (f *File) setContentTypePartProjectExtensions(contentType string) error {
var ok bool var ok bool
content := f.contentTypesReader() content, err := f.contentTypesReader()
if err != nil {
return err
}
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
for _, v := range content.Defaults { for _, v := range content.Defaults {
@ -516,4 +527,5 @@ func (f *File) setContentTypePartProjectExtensions(contentType string) {
ContentType: ContentTypeVBA, ContentType: ContentTypeVBA,
}) })
} }
return err
} }

View File

@ -219,26 +219,31 @@ func TestOpenReader(t *testing.T) {
assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
// Test open workbook with unsupported charset internal calculation chain. // Test open workbook with unsupported charset internal calculation chain.
source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx")) preset := func(filePath string) *bytes.Buffer {
assert.NoError(t, err) source, err := zip.OpenReader(filepath.Join("test", "Book1.xlsx"))
buf := new(bytes.Buffer)
zw := zip.NewWriter(buf)
for _, item := range source.File {
// The following statements can be simplified as zw.Copy(item) in go1.17
writer, err := zw.Create(item.Name)
assert.NoError(t, err) assert.NoError(t, err)
readerCloser, err := item.Open() buf := new(bytes.Buffer)
zw := zip.NewWriter(buf)
for _, item := range source.File {
// The following statements can be simplified as zw.Copy(item) in go1.17
writer, err := zw.Create(item.Name)
assert.NoError(t, err)
readerCloser, err := item.Open()
assert.NoError(t, err)
_, err = io.Copy(writer, readerCloser)
assert.NoError(t, err)
}
fi, err := zw.Create(filePath)
assert.NoError(t, err) assert.NoError(t, err)
_, err = io.Copy(writer, readerCloser) _, err = fi.Write(MacintoshCyrillicCharset)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, zw.Close())
return buf
}
for _, defaultXMLPath := range []string{defaultXMLPathCalcChain, defaultXMLPathStyles} {
_, err = OpenReader(preset(defaultXMLPath))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }
fi, err := zw.Create(defaultXMLPathCalcChain)
assert.NoError(t, err)
_, err = fi.Write(MacintoshCyrillicCharset)
assert.NoError(t, err)
assert.NoError(t, zw.Close())
_, err = OpenReader(buf)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test open spreadsheet with unzip size limit. // Test open spreadsheet with unzip size limit.
_, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100}) _, err = OpenFile(filepath.Join("test", "Book1.xlsx"), Options{UnzipSizeLimit: 100})
@ -466,29 +471,16 @@ func TestGetCellHyperLink(t *testing.T) {
func TestSetSheetBackground(t *testing.T) { func TestSetSheetBackground(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow() assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg")))
} assert.NoError(t, f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg")))
err = f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg"))
if !assert.NoError(t, err) {
t.FailNow()
}
err = f.SetSheetBackground("Sheet2", filepath.Join("test", "images", "background.jpg"))
if !assert.NoError(t, err) {
t.FailNow()
}
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackground.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetSheetBackground.xlsx")))
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
} }
func TestSetSheetBackgroundErrors(t *testing.T) { func TestSetSheetBackgroundErrors(t *testing.T) {
f, err := OpenFile(filepath.Join("test", "Book1.xlsx")) f, err := OpenFile(filepath.Join("test", "Book1.xlsx"))
if !assert.NoError(t, err) { assert.NoError(t, err)
t.FailNow()
}
err = f.SetSheetBackground("Sheet2", filepath.Join("test", "not_exists", "not_exists.png")) err = f.SetSheetBackground("Sheet2", filepath.Join("test", "not_exists", "not_exists.png"))
if assert.Error(t, err) { if assert.Error(t, err) {
@ -497,7 +489,16 @@ func TestSetSheetBackgroundErrors(t *testing.T) {
err = f.SetSheetBackground("Sheet2", filepath.Join("test", "Book1.xlsx")) err = f.SetSheetBackground("Sheet2", filepath.Join("test", "Book1.xlsx"))
assert.EqualError(t, err, ErrImgExt.Error()) assert.EqualError(t, err, ErrImgExt.Error())
// Test set sheet background on not exist worksheet.
err = f.SetSheetBackground("SheetN", filepath.Join("test", "images", "background.jpg"))
assert.EqualError(t, err, "sheet SheetN does not exist")
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
// Test set sheet background with unsupported charset content types.
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetSheetBackground("Sheet1", filepath.Join("test", "images", "background.jpg")), "XML syntax error on line 1: invalid UTF-8")
} }
// TestWriteArrayFormula tests the extended options of SetCellFormula by writing an array function // TestWriteArrayFormula tests the extended options of SetCellFormula by writing an array function
@ -1027,12 +1028,6 @@ func TestGetSheetComments(t *testing.T) {
assert.Equal(t, "", f.getSheetComments("sheet0")) assert.Equal(t, "", f.getSheetComments("sheet0"))
} }
func TestSetSheetVisible(t *testing.T) {
f := NewFile()
f.WorkBook.Sheets.Sheet[0].Name = "SheetN"
assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN does not exist")
}
func TestGetActiveSheetIndex(t *testing.T) { func TestGetActiveSheetIndex(t *testing.T) {
f := NewFile() f := NewFile()
f.WorkBook.BookViews = nil f.WorkBook.BookViews = nil
@ -1334,6 +1329,10 @@ func TestAddVBAProject(t *testing.T) {
// Test add VBA project twice. // Test add VBA project twice.
assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin"))) assert.NoError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin")))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddVBAProject.xlsm")))
// Test add VBs with unsupported charset workbook relationships.
f.Relationships.Delete(defaultXMLPathWorkbookRels)
f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddVBAProject(filepath.Join("test", "vbaProject.bin")), "XML syntax error on line 1: invalid UTF-8")
} }
func TestContentTypesReader(t *testing.T) { func TestContentTypesReader(t *testing.T) {
@ -1341,7 +1340,8 @@ func TestContentTypesReader(t *testing.T) {
f := NewFile() f := NewFile()
f.ContentTypes = nil f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
f.contentTypesReader() _, err := f.contentTypesReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }
func TestWorkbookReader(t *testing.T) { func TestWorkbookReader(t *testing.T) {
@ -1349,7 +1349,8 @@ func TestWorkbookReader(t *testing.T) {
f := NewFile() f := NewFile()
f.WorkBook = nil f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
f.workbookReader() _, err := f.workbookReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }
func TestWorkSheetReader(t *testing.T) { func TestWorkSheetReader(t *testing.T) {
@ -1373,19 +1374,28 @@ func TestWorkSheetReader(t *testing.T) {
func TestRelsReader(t *testing.T) { func TestRelsReader(t *testing.T) {
// Test unsupported charset. // Test unsupported charset.
f := NewFile() f := NewFile()
rels := "xl/_rels/workbook.xml.rels" rels := defaultXMLPathWorkbookRels
f.Relationships.Store(rels, nil) f.Relationships.Store(rels, nil)
f.Pkg.Store(rels, MacintoshCyrillicCharset) f.Pkg.Store(rels, MacintoshCyrillicCharset)
f.relsReader(rels) _, err := f.relsReader(rels)
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }
func TestDeleteSheetFromWorkbookRels(t *testing.T) { func TestDeleteSheetFromWorkbookRels(t *testing.T) {
f := NewFile() f := NewFile()
rels := "xl/_rels/workbook.xml.rels" rels := defaultXMLPathWorkbookRels
f.Relationships.Store(rels, nil) f.Relationships.Store(rels, nil)
assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "") assert.Equal(t, f.deleteSheetFromWorkbookRels("rID"), "")
} }
func TestUpdateLinkedValue(t *testing.T) {
f := NewFile()
// Test update lined value with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.UpdateLinkedValue(), "XML syntax error on line 1: invalid UTF-8")
}
func TestAttrValToInt(t *testing.T) { func TestAttrValToInt(t *testing.T) {
_, err := attrValToInt("r", []xml.Attr{ _, err := attrValToInt("r", []xml.Attr{
{Name: xml.Name{Local: "r"}, Value: "s"}, {Name: xml.Name{Local: "r"}, Value: "s"},

15
file.go
View File

@ -30,7 +30,7 @@ func NewFile() *File {
f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels)) f.Pkg.Store("_rels/.rels", []byte(xml.Header+templateRels))
f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp)) f.Pkg.Store(defaultXMLPathDocPropsApp, []byte(xml.Header+templateDocpropsApp))
f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore)) f.Pkg.Store(defaultXMLPathDocPropsCore, []byte(xml.Header+templateDocpropsCore))
f.Pkg.Store("xl/_rels/workbook.xml.rels", []byte(xml.Header+templateWorkbookRels)) f.Pkg.Store(defaultXMLPathWorkbookRels, []byte(xml.Header+templateWorkbookRels))
f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme)) f.Pkg.Store("xl/theme/theme1.xml", []byte(xml.Header+templateTheme))
f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet)) f.Pkg.Store("xl/worksheets/sheet1.xml", []byte(xml.Header+templateSheet))
f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles)) f.Pkg.Store(defaultXMLPathStyles, []byte(xml.Header+templateStyles))
@ -39,18 +39,19 @@ func NewFile() *File {
f.SheetCount = 1 f.SheetCount = 1
f.CalcChain, _ = f.calcChainReader() f.CalcChain, _ = f.calcChainReader()
f.Comments = make(map[string]*xlsxComments) f.Comments = make(map[string]*xlsxComments)
f.ContentTypes = f.contentTypesReader() f.ContentTypes, _ = f.contentTypesReader()
f.Drawings = sync.Map{} f.Drawings = sync.Map{}
f.Styles, _ = f.stylesReader() f.Styles, _ = f.stylesReader()
f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing) f.DecodeVMLDrawing = make(map[string]*decodeVmlDrawing)
f.VMLDrawing = make(map[string]*vmlDrawing) f.VMLDrawing = make(map[string]*vmlDrawing)
f.WorkBook = f.workbookReader() f.WorkBook, _ = f.workbookReader()
f.Relationships = sync.Map{} f.Relationships = sync.Map{}
f.Relationships.Store("xl/_rels/workbook.xml.rels", f.relsReader("xl/_rels/workbook.xml.rels")) rels, _ := f.relsReader(defaultXMLPathWorkbookRels)
f.Relationships.Store(defaultXMLPathWorkbookRels, rels)
f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml" f.sheetMap["Sheet1"] = "xl/worksheets/sheet1.xml"
ws, _ := f.workSheetReader("Sheet1") ws, _ := f.workSheetReader("Sheet1")
f.Sheet.Store("xl/worksheets/sheet1.xml", ws) f.Sheet.Store("xl/worksheets/sheet1.xml", ws)
f.Theme = f.themeReader() f.Theme, _ = f.themeReader()
return f return f
} }
@ -119,7 +120,9 @@ func (f *File) WriteTo(w io.Writer, opts ...Options) (int64, error) {
if !ok { if !ok {
return 0, ErrWorkbookFileFormat return 0, ErrWorkbookFileFormat
} }
f.setContentTypePartProjectExtensions(contentType) if err := f.setContentTypePartProjectExtensions(contentType); err != nil {
return 0, err
}
} }
if f.options != nil && f.options.Password != "" { if f.options != nil && f.options.Password != "" {
buf, err := f.WriteToBuffer() buf, err := f.WriteToBuffer()

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"os" "os"
"path/filepath"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -79,6 +80,14 @@ func TestWriteTo(t *testing.T) {
_, err := f.WriteTo(bufio.NewWriter(&buf)) _, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, ErrWorkbookFileFormat.Error()) assert.EqualError(t, err, ErrWorkbookFileFormat.Error())
} }
// Test write with unsupported charset content types.
{
f, buf := NewFile(), bytes.Buffer{}
f.ContentTypes, f.Path = nil, filepath.Join("test", "TestWriteTo.xlsx")
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
_, err := f.WriteTo(bufio.NewWriter(&buf))
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
}
} }
func TestClose(t *testing.T) { func TestClose(t *testing.T) {

View File

@ -187,7 +187,9 @@ func (f *File) AddPictureFromBytes(sheet, cell, opts, name, extension string, fi
if err != nil { if err != nil {
return err return err
} }
f.addContentTypePart(drawingID, "drawings") if err = f.addContentTypePart(drawingID, "drawings"); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship) f.addSheetNameSpace(sheet, SourceRelationship)
return err return err
} }
@ -201,7 +203,7 @@ func (f *File) deleteSheetRelationships(sheet, rID string) {
name = strings.ToLower(sheet) + ".xml" name = strings.ToLower(sheet) + ".xml"
} }
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels := f.relsReader(rels) sheetRels, _ := f.relsReader(rels)
if sheetRels == nil { if sheetRels == nil {
sheetRels = &xlsxRelationships{} sheetRels = &xlsxRelationships{}
} }
@ -235,11 +237,15 @@ func (f *File) addSheetDrawing(sheet string, rID int) {
// addSheetPicture provides a function to add picture element to // addSheetPicture provides a function to add picture element to
// xl/worksheets/sheet%d.xml by given worksheet name and relationship index. // xl/worksheets/sheet%d.xml by given worksheet name and relationship index.
func (f *File) addSheetPicture(sheet string, rID int) { func (f *File) addSheetPicture(sheet string, rID int) error {
ws, _ := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil {
return err
}
ws.Picture = &xlsxPicture{ ws.Picture = &xlsxPicture{
RID: "rId" + strconv.Itoa(rID), RID: "rId" + strconv.Itoa(rID),
} }
return err
} }
// countDrawings provides a function to get drawing files count storage in the // countDrawings provides a function to get drawing files count storage in the
@ -378,12 +384,15 @@ func (f *File) addMedia(file []byte, ext string) string {
// setContentTypePartImageExtensions provides a function to set the content // setContentTypePartImageExtensions provides a function to set the content
// type for relationship parts and the Main Document part. // type for relationship parts and the Main Document part.
func (f *File) setContentTypePartImageExtensions() { func (f *File) setContentTypePartImageExtensions() error {
imageTypes := map[string]string{ imageTypes := map[string]string{
"jpeg": "image/", "png": "image/", "gif": "image/", "svg": "image/", "tiff": "image/", "jpeg": "image/", "png": "image/", "gif": "image/", "svg": "image/", "tiff": "image/",
"emf": "image/x-", "wmf": "image/x-", "emz": "image/x-", "wmz": "image/x-", "emf": "image/x-", "wmf": "image/x-", "emz": "image/x-", "wmz": "image/x-",
} }
content := f.contentTypesReader() content, err := f.contentTypesReader()
if err != nil {
return err
}
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
for _, file := range content.Defaults { for _, file := range content.Defaults {
@ -395,13 +404,17 @@ func (f *File) setContentTypePartImageExtensions() {
ContentType: prefix + extension, ContentType: prefix + extension,
}) })
} }
return err
} }
// setContentTypePartVMLExtensions provides a function to set the content type // setContentTypePartVMLExtensions provides a function to set the content type
// for relationship parts and the Main Document part. // for relationship parts and the Main Document part.
func (f *File) setContentTypePartVMLExtensions() { func (f *File) setContentTypePartVMLExtensions() error {
vml := false var vml bool
content := f.contentTypesReader() content, err := f.contentTypesReader()
if err != nil {
return err
}
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
for _, v := range content.Defaults { for _, v := range content.Defaults {
@ -415,12 +428,13 @@ func (f *File) setContentTypePartVMLExtensions() {
ContentType: ContentTypeVML, ContentType: ContentTypeVML,
}) })
} }
return err
} }
// addContentTypePart provides a function to add content type part // addContentTypePart provides a function to add content type part
// relationships in the file [Content_Types].xml by given index. // relationships in the file [Content_Types].xml by given index.
func (f *File) addContentTypePart(index int, contentType string) { func (f *File) addContentTypePart(index int, contentType string) error {
setContentType := map[string]func(){ setContentType := map[string]func() error{
"comments": f.setContentTypePartVMLExtensions, "comments": f.setContentTypePartVMLExtensions,
"drawings": f.setContentTypePartImageExtensions, "drawings": f.setContentTypePartImageExtensions,
} }
@ -446,20 +460,26 @@ func (f *File) addContentTypePart(index int, contentType string) {
} }
s, ok := setContentType[contentType] s, ok := setContentType[contentType]
if ok { if ok {
s() if err := s(); err != nil {
return err
}
}
content, err := f.contentTypesReader()
if err != nil {
return err
} }
content := f.contentTypesReader()
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
for _, v := range content.Overrides { for _, v := range content.Overrides {
if v.PartName == partNames[contentType] { if v.PartName == partNames[contentType] {
return return err
} }
} }
content.Overrides = append(content.Overrides, xlsxOverride{ content.Overrides = append(content.Overrides, xlsxOverride{
PartName: partNames[contentType], PartName: partNames[contentType],
ContentType: contentTypes[contentType], ContentType: contentTypes[contentType],
}) })
return err
} }
// getSheetRelationshipsTargetByID provides a function to get Target attribute // getSheetRelationshipsTargetByID provides a function to get Target attribute
@ -471,7 +491,7 @@ func (f *File) getSheetRelationshipsTargetByID(sheet, rID string) string {
name = strings.ToLower(sheet) + ".xml" name = strings.ToLower(sheet) + ".xml"
} }
rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels" rels := "xl/worksheets/_rels/" + strings.TrimPrefix(name, "xl/worksheets/") + ".rels"
sheetRels := f.relsReader(rels) sheetRels, _ := f.relsReader(rels)
if sheetRels == nil { if sheetRels == nil {
sheetRels = &xlsxRelationships{} sheetRels = &xlsxRelationships{}
} }
@ -630,7 +650,7 @@ func (f *File) getPictureFromWsDr(row, col int, drawingRelationships string, wsD
// from xl/drawings/_rels/drawing%s.xml.rels by given file name and // from xl/drawings/_rels/drawing%s.xml.rels by given file name and
// relationship ID. // relationship ID.
func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship { func (f *File) getDrawingRelationships(rels, rID string) *xlsxRelationship {
if drawingRels := f.relsReader(rels); drawingRels != nil { if drawingRels, _ := f.relsReader(rels); drawingRels != nil {
drawingRels.Lock() drawingRels.Lock()
defer drawingRels.Unlock() defer drawingRels.Unlock()
for _, v := range drawingRels.Relationships { for _, v := range drawingRels.Relationships {

View File

@ -67,6 +67,12 @@ func TestAddPicture(t *testing.T) {
// Test write file to given path. // Test write file to given path.
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddPicture1.xlsx")))
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
// Test add picture with unsupported charset content types.
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddPictureFromBytes("Sheet1", "Q1", "", "Excel Logo", ".png", file), "XML syntax error on line 1: invalid UTF-8")
} }
func TestAddPictureErrors(t *testing.T) { func TestAddPictureErrors(t *testing.T) {
@ -236,3 +242,27 @@ func TestDrawingResize(t *testing.T) {
ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}} ws.(*xlsxWorksheet).MergeCells = &xlsxMergeCells{Cells: []*xlsxMergeCell{{Ref: "A:A"}}}
assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.AddPicture("Sheet1", "A1", filepath.Join("test", "images", "excel.jpg"), `{"autofit": true}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
} }
func TestSetContentTypePartImageExtensions(t *testing.T) {
f := NewFile()
// Test set content type part image extensions with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypePartImageExtensions(), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetContentTypePartVMLExtensions(t *testing.T) {
f := NewFile()
// Test set content type part VML extensions with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypePartVMLExtensions(), "XML syntax error on line 1: invalid UTF-8")
}
func TestAddContentTypePart(t *testing.T) {
f := NewFile()
// Test add content type part with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.addContentTypePart(0, "unknown"), "XML syntax error on line 1: invalid UTF-8")
}

View File

@ -160,10 +160,10 @@ func (f *File) AddPivotTable(opts *PivotTableOptions) error {
} }
pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels" pivotTableSheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(pivotTableSheetPath, "xl/worksheets/") + ".rels"
f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "") f.addRels(pivotTableSheetRels, SourceRelationshipPivotTable, sheetRelationshipsPivotTableXML, "")
f.addContentTypePart(pivotTableID, "pivotTable") if err = f.addContentTypePart(pivotTableID, "pivotTable"); err != nil {
f.addContentTypePart(pivotCacheID, "pivotCache") return err
}
return nil return f.addContentTypePart(pivotCacheID, "pivotCache")
} }
// parseFormatPivotTableSet provides a function to validate pivot table // parseFormatPivotTableSet provides a function to validate pivot table
@ -697,7 +697,7 @@ func (f *File) getPivotTableFieldOptions(name string, fields []PivotTableField)
// addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml. // addWorkbookPivotCache add the association ID of the pivot cache in workbook.xml.
func (f *File) addWorkbookPivotCache(RID int) int { func (f *File) addWorkbookPivotCache(RID int) int {
wb := f.workbookReader() wb, _ := f.workbookReader()
if wb.PivotCaches == nil { if wb.PivotCaches == nil {
wb.PivotCaches = &xlsxPivotCaches{} wb.PivotCaches = &xlsxPivotCaches{}
} }

View File

@ -259,6 +259,15 @@ func TestAddPivotTable(t *testing.T) {
// Test get pivot fields index with empty data range // Test get pivot fields index with empty data range
_, err = f.getPivotFieldsIndex([]PivotTableField{}, &PivotTableOptions{}) _, err = f.getPivotFieldsIndex([]PivotTableField{}, &PivotTableOptions{})
assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`) assert.EqualError(t, err, `parameter 'DataRange' parsing error: parameter is required`)
// Test add pivot table with unsupported charset content types.
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddPivotTable(&PivotTableOptions{
DataRange: "Sheet1!$A$1:$E$31",
PivotTableRange: "Sheet1!$G$2:$M$34",
Rows: []PivotTableField{{Data: "Year"}},
}), "XML syntax error on line 1: invalid UTF-8")
} }
func TestAddPivotRowFields(t *testing.T) { func TestAddPivotRowFields(t *testing.T) {

View File

@ -435,8 +435,13 @@ func (f *File) sharedStringsReader() (*xlsxSST, error) {
f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i f.sharedStringsMap[sharedStrings.SI[i].T.Val] = i
} }
} }
f.addContentTypePart(0, "sharedStrings") if err = f.addContentTypePart(0, "sharedStrings"); err != nil {
rels := f.relsReader(relPath) return f.SharedStrings, err
}
rels, err := f.relsReader(relPath)
if err != nil {
return f.SharedStrings, err
}
for _, rel := range rels.Relationships { for _, rel := range rels.Relationships {
if rel.Target == "/xl/sharedStrings.xml" { if rel.Target == "/xl/sharedStrings.xml" {
return f.SharedStrings, nil return f.SharedStrings, nil

View File

@ -235,9 +235,20 @@ func TestSharedStringsReader(t *testing.T) {
f := NewFile() f := NewFile()
// Test read shared string with unsupported charset. // Test read shared string with unsupported charset.
f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathSharedStrings, MacintoshCyrillicCharset)
f.sharedStringsReader() _, err := f.sharedStringsReader()
si := xlsxSI{} assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualValues(t, "", si.String()) // Test read shared strings with unsupported charset content types.
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
_, err = f.sharedStringsReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
// Test read shared strings with unsupported charset workbook relationships.
f = NewFile()
f.Relationships.Delete(defaultXMLPathWorkbookRels)
f.Pkg.Store(defaultXMLPathWorkbookRels, MacintoshCyrillicCharset)
_, err = f.sharedStringsReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }
func TestRowVisibility(t *testing.T) { func TestRowVisibility(t *testing.T) {

View File

@ -308,8 +308,7 @@ func (f *File) AddShape(sheet, cell, opts string) error {
if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil { if err = f.addDrawingShape(sheet, drawingXML, cell, options); err != nil {
return err return err
} }
f.addContentTypePart(drawingID, "drawings") return f.addContentTypePart(drawingID, "drawings")
return err
} }
// addDrawingShape provides a function to add preset geometry by given sheet, // addDrawingShape provides a function to add preset geometry by given sheet,

View File

@ -87,10 +87,15 @@ func TestAddShape(t *testing.T) {
} }
}`)) }`))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAddShape2.xlsx")))
// Test set row style with unsupported charset style sheet. // Test add shape with unsupported charset style sheet.
f.Styles = nil f.Styles = nil
f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8") assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8")
// Test add shape with unsupported charset content types.
f = NewFile()
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.AddShape("Sheet1", "B30", `{"type":"rect","paragraph":[{"text":"Rectangle"},{}]}`), "XML syntax error on line 1: invalid UTF-8")
} }
func TestAddDrawingShape(t *testing.T) { func TestAddDrawingShape(t *testing.T) {

129
sheet.go
View File

@ -17,7 +17,6 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -47,7 +46,7 @@ func (f *File) NewSheet(sheet string) int {
} }
f.DeleteSheet(sheet) f.DeleteSheet(sheet)
f.SheetCount++ f.SheetCount++
wb := f.workbookReader() wb, _ := f.workbookReader()
sheetID := 0 sheetID := 0
for _, v := range wb.Sheets.Sheet { for _, v := range wb.Sheets.Sheet {
if v.SheetID > sheetID { if v.SheetID > sheetID {
@ -56,7 +55,7 @@ func (f *File) NewSheet(sheet string) int {
} }
sheetID++ sheetID++
// Update [Content_Types].xml // Update [Content_Types].xml
f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet) _ = f.setContentTypes("/xl/worksheets/sheet"+strconv.Itoa(sheetID)+".xml", ContentTypeSpreadSheetMLWorksheet)
// Create new sheet /xl/worksheets/sheet%d.xml // Create new sheet /xl/worksheets/sheet%d.xml
f.setSheet(sheetID, sheet) f.setSheet(sheetID, sheet)
// Update workbook.xml.rels // Update workbook.xml.rels
@ -68,19 +67,17 @@ func (f *File) NewSheet(sheet string) int {
// contentTypesReader provides a function to get the pointer to the // contentTypesReader provides a function to get the pointer to the
// [Content_Types].xml structure after deserialization. // [Content_Types].xml structure after deserialization.
func (f *File) contentTypesReader() *xlsxTypes { func (f *File) contentTypesReader() (*xlsxTypes, error) {
var err error
if f.ContentTypes == nil { if f.ContentTypes == nil {
f.ContentTypes = new(xlsxTypes) f.ContentTypes = new(xlsxTypes)
f.ContentTypes.Lock() f.ContentTypes.Lock()
defer f.ContentTypes.Unlock() defer f.ContentTypes.Unlock()
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))). if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathContentTypes)))).
Decode(f.ContentTypes); err != nil && err != io.EOF { Decode(f.ContentTypes); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err) return f.ContentTypes, err
} }
} }
return f.ContentTypes return f.ContentTypes, nil
} }
// contentTypesWriter provides a function to save [Content_Types].xml after // contentTypesWriter provides a function to save [Content_Types].xml after
@ -215,14 +212,18 @@ func trimCell(column []xlsxC) []xlsxC {
// setContentTypes provides a function to read and update property of contents // setContentTypes provides a function to read and update property of contents
// type of the spreadsheet. // type of the spreadsheet.
func (f *File) setContentTypes(partName, contentType string) { func (f *File) setContentTypes(partName, contentType string) error {
content := f.contentTypesReader() content, err := f.contentTypesReader()
if err != nil {
return err
}
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
content.Overrides = append(content.Overrides, xlsxOverride{ content.Overrides = append(content.Overrides, xlsxOverride{
PartName: partName, PartName: partName,
ContentType: contentType, ContentType: contentType,
}) })
return err
} }
// setSheet provides a function to update sheet property by given index. // setSheet provides a function to update sheet property by given index.
@ -271,7 +272,7 @@ func (f *File) SetActiveSheet(index int) {
if index < 0 { if index < 0 {
index = 0 index = 0
} }
wb := f.workbookReader() wb, _ := f.workbookReader()
for activeTab := range wb.Sheets.Sheet { for activeTab := range wb.Sheets.Sheet {
if activeTab == index { if activeTab == index {
if wb.BookViews == nil { if wb.BookViews == nil {
@ -316,7 +317,7 @@ func (f *File) SetActiveSheet(index int) {
// spreadsheet. If not found the active sheet will be return integer 0. // spreadsheet. If not found the active sheet will be return integer 0.
func (f *File) GetActiveSheetIndex() (index int) { func (f *File) GetActiveSheetIndex() (index int) {
sheetID := f.getActiveSheetID() sheetID := f.getActiveSheetID()
wb := f.workbookReader() wb, _ := f.workbookReader()
if wb != nil { if wb != nil {
for idx, sheet := range wb.Sheets.Sheet { for idx, sheet := range wb.Sheets.Sheet {
if sheet.SheetID == sheetID { if sheet.SheetID == sheetID {
@ -331,7 +332,7 @@ func (f *File) GetActiveSheetIndex() (index int) {
// getActiveSheetID provides a function to get active sheet ID of the // getActiveSheetID provides a function to get active sheet ID of the
// spreadsheet. If not found the active sheet will be return integer 0. // spreadsheet. If not found the active sheet will be return integer 0.
func (f *File) getActiveSheetID() int { func (f *File) getActiveSheetID() int {
wb := f.workbookReader() wb, _ := f.workbookReader()
if wb != nil { if wb != nil {
if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 { if wb.BookViews != nil && len(wb.BookViews.WorkBookView) > 0 {
activeTab := wb.BookViews.WorkBookView[0].ActiveTab activeTab := wb.BookViews.WorkBookView[0].ActiveTab
@ -357,10 +358,10 @@ func (f *File) SetSheetName(source, target string) {
if strings.EqualFold(target, source) { if strings.EqualFold(target, source) {
return return
} }
content := f.workbookReader() wb, _ := f.workbookReader()
for k, v := range content.Sheets.Sheet { for k, v := range wb.Sheets.Sheet {
if v.Name == source { if v.Name == source {
content.Sheets.Sheet[k].Name = target wb.Sheets.Sheet[k].Name = target
f.sheetMap[target] = f.sheetMap[source] f.sheetMap[target] = f.sheetMap[source]
delete(f.sheetMap, source) delete(f.sheetMap, source)
} }
@ -422,7 +423,7 @@ func (f *File) GetSheetIndex(sheet string) int {
// fmt.Println(index, name) // fmt.Println(index, name)
// } // }
func (f *File) GetSheetMap() map[int]string { func (f *File) GetSheetMap() map[int]string {
wb := f.workbookReader() wb, _ := f.workbookReader()
sheetMap := map[int]string{} sheetMap := map[int]string{}
if wb != nil { if wb != nil {
for _, sheet := range wb.Sheets.Sheet { for _, sheet := range wb.Sheets.Sheet {
@ -435,7 +436,7 @@ func (f *File) GetSheetMap() map[int]string {
// GetSheetList provides a function to get worksheets, chart sheets, and // GetSheetList provides a function to get worksheets, chart sheets, and
// dialog sheets name list of the workbook. // dialog sheets name list of the workbook.
func (f *File) GetSheetList() (list []string) { func (f *File) GetSheetList() (list []string) {
wb := f.workbookReader() wb, _ := f.workbookReader()
if wb != nil { if wb != nil {
for _, sheet := range wb.Sheets.Sheet { for _, sheet := range wb.Sheets.Sheet {
list = append(list, sheet.Name) list = append(list, sheet.Name)
@ -448,8 +449,10 @@ func (f *File) GetSheetList() (list []string) {
// of the spreadsheet. // of the spreadsheet.
func (f *File) getSheetMap() map[string]string { func (f *File) getSheetMap() map[string]string {
maps := map[string]string{} maps := map[string]string{}
for _, v := range f.workbookReader().Sheets.Sheet { wb, _ := f.workbookReader()
for _, rel := range f.relsReader(f.getWorkbookRelsPath()).Relationships { rels, _ := f.relsReader(f.getWorkbookRelsPath())
for _, v := range wb.Sheets.Sheet {
for _, rel := range rels.Relationships {
if rel.ID == v.ID { if rel.ID == v.ID {
sheetXMLPath := f.getWorksheetPath(rel.Target) sheetXMLPath := f.getWorksheetPath(rel.Target)
if _, ok := f.Pkg.Load(sheetXMLPath); ok { if _, ok := f.Pkg.Load(sheetXMLPath); ok {
@ -498,10 +501,11 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
sheetXMLPath, _ := f.getSheetXMLPath(sheet) sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels" sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "") rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
f.addSheetPicture(sheet, rID) if err = f.addSheetPicture(sheet, rID); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship) f.addSheetNameSpace(sheet, SourceRelationship)
f.setContentTypePartImageExtensions() return f.setContentTypePartImageExtensions()
return err
} }
// DeleteSheet provides a function to delete worksheet in a workbook by given // DeleteSheet provides a function to delete worksheet in a workbook by given
@ -514,8 +518,8 @@ func (f *File) DeleteSheet(sheet string) {
return return
} }
sheetName := trimSheetName(sheet) sheetName := trimSheetName(sheet)
wb := f.workbookReader() wb, _ := f.workbookReader()
wbRels := f.relsReader(f.getWorkbookRelsPath()) wbRels, _ := f.relsReader(f.getWorkbookRelsPath())
activeSheetName := f.GetSheetName(f.GetActiveSheetIndex()) activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
deleteLocalSheetID := f.GetSheetIndex(sheet) deleteLocalSheetID := f.GetSheetIndex(sheet)
deleteAndAdjustDefinedNames(wb, deleteLocalSheetID) deleteAndAdjustDefinedNames(wb, deleteLocalSheetID)
@ -537,8 +541,8 @@ func (f *File) DeleteSheet(sheet string) {
} }
} }
target := f.deleteSheetFromWorkbookRels(v.ID) target := f.deleteSheetFromWorkbookRels(v.ID)
f.deleteSheetFromContentTypes(target) _ = f.deleteSheetFromContentTypes(target)
f.deleteCalcChain(f.getSheetID(sheet), "") _ = f.deleteCalcChain(f.getSheetID(sheet), "")
delete(f.sheetMap, v.Name) delete(f.sheetMap, v.Name)
f.Pkg.Delete(sheetXML) f.Pkg.Delete(sheetXML)
f.Pkg.Delete(rels) f.Pkg.Delete(rels)
@ -573,12 +577,12 @@ func deleteAndAdjustDefinedNames(wb *xlsxWorkbook, deleteLocalSheetID int) {
// deleteSheetFromWorkbookRels provides a function to remove worksheet // deleteSheetFromWorkbookRels provides a function to remove worksheet
// relationships by given relationships ID in the file workbook.xml.rels. // relationships by given relationships ID in the file workbook.xml.rels.
func (f *File) deleteSheetFromWorkbookRels(rID string) string { func (f *File) deleteSheetFromWorkbookRels(rID string) string {
content := f.relsReader(f.getWorkbookRelsPath()) rels, _ := f.relsReader(f.getWorkbookRelsPath())
content.Lock() rels.Lock()
defer content.Unlock() defer rels.Unlock()
for k, v := range content.Relationships { for k, v := range rels.Relationships {
if v.ID == rID { if v.ID == rID {
content.Relationships = append(content.Relationships[:k], content.Relationships[k+1:]...) rels.Relationships = append(rels.Relationships[:k], rels.Relationships[k+1:]...)
return v.Target return v.Target
} }
} }
@ -587,11 +591,14 @@ func (f *File) deleteSheetFromWorkbookRels(rID string) string {
// deleteSheetFromContentTypes provides a function to remove worksheet // deleteSheetFromContentTypes provides a function to remove worksheet
// relationships by given target name in the file [Content_Types].xml. // relationships by given target name in the file [Content_Types].xml.
func (f *File) deleteSheetFromContentTypes(target string) { func (f *File) deleteSheetFromContentTypes(target string) error {
if !strings.HasPrefix(target, "/") { if !strings.HasPrefix(target, "/") {
target = "/xl/" + target target = "/xl/" + target
} }
content := f.contentTypesReader() content, err := f.contentTypesReader()
if err != nil {
return err
}
content.Lock() content.Lock()
defer content.Unlock() defer content.Unlock()
for k, v := range content.Overrides { for k, v := range content.Overrides {
@ -599,6 +606,7 @@ func (f *File) deleteSheetFromContentTypes(target string) {
content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...) content.Overrides = append(content.Overrides[:k], content.Overrides[k+1:]...)
} }
} }
return err
} }
// CopySheet provides a function to duplicate a worksheet by gave source and // CopySheet provides a function to duplicate a worksheet by gave source and
@ -659,22 +667,25 @@ func (f *File) copySheet(from, to int) error {
// err := f.SetSheetVisible("Sheet1", false) // err := f.SetSheetVisible("Sheet1", false)
func (f *File) SetSheetVisible(sheet string, visible bool) error { func (f *File) SetSheetVisible(sheet string, visible bool) error {
sheet = trimSheetName(sheet) sheet = trimSheetName(sheet)
content := f.workbookReader() wb, err := f.workbookReader()
if err != nil {
return err
}
if visible { if visible {
for k, v := range content.Sheets.Sheet { for k, v := range wb.Sheets.Sheet {
if strings.EqualFold(v.Name, sheet) { if strings.EqualFold(v.Name, sheet) {
content.Sheets.Sheet[k].State = "" wb.Sheets.Sheet[k].State = ""
} }
} }
return nil return err
} }
count := 0 count := 0
for _, v := range content.Sheets.Sheet { for _, v := range wb.Sheets.Sheet {
if v.State != "hidden" { if v.State != "hidden" {
count++ count++
} }
} }
for k, v := range content.Sheets.Sheet { for k, v := range wb.Sheets.Sheet {
ws, err := f.workSheetReader(v.Name) ws, err := f.workSheetReader(v.Name)
if err != nil { if err != nil {
return err return err
@ -684,10 +695,10 @@ func (f *File) SetSheetVisible(sheet string, visible bool) error {
tabSelected = ws.SheetViews.SheetView[0].TabSelected tabSelected = ws.SheetViews.SheetView[0].TabSelected
} }
if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected { if strings.EqualFold(v.Name, sheet) && count > 1 && !tabSelected {
content.Sheets.Sheet[k].State = "hidden" wb.Sheets.Sheet[k].State = "hidden"
} }
} }
return nil return err
} }
// parsePanesOptions provides a function to parse the panes settings. // parsePanesOptions provides a function to parse the panes settings.
@ -830,10 +841,11 @@ func (f *File) SetPanes(sheet, panes string) error {
// //
// f.GetSheetVisible("Sheet1") // f.GetSheetVisible("Sheet1")
func (f *File) GetSheetVisible(sheet string) bool { func (f *File) GetSheetVisible(sheet string) bool {
content, name, visible := f.workbookReader(), trimSheetName(sheet), false name, visible := trimSheetName(sheet), false
for k, v := range content.Sheets.Sheet { wb, _ := f.workbookReader()
for k, v := range wb.Sheets.Sheet {
if strings.EqualFold(v.Name, name) { if strings.EqualFold(v.Name, name) {
if content.Sheets.Sheet[k].State == "" || content.Sheets.Sheet[k].State == "visible" { if wb.Sheets.Sheet[k].State == "" || wb.Sheets.Sheet[k].State == "visible" {
visible = true visible = true
} }
} }
@ -1460,7 +1472,10 @@ func (f *File) GetPageLayout(sheet string) (PageLayoutOptions, error) {
// Scope: "Sheet2", // Scope: "Sheet2",
// }) // })
func (f *File) SetDefinedName(definedName *DefinedName) error { func (f *File) SetDefinedName(definedName *DefinedName) error {
wb := f.workbookReader() wb, err := f.workbookReader()
if err != nil {
return err
}
d := xlsxDefinedName{ d := xlsxDefinedName{
Name: definedName.Name, Name: definedName.Name,
Comment: definedName.Comment, Comment: definedName.Comment,
@ -1499,7 +1514,10 @@ func (f *File) SetDefinedName(definedName *DefinedName) error {
// Scope: "Sheet2", // Scope: "Sheet2",
// }) // })
func (f *File) DeleteDefinedName(definedName *DefinedName) error { func (f *File) DeleteDefinedName(definedName *DefinedName) error {
wb := f.workbookReader() wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.DefinedNames != nil { if wb.DefinedNames != nil {
for idx, dn := range wb.DefinedNames.DefinedName { for idx, dn := range wb.DefinedNames.DefinedName {
scope := "Workbook" scope := "Workbook"
@ -1512,7 +1530,7 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error {
} }
if scope == deleteScope && dn.Name == definedName.Name { if scope == deleteScope && dn.Name == definedName.Name {
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...) wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
return nil return err
} }
} }
} }
@ -1523,7 +1541,7 @@ func (f *File) DeleteDefinedName(definedName *DefinedName) error {
// or worksheet. // or worksheet.
func (f *File) GetDefinedName() []DefinedName { func (f *File) GetDefinedName() []DefinedName {
var definedNames []DefinedName var definedNames []DefinedName
wb := f.workbookReader() wb, _ := f.workbookReader()
if wb.DefinedNames != nil { if wb.DefinedNames != nil {
for _, dn := range wb.DefinedNames.DefinedName { for _, dn := range wb.DefinedNames.DefinedName {
definedName := DefinedName{ definedName := DefinedName{
@ -1715,23 +1733,22 @@ func (f *File) RemovePageBreak(sheet, cell string) error {
// relsReader provides a function to get the pointer to the structure // relsReader provides a function to get the pointer to the structure
// after deserialization of xl/worksheets/_rels/sheet%d.xml.rels. // after deserialization of xl/worksheets/_rels/sheet%d.xml.rels.
func (f *File) relsReader(path string) *xlsxRelationships { func (f *File) relsReader(path string) (*xlsxRelationships, error) {
var err error
rels, _ := f.Relationships.Load(path) rels, _ := f.Relationships.Load(path)
if rels == nil { if rels == nil {
if _, ok := f.Pkg.Load(path); ok { if _, ok := f.Pkg.Load(path); ok {
c := xlsxRelationships{} c := xlsxRelationships{}
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))). if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(path)))).
Decode(&c); err != nil && err != io.EOF { Decode(&c); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err) return nil, err
} }
f.Relationships.Store(path, &c) f.Relationships.Store(path, &c)
} }
} }
if rels, _ = f.Relationships.Load(path); rels != nil { if rels, _ = f.Relationships.Load(path); rels != nil {
return rels.(*xlsxRelationships) return rels.(*xlsxRelationships), nil
} }
return nil return nil, nil
} }
// fillSheetData ensures there are enough rows, and columns in the chosen // fillSheetData ensures there are enough rows, and columns in the chosen

View File

@ -188,6 +188,17 @@ func TestDefinedName(t *testing.T) {
assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo) assert.Exactly(t, "Sheet1!$A$2:$D$5", f.GetDefinedName()[0].RefersTo)
assert.Exactly(t, 1, len(f.GetDefinedName())) assert.Exactly(t, 1, len(f.GetDefinedName()))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDefinedName.xlsx")))
// Test set defined name with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetDefinedName(&DefinedName{
Name: "Amount", RefersTo: "Sheet1!$A$2:$D$5",
}), "XML syntax error on line 1: invalid UTF-8")
// Test delete defined name with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.DeleteDefinedName(&DefinedName{Name: "Amount"}),
"XML syntax error on line 1: invalid UTF-8")
} }
func TestGroupSheets(t *testing.T) { func TestGroupSheets(t *testing.T) {
@ -367,6 +378,32 @@ func TestGetSheetID(t *testing.T) {
assert.NotEqual(t, -1, id) assert.NotEqual(t, -1, id)
} }
func TestSetSheetVisible(t *testing.T) {
f := NewFile()
f.WorkBook.Sheets.Sheet[0].Name = "SheetN"
assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "sheet SheetN does not exist")
// Test set sheet visible with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetSheetVisible("Sheet1", false), "XML syntax error on line 1: invalid UTF-8")
}
func TestSetContentTypes(t *testing.T) {
f := NewFile()
// Test set content type with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.setContentTypes("/xl/worksheets/sheet1.xml", ContentTypeSpreadSheetMLWorksheet), "XML syntax error on line 1: invalid UTF-8")
}
func TestDeleteSheetFromContentTypes(t *testing.T) {
f := NewFile()
// Test delete sheet from content types with unsupported charset content types.
f.ContentTypes = nil
f.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, f.deleteSheetFromContentTypes("/xl/worksheets/sheet1.xml"), "XML syntax error on line 1: invalid UTF-8")
}
func BenchmarkNewSheet(b *testing.B) { func BenchmarkNewSheet(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {

View File

@ -226,11 +226,12 @@ func (sw *StreamWriter) AddTable(hCell, vCell, opts string) error {
sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID) sw.tableParts = fmt.Sprintf(`<tableParts count="1"><tablePart r:id="rId%d"></tablePart></tableParts>`, rID)
sw.File.addContentTypePart(tableID, "table") if err = sw.File.addContentTypePart(tableID, "table"); err != nil {
return err
}
b, _ := xml.Marshal(table) b, _ := xml.Marshal(table)
sw.File.saveFileList(tableXML, b) sw.File.saveFileList(tableXML, b)
return nil return err
} }
// Extract values from a row in the StreamWriter. // Extract values from a row in the StreamWriter.
@ -471,6 +472,23 @@ func setCellFormula(c *xlsxC, formula string) {
} }
} }
// setCellTime provides a function to set number of a cell with a time.
func (sw *StreamWriter) setCellTime(c *xlsxC, val time.Time) error {
var date1904, isNum bool
wb, err := sw.File.workbookReader()
if err != nil {
return err
}
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
if isNum, err = c.setCellTime(val, date1904); err == nil && isNum && c.S == 0 {
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
c.S = style
}
return nil
}
// setCellValFunc provides a function to set value of a cell. // setCellValFunc provides a function to set value of a cell.
func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error { func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
var err error var err error
@ -488,15 +506,7 @@ func (sw *StreamWriter) setCellValFunc(c *xlsxC, val interface{}) error {
case time.Duration: case time.Duration:
c.T, c.V = setCellDuration(val) c.T, c.V = setCellDuration(val)
case time.Time: case time.Time:
var isNum bool err = sw.setCellTime(c, val)
date1904, wb := false, sw.File.workbookReader()
if wb != nil && wb.WorkbookPr != nil {
date1904 = wb.WorkbookPr.Date1904
}
if isNum, err = c.setCellTime(val, date1904); isNum && c.S == 0 {
style, _ := sw.File.NewStyle(&Style{NumFmt: 22})
c.S = style
}
case bool: case bool:
c.T, c.V = setCellBool(val) c.T, c.V = setCellBool(val)
case nil: case nil:

View File

@ -186,7 +186,7 @@ func TestStreamTable(t *testing.T) {
} }
// Write a table. // Write a table.
assert.NoError(t, streamWriter.AddTable("A1", "C2", ``)) assert.NoError(t, streamWriter.AddTable("A1", "C2", ""))
assert.NoError(t, streamWriter.Flush()) assert.NoError(t, streamWriter.Flush())
// Verify the table has names. // Verify the table has names.
@ -198,13 +198,17 @@ func TestStreamTable(t *testing.T) {
assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name) assert.Equal(t, "B", table.TableColumns.TableColumn[1].Name)
assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name) assert.Equal(t, "C", table.TableColumns.TableColumn[2].Name)
assert.NoError(t, streamWriter.AddTable("A1", "C1", ``)) assert.NoError(t, streamWriter.AddTable("A1", "C1", ""))
// Test add table with illegal options. // Test add table with illegal options.
assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string") assert.EqualError(t, streamWriter.AddTable("B26", "A21", `{x}`), "invalid character 'x' looking for beginning of object key string")
// Test add table with illegal cell reference. // Test add table with illegal cell reference.
assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, streamWriter.AddTable("A", "B1", `{}`), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.EqualError(t, streamWriter.AddTable("A1", "B", `{}`), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add table with unsupported charset content types.
file.ContentTypes = nil
file.Pkg.Store(defaultXMLPathContentTypes, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.AddTable("A1", "C2", ""), "XML syntax error on line 1: invalid UTF-8")
} }
func TestStreamMergeCells(t *testing.T) { func TestStreamMergeCells(t *testing.T) {
@ -242,7 +246,7 @@ func TestStreamMarshalAttrs(t *testing.T) {
} }
func TestStreamSetRow(t *testing.T) { func TestStreamSetRow(t *testing.T) {
// Test error exceptions // Test error exceptions.
file := NewFile() file := NewFile()
defer func() { defer func() {
assert.NoError(t, file.Close()) assert.NoError(t, file.Close())
@ -250,9 +254,13 @@ func TestStreamSetRow(t *testing.T) {
streamWriter, err := file.NewStreamWriter("Sheet1") streamWriter, err := file.NewStreamWriter("Sheet1")
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, streamWriter.SetRow("A", []interface{}{}), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
// Test set row with non-ascending row number // Test set row with non-ascending row number.
assert.NoError(t, streamWriter.SetRow("A1", []interface{}{})) assert.NoError(t, streamWriter.SetRow("A1", []interface{}{}))
assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error()) assert.EqualError(t, streamWriter.SetRow("A1", []interface{}{}), newStreamSetRowError(1).Error())
// Test set row with unsupported charset workbook.
file.WorkBook = nil
file.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, streamWriter.SetRow("A2", []interface{}{time.Now()}), "XML syntax error on line 1: invalid UTF-8")
} }
func TestStreamSetRowNilValues(t *testing.T) { func TestStreamSetRowNilValues(t *testing.T) {

View File

@ -17,7 +17,6 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io" "io"
"log"
"math" "math"
"reflect" "reflect"
"strconv" "strconv"
@ -3357,16 +3356,16 @@ func getPaletteColor(color string) string {
// themeReader provides a function to get the pointer to the xl/theme/theme1.xml // themeReader provides a function to get the pointer to the xl/theme/theme1.xml
// structure after deserialization. // structure after deserialization.
func (f *File) themeReader() *xlsxTheme { func (f *File) themeReader() (*xlsxTheme, error) {
if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok { if _, ok := f.Pkg.Load(defaultXMLPathTheme); !ok {
return nil return nil, nil
} }
theme := xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value} theme := xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))). if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(defaultXMLPathTheme)))).
Decode(&theme); err != nil && err != io.EOF { Decode(&theme); err != nil && err != io.EOF {
log.Printf("xml decoder error: %s", err) return &theme, err
} }
return &theme return &theme, nil
} }
// ThemeColor applied the color with tint value. // ThemeColor applied the color with tint value.

View File

@ -366,7 +366,9 @@ func TestThemeReader(t *testing.T) {
f := NewFile() f := NewFile()
// Test read theme with unsupported charset. // Test read theme with unsupported charset.
f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset) f.Pkg.Store(defaultXMLPathTheme, MacintoshCyrillicCharset)
assert.EqualValues(t, &xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}, f.themeReader()) theme, err := f.themeReader()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
assert.EqualValues(t, &xlsxTheme{XMLNSa: NameSpaceDrawingML.Value, XMLNSr: SourceRelationship.Value}, theme)
} }
func TestSetCellStyle(t *testing.T) { func TestSetCellStyle(t *testing.T) {

View File

@ -94,8 +94,7 @@ func (f *File) AddTable(sheet, hCell, vCell, opts string) error {
if err = f.addTable(sheet, tableXML, hCol, hRow, vCol, vRow, tableID, options); err != nil { if err = f.addTable(sheet, tableXML, hCol, hRow, vCol, vRow, tableID, options); err != nil {
return err return err
} }
f.addContentTypePart(tableID, "table") return f.addContentTypePart(tableID, "table")
return err
} }
// countTables provides a function to get table files count storage in the // countTables provides a function to get table files count storage in the
@ -301,7 +300,10 @@ func (f *File) AutoFilter(sheet, hCell, vCell, opts string) error {
cellStart, _ := CoordinatesToCellName(hCol, hRow, true) cellStart, _ := CoordinatesToCellName(hCol, hRow, true)
cellEnd, _ := CoordinatesToCellName(vCol, vRow, true) cellEnd, _ := CoordinatesToCellName(vCol, vRow, true)
ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase" ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase"
wb := f.workbookReader() wb, err := f.workbookReader()
if err != nil {
return err
}
sheetID := f.GetSheetIndex(sheet) sheetID := f.GetSheetIndex(sheet)
filterRange := fmt.Sprintf("'%s'!%s", sheet, ref) filterRange := fmt.Sprintf("'%s'!%s", sheet, ref)
d := xlsxDefinedName{ d := xlsxDefinedName{

View File

@ -78,9 +78,13 @@ func TestAutoFilter(t *testing.T) {
}) })
} }
// Test AutoFilter with illegal cell reference. // Test add auto filter with illegal cell reference.
assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// Test add auto filter with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.AutoFilter("Sheet1", "D4", "B1", formats[0]), "XML syntax error on line 1: invalid UTF-8")
} }
func TestAutoFilterError(t *testing.T) { func TestAutoFilterError(t *testing.T) {

View File

@ -23,6 +23,7 @@ const (
defaultXMLPathStyles = "xl/styles.xml" defaultXMLPathStyles = "xl/styles.xml"
defaultXMLPathTheme = "xl/theme/theme1.xml" defaultXMLPathTheme = "xl/theme/theme1.xml"
defaultXMLPathWorkbook = "xl/workbook.xml" defaultXMLPathWorkbook = "xl/workbook.xml"
defaultXMLPathWorkbookRels = "xl/_rels/workbook.xml.rels"
defaultTempFileSST = "sharedStrings" defaultTempFileSST = "sharedStrings"
) )

View File

@ -15,7 +15,6 @@ import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"io" "io"
"log"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -23,7 +22,10 @@ import (
// SetWorkbookProps provides a function to sets workbook properties. // SetWorkbookProps provides a function to sets workbook properties.
func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error { func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error {
wb := f.workbookReader() wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.WorkbookPr == nil { if wb.WorkbookPr == nil {
wb.WorkbookPr = new(xlsxWorkbookPr) wb.WorkbookPr = new(xlsxWorkbookPr)
} }
@ -44,20 +46,24 @@ func (f *File) SetWorkbookProps(opts *WorkbookPropsOptions) error {
// GetWorkbookProps provides a function to gets workbook properties. // GetWorkbookProps provides a function to gets workbook properties.
func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) { func (f *File) GetWorkbookProps() (WorkbookPropsOptions, error) {
wb, opts := f.workbookReader(), WorkbookPropsOptions{} var opts WorkbookPropsOptions
wb, err := f.workbookReader()
if err != nil {
return opts, err
}
if wb.WorkbookPr != nil { if wb.WorkbookPr != nil {
opts.Date1904 = boolPtr(wb.WorkbookPr.Date1904) opts.Date1904 = boolPtr(wb.WorkbookPr.Date1904)
opts.FilterPrivacy = boolPtr(wb.WorkbookPr.FilterPrivacy) opts.FilterPrivacy = boolPtr(wb.WorkbookPr.FilterPrivacy)
opts.CodeName = stringPtr(wb.WorkbookPr.CodeName) opts.CodeName = stringPtr(wb.WorkbookPr.CodeName)
} }
return opts, nil return opts, err
} }
// setWorkbook update workbook property of the spreadsheet. Maximum 31 // setWorkbook update workbook property of the spreadsheet. Maximum 31
// characters are allowed in sheet title. // characters are allowed in sheet title.
func (f *File) setWorkbook(name string, sheetID, rid int) { func (f *File) setWorkbook(name string, sheetID, rid int) {
content := f.workbookReader() wb, _ := f.workbookReader()
content.Sheets.Sheet = append(content.Sheets.Sheet, xlsxSheet{ wb.Sheets.Sheet = append(wb.Sheets.Sheet, xlsxSheet{
Name: trimSheetName(name), Name: trimSheetName(name),
SheetID: sheetID, SheetID: sheetID,
ID: "rId" + strconv.Itoa(rid), ID: "rId" + strconv.Itoa(rid),
@ -67,7 +73,7 @@ func (f *File) setWorkbook(name string, sheetID, rid int) {
// getWorkbookPath provides a function to get the path of the workbook.xml in // getWorkbookPath provides a function to get the path of the workbook.xml in
// the spreadsheet. // the spreadsheet.
func (f *File) getWorkbookPath() (path string) { func (f *File) getWorkbookPath() (path string) {
if rels := f.relsReader("_rels/.rels"); rels != nil { if rels, _ := f.relsReader("_rels/.rels"); rels != nil {
rels.Lock() rels.Lock()
defer rels.Unlock() defer rels.Unlock()
for _, rel := range rels.Relationships { for _, rel := range rels.Relationships {
@ -95,7 +101,7 @@ func (f *File) getWorkbookRelsPath() (path string) {
// workbookReader provides a function to get the pointer to the workbook.xml // workbookReader provides a function to get the pointer to the workbook.xml
// structure after deserialization. // structure after deserialization.
func (f *File) workbookReader() *xlsxWorkbook { func (f *File) workbookReader() (*xlsxWorkbook, error) {
var err error var err error
if f.WorkBook == nil { if f.WorkBook == nil {
wbPath := f.getWorkbookPath() wbPath := f.getWorkbookPath()
@ -107,10 +113,10 @@ func (f *File) workbookReader() *xlsxWorkbook {
} }
if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))). if err = f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(f.readXML(wbPath)))).
Decode(f.WorkBook); err != nil && err != io.EOF { Decode(f.WorkBook); err != nil && err != io.EOF {
log.Printf("xml decode error: %s", err) return f.WorkBook, err
} }
} }
return f.WorkBook return f.WorkBook, err
} }
// workBookWriter provides a function to save workbook.xml after serialize // workBookWriter provides a function to save workbook.xml after serialize

View File

@ -9,7 +9,8 @@ import (
func TestWorkbookProps(t *testing.T) { func TestWorkbookProps(t *testing.T) {
f := NewFile() f := NewFile()
assert.NoError(t, f.SetWorkbookProps(nil)) assert.NoError(t, f.SetWorkbookProps(nil))
wb := f.workbookReader() wb, err := f.workbookReader()
assert.NoError(t, err)
wb.WorkbookPr = nil wb.WorkbookPr = nil
expected := WorkbookPropsOptions{ expected := WorkbookPropsOptions{
Date1904: boolPtr(true), Date1904: boolPtr(true),
@ -20,4 +21,13 @@ func TestWorkbookProps(t *testing.T) {
opts, err := f.GetWorkbookProps() opts, err := f.GetWorkbookProps()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, opts) assert.Equal(t, expected, opts)
// Test set workbook properties with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.SetWorkbookProps(&expected), "XML syntax error on line 1: invalid UTF-8")
// Test get workbook properties with unsupported charset workbook.
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
_, err = f.GetWorkbookProps()
assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8")
} }