New function `GetPanes` for get sheet panes and view selection (#1556)

- Breaking changes: rename exported type `PaneOptions` to `Selection`
- Update unit tests
- Upgrade dependencies package
- Add internal error variables
- Simplify variable declarations
This commit is contained in:
vb6iscool 2023-06-08 09:50:38 +08:00 committed by GitHub
parent 661c0eade9
commit 78c974d855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 138 additions and 71 deletions

View File

@ -100,6 +100,12 @@ func newViewIdxError(viewIndex int) error {
return fmt.Errorf("view index %d out of range", viewIndex)
}
// newUnknownFilterTokenError defined the error message on receiving a unknown
// filter operator token.
func newUnknownFilterTokenError(token string) error {
return fmt.Errorf("unknown operator: %s", token)
}
var (
// ErrStreamSetColWidth defined the error message on set column width in
// stream writing mode.

4
go.mod
View File

@ -8,9 +8,9 @@ require (
github.com/stretchr/testify v1.8.0
github.com/xuri/efp v0.0.0-20230422071738-01f4e37c47e9
github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83
golang.org/x/crypto v0.8.0
golang.org/x/crypto v0.9.0
golang.org/x/image v0.5.0
golang.org/x/net v0.9.0
golang.org/x/net v0.10.0
golang.org/x/text v0.9.0
)

12
go.sum
View File

@ -22,8 +22,8 @@ github.com/xuri/nfp v0.0.0-20230503010013-3f38cdbb0b83/go.mod h1:WwHg+CVyzlv/TX9
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -32,8 +32,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -43,11 +43,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

View File

@ -1018,8 +1018,13 @@ var (
// applyBuiltInNumFmt provides a function to returns a value after formatted
// with built-in number format code, or specified sort date format code.
func (f *File) applyBuiltInNumFmt(c *xlsxC, fmtCode string, numFmtID int, date1904 bool, cellType CellType) string {
if numFmtID == 14 && f.options != nil && f.options.ShortDatePattern != "" {
fmtCode = f.options.ShortDatePattern
if f.options != nil && f.options.ShortDatePattern != "" {
if numFmtID == 14 {
fmtCode = f.options.ShortDatePattern
}
if numFmtID == 22 {
fmtCode = fmt.Sprintf("%s hh:mm", f.options.ShortDatePattern)
}
}
return format(c.V, fmtCode, date1904, cellType, f.options)
}
@ -1073,9 +1078,6 @@ func (f *File) langNumFmtFuncZhCN(numFmtID int) string {
// getBuiltInNumFmtCode convert number format index to number format code with
// specified locale and language.
func (f *File) getBuiltInNumFmtCode(numFmtID int) (string, bool) {
if numFmtID == 22 && f.options.ShortDatePattern != "" {
return fmt.Sprintf("%s hh:mm", f.options.ShortDatePattern), true
}
if fmtCode, ok := builtInNumFmt[numFmtID]; ok {
return fmtCode, true
}

View File

@ -772,7 +772,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
}
}
var s []*xlsxSelection
for _, p := range panes.Panes {
for _, p := range panes.Selection {
s = append(s, &xlsxSelection{
ActiveCell: p.ActiveCell,
Pane: p.Pane,
@ -859,7 +859,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
// YSplit: 0,
// TopLeftCell: "B1",
// ActivePane: "topRight",
// Panes: []excelize.PaneOptions{
// Selection: []excelize.Selection{
// {SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
// },
// })
@ -874,7 +874,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
// YSplit: 9,
// TopLeftCell: "A34",
// ActivePane: "bottomLeft",
// Panes: []excelize.PaneOptions{
// Selection: []excelize.Selection{
// {SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
// },
// })
@ -889,7 +889,7 @@ func (ws *xlsxWorksheet) setPanes(panes *Panes) error {
// YSplit: 1800,
// TopLeftCell: "N57",
// ActivePane: "bottomLeft",
// Panes: []excelize.PaneOptions{
// Selection: []excelize.Selection{
// {SQRef: "I36", ActiveCell: "I36"},
// {SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
// {SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
@ -908,6 +908,50 @@ func (f *File) SetPanes(sheet string, panes *Panes) error {
return ws.setPanes(panes)
}
// getPanes returns freeze panes, split panes, and views of the worksheet.
func (ws *xlsxWorksheet) getPanes() Panes {
var (
panes Panes
section []Selection
)
if ws.SheetViews == nil || len(ws.SheetViews.SheetView) < 1 {
return panes
}
sw := ws.SheetViews.SheetView[len(ws.SheetViews.SheetView)-1]
for _, s := range sw.Selection {
if s != nil {
section = append(section, Selection{
SQRef: s.SQRef,
ActiveCell: s.ActiveCell,
Pane: s.Pane,
})
}
}
panes.Selection = section
if sw.Pane == nil {
return panes
}
panes.ActivePane = sw.Pane.ActivePane
if sw.Pane.State == "frozen" {
panes.Freeze = true
}
panes.TopLeftCell = sw.Pane.TopLeftCell
panes.XSplit = int(sw.Pane.XSplit)
panes.YSplit = int(sw.Pane.YSplit)
return panes
}
// GetPanes provides a function to get freeze panes, split panes, and worksheet
// views by given worksheet name.
func (f *File) GetPanes(sheet string) (Panes, error) {
var panes Panes
ws, err := f.workSheetReader(sheet)
if err != nil {
return panes, err
}
return ws.getPanes(), err
}
// GetSheetVisible provides a function to get worksheet visible by given worksheet
// name. For example, get visible state of Sheet1:
//

View File

@ -37,25 +37,29 @@ func TestNewSheet(t *testing.T) {
assert.Equal(t, -1, sheetID)
}
func TestSetPanes(t *testing.T) {
func TestPanes(t *testing.T) {
f := NewFile()
assert.NoError(t, f.SetPanes("Sheet1", &Panes{Freeze: false, Split: false}))
_, err := f.NewSheet("Panes 2")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 2",
&Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
expected := Panes{
Freeze: true,
Split: false,
XSplit: 1,
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
))
}
assert.NoError(t, f.SetPanes("Panes 2", &expected))
panes, err := f.GetPanes("Panes 2")
assert.NoError(t, err)
assert.Equal(t, expected, panes)
_, err = f.NewSheet("Panes 3")
assert.NoError(t, err)
assert.NoError(t, f.SetPanes("Panes 3",
@ -66,7 +70,7 @@ func TestSetPanes(t *testing.T) {
YSplit: 1800,
TopLeftCell: "N57",
ActivePane: "bottomLeft",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "I36", ActiveCell: "I36"},
{SQRef: "G33", ActiveCell: "G33", Pane: "topRight"},
{SQRef: "J60", ActiveCell: "J60", Pane: "bottomLeft"},
@ -84,7 +88,7 @@ func TestSetPanes(t *testing.T) {
YSplit: 9,
TopLeftCell: "A34",
ActivePane: "bottomLeft",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "A11:XFD11", ActiveCell: "A11", Pane: "bottomLeft"},
},
},
@ -94,6 +98,26 @@ func TestSetPanes(t *testing.T) {
// Test set panes with invalid sheet name
assert.EqualError(t, f.SetPanes("Sheet:1", &Panes{Freeze: false, Split: false}), ErrSheetNameInvalid.Error())
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestSetPane.xlsx")))
// Test get panes with empty sheet views
f = NewFile()
ws, ok := f.Sheet.Load("xl/worksheets/sheet1.xml")
assert.True(t, ok)
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{}
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes without panes
ws.(*xlsxWorksheet).SheetViews = &xlsxSheetViews{SheetView: []xlsxSheetView{{}}}
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes without sheet views
ws.(*xlsxWorksheet).SheetViews = nil
_, err = f.GetPanes("Sheet1")
assert.NoError(t, err)
// Test get panes on not exists worksheet
_, err = f.GetPanes("SheetN")
assert.EqualError(t, err, "sheet SheetN does not exist")
// Test add pane on empty sheet views worksheet
f = NewFile()
f.checked = nil
@ -107,7 +131,7 @@ func TestSetPanes(t *testing.T) {
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
},

View File

@ -61,11 +61,7 @@ func (view *xlsxSheetView) setSheetView(opts *ViewOptions) {
view.TopLeftCell = *opts.TopLeftCell
}
if opts.View != nil {
if _, ok := map[string]interface{}{
"normal": nil,
"pageLayout": nil,
"pageBreakPreview": nil,
}[*opts.View]; ok {
if inStrSlice([]string{"normal", "pageLayout", "pageBreakPreview"}, *opts.View, true) != -1 {
view.View = *opts.View
}
}

View File

@ -173,7 +173,7 @@ func TestStreamSetPanes(t *testing.T) {
YSplit: 0,
TopLeftCell: "B1",
ActivePane: "topRight",
Panes: []PaneOptions{
Selection: []Selection{
{SQRef: "K16", ActiveCell: "K16", Pane: "topRight"},
},
}

View File

@ -430,22 +430,23 @@ func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string)
var filters []*xlsxFilter
filters = append(filters, &xlsxFilter{Val: tokens[0]})
fc.Filters = &xlsxFilters{Filter: filters}
} else if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
return
}
if len(exp) == 3 && exp[0] == 2 && exp[1] == 1 && exp[2] == 2 {
// Double equality with "or" operator.
var filters []*xlsxFilter
for _, v := range tokens {
filters = append(filters, &xlsxFilter{Val: v})
}
fc.Filters = &xlsxFilters{Filter: filters}
} else {
// Non default custom filter.
expRel := map[int]int{0: 0, 1: 2}
andRel := map[int]bool{0: true, 1: false}
for k, v := range tokens {
f.writeCustomFilter(fc, exp[expRel[k]], v)
if k == 1 {
fc.CustomFilters.And = andRel[exp[k]]
}
return
}
// Non default custom filter.
expRel, andRel := map[int]int{0: 0, 1: 2}, map[int]bool{0: true, 1: false}
for k, v := range tokens {
f.writeCustomFilter(fc, exp[expRel[k]], v)
if k == 1 {
fc.CustomFilters.And = andRel[exp[k]]
}
}
}
@ -467,11 +468,11 @@ func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string)
}
if fc.CustomFilters != nil {
fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter)
} else {
var customFilters []*xlsxCustomFilter
customFilters = append(customFilters, &customFilter)
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
return
}
var customFilters []*xlsxCustomFilter
customFilters = append(customFilters, &customFilter)
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
}
// parseFilterExpression provides a function to converts the tokens of a
@ -488,8 +489,7 @@ func (f *File) parseFilterExpression(expression string, tokens []string) ([]int,
if len(tokens) == 7 {
// The number of tokens will be either 3 (for 1 expression) or 7 (for 2
// expressions).
conditional := 0
c := tokens[3]
conditional, c := 0, tokens[3]
if conditionFormat.MatchString(c) {
conditional = 1
}
@ -501,17 +501,13 @@ func (f *File) parseFilterExpression(expression string, tokens []string) ([]int,
if err != nil {
return expressions, t, err
}
expressions = []int{expression1[0], conditional, expression2[0]}
t = []string{token1, token2}
} else {
exp, token, err := f.parseFilterTokens(expression, tokens)
if err != nil {
return expressions, t, err
}
expressions = exp
t = []string{token}
return []int{expression1[0], conditional, expression2[0]}, []string{token1, token2}, nil
}
return expressions, t, nil
exp, token, err := f.parseFilterTokens(expression, tokens)
if err != nil {
return expressions, t, err
}
return exp, []string{token}, nil
}
// parseFilterTokens provides a function to parse the 3 tokens of a filter
@ -534,7 +530,7 @@ func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, str
operator, ok := operators[strings.ToLower(tokens[1])]
if !ok {
// Convert the operator from a number to a descriptive string.
return []int{}, "", fmt.Errorf("unknown operator: %s", tokens[1])
return []int{}, "", newUnknownFilterTokenError(tokens[1])
}
token := tokens[2]
// Special handling for Blanks/NonBlanks.
@ -563,8 +559,7 @@ func (f *File) parseFilterTokens(expression string, tokens []string) ([]int, str
}
// If the string token contains an Excel match character then change the
// operator type to indicate a non "simple" equality.
re = matchFormat.MatchString(token)
if operator == 2 && re {
if re = matchFormat.MatchString(token); operator == 2 && re {
operator = 22
}
return []int{operator}, token, nil

View File

@ -894,8 +894,8 @@ type SparklineOptions struct {
EmptyCells string
}
// PaneOptions directly maps the settings of the pane.
type PaneOptions struct {
// Selection directly maps the settings of the worksheet selection.
type Selection struct {
SQRef string
ActiveCell string
Pane string
@ -909,7 +909,7 @@ type Panes struct {
YSplit int
TopLeftCell string
ActivePane string
Panes []PaneOptions
Selection []Selection
}
// ConditionalFormatOptions directly maps the conditional format settings of the cells.