This improves compatibility for adjusting tables and formula references on inserting/deleting columns or rows

This commit is contained in:
xuri 2023-10-31 00:01:57 +08:00
parent cf3e0164d9
commit 5bba8f9805
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
5 changed files with 105 additions and 71 deletions

View File

@ -53,6 +53,8 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
return err
}
f.adjustHyperlinks(ws, sheet, dir, num, offset)
ws.checkSheet()
_ = ws.checkRow()
f.adjustTable(ws, sheet, dir, num, offset)
if err = f.adjustMergeCells(ws, dir, num, offset); err != nil {
return err
@ -63,9 +65,6 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
return err
}
ws.checkSheet()
_ = ws.checkRow()
if ws.MergeCells != nil && len(ws.MergeCells.Cells) == 0 {
ws.MergeCells = nil
}
@ -279,10 +278,40 @@ func adjustFormulaRowNumber(name string, dir adjustDirection, num, offset int) (
return name, nil
}
// adjustFormulaOperandRef adjust cell reference in the operand tokens for the formula.
func adjustFormulaOperandRef(row, col, operand string, dir adjustDirection, num int, offset int) (string, string, string, error) {
if col != "" {
name, err := adjustFormulaColumnName(col, dir, num, offset)
if err != nil {
return row, col, operand, err
}
operand += name
col = ""
}
if row != "" {
name, err := adjustFormulaRowNumber(row, dir, num, offset)
if err != nil {
return row, col, operand, err
}
operand += name
row = ""
}
return row, col, operand, nil
}
// adjustFormulaOperand adjust range operand tokens for the formula.
func (f *File) adjustFormulaOperand(token efp.Token, dir adjustDirection, num int, offset int) (string, error) {
var col, row, operand string
for _, r := range token.TValue {
var (
err error
sheet, col, row, operand string
cell = token.TValue
tokens = strings.Split(token.TValue, "!")
)
if len(tokens) == 2 { // have a worksheet
sheet, cell = tokens[0], tokens[1]
operand = string(efp.QuoteSingle) + sheet + string(efp.QuoteSingle) + "!"
}
for _, r := range cell {
if ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') {
col += string(r)
continue
@ -299,21 +328,8 @@ func (f *File) adjustFormulaOperand(token efp.Token, dir adjustDirection, num in
}
continue
}
if row != "" {
name, err := adjustFormulaRowNumber(row, dir, num, offset)
if err != nil {
return operand, err
}
operand += name
row = ""
}
if col != "" {
name, err := adjustFormulaColumnName(col, dir, num, offset)
if err != nil {
return operand, err
}
operand += name
col = ""
if row, col, operand, err = adjustFormulaOperandRef(row, col, operand, dir, num, offset); err != nil {
return operand, err
}
operand += string(r)
}
@ -365,6 +381,10 @@ func (f *File) adjustFormulaRef(sheet, formula string, dir adjustDirection, num,
val += token.TValue + string(efp.ParenClose)
continue
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeText {
val += string(efp.QuoteDouble) + token.TValue + string(efp.QuoteDouble)
continue
}
val += token.TValue
}
return val, nil
@ -400,16 +420,7 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
}
for i := range ws.Hyperlinks.Hyperlink {
link := &ws.Hyperlinks.Hyperlink[i] // get reference
colNum, rowNum, _ := CellNameToCoordinates(link.Ref)
if dir == rows {
if rowNum >= num {
link.Ref, _ = CoordinatesToCellName(colNum, rowNum+offset)
}
} else {
if colNum >= num {
link.Ref, _ = CoordinatesToCellName(colNum+offset, rowNum)
}
}
link.Ref, _ = f.adjustFormulaRef(sheet, link.Ref, dir, num, offset)
}
}
@ -455,7 +466,8 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
if t.AutoFilter != nil {
t.AutoFilter.Ref = t.Ref
}
_, _ = f.setTableHeader(sheet, true, x1, y1, x2)
_ = f.setTableColumns(sheet, true, x1, y1, x2, &t)
t.TotalsRowCount = 0
table, _ := xml.Marshal(t)
f.saveFileList(tableXML, table)
}

