diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 46f92f5f..15c98857 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -5,7 +5,7 @@ jobs: test: strategy: matrix: - go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x] + go-version: [1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x] os: [ubuntu-latest, macos-latest, windows-latest] targetplatform: [x86, x64] diff --git a/col.go b/col.go index d3852048..74f9cdda 100644 --- a/col.go +++ b/col.go @@ -300,7 +300,7 @@ func (f *File) SetColVisible(sheet, columns string, visible bool) error { colData := xlsxCol{ Min: min, Max: max, - Width: defaultColWidth, // default width + Width: float64Ptr(defaultColWidth), Hidden: !visible, CustomWidth: true, } @@ -449,7 +449,7 @@ func (f *File) SetColStyle(sheet, columns string, styleID int) error { ws.Cols.Col = flatCols(xlsxCol{ Min: min, Max: max, - Width: defaultColWidth, + Width: float64Ptr(defaultColWidth), Style: styleID, }, ws.Cols.Col, func(fc, c xlsxCol) xlsxCol { fc.BestFit = c.BestFit @@ -493,7 +493,7 @@ func (f *File) SetColWidth(sheet, startCol, endCol string, width float64) error col := xlsxCol{ Min: min, Max: max, - Width: width, + Width: float64Ptr(width), CustomWidth: true, } if ws.Cols == nil { @@ -639,8 +639,8 @@ func (f *File) getColWidth(sheet string, col int) int { if ws.Cols != nil { var width float64 for _, v := range ws.Cols.Col { - if v.Min <= col && col <= v.Max { - width = v.Width + if v.Min <= col && col <= v.Max && v.Width != nil { + width = *v.Width } } if width != 0 { @@ -691,8 +691,8 @@ func (f *File) GetColWidth(sheet, col string) (float64, error) { if ws.Cols != nil { var width float64 for _, v := range ws.Cols.Col { - if v.Min <= colNum && colNum <= v.Max { - width = v.Width + if v.Min <= colNum && colNum <= v.Max && v.Width != nil { + width = *v.Width } } if width != 0 { diff --git a/errors.go b/errors.go index 471afa62..2a627d3d 100644 --- a/errors.go +++ b/errors.go @@ -230,6 +230,8 @@ var ( // ErrSheetNameLength defined the error message on receiving the sheet // name length exceeds the limit. ErrSheetNameLength = fmt.Errorf("the sheet name length exceeds the %d characters limit", MaxSheetNameLength) + // ErrCellStyles defined the error message on cell styles exceeds the limit. + ErrCellStyles = fmt.Errorf("the cell styles exceeds the %d limit", MaxCellStyles) // ErrUnprotectWorkbook defined the error message on workbook has set no // protection. ErrUnprotectWorkbook = errors.New("workbook has set no protect") diff --git a/lib_test.go b/lib_test.go index ec5fd640..ab0ccc9a 100644 --- a/lib_test.go +++ b/lib_test.go @@ -342,8 +342,10 @@ func TestReadBytes(t *testing.T) { } func TestUnzipToTemp(t *testing.T) { - if strings.HasPrefix(runtime.Version(), "go1.19") { - t.Skip() + for _, v := range []string{"go1.19", "go1.20"} { + if strings.HasPrefix(runtime.Version(), v) { + t.Skip() + } } os.Setenv("TMPDIR", "test") defer os.Unsetenv("TMPDIR") diff --git a/rows.go b/rows.go index 6b44d8a2..05257e53 100644 --- a/rows.go +++ b/rows.go @@ -363,7 +363,7 @@ func (f *File) SetRowHeight(sheet string, row int, height float64) error { prepareSheetXML(ws, 0, row) rowIdx := row - 1 - ws.SheetData.Row[rowIdx].Ht = height + ws.SheetData.Row[rowIdx].Ht = float64Ptr(height) ws.SheetData.Row[rowIdx].CustomHeight = true return nil } @@ -376,8 +376,8 @@ func (f *File) getRowHeight(sheet string, row int) int { defer ws.Unlock() for i := range ws.SheetData.Row { v := &ws.SheetData.Row[i] - if v.R == row && v.Ht != 0 { - return int(convertRowHeightToPixels(v.Ht)) + if v.R == row && v.Ht != nil { + return int(convertRowHeightToPixels(*v.Ht)) } } // Optimization for when the row heights haven't changed. @@ -404,8 +404,8 @@ func (f *File) GetRowHeight(sheet string, row int) (float64, error) { return ht, nil // it will be better to use 0, but we take care with BC } for _, v := range ws.SheetData.Row { - if v.R == row && v.Ht != 0 { - return v.Ht, nil + if v.R == row && v.Ht != nil { + return *v.Ht, nil } } // Optimization for when the row heights haven't changed. @@ -784,7 +784,7 @@ func checkRow(ws *xlsxWorksheet) error { // hasAttr determine if row non-default attributes. func (r *xlsxRow) hasAttr() bool { - return r.Spans != "" || r.S != 0 || r.CustomFormat || r.Ht != 0 || + return r.Spans != "" || r.S != 0 || r.CustomFormat || r.Ht != nil || r.Hidden || r.CustomHeight || r.OutlineLevel != 0 || r.Collapsed || r.ThickTop || r.ThickBot || r.Ph } diff --git a/sheet.go b/sheet.go index 3ea489a5..814989ad 100644 --- a/sheet.go +++ b/sheet.go @@ -1844,10 +1844,10 @@ func prepareSheetXML(ws *xlsxWorksheet, col int, row int) { defer ws.Unlock() rowCount := len(ws.SheetData.Row) sizeHint := 0 - var ht float64 + var ht *float64 var customHeight bool if ws.SheetFormatPr != nil && ws.SheetFormatPr.CustomHeight { - ht = ws.SheetFormatPr.DefaultRowHeight + ht = float64Ptr(ws.SheetFormatPr.DefaultRowHeight) customHeight = true } if rowCount > 0 { diff --git a/styles.go b/styles.go index af323245..e2be9936 100644 --- a/styles.go +++ b/styles.go @@ -1193,6 +1193,7 @@ func parseFormatStyleSet(style *Style) (*Style, error) { // // Style // ------------------ +// none // single // double // @@ -2047,8 +2048,7 @@ func (f *File) NewStyle(style *Style) (int, error) { applyAlignment, alignment := fs.Alignment != nil, newAlignment(fs) applyProtection, protection := fs.Protection != nil, newProtection(fs) - cellXfsID = setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection) - return cellXfsID, nil + return setCellXfs(s, fontID, numFmtID, fillID, borderID, applyAlignment, applyProtection, alignment, protection) } var getXfIDFuncs = map[string]func(int, xlsxXf, *Style) bool{ @@ -2620,7 +2620,7 @@ func newBorders(style *Style) *xlsxBorder { // setCellXfs provides a function to set describes all of the formatting for a // cell. -func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) int { +func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, applyAlignment, applyProtection bool, alignment *xlsxAlignment, protection *xlsxProtection) (int, error) { var xf xlsxXf xf.FontID = intPtr(fontID) if fontID != 0 { @@ -2638,6 +2638,9 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a if borderID != 0 { xf.ApplyBorder = boolPtr(true) } + if len(style.CellXfs.Xf) == MaxCellStyles { + return 0, ErrCellStyles + } style.CellXfs.Count = len(style.CellXfs.Xf) + 1 xf.Alignment = alignment if alignment != nil { @@ -2650,7 +2653,7 @@ func setCellXfs(style *xlsxStyleSheet, fontID, numFmtID, fillID, borderID int, a xfID := 0 xf.XfID = &xfID style.CellXfs.Xf = append(style.CellXfs.Xf, xf) - return style.CellXfs.Count - 1 + return style.CellXfs.Count - 1, nil } // GetCellStyle provides a function to get cell style index by given worksheet diff --git a/styles_test.go b/styles_test.go index 79fb7b3d..86860fad 100644 --- a/styles_test.go +++ b/styles_test.go @@ -325,6 +325,13 @@ func TestNewStyle(t *testing.T) { f.Pkg.Store(defaultXMLPathStyles, MacintoshCyrillicCharset) _, err = f.NewStyle(&Style{NumFmt: 165}) assert.EqualError(t, err, "XML syntax error on line 1: invalid UTF-8") + + // Test create cell styles reach maximum + f = NewFile() + f.Styles.CellXfs.Xf = make([]xlsxXf, MaxCellStyles) + f.Styles.CellXfs.Count = MaxCellStyles + _, err = f.NewStyle(&Style{NumFmt: 0}) + assert.Equal(t, ErrCellStyles, err) } func TestNewConditionalStyle(t *testing.T) { diff --git a/xmlDrawing.go b/xmlDrawing.go index 30b4d094..125e5e48 100644 --- a/xmlDrawing.go +++ b/xmlDrawing.go @@ -108,7 +108,7 @@ const ( // Excel specifications and limits const ( - MaxCellStyles = 64000 + MaxCellStyles = 65430 MaxColumns = 16384 MaxColumnWidth = 255 MaxFieldLength = 255 diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 4e3a3586..8b45a34e 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -280,16 +280,16 @@ type xlsxCols struct { // xlsxCol directly maps the col (Column Width & Formatting). Defines column // width and column formatting for one or more columns of the worksheet. type xlsxCol struct { - BestFit bool `xml:"bestFit,attr,omitempty"` - Collapsed bool `xml:"collapsed,attr,omitempty"` - CustomWidth bool `xml:"customWidth,attr,omitempty"` - Hidden bool `xml:"hidden,attr,omitempty"` - Max int `xml:"max,attr"` - Min int `xml:"min,attr"` - OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` - Phonetic bool `xml:"phonetic,attr,omitempty"` - Style int `xml:"style,attr,omitempty"` - Width float64 `xml:"width,attr,omitempty"` + BestFit bool `xml:"bestFit,attr,omitempty"` + Collapsed bool `xml:"collapsed,attr,omitempty"` + CustomWidth bool `xml:"customWidth,attr,omitempty"` + Hidden bool `xml:"hidden,attr,omitempty"` + Max int `xml:"max,attr"` + Min int `xml:"min,attr"` + OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` + Phonetic bool `xml:"phonetic,attr,omitempty"` + Style int `xml:"style,attr,omitempty"` + Width *float64 `xml:"width,attr"` } // xlsxDimension directly maps the dimension element in the namespace @@ -316,19 +316,19 @@ type xlsxSheetData struct { // about an entire row of a worksheet, and contains all cell definitions for a // particular row in the worksheet. type xlsxRow struct { - C []xlsxC `xml:"c"` - R int `xml:"r,attr,omitempty"` - Spans string `xml:"spans,attr,omitempty"` - S int `xml:"s,attr,omitempty"` - CustomFormat bool `xml:"customFormat,attr,omitempty"` - Ht float64 `xml:"ht,attr,omitempty"` - Hidden bool `xml:"hidden,attr,omitempty"` - CustomHeight bool `xml:"customHeight,attr,omitempty"` - OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` - Collapsed bool `xml:"collapsed,attr,omitempty"` - ThickTop bool `xml:"thickTop,attr,omitempty"` - ThickBot bool `xml:"thickBot,attr,omitempty"` - Ph bool `xml:"ph,attr,omitempty"` + C []xlsxC `xml:"c"` + R int `xml:"r,attr,omitempty"` + Spans string `xml:"spans,attr,omitempty"` + S int `xml:"s,attr,omitempty"` + CustomFormat bool `xml:"customFormat,attr,omitempty"` + Ht *float64 `xml:"ht,attr"` + Hidden bool `xml:"hidden,attr,omitempty"` + CustomHeight bool `xml:"customHeight,attr,omitempty"` + OutlineLevel uint8 `xml:"outlineLevel,attr,omitempty"` + Collapsed bool `xml:"collapsed,attr,omitempty"` + ThickTop bool `xml:"thickTop,attr,omitempty"` + ThickBot bool `xml:"thickBot,attr,omitempty"` + Ph bool `xml:"ph,attr,omitempty"` } // xlsxSortState directly maps the sortState element. This collection