Skip to content

Commit 40ed1d1

Browse files
committed
Fix potential file corrupted when changing cell value or the col/row
- Remove shared formula subsequent cell when setting the cell values - Support adjust table range when removing and inserting column/row
1 parent 6429588 commit 40ed1d1

File tree

8 files changed

+165
-42
lines changed

8 files changed

+165
-42
lines changed

adjust.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@
1111

1212
package excelize
1313

14+
import (
15+
"bytes"
16+
"encoding/xml"
17+
"io"
18+
"strings"
19+
)
20+
1421
type adjustDirection bool
1522

1623
const (
@@ -41,6 +48,7 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
4148
f.adjustColDimensions(ws, num, offset)
4249
}
4350
f.adjustHyperlinks(ws, sheet, dir, num, offset)
51+
f.adjustTable(ws, sheet, dir, num, offset)
4452
if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
4553
return err
4654
}
@@ -138,6 +146,54 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
138146
}
139147
}
140148

149+
// adjustTable provides a function to update the table when inserting or
150+
// deleting rows or columns.
151+
func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
152+
if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
153+
return
154+
}
155+
for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
156+
tbl := ws.TableParts.TableParts[idx]
157+
target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
158+
tableXML := strings.ReplaceAll(target, "..", "xl")
159+
content, ok := f.Pkg.Load(tableXML)
160+
if !ok {
161+
continue
162+
}
163+
t := xlsxTable{}
164+
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
165+
Decode(&t); err != nil && err != io.EOF {
166+
return
167+
}
168+
coordinates, err := areaRefToCoordinates(t.Ref)
169+
if err != nil {
170+
return
171+
}
172+
// Remove the table when deleting the header row of the table
173+
if dir == rows && num == coordinates[0] {
174+
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
175+
ws.TableParts.Count = len(ws.TableParts.TableParts)
176+
idx--
177+
continue
178+
}
179+
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
180+
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
181+
if y2-y1 < 2 || x2-x1 < 1 {
182+
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
183+
ws.TableParts.Count = len(ws.TableParts.TableParts)
184+
idx--
185+
continue
186+
}
187+
t.Ref, _ = f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
188+
if t.AutoFilter != nil {
189+
t.AutoFilter.Ref = t.Ref
190+
}
191+
_, _ = f.setTableHeader(sheet, x1, y1, x2)
192+
table, _ := xml.Marshal(t)
193+
f.saveFileList(tableXML, table)
194+
}
195+
}
196+
141197
// adjustAutoFilter provides a function to update the auto filter when
142198
// inserting or deleting rows or columns.
143199
func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error {
@@ -182,10 +238,13 @@ func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, nu
182238
if coordinates[3] >= num {
183239
coordinates[3] += offset
184240
}
185-
} else {
186-
if coordinates[2] >= num {
187-
coordinates[2] += offset
188-
}
241+
return coordinates
242+
}
243+
if coordinates[0] >= num {
244+
coordinates[0] += offset
245+
}
246+
if coordinates[2] >= num {
247+
coordinates[2] += offset
189248
}
190249
return coordinates
191250
}

adjust_test.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package excelize
22

33
import (
4+
"fmt"
5+
"path/filepath"
46
"testing"
57

68
"github.com/stretchr/testify/assert"
@@ -281,7 +283,7 @@ func TestAdjustAutoFilter(t *testing.T) {
281283
Ref: "A1:A3",
282284
},
283285
}, rows, 1, -1))
284-
// testing adjustAutoFilter with illegal cell coordinates.
286+
// Test adjustAutoFilter with illegal cell coordinates.
285287
assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{
286288
AutoFilter: &xlsxAutoFilter{
287289
Ref: "A:B1",
@@ -294,6 +296,36 @@ func TestAdjustAutoFilter(t *testing.T) {
294296
}, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
295297
}
296298

299+
func TestAdjustTable(t *testing.T) {
300+
f, sheetName := NewFile(), "Sheet1"
301+
for idx, tableRange := range [][]string{{"B2", "C3"}, {"E3", "F5"}, {"H5", "H8"}, {"J5", "K9"}} {
302+
assert.NoError(t, f.AddTable(sheetName, tableRange[0], tableRange[1], fmt.Sprintf(`{
303+
"table_name": "table%d",
304+
"table_style": "TableStyleMedium2",
305+
"show_first_column": true,
306+
"show_last_column": true,
307+
"show_row_stripes": false,
308+
"show_column_stripes": true
309+
}`, idx)))
310+
}
311+
assert.NoError(t, f.RemoveRow(sheetName, 2))
312+
assert.NoError(t, f.RemoveRow(sheetName, 3))
313+
assert.NoError(t, f.RemoveCol(sheetName, "H"))
314+
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx")))
315+
316+
f = NewFile()
317+
assert.NoError(t, f.AddTable(sheetName, "A1", "D5", ""))
318+
// Test adjust table with non-table part
319+
f.Pkg.Delete("xl/tables/table1.xml")
320+
assert.NoError(t, f.RemoveRow(sheetName, 1))
321+
// Test adjust table with unsupported charset
322+
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
323+
assert.NoError(t, f.RemoveRow(sheetName, 1))
324+
// Test adjust table with invalid table range reference
325+
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
326+
assert.NoError(t, f.RemoveRow(sheetName, 1))
327+
}
328+
297329
func TestAdjustHelper(t *testing.T) {
298330
f := NewFile()
299331
f.NewSheet("Sheet2")
@@ -303,10 +335,10 @@ func TestAdjustHelper(t *testing.T) {
303335
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
304336
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"},
305337
})
306-
// testing adjustHelper with illegal cell coordinates.
338+
// Test adjustHelper with illegal cell coordinates.
307339
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
308340
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
309-
// testing adjustHelper on not exists worksheet.
341+
// Test adjustHelper on not exists worksheet.
310342
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN is not exist")
311343
}
312344

cell.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,21 @@ func (c *xlsxC) hasValue() bool {
169169
return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
170170
}
171171

172+
// removeFormula delete formula for the cell.
173+
func (c *xlsxC) removeFormula(ws *xlsxWorksheet) {
174+
if c.F != nil && c.F.T == STCellFormulaTypeShared && c.F.Ref != "" {
175+
si := c.F.Si
176+
for r, row := range ws.SheetData.Row {
177+
for col, cell := range row.C {
178+
if cell.F != nil && cell.F.Si != nil && *cell.F.Si == *si {
179+
ws.SheetData.Row[r].C[col].F = nil
180+
}
181+
}
182+
}
183+
}
184+
c.F = nil
185+
}
186+
172187
// setCellIntFunc is a wrapper of SetCellInt.
173188
func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
174189
var err error
@@ -266,7 +281,8 @@ func (f *File) SetCellInt(sheet, axis string, value int) error {
266281
defer ws.Unlock()
267282
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
268283
cellData.T, cellData.V = setCellInt(value)
269-
cellData.F, cellData.IS = nil, nil
284+
cellData.removeFormula(ws)
285+
cellData.IS = nil
270286
return err
271287
}
272288

@@ -292,7 +308,8 @@ func (f *File) SetCellBool(sheet, axis string, value bool) error {
292308
defer ws.Unlock()
293309
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
294310
cellData.T, cellData.V = setCellBool(value)
295-
cellData.F, cellData.IS = nil, nil
311+
cellData.removeFormula(ws)
312+
cellData.IS = nil
296313
return err
297314
}
298315

@@ -330,7 +347,8 @@ func (f *File) SetCellFloat(sheet, axis string, value float64, precision, bitSiz
330347
defer ws.Unlock()
331348
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
332349
cellData.T, cellData.V = setCellFloat(value, precision, bitSize)
333-
cellData.F, cellData.IS = nil, nil
350+
cellData.removeFormula(ws)
351+
cellData.IS = nil
334352
return err
335353
}
336354

@@ -356,7 +374,8 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
356374
defer ws.Unlock()
357375
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
358376
cellData.T, cellData.V, err = f.setCellString(value)
359-
cellData.F, cellData.IS = nil, nil
377+
cellData.removeFormula(ws)
378+
cellData.IS = nil
360379
return err
361380
}
362381

@@ -455,7 +474,8 @@ func (f *File) SetCellDefault(sheet, axis, value string) error {
455474
defer ws.Unlock()
456475
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
457476
cellData.T, cellData.V = setCellDefault(value)
458-
cellData.F, cellData.IS = nil, nil
477+
cellData.removeFormula(ws)
478+
cellData.IS = nil
459479
return err
460480
}
461481

picture_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func TestGetPicture(t *testing.T) {
173173
}
174174

175175
func TestAddDrawingPicture(t *testing.T) {
176-
// testing addDrawingPicture with illegal cell coordinates.
176+
// Test addDrawingPicture with illegal cell coordinates.
177177
f := NewFile()
178178
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, 0, 0, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
179179
}

rows_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func TestInsertRow(t *testing.T) {
322322
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRow.xlsx")))
323323
}
324324

325-
// Testing internal structure state after insert operations. It is important
325+
// Test internal structure state after insert operations. It is important
326326
// for insert workflow to be constant to avoid side effect with functions
327327
// related to internal structure.
328328
func TestInsertRowInEmptyFile(t *testing.T) {

sheet.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -478,12 +478,11 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
478478
}
479479

480480
// DeleteSheet provides a function to delete worksheet in a workbook by given
481-
// worksheet name, the sheet names are not case-sensitive. The sheet names are
482-
// not case-sensitive. Use this method with caution, which will affect
483-
// changes in references such as formulas, charts, and so on. If there is any
484-
// referenced value of the deleted worksheet, it will cause a file error when
485-
// you open it. This function will be invalid when only the one worksheet is
486-
// left.
481+
// worksheet name, the sheet names are not case-sensitive. Use this method
482+
// with caution, which will affect changes in references such as formulas,
483+
// charts, and so on. If there is any referenced value of the deleted
484+
// worksheet, it will cause a file error when you open it. This function will
485+
// be invalid when only one worksheet is left
487486
func (f *File) DeleteSheet(name string) {
488487
if f.SheetCount == 1 || f.GetSheetIndex(name) == -1 {
489488
return

table.go

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -129,28 +129,18 @@ func (f *File) addSheetTable(sheet string, rID int) error {
129129
return err
130130
}
131131

132-
// addTable provides a function to add table by given worksheet name,
133-
// coordinate area and format set.
134-
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error {
135-
// Correct the minimum number of rows, the table at least two lines.
136-
if y1 == y2 {
137-
y2++
138-
}
139-
140-
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
141-
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
142-
if err != nil {
143-
return err
144-
}
145-
146-
var tableColumn []*xlsxTableColumn
147-
148-
idx := 0
132+
// setTableHeader provides a function to set cells value in header row for the
133+
// table.
134+
func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
135+
var (
136+
tableColumns []*xlsxTableColumn
137+
idx int
138+
)
149139
for i := x1; i <= x2; i++ {
150140
idx++
151141
cell, err := CoordinatesToCellName(i, y1)
152142
if err != nil {
153-
return err
143+
return tableColumns, err
154144
}
155145
name, _ := f.GetCellValue(sheet, cell)
156146
if _, err := strconv.Atoi(name); err == nil {
@@ -160,11 +150,28 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
160150
name = "Column" + strconv.Itoa(idx)
161151
_ = f.SetCellStr(sheet, cell, name)
162152
}
163-
tableColumn = append(tableColumn, &xlsxTableColumn{
153+
tableColumns = append(tableColumns, &xlsxTableColumn{
164154
ID: idx,
165155
Name: name,
166156
})
167157
}
158+
return tableColumns, nil
159+
}
160+
161+
// addTable provides a function to add table by given worksheet name,
162+
// coordinate area and format set.
163+
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error {
164+
// Correct the minimum number of rows, the table at least two lines.
165+
if y1 == y2 {
166+
y2++
167+
}
168+
169+
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
170+
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
171+
if err != nil {
172+
return err
173+
}
174+
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
168175
name := formatSet.TableName
169176
if name == "" {
170177
name = "Table" + strconv.Itoa(i)
@@ -179,8 +186,8 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
179186
Ref: ref,
180187
},
181188
TableColumns: &xlsxTableColumns{
182-
Count: idx,
183-
TableColumn: tableColumn,
189+
Count: len(tableColumns),
190+
TableColumn: tableColumns,
184191
},
185192
TableStyleInfo: &xlsxTableStyleInfo{
186193
Name: formatSet.TableStyle,

table_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ func TestAddTable(t *testing.T) {
4545
assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
4646
}
4747

48+
func TestSetTableHeader(t *testing.T) {
49+
f := NewFile()
50+
_, err := f.setTableHeader("Sheet1", 1, 0, 1)
51+
assert.EqualError(t, err, "invalid cell coordinates [1, 0]")
52+
}
53+
4854
func TestAutoFilter(t *testing.T) {
4955
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
5056

@@ -72,7 +78,7 @@ func TestAutoFilter(t *testing.T) {
7278
})
7379
}
7480

75-
// testing AutoFilter with illegal cell coordinates.
81+
// Test AutoFilter with illegal cell coordinates.
7682
assert.EqualError(t, f.AutoFilter("Sheet1", "A", "B1", ""), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
7783
assert.EqualError(t, f.AutoFilter("Sheet1", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
7884
}

0 commit comments

Comments
 (0)