mirror of
https://github.com/unidoc/unioffice.git
synced 2025-05-02 22:17:07 +08:00
spreadsheet: support adding/removing an auto filter
This commit is contained in:
parent
d8554f54de
commit
f70810321d
35
_examples/spreadsheet/sort-filter/main.go
Normal file
35
_examples/spreadsheet/sort-filter/main.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2017 Baliance. All rights reserved.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"baliance.com/gooxml/spreadsheet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
// add a single sheet
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
hdrRow := sheet.AddRow()
|
||||||
|
hdrRow.AddCell().SetString("Product Name")
|
||||||
|
hdrRow.AddCell().SetString("Quantity")
|
||||||
|
hdrRow.AddCell().SetString("Price")
|
||||||
|
sheet.SetAutoFilter("A1:C6")
|
||||||
|
|
||||||
|
// rows
|
||||||
|
for r := 0; r < 5; r++ {
|
||||||
|
row := sheet.AddRow()
|
||||||
|
row.AddCell().SetString(fmt.Sprintf("Product %d", r+1))
|
||||||
|
row.AddCell().SetNumber(float64(r + 2))
|
||||||
|
row.AddCell().SetNumber(float64(3*r + 1))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ss.Validate(); err != nil {
|
||||||
|
log.Fatalf("error validating sheet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.SaveToFile("sort-filter.xlsx")
|
||||||
|
}
|
BIN
_examples/spreadsheet/sort-filter/sort-filter.xlsx
Normal file
BIN
_examples/spreadsheet/sort-filter/sort-filter.xlsx
Normal file
Binary file not shown.
@ -8,6 +8,7 @@
|
|||||||
package spreadsheet
|
package spreadsheet
|
||||||
|
|
||||||
import sml "baliance.com/gooxml/schema/schemas.openxmlformats.org/spreadsheetml"
|
import sml "baliance.com/gooxml/schema/schemas.openxmlformats.org/spreadsheetml"
|
||||||
|
import "baliance.com/gooxml"
|
||||||
|
|
||||||
// DefinedName is a named range, formula, etc.
|
// DefinedName is a named range, formula, etc.
|
||||||
type DefinedName struct {
|
type DefinedName struct {
|
||||||
@ -28,3 +29,18 @@ func (d DefinedName) Name() string {
|
|||||||
func (d DefinedName) Content() string {
|
func (d DefinedName) Content() string {
|
||||||
return d.x.Content
|
return d.x.Content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetContent sets the defined name content.
|
||||||
|
func (d DefinedName) SetContent(s string) {
|
||||||
|
d.x.Content = s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHidden marks the defined name as hidden.
|
||||||
|
func (d DefinedName) SetHidden(b bool) {
|
||||||
|
d.x.HiddenAttr = gooxml.Bool(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHidden marks the defined name as hidden.
|
||||||
|
func (d DefinedName) SetLocalSheetID(id uint32) {
|
||||||
|
d.x.LocalSheetIdAttr = gooxml.Uint32(id)
|
||||||
|
}
|
||||||
|
@ -25,6 +25,11 @@ type Sheet struct {
|
|||||||
x *sml.Worksheet
|
x *sml.Worksheet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// X returns the inner wrapped XML type.
|
||||||
|
func (s Sheet) X() *sml.Worksheet {
|
||||||
|
return s.x
|
||||||
|
}
|
||||||
|
|
||||||
// Row will return a row with a given row number, creating a new row if
|
// Row will return a row with a given row number, creating a new row if
|
||||||
// necessary.
|
// necessary.
|
||||||
func (s Sheet) Row(rowNum uint32) Row {
|
func (s Sheet) Row(rowNum uint32) Row {
|
||||||
@ -193,3 +198,56 @@ func (s Sheet) RangeReference(n string) string {
|
|||||||
to := fmt.Sprintf("$%s$%d", tc, tr)
|
to := fmt.Sprintf("$%s$%d", tc, tr)
|
||||||
return fmt.Sprintf(`'%s'!%s:%s`, s.Name(), from, to)
|
return fmt.Sprintf(`'%s'!%s:%s`, s.Name(), from, to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoFilterName = "_xlnm._FilterDatabase"
|
||||||
|
|
||||||
|
// ClearAutoFilter removes the autofilters from the sheet.
|
||||||
|
func (s Sheet) ClearAutoFilter() {
|
||||||
|
s.x.AutoFilter = nil
|
||||||
|
sn := "'" + s.Name() + "'!"
|
||||||
|
// see if we have a defined auto filter name for the sheet
|
||||||
|
for _, dn := range s.w.DefinedNames() {
|
||||||
|
if dn.Name() == autoFilterName {
|
||||||
|
if strings.HasPrefix(dn.Content(), sn) {
|
||||||
|
s.w.RemoveDefinedName(dn)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAutoFilter creates autofilters on the sheet. These are the automatic
|
||||||
|
// filters that are common for a header row. The RangeRef should be of the form
|
||||||
|
// "A1:C5" and cover the entire range of cells to be filtered, not just the
|
||||||
|
// header. SetAutoFilter replaces any existing auto filter on the sheet.
|
||||||
|
func (s Sheet) SetAutoFilter(rangeRef string) {
|
||||||
|
// this should have no $ in it
|
||||||
|
rangeRef = strings.Replace(rangeRef, "$", "", -1)
|
||||||
|
|
||||||
|
s.x.AutoFilter = sml.NewCT_AutoFilter()
|
||||||
|
s.x.AutoFilter.RefAttr = gooxml.String(rangeRef)
|
||||||
|
sn := "'" + s.Name() + "'!"
|
||||||
|
var sdn DefinedName
|
||||||
|
|
||||||
|
// see if we already have a defined auto filter name for the sheet
|
||||||
|
for _, dn := range s.w.DefinedNames() {
|
||||||
|
if dn.Name() == autoFilterName {
|
||||||
|
if strings.HasPrefix(dn.Content(), sn) {
|
||||||
|
sdn = dn
|
||||||
|
// name must match, but make sure rangeRef matches as well
|
||||||
|
sdn.SetContent(s.RangeReference(rangeRef))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no existing name found, so add a new one
|
||||||
|
if sdn.X() == nil {
|
||||||
|
sdn = s.w.AddDefinedName(autoFilterName, s.RangeReference(rangeRef))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ws := range s.w.xws {
|
||||||
|
if ws == s.x {
|
||||||
|
sdn.SetLocalSheetID(uint32(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -82,3 +82,42 @@ func TestRowNumberValidation(t *testing.T) {
|
|||||||
t.Errorf("expected validation error with identically numbered rows")
|
t.Errorf("expected validation error with identically numbered rows")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAutoFilter(t *testing.T) {
|
||||||
|
wb := spreadsheet.New()
|
||||||
|
sheet := wb.AddSheet()
|
||||||
|
if len(wb.DefinedNames()) != 0 {
|
||||||
|
t.Errorf("expected no defined names for new workbook")
|
||||||
|
}
|
||||||
|
sheet.SetAutoFilter("A1:C10")
|
||||||
|
if len(wb.DefinedNames()) != 1 {
|
||||||
|
t.Errorf("expected a new defined names for the autofilter")
|
||||||
|
}
|
||||||
|
dn := wb.DefinedNames()[0]
|
||||||
|
expContent := "'Sheet 1'!$A$1:$C$10"
|
||||||
|
if dn.Content() != expContent {
|
||||||
|
t.Errorf("expected defined name content = '%s', got %s", expContent, dn.Content())
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.SetAutoFilter("A1:B10")
|
||||||
|
expContent = "'Sheet 1'!$A$1:$B$10"
|
||||||
|
// setting the filter again should re-write the defined name and not create a new one
|
||||||
|
if len(wb.DefinedNames()) != 1 {
|
||||||
|
t.Errorf("expected a new defined names for the autofilter")
|
||||||
|
}
|
||||||
|
dn = wb.DefinedNames()[0]
|
||||||
|
// but the content should have changed
|
||||||
|
if dn.Content() != expContent {
|
||||||
|
t.Errorf("expected defined name content = '%s', got %s", expContent, dn.Content())
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.ClearAutoFilter()
|
||||||
|
if len(wb.DefinedNames()) != 0 {
|
||||||
|
t.Errorf("clearing the filter should have removed the defined name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sheet.X().AutoFilter != nil {
|
||||||
|
t.Errorf("autofilter should have been nil after clear")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -351,6 +351,22 @@ func (wb *Workbook) AddDefinedName(name, ref string) DefinedName {
|
|||||||
return DefinedName{dn}
|
return DefinedName{dn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveDefinedName removes an existing defined name.
|
||||||
|
func (wb *Workbook) RemoveDefinedName(dn DefinedName) error {
|
||||||
|
if dn.X() == nil {
|
||||||
|
return errors.New("attempt to remove nil DefinedName")
|
||||||
|
}
|
||||||
|
for i, sdn := range wb.x.DefinedNames.DefinedName {
|
||||||
|
if sdn == dn.X() {
|
||||||
|
copy(wb.x.DefinedNames.DefinedName[i:], wb.x.DefinedNames.DefinedName[i+1:])
|
||||||
|
wb.x.DefinedNames.DefinedName[len(wb.x.DefinedNames.DefinedName)-1] = nil
|
||||||
|
wb.x.DefinedNames.DefinedName = wb.x.DefinedNames.DefinedName[:len(wb.x.DefinedNames.DefinedName)-1]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("defined name not found")
|
||||||
|
}
|
||||||
|
|
||||||
// DefinedNames returns a slice of all defined names in the workbook.
|
// DefinedNames returns a slice of all defined names in the workbook.
|
||||||
func (wb *Workbook) DefinedNames() []DefinedName {
|
func (wb *Workbook) DefinedNames() []DefinedName {
|
||||||
if wb.x.DefinedNames == nil {
|
if wb.x.DefinedNames == nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user