This fixes #1461, supports 0 row height and column width

- Increase max cell styles to 65430
- Add new exported error variable `ErrCellStyles`
- Update unit tests, support test under Go 1.20.x
This commit is contained in:
xuri 2023-02-02 22:02:32 +08:00
parent 85e0b6c56e
commit 12645e7116
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
10 changed files with 60 additions and 46 deletions

View File

@ -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]

14
col.go
View File

@ -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 {

View File

@ -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")

View File

@ -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")

12
rows.go
View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -108,7 +108,7 @@ const (
// Excel specifications and limits
const (
MaxCellStyles = 64000
MaxCellStyles = 65430
MaxColumns = 16384
MaxColumnWidth = 255
MaxFieldLength = 255

View File

@ -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