forked from p30928647/excelize
Breaking change: changed the third parameter for the `AddFilter`
- Support to add multiple filter columns - Remove the exported type `AutoFilterListOptions` - Support to specify if show header row of the table - Update unit tests and documents of the function
This commit is contained in:
parent
f707b2d2da
commit
dc3bf331d5
|
@ -252,7 +252,7 @@ func (f *File) adjustTable(ws *xlsxWorksheet, sheet string, dir adjustDirection,
|
|||
if t.AutoFilter != nil {
|
||||
t.AutoFilter.Ref = t.Ref
|
||||
}
|
||||
_, _ = f.setTableHeader(sheet, x1, y1, x2)
|
||||
_, _ = f.setTableHeader(sheet, true, x1, y1, x2)
|
||||
table, _ := xml.Marshal(t)
|
||||
f.saveFileList(tableXML, table)
|
||||
}
|
||||
|
|
|
@ -412,7 +412,7 @@ func TestInsertCols(t *testing.T) {
|
|||
assert.NoError(t, f.SetCellHyperLink(sheet1, "A5", "https://github.com/xuri/excelize", "External"))
|
||||
assert.NoError(t, f.MergeCell(sheet1, "A1", "C3"))
|
||||
|
||||
assert.NoError(t, f.AutoFilter(sheet1, "A2:B2", &AutoFilterOptions{Column: "B", Expression: "x != blanks"}))
|
||||
assert.NoError(t, f.AutoFilter(sheet1, "A2:B2", []AutoFilterOptions{{Column: "B", Expression: "x != blanks"}}))
|
||||
assert.NoError(t, f.InsertCols(sheet1, "A", 1))
|
||||
|
||||
// Test insert column with illegal cell reference
|
||||
|
|
|
@ -320,7 +320,7 @@ func TestRemoveRow(t *testing.T) {
|
|||
t.FailNow()
|
||||
}
|
||||
|
||||
err = f.AutoFilter(sheet1, "A2:A2", &AutoFilterOptions{Column: "A", Expression: "x != blanks"})
|
||||
err = f.AutoFilter(sheet1, "A2:A2", []AutoFilterOptions{{Column: "A", Expression: "x != blanks"}})
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
|
98
table.go
98
table.go
|
@ -131,7 +131,7 @@ func (f *File) addSheetTable(sheet string, rID int) error {
|
|||
|
||||
// setTableHeader provides a function to set cells value in header row for the
|
||||
// table.
|
||||
func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
|
||||
func (f *File) setTableHeader(sheet string, showHeaderRow bool, x1, y1, x2 int) ([]*xlsxTableColumn, error) {
|
||||
var (
|
||||
tableColumns []*xlsxTableColumn
|
||||
idx int
|
||||
|
@ -144,11 +144,15 @@ func (f *File) setTableHeader(sheet string, x1, y1, x2 int) ([]*xlsxTableColumn,
|
|||
}
|
||||
name, _ := f.GetCellValue(sheet, cell)
|
||||
if _, err := strconv.Atoi(name); err == nil {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
if showHeaderRow {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
name = "Column" + strconv.Itoa(idx)
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
if showHeaderRow {
|
||||
_ = f.SetCellStr(sheet, cell, name)
|
||||
}
|
||||
}
|
||||
tableColumns = append(tableColumns, &xlsxTableColumn{
|
||||
ID: idx,
|
||||
|
@ -188,13 +192,16 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
|
|||
if y1 == y2 {
|
||||
y2++
|
||||
}
|
||||
|
||||
hideHeaderRow := opts != nil && opts.ShowHeaderRow != nil && !*opts.ShowHeaderRow
|
||||
if hideHeaderRow {
|
||||
y1++
|
||||
}
|
||||
// Correct table range reference, such correct C1:B3 to B1:C3.
|
||||
ref, err := f.coordinatesToRangeRef([]int{x1, y1, x2, y2})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tableColumns, _ := f.setTableHeader(sheet, x1, y1, x2)
|
||||
tableColumns, _ := f.setTableHeader(sheet, !hideHeaderRow, x1, y1, x2)
|
||||
name := opts.Name
|
||||
if name == "" {
|
||||
name = "Table" + strconv.Itoa(i)
|
||||
|
@ -220,6 +227,10 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
|
|||
ShowColumnStripes: opts.ShowColumnStripes,
|
||||
},
|
||||
}
|
||||
if hideHeaderRow {
|
||||
t.AutoFilter = nil
|
||||
t.HeaderRowCount = intPtr(0)
|
||||
}
|
||||
table, _ := xml.Marshal(t)
|
||||
f.saveFileList(tableXML, table)
|
||||
return nil
|
||||
|
@ -230,12 +241,12 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
|
|||
// way of filtering a 2D range of data based on some simple criteria. For
|
||||
// example applying an auto filter to a cell range A1:D4 in the Sheet1:
|
||||
//
|
||||
// err := f.AutoFilter("Sheet1", "A1:D4", nil)
|
||||
// err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{})
|
||||
//
|
||||
// Filter data in an auto filter:
|
||||
//
|
||||
// err := f.AutoFilter("Sheet1", "A1:D4", &excelize.AutoFilterOptions{
|
||||
// Column: "B", Expression: "x != blanks",
|
||||
// err := f.AutoFilter("Sheet1", "A1:D4", []excelize.AutoFilterOptions{
|
||||
// {Column: "B", Expression: "x != blanks"},
|
||||
// })
|
||||
//
|
||||
// Column defines the filter columns in an auto filter range based on simple
|
||||
|
@ -296,7 +307,7 @@ func (f *File) addTable(sheet, tableXML string, x1, y1, x2, y2, i int, opts *Tab
|
|||
// x < 2000
|
||||
// col < 2000
|
||||
// Price < 2000
|
||||
func (f *File) AutoFilter(sheet, rangeRef string, opts *AutoFilterOptions) error {
|
||||
func (f *File) AutoFilter(sheet, rangeRef string, opts []AutoFilterOptions) error {
|
||||
coordinates, err := rangeRefToCoordinates(rangeRef)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -343,7 +354,7 @@ func (f *File) AutoFilter(sheet, rangeRef string, opts *AutoFilterOptions) error
|
|||
|
||||
// autoFilter provides a function to extract the tokens from the filter
|
||||
// expression. The tokens are mainly non-whitespace groups.
|
||||
func (f *File) autoFilter(sheet, ref string, columns, col int, opts *AutoFilterOptions) error {
|
||||
func (f *File) autoFilter(sheet, ref string, columns, col int, opts []AutoFilterOptions) error {
|
||||
ws, err := f.workSheetReader(sheet)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -356,66 +367,65 @@ func (f *File) autoFilter(sheet, ref string, columns, col int, opts *AutoFilterO
|
|||
Ref: ref,
|
||||
}
|
||||
ws.AutoFilter = filter
|
||||
if opts == nil || opts.Column == "" || opts.Expression == "" {
|
||||
return nil
|
||||
for _, opt := range opts {
|
||||
if opt.Column == "" || opt.Expression == "" {
|
||||
continue
|
||||
}
|
||||
fsCol, err := ColumnNameToNumber(opt.Column)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset := fsCol - col
|
||||
if offset < 0 || offset > columns {
|
||||
return fmt.Errorf("incorrect index of column '%s'", opt.Column)
|
||||
}
|
||||
fc := &xlsxFilterColumn{ColID: offset}
|
||||
re := regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
|
||||
token := re.FindAllString(opt.Expression, -1)
|
||||
if len(token) != 3 && len(token) != 7 {
|
||||
return fmt.Errorf("incorrect number of tokens in criteria '%s'", opt.Expression)
|
||||
}
|
||||
expressions, tokens, err := f.parseFilterExpression(opt.Expression, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.writeAutoFilter(fc, expressions, tokens)
|
||||
filter.FilterColumn = append(filter.FilterColumn, fc)
|
||||
}
|
||||
|
||||
fsCol, err := ColumnNameToNumber(opts.Column)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offset := fsCol - col
|
||||
if offset < 0 || offset > columns {
|
||||
return fmt.Errorf("incorrect index of column '%s'", opts.Column)
|
||||
}
|
||||
|
||||
filter.FilterColumn = append(filter.FilterColumn, &xlsxFilterColumn{
|
||||
ColID: offset,
|
||||
})
|
||||
re := regexp.MustCompile(`"(?:[^"]|"")*"|\S+`)
|
||||
token := re.FindAllString(opts.Expression, -1)
|
||||
if len(token) != 3 && len(token) != 7 {
|
||||
return fmt.Errorf("incorrect number of tokens in criteria '%s'", opts.Expression)
|
||||
}
|
||||
expressions, tokens, err := f.parseFilterExpression(opts.Expression, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.writeAutoFilter(filter, expressions, tokens)
|
||||
ws.AutoFilter = filter
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAutoFilter provides a function to check for single or double custom
|
||||
// filters as default filters and handle them accordingly.
|
||||
func (f *File) writeAutoFilter(filter *xlsxAutoFilter, exp []int, tokens []string) {
|
||||
func (f *File) writeAutoFilter(fc *xlsxFilterColumn, exp []int, tokens []string) {
|
||||
if len(exp) == 1 && exp[0] == 2 {
|
||||
// Single equality.
|
||||
var filters []*xlsxFilter
|
||||
filters = append(filters, &xlsxFilter{Val: tokens[0]})
|
||||
filter.FilterColumn[0].Filters = &xlsxFilters{Filter: filters}
|
||||
fc.Filters = &xlsxFilters{Filter: filters}
|
||||
} else 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})
|
||||
}
|
||||
filter.FilterColumn[0].Filters = &xlsxFilters{Filter: filters}
|
||||
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(filter, exp[expRel[k]], v)
|
||||
f.writeCustomFilter(fc, exp[expRel[k]], v)
|
||||
if k == 1 {
|
||||
filter.FilterColumn[0].CustomFilters.And = andRel[exp[k]]
|
||||
fc.CustomFilters.And = andRel[exp[k]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeCustomFilter provides a function to write the <customFilter> element.
|
||||
func (f *File) writeCustomFilter(filter *xlsxAutoFilter, operator int, val string) {
|
||||
func (f *File) writeCustomFilter(fc *xlsxFilterColumn, operator int, val string) {
|
||||
operators := map[int]string{
|
||||
1: "lessThan",
|
||||
2: "equal",
|
||||
|
@ -429,12 +439,12 @@ func (f *File) writeCustomFilter(filter *xlsxAutoFilter, operator int, val strin
|
|||
Operator: operators[operator],
|
||||
Val: val,
|
||||
}
|
||||
if filter.FilterColumn[0].CustomFilters != nil {
|
||||
filter.FilterColumn[0].CustomFilters.CustomFilter = append(filter.FilterColumn[0].CustomFilters.CustomFilter, &customFilter)
|
||||
if fc.CustomFilters != nil {
|
||||
fc.CustomFilters.CustomFilter = append(fc.CustomFilters.CustomFilter, &customFilter)
|
||||
} else {
|
||||
var customFilters []*xlsxCustomFilter
|
||||
customFilters = append(customFilters, &customFilter)
|
||||
filter.FilterColumn[0].CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
|
||||
fc.CustomFilters = &xlsxCustomFilters{CustomFilter: customFilters}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,12 +16,14 @@ func TestAddTable(t *testing.T) {
|
|||
assert.NoError(t, f.AddTable("Sheet2", "A2:B5", &TableOptions{
|
||||
Name: "table",
|
||||
StyleName: "TableStyleMedium2",
|
||||
ShowColumnStripes: true,
|
||||
ShowFirstColumn: true,
|
||||
ShowLastColumn: true,
|
||||
ShowRowStripes: boolPtr(true),
|
||||
ShowColumnStripes: true,
|
||||
},
|
||||
))
|
||||
}))
|
||||
assert.NoError(t, f.AddTable("Sheet2", "D1:D11", &TableOptions{
|
||||
ShowHeaderRow: boolPtr(false),
|
||||
}))
|
||||
assert.NoError(t, f.AddTable("Sheet2", "F1:F1", &TableOptions{StyleName: "TableStyleMedium8"}))
|
||||
|
||||
// Test add table in not exist worksheet
|
||||
|
@ -60,7 +62,7 @@ func TestAddTable(t *testing.T) {
|
|||
|
||||
func TestSetTableHeader(t *testing.T) {
|
||||
f := NewFile()
|
||||
_, err := f.setTableHeader("Sheet1", 1, 0, 1)
|
||||
_, err := f.setTableHeader("Sheet1", true, 1, 0, 1)
|
||||
assert.EqualError(t, err, "invalid cell reference [1, 0]")
|
||||
}
|
||||
|
||||
|
@ -68,16 +70,16 @@ func TestAutoFilter(t *testing.T) {
|
|||
outFile := filepath.Join("test", "TestAutoFilter%d.xlsx")
|
||||
f, err := prepareTestBook1()
|
||||
assert.NoError(t, err)
|
||||
for i, opts := range []*AutoFilterOptions{
|
||||
nil,
|
||||
{Column: "B", Expression: ""},
|
||||
{Column: "B", Expression: "x != blanks"},
|
||||
{Column: "B", Expression: "x == blanks"},
|
||||
{Column: "B", Expression: "x != nonblanks"},
|
||||
{Column: "B", Expression: "x == nonblanks"},
|
||||
{Column: "B", Expression: "x <= 1 and x >= 2"},
|
||||
{Column: "B", Expression: "x == 1 or x == 2"},
|
||||
{Column: "B", Expression: "x == 1 or x == 2*"},
|
||||
for i, opts := range [][]AutoFilterOptions{
|
||||
{},
|
||||
{{Column: "B", Expression: ""}},
|
||||
{{Column: "B", Expression: "x != blanks"}},
|
||||
{{Column: "B", Expression: "x == blanks"}},
|
||||
{{Column: "B", Expression: "x != nonblanks"}},
|
||||
{{Column: "B", Expression: "x == nonblanks"}},
|
||||
{{Column: "B", Expression: "x <= 1 and x >= 2"}},
|
||||
{{Column: "B", Expression: "x == 1 or x == 2"}},
|
||||
{{Column: "B", Expression: "x == 1 or x == 2*"}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
|
||||
assert.NoError(t, f.AutoFilter("Sheet1", "D4:B1", opts))
|
||||
|
@ -100,13 +102,13 @@ func TestAutoFilterError(t *testing.T) {
|
|||
outFile := filepath.Join("test", "TestAutoFilterError%d.xlsx")
|
||||
f, err := prepareTestBook1()
|
||||
assert.NoError(t, err)
|
||||
for i, opts := range []*AutoFilterOptions{
|
||||
{Column: "B", Expression: "x <= 1 and x >= blanks"},
|
||||
{Column: "B", Expression: "x -- y or x == *2*"},
|
||||
{Column: "B", Expression: "x != y or x ? *2"},
|
||||
{Column: "B", Expression: "x -- y o r x == *2"},
|
||||
{Column: "B", Expression: "x -- y"},
|
||||
{Column: "A", Expression: "x -- y"},
|
||||
for i, opts := range [][]AutoFilterOptions{
|
||||
{{Column: "B", Expression: "x <= 1 and x >= blanks"}},
|
||||
{{Column: "B", Expression: "x -- y or x == *2*"}},
|
||||
{{Column: "B", Expression: "x != y or x ? *2"}},
|
||||
{{Column: "B", Expression: "x -- y o r x == *2"}},
|
||||
{{Column: "B", Expression: "x -- y"}},
|
||||
{{Column: "A", Expression: "x -- y"}},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("Expression%d", i+1), func(t *testing.T) {
|
||||
if assert.Error(t, f.AutoFilter("Sheet2", "D4:B1", opts)) {
|
||||
|
@ -115,22 +117,22 @@ func TestAutoFilterError(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, &AutoFilterOptions{
|
||||
assert.EqualError(t, f.autoFilter("SheetN", "A1", 1, 1, []AutoFilterOptions{{
|
||||
Column: "A",
|
||||
Expression: "",
|
||||
}), "sheet SheetN does not exist")
|
||||
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &AutoFilterOptions{
|
||||
}}), "sheet SheetN does not exist")
|
||||
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{
|
||||
Column: "-",
|
||||
Expression: "-",
|
||||
}), newInvalidColumnNameError("-").Error())
|
||||
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, &AutoFilterOptions{
|
||||
}}), newInvalidColumnNameError("-").Error())
|
||||
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 100, []AutoFilterOptions{{
|
||||
Column: "A",
|
||||
Expression: "-",
|
||||
}), `incorrect index of column 'A'`)
|
||||
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, &AutoFilterOptions{
|
||||
}}), `incorrect index of column 'A'`)
|
||||
assert.EqualError(t, f.autoFilter("Sheet1", "A1", 1, 1, []AutoFilterOptions{{
|
||||
Column: "A",
|
||||
Expression: "-",
|
||||
}), `incorrect number of tokens in criteria '-'`)
|
||||
}}), `incorrect number of tokens in criteria '-'`)
|
||||
}
|
||||
|
||||
func TestParseFilterTokens(t *testing.T) {
|
||||
|
|
12
xmlTable.go
12
xmlTable.go
|
@ -26,7 +26,7 @@ type xlsxTable struct {
|
|||
DisplayName string `xml:"displayName,attr,omitempty"`
|
||||
HeaderRowBorderDxfID int `xml:"headerRowBorderDxfId,attr,omitempty"`
|
||||
HeaderRowCellStyle string `xml:"headerRowCellStyle,attr,omitempty"`
|
||||
HeaderRowCount int `xml:"headerRowCount,attr,omitempty"`
|
||||
HeaderRowCount *int `xml:"headerRowCount,attr"`
|
||||
HeaderRowDxfID int `xml:"headerRowDxfId,attr,omitempty"`
|
||||
ID int `xml:"id,attr"`
|
||||
InsertRow bool `xml:"insertRow,attr,omitempty"`
|
||||
|
@ -200,21 +200,15 @@ type xlsxTableStyleInfo struct {
|
|||
type TableOptions struct {
|
||||
Name string
|
||||
StyleName string
|
||||
ShowColumnStripes bool
|
||||
ShowFirstColumn bool
|
||||
ShowHeaderRow *bool
|
||||
ShowLastColumn bool
|
||||
ShowRowStripes *bool
|
||||
ShowColumnStripes bool
|
||||
}
|
||||
|
||||
// AutoFilterListOptions directly maps the auto filter list settings.
|
||||
type AutoFilterListOptions struct {
|
||||
Column string
|
||||
Value []int
|
||||
}
|
||||
|
||||
// AutoFilterOptions directly maps the auto filter settings.
|
||||
type AutoFilterOptions struct {
|
||||
Column string
|
||||
Expression string
|
||||
FilterList []AutoFilterListOptions
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue