已验证 提交 a0252bd0 编写于 作者: xurime's avatar xurime

Support update defined names on inserting/deleting columns/rows

上级 6cc1a547
......@@ -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
continue
}
if ws.Cols.Col[i].Min < col && ws.Cols.Col[i].Max >= col {
ws.Cols.Col[i].Max += offset
}
}
if offset < 0 {
if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
if len(ws.Cols.Col) > 1 {
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:]...)
} else {
ws.Cols.Col = nil
i--
continue
}
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 {
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
}
}
continue
}
if ws.Cols.Col[i].Min == col && ws.Cols.Col[i].Max == col {
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].Min += offset
}
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)
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 = ""
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, abs, err
}
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)
if err != nil {
return operand, err
}
operand += name
col = ""
col, operand, abs, err = adjustFormulaColumnName(col, operand, abs, keepRelative, dir, num, offset)
if err != nil {
return operand, err
}
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")
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册