Support update defined names on inserting/deleting columns/rows

This commit is contained in:
xuri 2023-11-08 00:01:35 +08:00
parent 6cc1a547ab
commit a0252bd05a
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
2 changed files with 174 additions and 76 deletions

143
adjust.go
View File

@ -30,9 +30,12 @@ const (
) )
// adjustHelperFunc defines functions to adjust helper. // adjustHelperFunc defines functions to adjust helper.
var adjustHelperFunc = [6]func(*File, *xlsxWorksheet, string, adjustDirection, int, int, int) error{ var adjustHelperFunc = [7]func(*File, *xlsxWorksheet, string, adjustDirection, int, int, int) error{
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustTable(ws, sheet, dir, num, offset, sheetID) return f.adjustDefinedNames(ws, sheet, dir, num, offset, sheetID)
},
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustDrawings(ws, sheet, dir, num, offset, sheetID)
}, },
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID) return f.adjustMergeCells(ws, sheet, dir, num, offset, sheetID)
@ -44,10 +47,10 @@ var adjustHelperFunc = [6]func(*File, *xlsxWorksheet, string, adjustDirection, i
return f.adjustCalcChain(ws, sheet, dir, num, offset, sheetID) return f.adjustCalcChain(ws, sheet, dir, num, offset, sheetID)
}, },
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustVolatileDeps(ws, sheet, dir, num, offset, sheetID) return f.adjustTable(ws, sheet, dir, num, offset, sheetID)
}, },
func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error { func(f *File, ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
return f.adjustDrawings(ws, sheet, dir, num, offset, sheetID) return f.adjustVolatileDeps(ws, sheet, dir, num, offset, sheetID)
}, },
} }
@ -60,7 +63,7 @@ var adjustHelperFunc = [6]func(*File, *xlsxWorksheet, string, adjustDirection, i
// row: Index number of the row we're inserting/deleting before // row: Index number of the row we're inserting/deleting before
// offset: Number of rows/column to insert/delete negative values indicate deletion // offset: Number of rows/column to insert/delete negative values indicate deletion
// //
// TODO: adjustComments, adjustDataValidations, adjustDrawings, adjustPageBreaks, adjustProtectedCells // TODO: adjustComments, adjustDataValidations, adjustPageBreaks, adjustProtectedCells
func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error { func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int) error {
ws, err := f.workSheetReader(sheet) ws, err := f.workSheetReader(sheet)
if err != nil { if err != nil {
@ -97,38 +100,34 @@ func (f *File) adjustCols(ws *xlsxWorksheet, col, offset int) error {
} }
for i := 0; i < len(ws.Cols.Col); i++ { for i := 0; i < len(ws.Cols.Col); i++ {
if offset > 0 { if offset > 0 {
if ws.Cols.Col[i].Max+1 == col {
ws.Cols.Col[i].Max += offset
continue
}
if ws.Cols.Col[i].Min >= col { if ws.Cols.Col[i].Min >= col {
ws.Cols.Col[i].Min += offset if ws.Cols.Col[i].Min += offset; ws.Cols.Col[i].Min > MaxColumns {
ws.Cols.Col[i].Max += offset ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
i--
continue continue
} }
if ws.Cols.Col[i].Min < col && ws.Cols.Col[i].Max >= col { }
ws.Cols.Col[i].Max += offset if ws.Cols.Col[i].Max >= col || ws.Cols.Col[i].Max+1 == col {
if ws.Cols.Col[i].Max += offset; ws.Cols.Col[i].Max > MaxColumns {
ws.Cols.Col[i].Max = MaxColumns
} }
} }
if offset < 0 { continue
}
if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col { if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
if len(ws.Cols.Col) > 1 {
ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...) ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
} else {
ws.Cols.Col = nil
}
i-- i--
continue continue
} }
if ws.Cols.Col[i].Min > col { if ws.Cols.Col[i].Min > col {
ws.Cols.Col[i].Min += offset ws.Cols.Col[i].Min += offset
ws.Cols.Col[i].Max += offset
continue
} }
if ws.Cols.Col[i].Min <= col && ws.Cols.Col[i].Max >= col { if ws.Cols.Col[i].Max >= col {
ws.Cols.Col[i].Max += offset ws.Cols.Col[i].Max += offset
} }
} }
if len(ws.Cols.Col) == 0 {
ws.Cols = nil
} }
return nil return nil
} }
@ -274,7 +273,7 @@ func (f *File) adjustFormula(sheet, sheetN string, formula *xlsxF, dir adjustDir
} }
} }
if formula.Content != "" { if formula.Content != "" {
if formula.Content, err = f.adjustFormulaRef(sheet, sheetN, formula.Content, dir, num, offset); err != nil { if formula.Content, err = f.adjustFormulaRef(sheet, sheetN, formula.Content, false, dir, num, offset); err != nil {
return err return err
} }
} }
@ -297,62 +296,60 @@ func escapeSheetName(name string) string {
if strings.IndexFunc(name, func(r rune) bool { if strings.IndexFunc(name, func(r rune) bool {
return !unicode.IsLetter(r) && !unicode.IsNumber(r) return !unicode.IsLetter(r) && !unicode.IsNumber(r)
}) != -1 { }) != -1 {
return string(efp.QuoteSingle) + name + string(efp.QuoteSingle) return "'" + strings.ReplaceAll(name, "'", "''") + "'"
} }
return name return name
} }
// adjustFormulaColumnName adjust column name in the formula reference. // adjustFormulaColumnName adjust column name in the formula reference.
func adjustFormulaColumnName(name string, dir adjustDirection, num, offset int) (string, error) { func adjustFormulaColumnName(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) {
if name == "" || (!abs && keepRelative) {
return "", operand + name, abs, nil
}
col, err := ColumnNameToNumber(name) col, err := ColumnNameToNumber(name)
if err != nil { if err != nil {
return name, err return "", operand, false, err
} }
if dir == columns && col >= num { if dir == columns && col >= num {
col += offset col += offset
return ColumnNumberToName(col) colName, err := ColumnNumberToName(col)
return "", operand + colName, false, err
} }
return name, nil return "", operand + name, false, nil
} }
// adjustFormulaRowNumber adjust row number in the formula reference. // adjustFormulaRowNumber adjust row number in the formula reference.
func adjustFormulaRowNumber(name string, dir adjustDirection, num, offset int) (string, error) { func adjustFormulaRowNumber(name, operand string, abs, keepRelative bool, dir adjustDirection, num, offset int) (string, string, bool, error) {
if name == "" || (!abs && keepRelative) {
return "", operand + name, abs, nil
}
row, _ := strconv.Atoi(name) row, _ := strconv.Atoi(name)
if dir == rows && row >= num { if dir == rows && row >= num {
row += offset row += offset
if row > TotalRows { if row <= 0 || row > TotalRows {
return name, ErrMaxRows return "", operand + name, false, ErrMaxRows
} }
return strconv.Itoa(row), nil return "", operand + strconv.Itoa(row), false, nil
} }
return name, nil return "", operand + name, false, nil
} }
// adjustFormulaOperandRef adjust cell reference in the operand tokens for the formula. // 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) { func adjustFormulaOperandRef(row, col, operand string, abs, keepRelative bool, dir adjustDirection, num int, offset int) (string, string, string, bool, error) {
if col != "" { var err error
name, err := adjustFormulaColumnName(col, dir, num, offset) col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
if err != nil { if err != nil {
return row, col, operand, err return row, col, operand, abs, err
} }
operand += name row, operand, abs, err = adjustFormulaRowNumber(row, operand, abs, keepRelative, dir, num, offset)
col = "" return row, col, operand, abs, err
}
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. // adjustFormulaOperand adjust range operand tokens for the formula.
func (f *File) adjustFormulaOperand(sheet, sheetN string, token efp.Token, dir adjustDirection, num int, offset int) (string, error) { func (f *File) adjustFormulaOperand(sheet, sheetN string, keepRelative bool, token efp.Token, dir adjustDirection, num int, offset int) (string, error) {
var ( var (
err error err error
abs bool
sheetName, col, row, operand string sheetName, col, row, operand string
cell = token.TValue cell = token.TValue
tokens = strings.Split(token.TValue, "!") tokens = strings.Split(token.TValue, "!")
@ -365,34 +362,38 @@ func (f *File) adjustFormulaOperand(sheet, sheetN string, token efp.Token, dir a
return operand + cell, err return operand + cell, err
} }
for _, r := range cell { for _, r := range cell {
if r == '$' {
if col, operand, _, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset); err != nil {
return operand, err
}
abs = true
operand += string(r)
continue
}
if ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') { if ('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') {
col += string(r) col += string(r)
continue continue
} }
if '0' <= r && r <= '9' { if '0' <= r && r <= '9' {
row += string(r) row += string(r)
if col != "" { col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
name, err := adjustFormulaColumnName(col, dir, num, offset)
if err != nil { if err != nil {
return operand, err return operand, err
} }
operand += name
col = ""
}
continue continue
} }
if row, col, operand, err = adjustFormulaOperandRef(row, col, operand, dir, num, offset); err != nil { if row, col, operand, abs, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset); err != nil {
return operand, err return operand, err
} }
operand += string(r) operand += string(r)
} }
_, _, operand, err = adjustFormulaOperandRef(row, col, operand, dir, num, offset) _, _, operand, _, err = adjustFormulaOperandRef(row, col, operand, abs, keepRelative, dir, num, offset)
return operand, err return operand, err
} }
// adjustFormulaRef returns adjusted formula by giving adjusting direction and // adjustFormulaRef returns adjusted formula by giving adjusting direction and
// the base number of column or row, and offset. // the base number of column or row, and offset.
func (f *File) adjustFormulaRef(sheet, sheetN, formula string, dir adjustDirection, num, offset int) (string, error) { func (f *File) adjustFormulaRef(sheet, sheetN, formula string, keepRelative bool, dir adjustDirection, num, offset int) (string, error) {
var ( var (
val string val string
definedNames []string definedNames []string
@ -404,6 +405,10 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, dir adjustDirecti
} }
} }
for _, token := range ps.Parse(formula) { for _, token := range ps.Parse(formula) {
if token.TType == efp.TokenTypeUnknown {
val = formula
break
}
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange { if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange {
if inStrSlice(definedNames, token.TValue, true) != -1 { if inStrSlice(definedNames, token.TValue, true) != -1 {
val += token.TValue val += token.TValue
@ -413,7 +418,7 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, dir adjustDirecti
val += token.TValue val += token.TValue
continue continue
} }
operand, err := f.adjustFormulaOperand(sheet, sheetN, token, dir, num, offset) operand, err := f.adjustFormulaOperand(sheet, sheetN, keepRelative, token, dir, num, offset)
if err != nil { if err != nil {
return val, err return val, err
} }
@ -467,7 +472,7 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
} }
for i := range ws.Hyperlinks.Hyperlink { for i := range ws.Hyperlinks.Hyperlink {
link := &ws.Hyperlinks.Hyperlink[i] // get reference link := &ws.Hyperlinks.Hyperlink[i] // get reference
link.Ref, _ = f.adjustFormulaRef(sheet, sheet, link.Ref, dir, num, offset) link.Ref, _ = f.adjustFormulaRef(sheet, sheet, link.Ref, false, dir, num, offset)
} }
} }
@ -488,7 +493,7 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
t := xlsxTable{} t := xlsxTable{}
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))). if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
Decode(&t); err != nil && err != io.EOF { Decode(&t); err != nil && err != io.EOF {
return nil return err
} }
coordinates, err := rangeRefToCoordinates(t.Ref) coordinates, err := rangeRefToCoordinates(t.Ref)
if err != nil { if err != nil {
@ -884,3 +889,21 @@ func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirecti
} }
return nil return nil
} }
// adjustDefinedNames updates the cell reference of the defined names when
// inserting or deleting rows or columns.
func (f *File) adjustDefinedNames(ws *xlsxWorksheet, sheet string, dir adjustDirection, num, offset, sheetID int) error {
wb, err := f.workbookReader()
if err != nil {
return err
}
if wb.DefinedNames != nil {
for i := 0; i < len(wb.DefinedNames.DefinedName); i++ {
data := wb.DefinedNames.DefinedName[i].Data
if data, err = f.adjustFormulaRef(sheet, "", data, true, dir, num, offset); err == nil {
wb.DefinedNames.DefinedName[i].Data = data
}
}
}
return nil
}

View File

@ -334,7 +334,7 @@ func TestAdjustTable(t *testing.T) {
assert.NoError(t, f.RemoveRow(sheetName, 1)) assert.NoError(t, f.RemoveRow(sheetName, 1))
// Test adjust table with unsupported charset // Test adjust table with unsupported charset
f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset) f.Pkg.Store("xl/tables/table1.xml", MacintoshCyrillicCharset)
assert.NoError(t, f.RemoveRow(sheetName, 1)) assert.EqualError(t, f.RemoveRow(sheetName, 1), "XML syntax error on line 1: invalid UTF-8")
// Test adjust table with invalid table range reference // Test adjust table with invalid table range reference
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`)) f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
assert.Equal(t, ErrParameterInvalid, f.RemoveRow(sheetName, 1)) assert.Equal(t, ErrParameterInvalid, f.RemoveRow(sheetName, 1))
@ -452,6 +452,17 @@ func TestAdjustCols(t *testing.T) {
assert.NoError(t, f.RemoveCol(sheetName, "A")) assert.NoError(t, f.RemoveCol(sheetName, "A"))
assert.NoError(t, f.Close()) assert.NoError(t, f.Close())
f = NewFile()
assert.NoError(t, f.SetColWidth("Sheet1", "XFB", "XFC", 12))
assert.NoError(t, f.InsertCols("Sheet1", "A", 2))
ws, ok = f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
assert.Equal(t, MaxColumns, ws.(*xlsxWorksheet).Cols.Col[0].Min)
assert.Equal(t, MaxColumns, ws.(*xlsxWorksheet).Cols.Col[0].Max)
assert.NoError(t, f.InsertCols("Sheet1", "A", 2))
assert.Nil(t, ws.(*xlsxWorksheet).Cols)
} }
func TestAdjustColDimensions(t *testing.T) { func TestAdjustColDimensions(t *testing.T) {
@ -550,9 +561,9 @@ func TestAdjustFormula(t *testing.T) {
assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "-"}, rows, 0, 0, false)) assert.Equal(t, newCellNameToCoordinatesError("-", newInvalidCellNameError("-")), f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "-"}, rows, 0, 0, false))
assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "XFD1:XFD1"}, columns, 0, 1, false)) assert.Equal(t, ErrColumnNumber, f.adjustFormula("Sheet1", "Sheet1", &xlsxF{Ref: "XFD1:XFD1"}, columns, 0, 1, false))
_, err := f.adjustFormulaRef("Sheet1", "Sheet1", "XFE1", columns, 0, 1) _, err := f.adjustFormulaRef("Sheet1", "Sheet1", "XFE1", false, columns, 0, 1)
assert.Equal(t, ErrColumnNumber, err) assert.Equal(t, ErrColumnNumber, err)
_, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", columns, 0, 1) _, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", false, columns, 0, 1)
assert.Equal(t, ErrColumnNumber, err) assert.Equal(t, ErrColumnNumber, err)
f = NewFile() f = NewFile()
@ -1028,3 +1039,67 @@ func TestAdjustDrawings(t *testing.T) {
f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+`<wsDr xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><twoCellAnchor><from><col>0</col><colOff>0</colOff><row>0</row><rowOff>0</rowOff></from><to><col>1</col><colOff>0</colOff><row>1</row><rowOff>0</rowOff></to><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"></mc:AlternateContent><clientData/></twoCellAnchor></wsDr>`)) f.Pkg.Store("xl/drawings/drawing1.xml", []byte(xml.Header+`<wsDr xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"><twoCellAnchor><from><col>0</col><colOff>0</colOff><row>0</row><rowOff>0</rowOff></from><to><col>1</col><colOff>0</colOff><row>1</row><rowOff>0</rowOff></to><mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"></mc:AlternateContent><clientData/></twoCellAnchor></wsDr>`))
assert.NoError(t, f.InsertCols("Sheet1", "A", 1)) assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
} }
func TestAdjustDefinedNames(t *testing.T) {
f := NewFile()
_, err := f.NewSheet("Sheet2")
assert.NoError(t, err)
for _, dn := range []*DefinedName{
{Name: "Name1", RefersTo: "Sheet1!$XFD$1"},
{Name: "Name2", RefersTo: "Sheet2!$C$1", Scope: "Sheet1"},
{Name: "Name3", RefersTo: "Sheet2!$C$1:$D$2", Scope: "Sheet1"},
{Name: "Name4", RefersTo: "Sheet2!$C1:D$2"},
{Name: "Name5", RefersTo: "Sheet2!C$1:$D2"},
{Name: "Name6", RefersTo: "Sheet2!C:$D"},
{Name: "Name7", RefersTo: "Sheet2!$C:D"},
{Name: "Name8", RefersTo: "Sheet2!C:D"},
{Name: "Name9", RefersTo: "Sheet2!$C:$D"},
{Name: "Name10", RefersTo: "Sheet2!1:2"},
} {
assert.NoError(t, f.SetDefinedName(dn))
}
assert.NoError(t, f.InsertCols("Sheet1", "A", 1))
assert.NoError(t, f.InsertRows("Sheet1", 1, 1))
assert.NoError(t, f.InsertCols("Sheet2", "A", 1))
assert.NoError(t, f.InsertRows("Sheet2", 1, 1))
definedNames := f.GetDefinedName()
for i, expected := range []string{
"Sheet1!$XFD$2",
"Sheet2!$D$2",
"Sheet2!$D$2:$E$3",
"Sheet2!$D1:D$3",
"Sheet2!C$2:$E2",
"Sheet2!C:$E",
"Sheet2!$D:D",
"Sheet2!C:D",
"Sheet2!$D:$E",
"Sheet2!1:2",
} {
assert.Equal(t, expected, definedNames[i].RefersTo)
}
f = NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name1",
RefersTo: "Sheet1!$A$1",
Scope: "Sheet1",
}))
assert.NoError(t, f.RemoveCol("Sheet1", "A"))
definedNames = f.GetDefinedName()
assert.Equal(t, "Sheet1!$A$1", definedNames[0].RefersTo)
f = NewFile()
assert.NoError(t, f.SetDefinedName(&DefinedName{
Name: "Name1",
RefersTo: "'1.A & B C'!#REF!",
Scope: "Sheet1",
}))
assert.NoError(t, f.RemoveCol("Sheet1", "A"))
definedNames = f.GetDefinedName()
assert.Equal(t, "'1.A & B C'!#REF!", definedNames[0].RefersTo)
f = NewFile()
f.WorkBook = nil
f.Pkg.Store(defaultXMLPathWorkbook, MacintoshCyrillicCharset)
assert.EqualError(t, f.adjustDefinedNames(nil, "Sheet1", columns, 0, 0, 1), "XML syntax error on line 1: invalid UTF-8")
}