forked from p30928647/excelize
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
This commit is contained in:
parent
6429588e14
commit
40ed1d1b81
67
adjust.go
67
adjust.go
|
@ -11,6 +11,13 @@
|
|||
|
||||
package excelize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type adjustDirection bool
|
||||
|
||||
const (
|
||||
|
@ -41,6 +48,7 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
|
|||
f.adjustColDimensions(ws, 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 {
|
||||
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
|
||||
// inserting or deleting rows or columns.
|
||||
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 {
|
||||
coordinates[3] += offset
|
||||
}
|
||||
} else {
|
||||
if coordinates[2] >= num {
|
||||
coordinates[2] += offset
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
if coordinates[0] >= num {
|
||||
coordinates[0] += offset
|
||||
}
|
||||
if coordinates[2] >= num {
|
||||
coordinates[2] += offset
|
||||
}
|
||||
return coordinates
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package excelize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -281,7 +283,7 @@ func TestAdjustAutoFilter(t *testing.T) {
|
|||
Ref: "A1:A3",
|
||||
},
|
||||
}, rows, 1, -1))
|
||||
// testing adjustAutoFilter with illegal cell coordinates.
|
||||
// Test adjustAutoFilter with illegal cell coordinates.
|
||||
assert.EqualError(t, f.adjustAutoFilter(&xlsxWorksheet{
|
||||
AutoFilter: &xlsxAutoFilter{
|
||||
Ref: "A:B1",
|
||||
|
@ -294,6 +296,36 @@ func TestAdjustAutoFilter(t *testing.T) {
|
|||
}, 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) {
|
||||
f := NewFile()
|
||||
f.NewSheet("Sheet2")
|
||||
|
@ -303,10 +335,10 @@ func TestAdjustHelper(t *testing.T) {
|
|||
f.Sheet.Store("xl/worksheets/sheet2.xml", &xlsxWorksheet{
|
||||
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("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")
|
||||
}
|
||||
|
||||
|
|
30
cell.go
30
cell.go
|
@ -169,6 +169,21 @@ func (c *xlsxC) hasValue() bool {
|
|||
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.
|
||||
func (f *File) setCellIntFunc(sheet, axis string, value interface{}) error {
|
||||
var err error
|
||||
|
@ -266,7 +281,8 @@ func (f *File) SetCellInt(sheet, axis string, value int) error {
|
|||
defer ws.Unlock()
|
||||
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
|
||||
cellData.T, cellData.V = setCellInt(value)
|
||||
cellData.F, cellData.IS = nil, nil
|
||||
cellData.removeFormula(ws)
|
||||
cellData.IS = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -292,7 +308,8 @@ func (f *File) SetCellBool(sheet, axis string, value bool) error {
|
|||
defer ws.Unlock()
|
||||
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
|
||||
cellData.T, cellData.V = setCellBool(value)
|
||||
cellData.F, cellData.IS = nil, nil
|
||||
cellData.removeFormula(ws)
|
||||
cellData.IS = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -330,7 +347,8 @@ func (f *File) SetCellFloat(sheet, axis string, value float64, precision, bitSiz
|
|||
defer ws.Unlock()
|
||||
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
|
||||
cellData.T, cellData.V = setCellFloat(value, precision, bitSize)
|
||||
cellData.F, cellData.IS = nil, nil
|
||||
cellData.removeFormula(ws)
|
||||
cellData.IS = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -356,7 +374,8 @@ func (f *File) SetCellStr(sheet, axis, value string) error {
|
|||
defer ws.Unlock()
|
||||
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
|
||||
cellData.T, cellData.V, err = f.setCellString(value)
|
||||
cellData.F, cellData.IS = nil, nil
|
||||
cellData.removeFormula(ws)
|
||||
cellData.IS = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -455,7 +474,8 @@ func (f *File) SetCellDefault(sheet, axis, value string) error {
|
|||
defer ws.Unlock()
|
||||
cellData.S = f.prepareCellStyle(ws, col, row, cellData.S)
|
||||
cellData.T, cellData.V = setCellDefault(value)
|
||||
cellData.F, cellData.IS = nil, nil
|
||||
cellData.removeFormula(ws)
|
||||
cellData.IS = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ func TestGetPicture(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAddDrawingPicture(t *testing.T) {
|
||||
// testing addDrawingPicture with illegal cell coordinates.
|
||||
// Test addDrawingPicture with illegal cell coordinates.
|
||||
f := NewFile()
|
||||
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")))
|
||||
}
|
||||
|
||||
// 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
|
||||
// related to internal structure.
|
||||
func TestInsertRowInEmptyFile(t *testing.T) {
|
||||
|
|
11
sheet.go
11
sheet.go
|
@ -478,12 +478,11 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
|
|||
}
|
||||
|
||||
// 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
|
||||
// not case-sensitive. Use this method with caution, which will affect
|
||||
// changes in references such as formulas, charts, and so on. If there is any
|
||||
// referenced value of the deleted worksheet, it will cause a file error when
|
||||
// you open it. This function will be invalid when only the one worksheet is
|
||||
// left.
|
||||
// worksheet name, the sheet names are not case-sensitive. Use this method
|
||||
// with caution, which will affect changes in references such as formulas,
|
||||
// charts, and so on. If there is any referenced value of the deleted
|
||||
// worksheet, it will cause a file error when you open it. This function will
|
||||
// be invalid when only one worksheet is left
|
||||
func (f *File) DeleteSheet(name string) {
|
||||
if f.SheetCount == 1 || f.GetSheetIndex(name) == -1 {
|
||||
return
|
||||
|
|
57
table.go
57
table.go
|
@ -129,6 +129,35 @@ func (f *File) addSheetTable(sheet string, rID int) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// setTableHeader provides a function to set cells value in header row for the
|
||||
// table.
|
||||
func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
|
||||
var (
|
||||
tableColumns []*xlsxTableColumn
|
||||
idx int
|
||||
)
|
||||
for i := x1; i <= x2; i++ {
|
||||
idx++
|
||||
cell, err := CoordinatesToCellName(i, y1)
|
||||
if err != nil {
|
||||
return tableColumns, err
|
||||
}
|
||||
name, _ := f.GetCellValue(sheet, cell)
|
||||
if _, err := strconv.Atoi(name); err == nil {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
if name == "" {
|
||||
name = "Column" + strconv.Itoa(idx)
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
tableColumns = append(tableColumns, &xlsxTableColumn{
|
||||
ID: idx,
|
||||
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 {
|
||||
|
@ -142,29 +171,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, formatSet
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var tableColumn []*xlsxTableColumn
|
||||
|
||||
idx := 0
|
||||
for i := x1; i <= x2; i++ {
|
||||
idx++
|
||||
cell, err := CoordinatesToCellName(i, y1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, _ := f.GetCellValue(sheet, cell)
|
||||
if _, err := strconv.Atoi(name); err == nil {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
if name == "" {
|
||||
name = "Column" + strconv.Itoa(idx)
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
tableColumn = append(tableColumn, &xlsxTableColumn{
|
||||
ID: idx,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
|
||||
name := formatSet.TableName
|
||||
if name == "" {
|
||||
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,
|
||||
},
|
||||
TableColumns: &xlsxTableColumns{
|
||||
Count: idx,
|
||||
TableColumn: tableColumn,
|
||||
Count: len(tableColumns),
|
||||
TableColumn: tableColumns,
|
||||
},
|
||||
TableStyleInfo: &xlsxTableStyleInfo{
|
||||
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]")
|
||||
}
|
||||
|
||||
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) {
|
||||
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", "A1", "B", ""), newCellNameToCoordinatesError("B", newInvalidCellNameError("B")).Error())
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue