diff --git a/file.go b/file.go index 1d34a478..9733d421 100644 --- a/file.go +++ b/file.go @@ -296,6 +296,41 @@ func addRelationshipNameSpaceToWorksheet(worksheetMarshal string) string { return newSheetMarshall } +func cellIDStringWithFixed(cellIDString string) string { + letterPart := strings.Map(letterOnlyMapF, cellIDString) + intPart := strings.Map(intOnlyMapF, cellIDString) + + if letterPart != "" && intPart == "" { + return fixedCellRefChar + letterPart + } else if letterPart != "" && intPart != "" { + return fixedCellRefChar + letterPart + fixedCellRefChar + intPart + } + + return "" +} + +// AutoFilter doesn't work in LibreOffice unless a special "FilterDatabase" tag +// is present in the "DefinedNames" array. See: +// - https://github.com/SheetJS/sheetjs/issues/1165 +// - https://bugs.documentfoundation.org/show_bug.cgi?id=118592 +func autoFilterDefinedName(sheet *Sheet, sheetIndex int) (*xlsxDefinedName, error) { + if sheet.AutoFilter == nil { + return nil, nil + } + + return &xlsxDefinedName{ + Data: fmt.Sprintf( + "'%s'!%v:%v", + strings.ReplaceAll(sheet.Name, "'", "''"), + cellIDStringWithFixed(sheet.AutoFilter.TopLeftCell), + cellIDStringWithFixed(sheet.AutoFilter.BottomRightCell), + ), + Name: "_xlnm._FilterDatabase", + LocalSheetID: sheetIndex - 1, + Hidden: true, + }, nil +} + // MakeStreamParts constructs a map of file name to XML content // representing the file in terms of the structure of an XLSX file. func (f *File) MakeStreamParts() (map[string]string, error) { @@ -370,6 +405,14 @@ func (f *File) MakeStreamParts() (map[string]string, error) { return parts, err } } + + definedName, err := autoFilterDefinedName(sheet, sheetIndex) + if err != nil { + return parts, err + } else if definedName != nil { + workbook.DefinedNames.DefinedName = append(workbook.DefinedNames.DefinedName, *definedName) + } + sheetIndex++ } @@ -511,6 +554,14 @@ func (f *File) MarshallParts(zipWriter *zip.Writer) error { return wrap(err) } } + + definedName, err := autoFilterDefinedName(sheet, sheetIndex) + if err != nil { + return wrap(err) + } else if definedName != nil { + workbook.DefinedNames.DefinedName = append(workbook.DefinedNames.DefinedName, *definedName) + } + sheetIndex++ } diff --git a/file_test.go b/file_test.go index 14ec2687..532021f4 100644 --- a/file_test.go +++ b/file_test.go @@ -965,6 +965,25 @@ func TestFile(t *testing.T) { c.Assert(s.Hidden, qt.Equals, true) }) + csRunO(c, "TestMarshalFileWithAutoFilter", func(c *qt.C, option FileOption) { + var f *File + f = NewFile(option) + sheet1, _ := f.AddSheet("MySheet") + sheet1.AutoFilter = &AutoFilter{ + TopLeftCell: "A1", + BottomRightCell: "D", + } + + row1 := sheet1.AddRow() + cell1 := row1.AddCell() + cell1.SetString("A cell!") + + parts, err := f.MakeStreamParts() + c.Assert(err, qt.IsNil) + c.Assert(parts["xl/workbook.xml"], qt.Contains, ``) + c.Assert(parts["xl/worksheets/sheet1.xml"], qt.Contains, ``) + }) + // We can save a File as a valid XLSX file at a given path. csRunO(c, "TestSaveFileWithHyperlinks", func(c *qt.C, option FileOption) { tmpPath, err := ioutil.TempDir("", "testsavefilewithhyperlinks") diff --git a/xmlWorkbook.go b/xmlWorkbook.go index 18e743fa..6ad59683 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -141,7 +141,7 @@ type xlsxDefinedName struct { Help string `xml:"help,attr,omitempty"` ShortcutKey string `xml:"shortcutKey,attr,omitempty"` StatusBar string `xml:"statusBar,attr,omitempty"` - LocalSheetID int `xml:"localSheetId,attr,omitempty"` + LocalSheetID int `xml:"localSheetId,attr"` FunctionGroupID int `xml:"functionGroupId,attr,omitempty"` Function bool `xml:"function,attr,omitempty"` Hidden bool `xml:"hidden,attr,omitempty"`