diff --git a/errors.go b/errors.go index 96eed6fc..b8d2022f 100644 --- a/errors.go +++ b/errors.go @@ -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. diff --git a/go.mod b/go.mod index 176c00a8..58a3d715 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index c5062b39..35ed9c29 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/numfmt.go b/numfmt.go index b75117a6..01232544 100644 --- a/numfmt.go +++ b/numfmt.go @@ -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 } diff --git a/sheet.go b/sheet.go index 8d441dd4..bc35aa7e 100644 --- a/sheet.go +++ b/sheet.go @@ -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: // diff --git a/sheet_test.go b/sheet_test.go index cfed8fdb..8d42fd54 100644 --- a/sheet_test.go +++ b/sheet_test.go @@ -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"}, }, }, diff --git a/sheetview.go b/sheetview.go index 65b1354c..3ca3d8c4 100644 --- a/sheetview.go +++ b/sheetview.go @@ -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 } } diff --git a/stream_test.go b/stream_test.go index d5f3ed21..406de659 100644 --- a/stream_test.go +++ b/stream_test.go @@ -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"}, }, } diff --git a/table.go b/table.go index b63fe276..d59656da 100644 --- a/table.go +++ b/table.go @@ -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 diff --git a/xmlWorksheet.go b/xmlWorksheet.go index 79170dec..22ec03e3 100644 --- a/xmlWorksheet.go +++ b/xmlWorksheet.go @@ -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.