View File

@ -583,11 +583,11 @@ func TestAdjustFormula(t *testing.T) {
// Test adjust formula on duplicate row with relative and absolute cell references
f = NewFile()
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "A$10+$A11"))
assert.NoError(t, f.SetCellFormula("Sheet1", "B10", "A$10+$A11&\" \""))
assert.NoError(t, f.DuplicateRowTo("Sheet1", 10, 2))
formula, err = f.GetCellFormula("Sheet1", "B2")
assert.NoError(t, err)
assert.Equal(t, "A$2+$A3", formula)
assert.Equal(t, "A$2+$A3&\" \"", formula)
t.Run("for_cells_affected_directly", func(t *testing.T) {
// Test insert row in middle of range with relative and absolute cell references
@ -699,17 +699,17 @@ func TestAdjustFormula(t *testing.T) {
f = NewFile()
// Test adjust formula on insert row in the middle of the range
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM(A2,A3)"))
assert.NoError(t, f.SetCellFormula("Sheet1", "B1", "SUM('Sheet1'!A2,A3)"))
assert.NoError(t, f.InsertRows("Sheet1", 3, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(A2,A4)", formula)
assert.Equal(t, "SUM('Sheet1'!A2,A4)", formula)
// Test adjust formula on insert row at the top of the range
assert.NoError(t, f.InsertRows("Sheet1", 2, 1))
formula, err = f.GetCellFormula("Sheet1", "B1")
assert.NoError(t, err)
assert.Equal(t, "SUM(A3,A5)", formula)
assert.Equal(t, "SUM('Sheet1'!A3,A5)", formula)
f = NewFile()
// Test adjust formula on insert col in the middle of the range

View File

@ -254,37 +254,58 @@ func (f *File) addSheetTable(sheet string, rID int) error {
return err
}
// setTableHeader provides a function to set cells value in header row for the
// setTableColumns provides a function to set cells value in header row for the
// table.
func (f *File) setTableHeader(sheet string, showHeaderRow bool, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
func (f *File) setTableColumns(sheet string, showHeaderRow bool, x1, y1, x2 int, tbl *xlsxTable) error {
var (
tableColumns []*xlsxTableColumn
idx int
idx int
header []string
tableColumns []*xlsxTableColumn
getTableColumn = func(name string) *xlsxTableColumn {
if tbl != nil && tbl.TableColumns != nil {
for _, column := range tbl.TableColumns.TableColumn {
if column.Name == name {
return column
}
}
}
return nil
}
)
for i := x1; i <= x2; i++ {
idx++
cell, err := CoordinatesToCellName(i, y1)
if err != nil {
return tableColumns, err
return err
}
name, _ := f.GetCellValue(sheet, cell)
name, _ := f.GetCellValue(sheet, cell, Options{RawCellValue: true})
if _, err := strconv.Atoi(name); err == nil {
if showHeaderRow {
_ = f.SetCellStr(sheet, cell, name)
}
}
if name == "" {
if name == "" || inStrSlice(header, name, true) != -1 {
name = "Column" + strconv.Itoa(idx)
if showHeaderRow {
_ = f.SetCellStr(sheet, cell, name)
}
}
header = append(header, name)
if column := getTableColumn(name); column != nil {
column.ID, column.DataDxfID = idx, 0
tableColumns = append(tableColumns, column)
continue
}
tableColumns = append(tableColumns, &xlsxTableColumn{
ID: idx,
Name: name,
})
}
return tableColumns, nil
tbl.TableColumns = &xlsxTableColumns{
Count: len(tableColumns),
TableColumn: tableColumns,
}
return nil
}
// checkDefinedName check whether there are illegal characters in the defined
@ -326,7 +347,6 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
if err != nil {
return err
}
tableColumns, _ := f.setTableHeader(sheet, !hideHeaderRow, x1, y1, x2)
name := opts.Name
if name == "" {
name = "Table" + strconv.Itoa(i)
@ -340,10 +360,6 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
AutoFilter: &xlsxAutoFilter{
Ref: ref,
},
TableColumns: &xlsxTableColumns{
Count: len(tableColumns),
TableColumn: tableColumns,
},
TableStyleInfo: &xlsxTableStyleInfo{
Name: opts.StyleName,
ShowFirstColumn: opts.ShowFirstColumn,
@ -352,6 +368,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
ShowColumnStripes: opts.ShowColumnStripes,
},
}
_ = f.setTableColumns(sheet, !hideHeaderRow, x1, y1, x2, &t)
if hideHeaderRow {
t.AutoFilter = nil
t.HeaderRowCount = intPtr(0)

View File

@ -137,10 +137,9 @@ func TestDeleteTable(t *testing.T) {
assert.Equal(t, "Values", val)
}
func TestSetTableHeader(t *testing.T) {
func TestSetTableColumns(t *testing.T) {
f := NewFile()
_, err := f.setTableHeader("Sheet1", true, 1, 0, 1)
assert.Equal(t, newCoordinatesToCellNameError(1, 0), err)
assert.Equal(t, newCoordinatesToCellNameError(1, 0), f.setTableColumns("Sheet1", true, 1, 0, 1, nil))
}
func TestAutoFilter(t *testing.T) {

View File

@ -21,22 +21,28 @@ import "encoding/xml"
type xlsxTable struct {
XMLName xml.Name `xml:"table"`
XMLNS string `xml:"xmlns,attr"`
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
DisplayName string `xml:"displayName,attr,omitempty"`
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
HeaderRowCount *int `xml:"headerRowCount,attr"`
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
ID int `xml:"id,attr"`
Name string `xml:"name,attr"`
DisplayName string `xml:"displayName,attr,omitempty"`
Comment string `xml:"comment,attr,omitempty"`
Ref string `xml:"ref,attr"`
TableType string `xml:"tableType,attr,omitempty"`
HeaderRowCount *int `xml:"headerRowCount,attr"`
InsertRow bool `xml:"insertRow,attr,omitempty"`
InsertRowShift bool `xml:"insertRowShift,attr,omitempty"`
Name string `xml:"name,attr"`
Published bool `xml:"published,attr,omitempty"`
Ref string `xml:"ref,attr"`
TotalsRowCount int `xml:"totalsRowCount,attr,omitempty"`
TotalsRowShown *bool `xml:"totalsRowShown,attr"`
Published bool `xml:"published,attr,omitempty"`
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
TotalsRowShown bool `xml:"totalsRowShown,attr"`
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
TableBorderDxfId int `xml:"tableBorderDxfId,attr,omitempty"`
TotalsRowBorderDxfId int `xml:"totalsRowBorderDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
ConnectionId int `xml:"connectionId,attr,omitempty"`
AutoFilter *xlsxAutoFilter `xml:"autoFilter"`
TableColumns *xlsxTableColumns `xml:"tableColumns"`
TableStyleInfo *xlsxTableStyleInfo `xml:"tableStyleInfo"`
@ -171,18 +177,18 @@ type xlsxTableColumns struct {
// xlsxTableColumn directly maps the element representing a single column for
// this table.
type xlsxTableColumn struct {
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
ID int `xml:"id,attr"`
UniqueName string `xml:"uniqueName,attr,omitempty"`
Name string `xml:"name,attr"`
QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"`
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
TotalsRowFunction string `xml:"totalsRowFunction,attr,omitempty"`
TotalsRowLabel string `xml:"totalsRowLabel,attr,omitempty"`
UniqueName string `xml:"uniqueName,attr,omitempty"`
QueryTableFieldID int `xml:"queryTableFieldId,attr,omitempty"`
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
DataDxfID int `xml:"dataDxfId,attr,omitempty"`
TotalsRowDxfID int `xml:"totalsRowDxfId,attr,omitempty"`
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
DataCellStyle string `xml:"dataCellStyle,attr,omitempty"`
TotalsRowCellStyle string `xml:"totalsRowCellStyle,attr,omitempty"`
}
// xlsxTableStyleInfo directly maps the tableStyleInfo element. This element