diff --git a/docs/images/autofilter.png b/docs/images/autofilter.png new file mode 100644 index 00000000..617f0988 Binary files /dev/null and b/docs/images/autofilter.png differ diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index e417f8a1..e8dff52d 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -177,6 +177,14 @@ typedef struct lxw_print_area { lxw_col_t last_col; } lxw_print_area; +typedef struct lxw_autofilter { + uint8_t in_use; + lxw_row_t first_row; + lxw_row_t last_row; + lxw_col_t first_col; + lxw_col_t last_col; +} lxw_autofilter; + /** * @brief Header and footer options. * @@ -269,6 +277,7 @@ typedef struct lxw_worksheet { struct lxw_repeat_rows repeat_rows; struct lxw_repeat_cols repeat_cols; struct lxw_print_area print_area; + struct lxw_autofilter autofilter; uint16_t merged_range_count; @@ -894,6 +903,42 @@ uint8_t worksheet_merge_range(lxw_worksheet *worksheet, lxw_row_t first_row, lxw_col_t last_col, const char *string, lxw_format *format); +/** + * @brief Set the autofilter area in the worksheet. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param first_row The first row of the range. (All zero indexed.) + * @param first_col The first column of the range. + * @param last_row The last row of the range. + * @param last_col The last col of the range. + * + * @return 0 for success, non-zero on error. + * + * The `%worksheet_autofilter()` method allows an autofilter to be added to a + * worksheet. + * + * An autofilter is a way of adding drop down lists to the headers of a 2D + * range of worksheet data. This allows users to filter the data based on + * simple criteria so that some data is shown and some is hidden. + * + * @image html autofilter.png + * + * To add an autofilter to a worksheet: + * + * @code + * worksheet_autofilter(worksheet, 0, 0, 50, 3); + * + * // Same as above using the RANGE() macro. + * worksheet_autofilter(worksheet, RANGE("A1:D51")); + * @endcode + * + * Note: it isn't currently possible to apply filter conditions to the + * autofilter. + */ +uint8_t worksheet_autofilter(lxw_worksheet *worksheet, lxw_row_t first_row, + lxw_col_t first_col, lxw_row_t last_row, + lxw_col_t last_col); + /** * @brief Make a worksheet the active, i.e., visible worksheet. * diff --git a/src/packager.c b/src/packager.c index c9b00410..8d87c6eb 100644 --- a/src/packager.c +++ b/src/packager.c @@ -187,7 +187,9 @@ _write_app_file(lxw_packager *self) /* Add the Named Ranges parts. */ TAILQ_FOREACH(defined_name, workbook->defined_names, list_pointers) { - tmp_name = strstr(defined_name->name, "_xlnm.ZZZ"); + + /*Ignore autofilters. */ + tmp_name = strstr(defined_name->name, "FilterDatabase"); if (!tmp_name) { _add_part_name(app, defined_name->app_name); diff --git a/src/workbook.c b/src/workbook.c index 76c47e76..d95d4fae 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -526,10 +526,33 @@ _prepare_defined_names(lxw_workbook *self) STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { + /* + * Check for autofilter settings. + */ + if (worksheet->autofilter.in_use) { + + /* Autofilters are different to other defined names. They are + * hidden and thus not stored in the App file. This entry + * will be filtered out in the Packager module. */ + strncat(app_name, "FilterDatabase", LXW_DEFINED_NAME_LENGTH - 1); + + lxw_range_abs(area, + worksheet->autofilter.first_row, + worksheet->autofilter.first_col, + worksheet->autofilter.last_row, + worksheet->autofilter.last_col); + + __builtin_snprintf(range, LXW_DEFINED_NAME_LENGTH - 1, "%s!%s", + worksheet->quoted_name, area); + + _store_defined_name(self, "_xlnm._FilterDatabase", app_name, + range, worksheet->index, LXW_TRUE); + } + /* * Check for Print Area settings. */ - if (worksheet->print_area.in_use) { + else if (worksheet->print_area.in_use) { __builtin_snprintf(app_name, LXW_DEFINED_NAME_LENGTH - 1, "%s!Print_Area", worksheet->quoted_name); diff --git a/src/worksheet.c b/src/worksheet.c index 08c71994..0dc952a1 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -1558,6 +1558,32 @@ _worksheet_write_col_breaks(lxw_worksheet *self) _FREE_ATTRIBUTES(); } +/* + * Write the element. + */ +STATIC void +_worksheet_write_auto_filter(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char range[MAX_CELL_RANGE_LENGTH]; + + if (!self->autofilter.in_use) + return; + + lxw_range(range, + self->autofilter.first_row, + self->autofilter.first_col, + self->autofilter.last_row, self->autofilter.last_col); + + _INIT_ATTRIBUTES(); + _PUSH_ATTRIBUTES_STR("ref", range); + + _xml_empty_tag(self->file, "autoFilter", &attributes); + + _FREE_ATTRIBUTES(); +} + /* * Assemble and write the XML file. */ @@ -1591,6 +1617,9 @@ _worksheet_assemble_xml_file(lxw_worksheet *self) else _worksheet_write_optimized_sheet_data(self); + /* Write the autoFilter element. */ + _worksheet_write_auto_filter(self); + /* Write the mergeCells element. */ _worksheet_write_merge_cells(self); @@ -2129,6 +2158,49 @@ worksheet_merge_range(lxw_worksheet *self, lxw_row_t first_row, return 0; } +/* + * Set the autofilter area in the worksheet. + */ +uint8_t +worksheet_autofilter(lxw_worksheet *self, lxw_row_t first_row, + lxw_col_t first_col, lxw_row_t last_row, + lxw_col_t last_col) +{ + lxw_row_t tmp_row; + lxw_col_t tmp_col; + int8_t err; + + /* Excel doesn't allow a single cell to be merged */ + if (first_row == last_row && first_col == last_col) + return 1; + + /* Swap last row/col with first row/col as necessary */ + if (first_row > last_row) { + tmp_row = last_row; + last_row = first_row; + first_row = tmp_row; + } + if (first_col > last_col) { + tmp_col = last_col; + last_col = first_col; + first_col = tmp_col; + } + + /* Check that column number is valid and store the max value */ + err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE); + + if (err) + return err; + + self->autofilter.in_use = LXW_TRUE; + self->autofilter.first_row = first_row; + self->autofilter.first_col = first_col; + self->autofilter.last_row = last_row; + self->autofilter.last_col = last_col; + + return 0; +} + /* * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab * highlighted. diff --git a/test/functional/src/test_autofilter00.c b/test/functional/src/test_autofilter00.c new file mode 100644 index 00000000..6b870789 --- /dev/null +++ b/test/functional/src/test_autofilter00.c @@ -0,0 +1,95 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_autofilter00.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + uint16_t i; + + struct row { + char region[16]; + char item[16]; + int volume; + char month[16]; + }; + + struct row data[] = { + {"East", "Apple", 9000, "July" }, + {"East", "Apple", 5000, "July" }, + {"South", "Orange", 9000, "September" }, + {"North", "Apple", 2000, "November" }, + {"West", "Apple", 9000, "November" }, + {"South", "Pear", 7000, "October" }, + {"North", "Pear", 9000, "August" }, + {"West", "Orange", 1000, "December" }, + {"West", "Grape", 1000, "November" }, + {"South", "Pear", 10000, "April" }, + {"West", "Grape", 6000, "January" }, + {"South", "Orange", 3000, "May" }, + {"North", "Apple", 3000, "December" }, + {"South", "Apple", 7000, "February" }, + {"West", "Grape", 1000, "December" }, + {"East", "Grape", 8000, "February" }, + {"South", "Grape", 10000, "June" }, + {"West", "Pear", 7000, "December" }, + {"South", "Apple", 2000, "October" }, + {"East", "Grape", 7000, "December" }, + {"North", "Grape", 6000, "April" }, + {"East", "Pear", 8000, "February" }, + {"North", "Apple", 7000, "August" }, + {"North", "Orange", 7000, "July" }, + {"North", "Apple", 6000, "June" }, + {"South", "Grape", 8000, "September" }, + {"West", "Apple", 3000, "October" }, + {"South", "Orange", 10000, "November" }, + {"West", "Grape", 4000, "July" }, + {"North", "Orange", 5000, "August" }, + {"East", "Orange", 1000, "November" }, + {"East", "Orange", 4000, "October" }, + {"North", "Grape", 5000, "August" }, + {"East", "Apple", 1000, "December" }, + {"South", "Apple", 10000, "March" }, + {"East", "Grape", 7000, "October" }, + {"West", "Grape", 1000, "September" }, + {"East", "Grape", 10000, "October" }, + {"South", "Orange", 8000, "March" }, + {"North", "Apple", 4000, "July" }, + {"South", "Orange", 5000, "July" }, + {"West", "Apple", 4000, "June" }, + {"East", "Apple", 5000, "April" }, + {"North", "Pear", 3000, "August" }, + {"East", "Grape", 9000, "November" }, + {"North", "Orange", 8000, "October" }, + {"East", "Apple", 10000, "June" }, + {"South", "Pear", 1000, "December" }, + {"North", "Grape", 10000, "July" }, + {"East", "Grape", 6000, "February" } + }; + + + /* Write the column headers. */ + worksheet_write_string(worksheet, 0, 0, "Region", NULL); + worksheet_write_string(worksheet, 0, 1, "Item", NULL); + worksheet_write_string(worksheet, 0, 2, "Volume" , NULL); + worksheet_write_string(worksheet, 0, 3, "Month", NULL); + + + /* Write the row data. */ + for (i = 0; i < sizeof(data)/sizeof(struct row); i++) { + worksheet_write_string(worksheet, i + 1, 0, data[i].region, NULL); + worksheet_write_string(worksheet, i + 1, 1, data[i].item, NULL); + worksheet_write_number(worksheet, i + 1, 2, data[i].volume , NULL); + worksheet_write_string(worksheet, i + 1, 3, data[i].month, NULL); + } + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_autofilter01.c b/test/functional/src/test_autofilter01.c new file mode 100644 index 00000000..b9b35795 --- /dev/null +++ b/test/functional/src/test_autofilter01.c @@ -0,0 +1,99 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_autofilter01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + uint16_t i; + + struct row { + char region[16]; + char item[16]; + int volume; + char month[16]; + }; + + struct row data[] = { + {"East", "Apple", 9000, "July" }, + {"East", "Apple", 5000, "July" }, + {"South", "Orange", 9000, "September" }, + {"North", "Apple", 2000, "November" }, + {"West", "Apple", 9000, "November" }, + {"South", "Pear", 7000, "October" }, + {"North", "Pear", 9000, "August" }, + {"West", "Orange", 1000, "December" }, + {"West", "Grape", 1000, "November" }, + {"South", "Pear", 10000, "April" }, + {"West", "Grape", 6000, "January" }, + {"South", "Orange", 3000, "May" }, + {"North", "Apple", 3000, "December" }, + {"South", "Apple", 7000, "February" }, + {"West", "Grape", 1000, "December" }, + {"East", "Grape", 8000, "February" }, + {"South", "Grape", 10000, "June" }, + {"West", "Pear", 7000, "December" }, + {"South", "Apple", 2000, "October" }, + {"East", "Grape", 7000, "December" }, + {"North", "Grape", 6000, "April" }, + {"East", "Pear", 8000, "February" }, + {"North", "Apple", 7000, "August" }, + {"North", "Orange", 7000, "July" }, + {"North", "Apple", 6000, "June" }, + {"South", "Grape", 8000, "September" }, + {"West", "Apple", 3000, "October" }, + {"South", "Orange", 10000, "November" }, + {"West", "Grape", 4000, "July" }, + {"North", "Orange", 5000, "August" }, + {"East", "Orange", 1000, "November" }, + {"East", "Orange", 4000, "October" }, + {"North", "Grape", 5000, "August" }, + {"East", "Apple", 1000, "December" }, + {"South", "Apple", 10000, "March" }, + {"East", "Grape", 7000, "October" }, + {"West", "Grape", 1000, "September" }, + {"East", "Grape", 10000, "October" }, + {"South", "Orange", 8000, "March" }, + {"North", "Apple", 4000, "July" }, + {"South", "Orange", 5000, "July" }, + {"West", "Apple", 4000, "June" }, + {"East", "Apple", 5000, "April" }, + {"North", "Pear", 3000, "August" }, + {"East", "Grape", 9000, "November" }, + {"North", "Orange", 8000, "October" }, + {"East", "Apple", 10000, "June" }, + {"South", "Pear", 1000, "December" }, + {"North", "Grape", 10000, "July" }, + {"East", "Grape", 6000, "February" } + }; + + + /* Write the column headers. */ + worksheet_write_string(worksheet, 0, 0, "Region", NULL); + worksheet_write_string(worksheet, 0, 1, "Item", NULL); + worksheet_write_string(worksheet, 0, 2, "Volume" , NULL); + worksheet_write_string(worksheet, 0, 3, "Month", NULL); + + + /* Write the row data. */ + for (i = 0; i < sizeof(data)/sizeof(struct row); i++) { + worksheet_write_string(worksheet, i + 1, 0, data[i].region, NULL); + worksheet_write_string(worksheet, i + 1, 1, data[i].item, NULL); + worksheet_write_number(worksheet, i + 1, 2, data[i].volume , NULL); + worksheet_write_string(worksheet, i + 1, 3, data[i].month, NULL); + } + + + worksheet_autofilter(worksheet, 0, 0, 50, 3); + + + return workbook_close(workbook); +} diff --git a/test/functional/test_autofilter.py b/test/functional/test_autofilter.py new file mode 100644 index 00000000..e4d4940e --- /dev/null +++ b/test/functional/test_autofilter.py @@ -0,0 +1,22 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_autofilter00(self): + self.run_exe_test('test_autofilter00') + + def test_autofilter01(self): + self.run_exe_test('test_autofilter01') + + diff --git a/test/functional/xlsx_files/autofilter00.xlsx b/test/functional/xlsx_files/autofilter00.xlsx new file mode 100644 index 00000000..bb123526 Binary files /dev/null and b/test/functional/xlsx_files/autofilter00.xlsx differ diff --git a/test/functional/xlsx_files/autofilter01.xlsx b/test/functional/xlsx_files/autofilter01.xlsx new file mode 100644 index 00000000..fa923adf Binary files /dev/null and b/test/functional/xlsx_files/autofilter01.xlsx differ