mirror of
https://github.com/jmcnamara/libxlsxwriter.git
synced 2026-05-21 06:45:21 -06:00
Initial autofilter support.
This commit is contained in:
parent
8f764c632b
commit
269442101c
10 changed files with 360 additions and 2 deletions
BIN
docs/images/autofilter.png
Normal file
BIN
docs/images/autofilter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -1558,6 +1558,32 @@ _worksheet_write_col_breaks(lxw_worksheet *self)
|
|||
_FREE_ATTRIBUTES();
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the <autoFilter> 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.
|
||||
|
|
|
|||
95
test/functional/src/test_autofilter00.c
Normal file
95
test/functional/src/test_autofilter00.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
99
test/functional/src/test_autofilter01.c
Normal file
99
test/functional/src/test_autofilter01.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
22
test/functional/test_autofilter.py
Normal file
22
test/functional/test_autofilter.py
Normal file
|
|
@ -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')
|
||||
|
||||
|
||||
BIN
test/functional/xlsx_files/autofilter00.xlsx
Normal file
BIN
test/functional/xlsx_files/autofilter00.xlsx
Normal file
Binary file not shown.
BIN
test/functional/xlsx_files/autofilter01.xlsx
Normal file
BIN
test/functional/xlsx_files/autofilter01.xlsx
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue