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
pull/2/head
xuri 3 years ago
parent 6429588e14
commit 40ed1d1b81
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -11,6 +11,13 @@
package excelize package excelize
import (
"bytes"
"encoding/xml"
"io"
"strings"
)
type adjustDirection bool type adjustDirection bool
const ( const (
@ -41,6 +48,7 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
f.adjustColDimensions(ws, num, offset) f.adjustColDimensions(ws, num, offset)
} }
f.adjustHyperlinks(ws, sheet, dir, num, offset) f.adjustHyperlinks(ws, sheet, dir, num, offset)
f.adjustTable(ws, sheet, dir, num, offset)
if err = f.adjustMergeCells(ws, dir, num, offset); err != nil { if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
return err return err
} }
@ -138,6 +146,54 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
} }
} }
// adjustTable provides a function to update the table when inserting or
// deleting rows or columns.
func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset int) {
if ws.TableParts == nil || len(ws.TableParts.TableParts) == 0 {
return
}
for idx := 0; idx < len(ws.TableParts.TableParts); idx++ {
tbl := ws.TableParts.TableParts[idx]
target := f.getSheetRelationshipsTargetByID(sheet, tbl.RID)
tableXML := strings.ReplaceAll(target, "..", "xl")
content, ok := f.Pkg.Load(tableXML)
if !ok {
continue
}
t := xlsxTable{}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(&t); err != nil && err != io.EOF {
return
}
coordinates, err := areaRefToCoordinates(t.Ref)
if err != nil {
return
}
// Remove the table when deleting the header row of the table
if dir == rows && num == coordinates[0] {
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
ws.TableParts.Count = len(ws.TableParts.TableParts)
idx--
continue
}
coordinates = f.adjustAutoFilterHelper(dir, coordinates, num, offset)
x1, y1, x2, y2 := coordinates[0], coordinates[1], coordinates[2], coordinates[3]
if y2-y1 < 2 || x2-x1 < 1 {
ws.TableParts.TableParts = append(ws.TableParts.TableParts[:idx], ws.TableParts.TableParts[idx+1:]...)
ws.TableParts.Count = len(ws.TableParts.TableParts)
idx--
continue
}
t.Ref, _ = f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
if t.AutoFilter != nil {
t.AutoFilter.Ref = t.Ref
}
_, _ = f.setTableHeader(sheet, x1, y1, x2)
table, _ := xml.Marshal(t)
f.saveFileList(tableXML, table)
}
}
// adjustAutoFilter provides a function to update the auto filter when // adjustAutoFilter provides a function to update the auto filter when
// inserting or deleting rows or columns. // inserting or deleting rows or columns.
func (f *File) adjustAutoFilter(ws *xlsxWorksheet, dir adjustDirection, num, offset int) error { 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
if coordinates[3] >= num { if coordinates[3] >= num {
coordinates[3] += offset coordinates[3] += offset
} }
} else { return coordinates
if coordinates[2] >= num { }
coordinates[2] += offset if coordinates[0] >= num {
} coordinates[0] += offset
}
if coordinates[2] >= num {
coordinates[2] += offset
} }
return coordinates return coordinates
} }

