Support update defined names on inserting/deleting columns/rows
This commit is contained in:
parent
6cc1a547ab
commit
a0252bd05a
143
adjust.go
143
adjust.go
|
@ -30,9 +30,12 @@ const (
|
|||
)
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
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)
|
||||
},
|
||||
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 {
|
||||
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
|
||||
// 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 {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
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++ {
|
||||
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 {
|
||||
ws.Cols.Col[i].Min += offset
|
||||
ws.Cols.Col[i].Max += offset
|
||||
if ws.Cols.Col[i].Min += offset; ws.Cols.Col[i].Min > MaxColumns {
|
||||
ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
|
||||
i--
|
||||
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 len(ws.Cols.Col) > 1 {
|
||||
ws.Cols.Col = append(ws.Cols.Col[:i], ws.Cols.Col[i+1:]...)
|
||||
} else {
|
||||
ws.Cols.Col = nil
|
||||
}
|
||||
i--
|
||||
continue
|
||||
}
|
||||
if ws.Cols.Col[i].Min > col {
|
||||
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
|
||||
}
|
||||
}
|
||||
if len(ws.Cols.Col) == 0 {
|
||||
ws.Cols = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -274,7 +273,7 @@ func (f *File) adjustFormula(sheet, sheetN string, formula *xlsxF, dir adjustDir
|
|||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -297,62 +296,60 @@ func escapeSheetName(name string) string {
|
|||
if strings.IndexFunc(name, func(r rune) bool {
|
||||
return !unicode.IsLetter(r) && !unicode.IsNumber(r)
|
||||
}) != -1 {
|
||||
return string(efp.QuoteSingle) + name + string(efp.QuoteSingle)
|
||||
return "'" + strings.ReplaceAll(name, "'", "''") + "'"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return name, err
|
||||
return "", operand, false, err
|
||||
}
|
||||
if dir == columns && col >= num {
|
||||
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.
|
||||
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)
|
||||
if dir == rows && row >= num {
|
||||
row += offset
|
||||
if row > TotalRows {
|
||||
return name, ErrMaxRows
|
||||
if row <= 0 || row > TotalRows {
|
||||
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.
|
||||
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)
|
||||
func adjustFormulaOperandRef(row, col, operand string, abs, keepRelative bool, dir adjustDirection, num int, offset int) (string, string, string, bool, error) {
|
||||
var err error
|
||||
col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
|
||||
if err != nil {
|
||||
return row, col, operand, err
|
||||
return row, col, operand, abs, 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
|
||||
row, operand, abs, err = adjustFormulaRowNumber(row, operand, abs, keepRelative, dir, num, offset)
|
||||
return row, col, operand, abs, err
|
||||
}
|
||||
|
||||
// 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 (
|
||||
err error
|
||||
abs bool
|
||||
sheetName, col, row, operand string
|
||||
cell = 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
|
||||
}
|
||||
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') {
|
||||
col += string(r)
|
||||
continue
|
||||
}
|
||||
if '0' <= r && r <= '9' {
|
||||
row += string(r)
|
||||
if col != "" {
|
||||
name, err := adjustFormulaColumnName(col, dir, num, offset)
|
||||
col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
|
||||
if err != nil {
|
||||
return operand, err
|
||||
}
|
||||
operand += name
|
||||
col = ""
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// adjustFormulaRef returns adjusted formula by giving adjusting direction and
|
||||
// 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 (
|
||||
val string
|
||||
definedNames []string
|
||||
|
@ -404,6 +405,10 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, dir adjustDirecti
|
|||
}
|
||||
}
|
||||
for _, token := range ps.Parse(formula) {
|
||||
if token.TType == efp.TokenTypeUnknown {
|
||||
val = formula
|
||||
break
|
||||
}
|
||||
if token.TType == efp.TokenTypeOperand && token.TSubType == efp.TokenSubTypeRange {
|
||||
if inStrSlice(definedNames, token.TValue, true) != -1 {
|
||||
val += token.TValue
|
||||
|
@ -413,7 +418,7 @@ func (f *File) adjustFormulaRef(sheet, sheetN, formula string, dir adjustDirecti
|
|||
val += token.TValue
|
||||
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 {
|
||||
return val, err
|
||||
}
|
||||
|
@ -467,7 +472,7 @@ func (f *File) adjustHyperlinks(ws *xlsxWorksheet, sheet string, dir adjustDirec
|
|||
}
|
||||
for i := range ws.Hyperlinks.Hyperlink {
|
||||
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{}
|
||||
if err := f.xmlNewDecoder(bytes.NewReader(namespaceStrictToTransitional(content.([]byte)))).
|
||||
Decode(&t); err != nil && err != io.EOF {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
coordinates, err := rangeRefToCoordinates(t.Ref)
|
||||
if err != nil {
|
||||
|
@ -884,3 +889,21 @@ func (f *File) adjustDrawings(ws *xlsxWorksheet, sheet string, dir adjustDirecti
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ func TestAdjustTable(t *testing.T) {
|
|||
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))
|
||||
assert.EqualError(t, f.RemoveRow(sheetName, 1), "XML syntax error on line 1: invalid UTF-8")
|
||||
// Test adjust table with invalid table range reference
|
||||
f.Pkg.Store("xl/tables/table1.xml", []byte(`<table ref="-" />`))
|
||||
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.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) {
|
||||
|
@ -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, 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)
|
||||
_, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", columns, 0, 1)
|
||||
_, err = f.adjustFormulaRef("Sheet1", "Sheet1", "XFD1", false, columns, 0, 1)
|
||||
assert.Equal(t, ErrColumnNumber, err)
|
||||
|
||||
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>`))
|
||||
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")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue