Skip to content
This repository was archived by the owner on Aug 13, 2025. It is now read-only.

Commit 2f3ecd9

Browse files
authored
Merge pull request #786 from tealeg/cell-store-sheet-name-is-stable
stable cellStoreName in they key for the cellstore
2 parents 8086d9b + aad3a7e commit 2f3ecd9

File tree

3 files changed

+64
-13
lines changed

3 files changed

+64
-13
lines changed

file.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"os"
1111
"strconv"
1212
"strings"
13-
"unicode/utf8"
1413
)
1514

1615
// File is a high level structure providing a slice of Sheet structs
@@ -203,22 +202,16 @@ func (f *File) AddSheetWithCellStore(sheetName string, constructor CellStoreCons
203202
if _, exists := f.Sheet[sheetName]; exists {
204203
return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
205204
}
206-
runeLength := utf8.RuneCountInString(sheetName)
207-
if runeLength > 31 || runeLength == 0 {
208-
return nil, fmt.Errorf("sheet name must be 31 or fewer characters long. It is currently '%d' characters long", runeLength)
209-
}
210-
// Iterate over the runes
211-
for _, r := range sheetName {
212-
// Excel forbids : \ / ? * [ ]
213-
if r == ':' || r == '\\' || r == '/' || r == '?' || r == '*' || r == '[' || r == ']' {
214-
return nil, fmt.Errorf("sheet name must not contain any restricted characters : \\ / ? * [ ] but contains '%s'", string(r))
215-
}
205+
206+
if err := IsSaneSheetName(sheetName); err != nil {
207+
return nil, fmt.Errorf("sheet name is not valid: %w", err)
216208
}
217209
sheet := &Sheet{
218210
Name: sheetName,
219211
File: f,
220212
Selected: len(f.Sheets) == 0,
221213
Cols: &ColStore{},
214+
cellStoreName: sheetName,
222215
}
223216

224217
sheet.cellStore, err = constructor()
@@ -235,6 +228,9 @@ func (f *File) AppendSheet(sheet Sheet, sheetName string) (*Sheet, error) {
235228
if _, exists := f.Sheet[sheetName]; exists {
236229
return nil, fmt.Errorf("duplicate sheet name '%s'.", sheetName)
237230
}
231+
if err := IsSaneSheetName(sheetName); err != nil {
232+
return nil, fmt.Errorf("sheet name is not valid: %w", err)
233+
}
238234
sheet.Name = sheetName
239235
sheet.File = f
240236
sheet.Selected = len(f.Sheets) == 0

file_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ func TestFile(t *testing.T) {
360360
csRunO(c, "TestAddSheetWithEmptyName", func(c *qt.C, option FileOption) {
361361
f := NewFile(option)
362362
_, err := f.AddSheet("")
363-
c.Assert(err.Error(), qt.Equals, "sheet name must be 31 or fewer characters long. It is currently '0' characters long")
363+
c.Assert(err.Error(), qt.Contains, "sheet name must be 31 or fewer characters long. It is currently '0' characters long")
364364
})
365365

366366
// Test that we can append a sheet to a File
@@ -386,6 +386,37 @@ func TestFile(t *testing.T) {
386386
c.Assert(err.Error(), qt.Equals, "duplicate sheet name 'MySheet'.")
387387
})
388388

389+
// Test that AppendSheet doesn't lose rows because of a change in the sheet name (this really occurred see https://github.com/tealeg/xlsx/issues/783 )
390+
csRunO(c, "TestAppendSheetWithNewSheetNameDoesNotLoseRows", func(c *qt.C, option FileOption) {
391+
392+
sourceFile, err := OpenFile("./testdocs/original.xlsx")
393+
c.Assert(err, qt.IsNil)
394+
c.Assert(len(sourceFile.Sheets), qt.Equals, 1)
395+
s := sourceFile.Sheets[0]
396+
397+
f := NewFile(option)
398+
_, err = f.AppendSheet(*s, "Dave")
399+
c.Assert(err, qt.IsNil)
400+
401+
tmp := c.TempDir()
402+
p := filepath.Join(tmp, "blokes.xlsx")
403+
err = f.Save(p)
404+
c.Assert(err, qt.IsNil)
405+
406+
blokes, err := OpenFile(p)
407+
c.Assert(err, qt.IsNil)
408+
409+
410+
dave := blokes.Sheets[0]
411+
if dave.currentRow != nil {
412+
c.Assert(dave.cellStore.WriteRow(dave.currentRow), qt.IsNil)
413+
}
414+
cell, err := dave.Cell(0, 0)
415+
c.Assert(err, qt.IsNil)
416+
c.Assert(cell.String(), qt.Equals, "Column A")
417+
418+
})
419+
389420
// Test that we can read & create a 31 rune sheet name
390421
csRunO(c, "TestMaxSheetNameLength", func(c *qt.C, option FileOption) {
391422
// Open a genuine xlsx created by Microsoft Excel 2007

sheet.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"strconv"
99
"strings"
10+
"unicode/utf8"
1011

1112
"github.com/shabbyrobe/xmlwriter"
1213
)
@@ -28,6 +29,9 @@ type Sheet struct {
2829
DataValidations []*xlsxDataValidation
2930
cellStore CellStore
3031
currentRow *Row
32+
cellStoreName string // The first part of the key used in
33+
// the cellStore. This name is stable,
34+
// unlike the Name, which can change
3135
}
3236

3337
// NewSheet constructs a Sheet with the default CellStore and returns
@@ -39,9 +43,13 @@ func NewSheet(name string) (*Sheet, error) {
3943
// NewSheetWithCellStore constructs a Sheet, backed by a CellStore,
4044
// for which you must provide the constructor function.
4145
func NewSheetWithCellStore(name string, constructor CellStoreConstructor) (*Sheet, error) {
46+
if err := IsSaneSheetName(name); err != nil {
47+
return nil, fmt.Errorf("sheet name is invalid: %w", err)
48+
}
4249
sheet := &Sheet{
4350
Name: name,
4451
Cols: &ColStore{},
52+
cellStoreName: name,
4553
}
4654
var err error
4755
sheet.cellStore, err = constructor()
@@ -207,7 +215,7 @@ func (s *Sheet) AddRow() *Row {
207215
}
208216

209217
func makeRowKey(s *Sheet, i int) string {
210-
return fmt.Sprintf("%s:%06d", s.Name, i)
218+
return fmt.Sprintf("%s:%06d", s.cellStoreName, i)
211219
}
212220

213221
// Add a new Row to a Sheet at a specific index
@@ -936,3 +944,19 @@ func handleNumFmtIdForXLSX(NumFmtId int, styles *xlsxStyleSheet) (XfId int) {
936944
XfId = styles.addCellXf(xCellXf)
937945
return
938946
}
947+
948+
949+
func IsSaneSheetName(sheetName string) error {
950+
runeLength := utf8.RuneCountInString(sheetName)
951+
if runeLength > 31 || runeLength == 0 {
952+
return fmt.Errorf("sheet name must be 31 or fewer characters long. It is currently '%d' characters long", runeLength)
953+
}
954+
// Iterate over the runes
955+
for _, r := range sheetName {
956+
// Excel forbids : \ / ? * [ ]
957+
if r == ':' || r == '\\' || r == '/' || r == '?' || r == '*' || r == '[' || r == ']' {
958+
return fmt.Errorf("sheet name must not contain any restricted characters : \\ / ? * [ ] but contains '%s'", string(r))
959+
}
960+
}
961+
return nil
962+
}

0 commit comments

Comments
 (0)