This improves compatibility for adjusting tables and formula references on inserting/deleting columns or rows
This commit is contained in:
parent
cf3e0164d9
commit
5bba8f9805
74
adjust.go
74
adjust.go
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
43
table.go
43
table.go
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
44
xmlTable.go
44
xmlTable.go
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue