forked from p30928647/excelize
- correct adjust calculation chain in duplicate rows - correct adjust defined name in the workbook when delete worksheet - use absolute reference in the auto filters defined name to make it compatible with OpenOffice - API `CoordinatesToCellName` have a new optional param to specify if using an absolute reference format - Fix cyclomatic complexity issue of internal function `newFills` and `parseToken`
This commit is contained in:
parent
db7b4ee362
commit
1f329e8f96
12
adjust.go
12
adjust.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
|
@ -39,6 +39,7 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sheetID := f.getSheetID(sheet)
|
||||
if dir == rows {
|
||||
f.adjustRowDimensions(ws, num, offset)
|
||||
} else {
|
||||
|
@ -51,7 +52,7 @@ func (f *File) adjustHelper(sheet string, dir adjustDirection, num, offset int)
|
|||
if err = f.adjustAutoFilter(ws, dir, num, offset); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = f.adjustCalcChain(dir, num, offset); err != nil {
|
||||
if err = f.adjustCalcChain(dir, num, offset, sheetID); err != nil {
|
||||
return err
|
||||
}
|
||||
checkSheet(ws)
|
||||
|
@ -197,7 +198,7 @@ func (f *File) adjustAutoFilterHelper(dir adjustDirection, coordinates []int, nu
|
|||
// areaRefToCoordinates provides a function to convert area reference to a
|
||||
// pair of coordinates.
|
||||
func (f *File) areaRefToCoordinates(ref string) ([]int, error) {
|
||||
rng := strings.Split(ref, ":")
|
||||
rng := strings.Split(strings.Replace(ref, "$", "", -1), ":")
|
||||
return areaRangeToCoordinates(rng[0], rng[1])
|
||||
}
|
||||
|
||||
|
@ -310,11 +311,14 @@ func (f *File) deleteMergeCell(ws *xlsxWorksheet, idx int) {
|
|||
|
||||
// adjustCalcChain provides a function to update the calculation chain when
|
||||
// inserting or deleting rows or columns.
|
||||
func (f *File) adjustCalcChain(dir adjustDirection, num, offset int) error {
|
||||
func (f *File) adjustCalcChain(dir adjustDirection, num, offset, sheetID int) error {
|
||||
if f.CalcChain == nil {
|
||||
return nil
|
||||
}
|
||||
for index, c := range f.CalcChain.C {
|
||||
if c.I != sheetID {
|
||||
continue
|
||||
}
|
||||
colNum, rowNum, err := CellNameToCoordinates(c.R)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -98,13 +98,13 @@ func TestAdjustCalcChain(t *testing.T) {
|
|||
f := NewFile()
|
||||
f.CalcChain = &xlsxCalcChain{
|
||||
C: []xlsxCalcChainC{
|
||||
{R: "B2"},
|
||||
{R: "B2", I: 2}, {R: "B2", I: 1},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, f.InsertCol("Sheet1", "A"))
|
||||
assert.NoError(t, f.InsertRow("Sheet1", 1))
|
||||
|
||||
f.CalcChain.C[0].R = "invalid coordinates"
|
||||
f.CalcChain.C[1].R = "invalid coordinates"
|
||||
assert.EqualError(t, f.InsertCol("Sheet1", "A"), `cannot convert cell "invalid coordinates" to coordinates: invalid cell name "invalid coordinates"`)
|
||||
f.CalcChain = nil
|
||||
assert.NoError(t, f.InsertCol("Sheet1", "A"))
|
||||
|
|
116
calc.go
116
calc.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
|
@ -288,7 +288,7 @@ func getPriority(token efp.Token) (pri int) {
|
|||
if token.TValue == "-" && token.TType == efp.TokenTypeOperatorPrefix {
|
||||
pri = 6
|
||||
}
|
||||
if token.TSubType == efp.TokenSubTypeStart && token.TType == efp.TokenTypeSubexpression { // (
|
||||
if isBeginParenthesesToken(token) { // (
|
||||
pri = 0
|
||||
}
|
||||
return
|
||||
|
@ -356,7 +356,7 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
|
|||
}
|
||||
|
||||
// function start
|
||||
if token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart {
|
||||
if isFunctionStartToken(token) {
|
||||
opfStack.Push(token)
|
||||
argsStack.Push(list.New().Init())
|
||||
continue
|
||||
|
@ -436,44 +436,8 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
|
|||
Type: ArgString,
|
||||
})
|
||||
}
|
||||
|
||||
// current token is function stop
|
||||
if token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop {
|
||||
for !opftStack.Empty() {
|
||||
// calculate trigger
|
||||
topOpt := opftStack.Peek().(efp.Token)
|
||||
if err := calculate(opfdStack, topOpt); err != nil {
|
||||
return efp.Token{}, err
|
||||
}
|
||||
opftStack.Pop()
|
||||
}
|
||||
|
||||
// push opfd to args
|
||||
if opfdStack.Len() > 0 {
|
||||
argsStack.Peek().(*list.List).PushBack(formulaArg{
|
||||
String: opfdStack.Pop().(efp.Token).TValue,
|
||||
Type: ArgString,
|
||||
})
|
||||
}
|
||||
// call formula function to evaluate
|
||||
arg := callFuncByName(&formulaFuncs{}, strings.NewReplacer(
|
||||
"_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue),
|
||||
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
|
||||
if arg.Type == ArgError {
|
||||
return efp.Token{}, errors.New(arg.Value())
|
||||
}
|
||||
argsStack.Pop()
|
||||
opfStack.Pop()
|
||||
if opfStack.Len() > 0 { // still in function stack
|
||||
if nextToken.TType == efp.TokenTypeOperatorInfix {
|
||||
// mathematics calculate in formula function
|
||||
opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
||||
} else {
|
||||
argsStack.Peek().(*list.List).PushBack(arg)
|
||||
}
|
||||
} else {
|
||||
opdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
||||
}
|
||||
if err = evalInfixExpFunc(token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
|
||||
return efp.Token{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -490,6 +454,50 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
|
|||
return opdStack.Peek().(efp.Token), err
|
||||
}
|
||||
|
||||
// evalInfixExpFunc evaluate formula function in the infix expression.
|
||||
func evalInfixExpFunc(token, nextToken efp.Token, opfStack, opdStack, opftStack, opfdStack, argsStack *Stack) error {
|
||||
if !isFunctionStopToken(token) {
|
||||
return nil
|
||||
}
|
||||
// current token is function stop
|
||||
for !opftStack.Empty() {
|
||||
// calculate trigger
|
||||
topOpt := opftStack.Peek().(efp.Token)
|
||||
if err := calculate(opfdStack, topOpt); err != nil {
|
||||
return err
|
||||
}
|
||||
opftStack.Pop()
|
||||
}
|
||||
|
||||
// push opfd to args
|
||||
if opfdStack.Len() > 0 {
|
||||
argsStack.Peek().(*list.List).PushBack(formulaArg{
|
||||
String: opfdStack.Pop().(efp.Token).TValue,
|
||||
Type: ArgString,
|
||||
})
|
||||
}
|
||||
// call formula function to evaluate
|
||||
arg := callFuncByName(&formulaFuncs{}, strings.NewReplacer(
|
||||
"_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue),
|
||||
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
|
||||
if arg.Type == ArgError {
|
||||
return errors.New(arg.Value())
|
||||
}
|
||||
argsStack.Pop()
|
||||
opfStack.Pop()
|
||||
if opfStack.Len() > 0 { // still in function stack
|
||||
if nextToken.TType == efp.TokenTypeOperatorInfix {
|
||||
// mathematics calculate in formula function
|
||||
opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
||||
} else {
|
||||
argsStack.Peek().(*list.List).PushBack(arg)
|
||||
}
|
||||
} else {
|
||||
opdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcPow evaluate exponentiation arithmetic operations.
|
||||
func calcPow(rOpd, lOpd string, opdStack *Stack) error {
|
||||
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
|
||||
|
@ -722,6 +730,26 @@ func (f *File) parseOperatorPrefixToken(optStack, opdStack *Stack, token efp.Tok
|
|||
return
|
||||
}
|
||||
|
||||
// isFunctionStartToken determine if the token is function stop.
|
||||
func isFunctionStartToken(token efp.Token) bool {
|
||||
return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStart
|
||||
}
|
||||
|
||||
// isFunctionStopToken determine if the token is function stop.
|
||||
func isFunctionStopToken(token efp.Token) bool {
|
||||
return token.TType == efp.TokenTypeFunction && token.TSubType == efp.TokenSubTypeStop
|
||||
}
|
||||
|
||||
// isBeginParenthesesToken determine if the token is begin parentheses: (.
|
||||
func isBeginParenthesesToken(token efp.Token) bool {
|
||||
return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart
|
||||
}
|
||||
|
||||
// isEndParenthesesToken determine if the token is end parentheses: ).
|
||||
func isEndParenthesesToken(token efp.Token) bool {
|
||||
return token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStop
|
||||
}
|
||||
|
||||
// isOperatorPrefixToken determine if the token is parse operator prefix
|
||||
// token.
|
||||
func isOperatorPrefixToken(token efp.Token) bool {
|
||||
|
@ -771,11 +799,11 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
|
|||
return err
|
||||
}
|
||||
}
|
||||
if token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStart { // (
|
||||
if isBeginParenthesesToken(token) { // (
|
||||
optStack.Push(token)
|
||||
}
|
||||
if token.TType == efp.TokenTypeSubexpression && token.TSubType == efp.TokenSubTypeStop { // )
|
||||
for optStack.Peek().(efp.Token).TSubType != efp.TokenSubTypeStart && optStack.Peek().(efp.Token).TType != efp.TokenTypeSubexpression { // != (
|
||||
if isEndParenthesesToken(token) { // )
|
||||
for !isBeginParenthesesToken(optStack.Peek().(efp.Token)) { // != (
|
||||
topOpt := optStack.Peek().(efp.Token)
|
||||
if err := calculate(opdStack, topOpt); err != nil {
|
||||
return err
|
||||
|
|
13
lib.go
13
lib.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
|
@ -201,13 +201,20 @@ func CellNameToCoordinates(cell string) (int, int, error) {
|
|||
// Example:
|
||||
//
|
||||
// excelize.CoordinatesToCellName(1, 1) // returns "A1", nil
|
||||
// excelize.CoordinatesToCellName(1, 1, true) // returns "$A$1", nil
|
||||
//
|
||||
func CoordinatesToCellName(col, row int) (string, error) {
|
||||
func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
|
||||
if col < 1 || row < 1 {
|
||||
return "", fmt.Errorf("invalid cell coordinates [%d, %d]", col, row)
|
||||
}
|
||||
sign := ""
|
||||
for _, a := range abs {
|
||||
if a {
|
||||
sign = "$"
|
||||
}
|
||||
}
|
||||
colname, err := ColumnNumberToName(col)
|
||||
return colname + strconv.Itoa(row), err
|
||||
return sign + colname + sign + strconv.Itoa(row), err
|
||||
}
|
||||
|
||||
// boolPtr returns a pointer to a bool with the given value.
|
||||
|
|
20
sheet.go
20
sheet.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
|
@ -500,6 +500,22 @@ func (f *File) DeleteSheet(name string) {
|
|||
wb := f.workbookReader()
|
||||
wbRels := f.relsReader(f.getWorkbookRelsPath())
|
||||
activeSheetName := f.GetSheetName(f.GetActiveSheetIndex())
|
||||
deleteSheetID := f.getSheetID(name)
|
||||
// Delete and adjust defined names
|
||||
if wb.DefinedNames != nil {
|
||||
for idx := 0; idx < len(wb.DefinedNames.DefinedName); idx++ {
|
||||
dn := wb.DefinedNames.DefinedName[idx]
|
||||
if dn.LocalSheetID != nil {
|
||||
sheetID := *dn.LocalSheetID + 1
|
||||
if sheetID == deleteSheetID {
|
||||
wb.DefinedNames.DefinedName = append(wb.DefinedNames.DefinedName[:idx], wb.DefinedNames.DefinedName[idx+1:]...)
|
||||
idx--
|
||||
} else if sheetID > deleteSheetID {
|
||||
wb.DefinedNames.DefinedName[idx].LocalSheetID = intPtr(*dn.LocalSheetID - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for idx, sheet := range wb.Sheets.Sheet {
|
||||
if sheet.Name == sheetName {
|
||||
wb.Sheets.Sheet = append(wb.Sheets.Sheet[:idx], wb.Sheets.Sheet[idx+1:]...)
|
||||
|
@ -517,7 +533,7 @@ func (f *File) DeleteSheet(name string) {
|
|||
}
|
||||
target := f.deleteSheetFromWorkbookRels(sheet.ID)
|
||||
f.deleteSheetFromContentTypes(target)
|
||||
f.deleteCalcChain(sheet.SheetID, "") // Delete CalcChain
|
||||
f.deleteCalcChain(sheet.SheetID, "")
|
||||
delete(f.sheetMap, sheetName)
|
||||
delete(f.XLSX, sheetXML)
|
||||
delete(f.XLSX, rels)
|
||||
|
|
|
@ -383,6 +383,19 @@ func TestDeleteSheet(t *testing.T) {
|
|||
f.DeleteSheet("Sheet1")
|
||||
assert.Equal(t, "Sheet2", f.GetSheetName(f.GetActiveSheetIndex()))
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet.xlsx")))
|
||||
// Test with auto filter defined names
|
||||
f = NewFile()
|
||||
f.NewSheet("Sheet2")
|
||||
f.NewSheet("Sheet3")
|
||||
assert.NoError(t, f.SetCellValue("Sheet1", "A1", "A"))
|
||||
assert.NoError(t, f.SetCellValue("Sheet2", "A1", "A"))
|
||||
assert.NoError(t, f.SetCellValue("Sheet3", "A1", "A"))
|
||||
assert.NoError(t, f.AutoFilter("Sheet1", "A1", "A1", ""))
|
||||
assert.NoError(t, f.AutoFilter("Sheet2", "A1", "A1", ""))
|
||||
assert.NoError(t, f.AutoFilter("Sheet3", "A1", "A1", ""))
|
||||
f.DeleteSheet("Sheet2")
|
||||
f.DeleteSheet("Sheet1")
|
||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestDeleteSheet2.xlsx")))
|
||||
}
|
||||
|
||||
func BenchmarkNewSheet(b *testing.B) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
|
@ -2410,8 +2410,6 @@ func newFills(style *Style, fg bool) *xlsxFill {
|
|||
gradient.Left = 0.5
|
||||
gradient.Right = 0.5
|
||||
gradient.Top = 0.5
|
||||
default:
|
||||
break
|
||||
}
|
||||
var stops []*xlsxGradientFillStop
|
||||
for index, color := range style.Fill.Color {
|
||||
|
|
6
table.go
6
table.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of
|
||||
// Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
|
||||
// this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
//
|
||||
|
@ -287,8 +287,8 @@ func (f *File) AutoFilter(sheet, hcell, vcell, format string) error {
|
|||
}
|
||||
|
||||
formatSet, _ := parseAutoFilterSet(format)
|
||||
cellStart, _ := CoordinatesToCellName(hcol, hrow)
|
||||
cellEnd, _ := CoordinatesToCellName(vcol, vrow)
|
||||
cellStart, _ := CoordinatesToCellName(hcol, hrow, true)
|
||||
cellEnd, _ := CoordinatesToCellName(vcol, vrow, true)
|
||||
ref, filterDB := cellStart+":"+cellEnd, "_xlnm._FilterDatabase"
|
||||
wb := f.workbookReader()
|
||||
sheetID := f.GetSheetIndex(sheet)
|
||||
|
|
Loading…
Reference in New Issue