diff --git a/adjust.go b/adjust.go index 95832c2b..b6e16e74 100644 --- a/adjust.go +++ b/adjust.go @@ -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) } diff --git a/col_test.go b/col_test.go index 8e15bebc..0e686a95 100644 --- a/col_test.go +++ b/col_test.go @@ -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 diff --git a/rows_test.go b/rows_test.go index 8b5008f9..5de8d397 100644 --- a/rows_test.go +++ b/rows_test.go @@ -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() } diff --git a/table.go b/table.go index a00b0a20..60cfb9ae 100644 --- a/table.go +++ b/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 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} } } diff --git a/table_test.go b/table_test.go index 33ce2e90..f55a5a0c 100644 --- a/table_test.go +++ b/table_test.go @@ -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) { diff --git a/xmlTable.go b/xmlTable.go index 5710bc06..0779a8e0 100644 --- a/xmlTable.go +++ b/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 }