@ -1,6 +1,8 @@
package excelize package excelize
import ( import (
"fmt"
"path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -281,7 +283,7 @@ func TestAdjustAutoFilter(t *testing.T) {
Ref: "A1:A3", Ref: "A1:A3",
}, },
}, rows, 1, -1)) }, rows, 1, -1))
// testing adjustAutoFilter with illegal cell coordinates. // Test adjustAutoFilter with illegal cell coordinates.
assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{ assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{ AutoFilter: &xlsxAutoFilter{
Ref: "A:B1", Ref: "A:B1",
@ -294,6 +296,36 @@ func TestAdjustAutoFilter(t *testing.T) {
}, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) }, rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
} }
func TestAdjustTable(t *testing.T) {
f, sheetName := NewFile(), "Sheet1"
for idx, tableRange := range [][]string{{"B2", "C3"}, {"E3", "F5"}, {"H5", "H8"}, {"J5", "K9"}} {
assert.NoError(t, f.AddTable(sheetName, tableRange[0], tableRange[1], fmt.Sprintf(`{
"table_name": "table%d",
"table_style": "TableStyleMedium2",
"show_first_column": true,
"show_last_column": true,
"show_row_stripes": false,
"show_column_stripes": true
}`, idx)))
}
assert.NoError(t, f.RemoveRow(sheetName, 2))
assert.NoError(t, f.RemoveRow(sheetName, 3))
assert.NoError(t, f.RemoveCol(sheetName, "H"))
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestAdjustTable.xlsx")))
f = NewFile()
assert.NoError(t, f.AddTable(sheetName, "A1", "D5", ""))
// Test adjust table with non-table part
f.Pkg.Delete("xl/tables/table1.xml")
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with unsupported charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with invalid table range reference
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
assert.NoError(t, f.RemoveRow(sheetName, 1))
}
func TestAdjustHelper(t *testing.T) { func TestAdjustHelper(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") f.NewSheet("Sheet2")
@ -303,10 +335,10 @@ func TestAdjustHelper(t *testing.T) {
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{ f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
AutoFilter: &xlsxAutoFilter{Ref: "A1:B"}, AutoFilter: &xlsxAutoFilter{Ref: "A1:B"},
}) })
// testing adjustHelper with illegal cell coordinates. // Test adjustHelper with illegal cell coordinates.
assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.adjustHelper("Sheet1", rows, 0, 0), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error()) assert.EqualError(t, f.adjustHelper("Sheet2", rows, 0, 0), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
// testing adjustHelper on not exists worksheet. // Test adjustHelper on not exists worksheet.
assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN is not exist") assert.EqualError(t, f.adjustHelper("SheetN", rows, 0, 0), "sheet SheetN is not exist")
} }

@ -169,6 +169,21 @@ func (c *xlsxC) hasValue() bool {
return c.S != 0 || c.V != "" || c.F != nil || c.T != "" return c.S != 0 || c.V != "" || c.F != nil || c.T != ""
} }
// removeFormula delete formula for the cell.
func (c *xlsxC) removeFormula(ws *xlsxWorksheet) {
if c.F != nil && c.F.T == STCellFormulaTypeShared && c.F.Ref != "" {
si := c.F.Si
for r, row := range ws.SheetData.Row {
for col, cell := range row.C {
if cell.F != nil && cell.F.Si != nil && *cell.F.Si == *si {
ws.SheetData.Row[r].C[col].F = nil
}
}
}
}
c.F = nil
}
// setCellIntFunc is a wrapper of SetCellInt. // setCellIntFunc is a wrapper of SetCellInt.
func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error { func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
var err error var err error
@ -266,7 +281,8 @@ func (f *File) SetCellInt(sheet, axis string, value int) error {
defer ws.Unlock() defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
cellData.T, cellData.V = setCellInt(value) cellData.T, cellData.V = setCellInt(value)
cellData.F, cellData.IS = nil, nil cellData.removeFormula(ws)
cellData.IS = nil
return err return err
} }
@ -292,7 +308,8 @@ func (f *File) SetCellBool(sheet, axis string, value bool) error {
defer ws.Unlock() defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
cellData.T, cellData.V = setCellBool(value) cellData.T, cellData.V = setCellBool(value)
cellData.F, cellData.IS = nil, nil cellData.removeFormula(ws)
cellData.IS = nil
return err return err
} }
@ -330,7 +347,8 @@ func (f *File) SetCellFloat(sheet, axis string, value float64, precision, bitSiz
defer ws.Unlock() defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
cellData.T, cellData.V = setCellFloat(value, precision, bitSize) cellData.T, cellData.V = setCellFloat(value, precision, bitSize)
cellData.F, cellData.IS = nil, nil cellData.removeFormula(ws)
cellData.IS = nil
return err return err
} }
@ -356,7 +374,8 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
defer ws.Unlock() defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
cellData.T, cellData.V, err = f.setCellString(value) cellData.T, cellData.V, err = f.setCellString(value)
cellData.F, cellData.IS = nil, nil cellData.removeFormula(ws)
cellData.IS = nil
return err return err
} }
@ -455,7 +474,8 @@ func (f *File) SetCellDefault(sheet, axis, value string) error {
defer ws.Unlock() defer ws.Unlock()
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S) cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
cellData.T, cellData.V = setCellDefault(value) cellData.T, cellData.V = setCellDefault(value)
cellData.F, cellData.IS = nil, nil cellData.removeFormula(ws)
cellData.IS = nil
return err return err
} }

@ -173,7 +173,7 @@ func TestGetPicture(t *testing.T) {
} }
func TestAddDrawingPicture(t *testing.T) { func TestAddDrawingPicture(t *testing.T) {
// testing addDrawingPicture with illegal cell coordinates. // Test addDrawingPicture with illegal cell coordinates.
f := NewFile() f := NewFile()
assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, 0, 0, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error()) assert.EqualError(t, f.addDrawingPicture("sheet1", "", "A", "", 0, 0, 0, 0, nil), newCellNameToCoordinatesError("A", newInvalidCellNameError("A")).Error())
} }

@ -322,7 +322,7 @@ func TestInsertRow(t *testing.T) {
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRow.xlsx"))) assert.NoError(t, f.SaveAs(filepath.Join("test", "TestInsertRow.xlsx")))
} }
// Testing internal structure state after insert operations. It is important // Test internal structure state after insert operations. It is important
// for insert workflow to be constant to avoid side effect with functions // for insert workflow to be constant to avoid side effect with functions
// related to internal structure. // related to internal structure.
func TestInsertRowInEmptyFile(t *testing.T) { func TestInsertRowInEmptyFile(t *testing.T) {

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

@ -129,28 +129,18 @@ func (f *File) addSheetTable(sheet string, rID int) error {
return err return err
} }
// addTable provides a function to add table by given worksheet name, // setTableHeader provides a function to set cells value in header row for the
// coordinate area and format set. // table.
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error { func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
// Correct the minimum number of rows, the table at least two lines. var (
if y1 == y2 { tableColumns []*xlsxTableColumn
y2++ idx int
} )
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
if err != nil {
return err
}
var tableColumn []*xlsxTableColumn
idx := 0
for i := x1; i <= x2; i++ { for i := x1; i <= x2; i++ {
idx++ idx++
cell, err := CoordinatesToCellName(i, y1) cell, err := CoordinatesToCellName(i, y1)
if err != nil { if err != nil {
return err return tableColumns, err
} }
name, _ := f.GetCellValue(sheet, cell) name, _ := f.GetCellValue(sheet, cell)
if _, err := strconv.Atoi(name); err == nil { 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
name = "Column" + strconv.Itoa(idx) name = "Column" + strconv.Itoa(idx)
_ = f.SetCellStr(sheet, cell, name) _ = f.SetCellStr(sheet, cell, name)
} }
tableColumn = append(tableColumn, &xlsxTableColumn{ tableColumns = append(tableColumns, &xlsxTableColumn{
ID: idx, ID: idx,
Name: name, Name: name,
}) })
} }
return tableColumns, nil
}
// addTable provides a function to add table by given worksheet name,
// coordinate area and format set.
func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet *formatTable) error {
// Correct the minimum number of rows, the table at least two lines.
if y1 == y2 {
y2++
}
// Correct table reference coordinate area, such correct C1:B3 to B1:C3.
ref, err := f.coordinatesToAreaRef([]int{x1, y1, x2, y2})
if err != nil {
return err
}
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
name := formatSet.TableName name := formatSet.TableName
if name == "" { if name == "" {
name = "Table" + strconv.Itoa(i) name = "Table" + strconv.Itoa(i)
@ -179,8 +186,8 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
Ref: ref, Ref: ref,
}, },
TableColumns: &xlsxTableColumns{ TableColumns: &xlsxTableColumns{
Count: idx, Count: len(tableColumns),
TableColumn: tableColumn, TableColumn: tableColumns,
}, },
TableStyleInfo: &xlsxTableStyleInfo{ TableStyleInfo: &xlsxTableStyleInfo{
Name: formatSet.TableStyle, Name: formatSet.TableStyle,

@ -45,6 +45,12 @@ func TestAddTable(t *testing.T) {
assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]") assert.EqualError(t, f.addTable("sheet1", "", 1, 1, 0, 0, 0, nil), "invalid cell coordinates [0, 0]")
} }
func TestSetTableHeader(t *testing.T) {
f := NewFile()
_, err := f.setTableHeader("Sheet1", 1, 0, 1)
assert.EqualError(t, err, "invalid cell coordinates [1, 0]")
}
func TestAutoFilter(t *testing.T) { func TestAutoFilter(t *testing.T) {
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx") outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
@ -72,7 +78,7 @@ func TestAutoFilter(t *testing.T) {
}) })
} }
// testing AutoFilter with illegal cell coordinates. // Test AutoFilter with illegal cell coordinates.
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())
} }

Loading…
Cancel
Save