Added defined names.

This commit is contained in:
John McNamara 2015-04-06 23:46:39 +01:00
parent 108f3966b3
commit be3745e4fa
32 changed files with 463 additions and 33 deletions

View File

@ -24,6 +24,7 @@ my @examples = (
[ 'dates_and_times03.c', 'Dates and times with different formats' ],
[ 'utf8.c', 'A example of some UTF-8 text' ],
[ 'constant_memory.c', 'Write a large file with constant memory usage' ],
[ 'defined_name.c', 'Example of how to create defined names' ],
);
# Convert the array refs to a hash for lookups.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -92,11 +92,22 @@ A simple Unicode UTF-8 example. Note, the source file is UTF-8 encoded.
Next example: @ref constant_memory.c
@image html utf8.png
@example constant_memory.c
Example of using libxlsxwriter for writing large files in constant memory
mode.
Next example: @ref defined_name.c
@image html constant_memory.png
@example defined_name.c
Example of how to create defined names (named ranges) using libxlsxwriter.
Defined names are used to define descriptive names to represent a value, a
single cell or a range of cells in a workbook or worksheet.
@image html defined_name.png
*/

View File

@ -1,7 +1,7 @@
/*
* Anatomy of a simple libxlsxwriter program.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -2,7 +2,7 @@
* Example of using libxlsxwriter for writing large files in constant memory
* mode.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -6,7 +6,7 @@
* An easier approach using a lxw_datetime struct is shown in example
* dates_and_times02.c.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -2,7 +2,7 @@
* Example of writing dates and times in Excel using an lxw_datetime struct
* and date formatting.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -1,7 +1,7 @@
/*
* Example of writing dates and times in Excel using different date formats.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

57
examples/defined_name.c Normal file
View File

@ -0,0 +1,57 @@
/*
* Example of how to create defined names using libxlsxwriter. This method is
* used to define a user friendly name to represent a value, a single cell or
* a range of cells in a workbook.
*
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/
#include "xlsxwriter.h"
int main() {
lxw_workbook *workbook = new_workbook("defined_name.xlsx");
lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL);
lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL);
/* Define some global/workbook names. */
workbook_define_name(workbook, "Sales", "=!G1:H10");
workbook_define_name(workbook, "Exchange_rate", "=0.96");
workbook_define_name(workbook, "Sales", "=Sheet1!$G$1:$H$10");
/* Define a local/worksheet name. */
workbook_define_name(workbook, "Sheet2!Sales", "=Sheet2!$G$1:$G$10");
/* Write some text to the first worksheet and a defined names in a formula. */
worksheet_set_column(worksheet1, 0, 0, 45, NULL, NULL);
worksheet_write_string(worksheet1, 0, 0,
"This worksheet contains some defined names.", NULL);
worksheet_write_string(worksheet1, 1, 0,
"See Formulas -> Name Manager above.", NULL);
worksheet_write_string(worksheet1, 2, 0,
"Example formula in cell B3 ->", NULL);
worksheet_write_formula(worksheet1, 2, 1, "=Exchange_rate", NULL);
/* Write some text to the second worksheet and a defined names in a formula. */
worksheet_set_column(worksheet2, 0, 0, 45, NULL, NULL);
worksheet_write_string(worksheet2, 0, 0,
"This worksheet contains some defined names.", NULL);
worksheet_write_string(worksheet2, 1, 0,
"See Formulas -> Name Manager above.", NULL);
worksheet_write_string(worksheet2, 2, 0,
"Example formula in cell B3 ->", NULL);
worksheet_write_formula(worksheet2, 2, 1, "=Exchange_rate", NULL);
return workbook_close(workbook);
}

View File

@ -1,7 +1,7 @@
/*
* A simple example of some of the features of the libxlsxwriter library.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -2,7 +2,7 @@
* Example of writing some data with font formatting to a simple Excel
* file using libxlsxwriter.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -2,7 +2,7 @@
* Example of writing some data with numeric formatting to a simple Excel file
* using libxlsxwriter.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -1,7 +1,7 @@
/*
* Example of writing some data to a simple Excel file using libxlsxwriter.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -5,7 +5,7 @@
* This program is shown, with explanations, in Tutorial 1 of the
* libxlsxwriter documentation.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -5,7 +5,7 @@
* This program is shown, with explanations, in Tutorial 2 of the
* libxlsxwriter documentation.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -5,7 +5,7 @@
* This program is shown, with explanations, in Tutorial 3 of the
* libxlsxwriter documentation.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -3,7 +3,7 @@
*
* Note: The source file must be UTF-8 encoded.
*
* Copyright 2014, John McNamara, jmcnamara@cpan.org
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/

View File

@ -128,6 +128,8 @@ double _datetime_to_excel_date(lxw_datetime *datetime, uint8_t date_1904);
char *lxw_strdup(const char *str);
void lxw_str_tolower(char *str);
FILE *lxw_tmpfile(void);
/* Declarations required for unit testing. */

View File

@ -51,7 +51,7 @@
/* Define the queue.h structs for the workbook lists. */
STAILQ_HEAD(lxw_worksheets, lxw_worksheet);
LIST_HEAD(lxw_defined_names, lxw_defined_name);
TAILQ_HEAD(lxw_defined_names, lxw_defined_name);
#define LXW_DEFINED_NAME_LENGTH 128
@ -61,9 +61,11 @@ typedef struct lxw_defined_name {
uint8_t hidden;
char name[LXW_DEFINED_NAME_LENGTH];
char range[LXW_DEFINED_NAME_LENGTH];
char normalised_name[LXW_DEFINED_NAME_LENGTH];
char normalised_sheetname[LXW_DEFINED_NAME_LENGTH];
/* List pointers for queue.h. */
LIST_ENTRY (lxw_defined_name) list_pointers;
TAILQ_ENTRY (lxw_defined_name) list_pointers;
} lxw_defined_name;
/**
@ -275,6 +277,58 @@ lxw_format *workbook_add_format(lxw_workbook *workbook);
*/
uint8_t workbook_close(lxw_workbook *workbook);
/**
* @brief Create a defined name in the workbook to use as a variable.
*
* @param workbook Pointer to a lxw_workbook instance.
* @param name The defined name.
* @param formula The cell or range that the defined name refers to.
*
* @return 0 for success, non-zero on errror.
*
* This method is used to defined a name that can be used to represent a
* value, a single cell or a range of cells in a workbook: These defined names
* can then be used in formulas:
*
* @code
* workbook_define_name(workbook, "Exchange_rate", "=0.96");
* worksheet_write_formula(worksheet, 2, 1, "=Exchange_rate", NULL);
*
* @endcode
*
* @image html defined_name.png
*
* As in Excel a name defined like this is "global" to the workbook and can be
* referred to from any worksheet:
*
* @code
* // Global workbook name.
* workbook_define_name(workbook, "Sales", "=Sheet1!$G$1:$H$10");
* @endcode
*
* It is also possible to define a local/worksheet name by prefixing it with
* the sheet name using the syntax `'sheetname!definedname'`:
*
* @code
* // Local worksheet name.
* workbook_define_name(workbook, "Sheet2!Sales", "=Sheet2!$G$1:$G$10");
* @endcode
*
* If the sheet name contains spaces or special characters you must follow the
* Excel convention and enclose it in single quotes:
*
* @code
* workbook_define_name(workbook, "'New Data'!Sales", "=Sheet2!$G$1:$G$10");
* @endcode
*
* The rules for names in Excel are explained in the
* [Miscrosoft Office
documentation](http://office.microsoft.com/en-001/excel-help/define-and-use-names-in-formulas-HA010147120.aspx).
*
*/
uint8_t workbook_define_name(lxw_workbook *workbook, const char *name,
const char *formula);
void _free_workbook(lxw_workbook *workbook);
void _workbook_assemble_xml_file(lxw_workbook *workbook);
void _set_default_xf_indices(lxw_workbook *workbook);
@ -298,6 +352,10 @@ STATIC void _write_defined_name(lxw_workbook *self,
lxw_defined_name *define_name);
STATIC void _write_defined_names(lxw_workbook *self);
STATIC uint8_t _store_defined_name(lxw_workbook *self, const char *name,
const char *formula, int16_t index,
uint8_t hidden);
#endif /* TESTING */
/* *INDENT-OFF* */

View File

@ -169,19 +169,38 @@ _write_app_file(lxw_packager *self)
{
lxw_workbook *workbook = self->workbook;
lxw_worksheet *worksheet;
lxw_defined_name *defined_name;
lxw_app *app = _new_app();
char num_sheets[ATTR_32] = { 0 };
uint16_t named_range_count = 0;
char *tmp_name;
char number[ATTR_32] = { 0 };
app->file = lxw_tmpfile();
__builtin_snprintf(num_sheets, ATTR_32, "%d", self->workbook->num_sheets);
__builtin_snprintf(number, ATTR_32, "%d", self->workbook->num_sheets);
_add_heading_pair(app, "Worksheets", num_sheets);
_add_heading_pair(app, "Worksheets", number);
STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) {
_add_part_name(app, worksheet->name);
}
/* Add the Named Ranges parts. */
TAILQ_FOREACH(defined_name, workbook->defined_names, list_pointers) {
tmp_name = strstr(defined_name->name, "_xlnm.");
if (!tmp_name) {
_add_part_name(app, defined_name->name);
named_range_count++;
}
}
/* Add the Named Range heading pairs. */
if (named_range_count) {
__builtin_snprintf(number, ATTR_32, "%d", named_range_count);
_add_heading_pair(app, "Named Ranges", number);
}
_app_assemble_xml_file(app);
_add_file_to_zip(self, app->file, "docProps/app.xml");

View File

@ -342,6 +342,16 @@ lxw_strdup(const char *str)
return copy;
}
/* Simple tolower() for strings. */
void
lxw_str_tolower(char *str)
{
int i;
for (i = 0; str[i]; i++)
str[i] = tolower(str[i]);
}
/*
* Thin wrapper for tmpfile() so it can be over-ridden with a safer version if
* required.

View File

@ -65,9 +65,9 @@ _free_workbook(lxw_workbook *workbook)
}
/* Free the defined_names in the workbook. */
while (!LIST_EMPTY(workbook->defined_names)) {
defined_name = LIST_FIRST(workbook->defined_names);
LIST_REMOVE(defined_name, list_pointers);
while (!TAILQ_EMPTY(workbook->defined_names)) {
defined_name = TAILQ_FIRST(workbook->defined_names);
TAILQ_REMOVE(workbook->defined_names, defined_name, list_pointers);
free(defined_name);
}
@ -370,6 +370,141 @@ _prepare_workbook(lxw_workbook *self)
}
/*
* Compare two defined_name structures.
*/
static int
_compare_defined_names(lxw_defined_name *a, lxw_defined_name *b)
{
int res = strcmp(a->normalised_name, b->normalised_name);
/* Primary comparison based on defined name. */
if (res)
return res;
/* Secondary comparison based on worksheet name. */
res = strcmp(a->normalised_sheetname, b->normalised_sheetname);
return res;
}
STATIC uint8_t
_store_defined_name(lxw_workbook *self, const char *name,
const char *formula, int16_t index, uint8_t hidden)
{
lxw_worksheet *worksheet;
lxw_defined_name *defined_name;
lxw_defined_name *list_defined_name;
char name_copy[LXW_DEFINED_NAME_LENGTH];
char *tmp_name;
char *worksheet_name;
/* Do some checks on the input data */
if (!name || !formula)
return 1;
if (strlen(name) > LXW_DEFINED_NAME_LENGTH ||
strlen(formula) > LXW_DEFINED_NAME_LENGTH) {
return 1;
}
defined_name = calloc(1, sizeof(struct lxw_defined_name));
RETURN_ON_MEM_ERROR(defined_name, 1);
/* Copy the user input string. */
strcpy(name_copy, name);
/* Set the worksheet index or -1 for a global defined name. */
defined_name->index = index;
defined_name->hidden = hidden;
/* Check for local defined names like like "Sheet1!name". */
tmp_name = strchr(name_copy, '!');
if (tmp_name != NULL) {
/* Split the string into the worksheet name and define name. */
*tmp_name = '\0';
tmp_name++;
worksheet_name = name_copy;
/* Remove any worksheet quoting. */
if (worksheet_name[0] == '\'')
worksheet_name++;
if (worksheet_name[strlen(worksheet_name) - 1] == '\'')
worksheet_name[strlen(worksheet_name) - 1] = '\0';
/* Search for worksheet name to get the equivalent worksheet index. */
STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) {
if (strcmp(worksheet_name, worksheet->name) == 0) {
defined_name->index = worksheet->index;
strcpy(defined_name->normalised_sheetname, worksheet_name);
}
}
/* If we didn't find the worksheet name we exit. */
if (defined_name->index == -1)
goto mem_error;
strcpy(defined_name->name, tmp_name);
}
else {
/* For non-local names we just store the defined name string. */
strcpy(defined_name->name, name_copy);
}
/* We need to normalise the defined names for sorting. This involves
* removing any _xlnm namespace from the string and converting it to
* lowercase. */
tmp_name = strstr(defined_name->name, "_xlnm.");
if (tmp_name != NULL)
strcpy(defined_name->normalised_name, tmp_name + 6);
else
strcpy(defined_name->normalised_name, defined_name->name);
lxw_str_tolower(defined_name->normalised_name);
lxw_str_tolower(defined_name->normalised_sheetname);
/* Strip leading "=" from the formula. */
if (formula[0] == '=')
strcpy(defined_name->range, formula + 1);
else
strcpy(defined_name->range, formula);
/* We add the defined name to the list in sorted order. */
list_defined_name = TAILQ_FIRST(self->defined_names);
if (list_defined_name == NULL ||
_compare_defined_names(defined_name, list_defined_name) < 1) {
/* List is empty or defined name goes to the head. */
TAILQ_INSERT_HEAD(self->defined_names, defined_name, list_pointers);
return 0;
}
TAILQ_FOREACH(list_defined_name, self->defined_names, list_pointers) {
int res = _compare_defined_names(defined_name, list_defined_name);
/* The entry already exists. We exit and don't overwrite. */
if (res == 0)
goto mem_error;
/* New defined name is inserted in sorted order before other entries. */
if (res < 0) {
TAILQ_INSERT_BEFORE(list_defined_name, defined_name,
list_pointers);
return 0;
}
}
/* If the entry wasn't less than any of the entries in the list we add it
* to the end. */
TAILQ_INSERT_TAIL(self->defined_names, defined_name, list_pointers);
return 0;
mem_error:
free(defined_name);
return 1;
}
/*****************************************************************************
*
* XML functions.
@ -578,12 +713,12 @@ _write_defined_names(lxw_workbook *self)
{
lxw_defined_name *defined_name;
if (LIST_EMPTY(self->defined_names))
if (TAILQ_EMPTY(self->defined_names))
return;
_xml_start_tag(self->file, "definedNames", NULL);
LIST_FOREACH(defined_name, self->defined_names, list_pointers) {
TAILQ_FOREACH(defined_name, self->defined_names, list_pointers) {
_write_defined_name(self, defined_name);
}
@ -675,7 +810,7 @@ new_workbook_opt(const char *filename, lxw_workbook_options *options)
/* Add the defined_names list. */
workbook->defined_names = calloc(1, sizeof(struct lxw_defined_names));
GOTO_LABEL_ON_MEM_ERROR(workbook->defined_names, mem_error);
LIST_INIT(workbook->defined_names);
TAILQ_INIT(workbook->defined_names);
/* Add the shared strings table. */
workbook->sst = _new_sst();
@ -813,3 +948,14 @@ workbook_close(lxw_workbook *self)
mem_error:
return 1;
}
/*
* Create a defined name in Excel. We handle global/workbook level names and
* local/worksheet names.
*/
uint8_t
workbook_define_name(lxw_workbook *self, const char *name,
const char *formula)
{
return _store_defined_name(self, name, formula, -1, 0);
}

View File

@ -0,0 +1,22 @@
/*****************************************************************************
* Test cases for libxlsxwriter.
*
* Simple test case for defined names.
*
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/
#include "xlsxwriter.h"
int main() {
lxw_workbook *workbook = new_workbook("test_defined_name02.xlsx");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "sheet One");
workbook_define_name(workbook, "Sales", "='sheet One'!$G$1:$H$10");
(void)worksheet;
return workbook_close(workbook);
}

View File

@ -0,0 +1,22 @@
/*****************************************************************************
* Test cases for libxlsxwriter.
*
* Simple test case for defined names.
*
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/
#include "xlsxwriter.h"
int main() {
lxw_workbook *workbook = new_workbook("test_defined_name03.xlsx");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, "sheet One");
workbook_define_name(workbook, "Sales", "='sheet One'!G1:H10");
(void)worksheet;
return workbook_close(workbook);
}

View File

@ -0,0 +1,27 @@
/*****************************************************************************
* Test cases for libxlsxwriter.
*
* Simple test case for defined names.
*
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org
*
*/
#include "xlsxwriter.h"
int main() {
lxw_workbook *workbook = new_workbook("test_defined_name04.xlsx");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
workbook_define_name(workbook, "\\__", "=Sheet1!$A$1");
workbook_define_name(workbook, "a3f6", "=Sheet1!$A$2");
workbook_define_name(workbook, "afoo.bar", "=Sheet1!$A$3");
workbook_define_name(workbook, "étude", "=Sheet1!$A$4");
workbook_define_name(workbook, "eésumé", "=Sheet1!$A$5");
workbook_define_name(workbook, "a", "=Sheet1!$A$6");
(void)worksheet;
return workbook_close(workbook);
}

View File

@ -0,0 +1,27 @@
###############################################################################
#
# 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_defined_name01(self):
# self.run_exe_test('test_defined_name01')
def test_defined_name02(self):
self.run_exe_test('test_defined_name02')
def test_defined_name03(self):
self.run_exe_test('test_defined_name03')
def test_defined_name04(self):
self.run_exe_test('test_defined_name04')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -12,12 +12,10 @@
/* Test the _write_defined_name() method. */
CTEST(workbook, write_defined_name) {
char* got;
char exp[] = "<definedName name=\"_xlnm.Print_Titles\" localSheetId=\"0\">Sheet1!$1:$1</definedName>";
FILE* testfile = tmpfile();
lxw_defined_name defined_name = {0, 0, "_xlnm.Print_Titles", "Sheet1!$1:$1", {NULL, NULL}};
lxw_defined_name defined_name = {0, 0, "_xlnm.Print_Titles", "Sheet1!$1:$1", "", "", {NULL, NULL}};
lxw_workbook *workbook = new_workbook(NULL);

View File

@ -17,15 +17,12 @@ CTEST(workbook, write_defined_names) {
char* got;
char exp[] = "<definedNames><definedName name=\"_xlnm.Print_Titles\" localSheetId=\"0\">Sheet1!$1:$1</definedName></definedNames>";
FILE* testfile = tmpfile();
lxw_defined_name *defined_name = calloc(1, sizeof(struct lxw_defined_name));
strcpy(defined_name->name, "_xlnm.Print_Titles");
strcpy(defined_name->range, "Sheet1!$1:$1");
lxw_workbook *workbook = new_workbook(NULL);
workbook->file = testfile;
LIST_INSERT_HEAD(workbook->defined_names, defined_name, list_pointers);
workbook_add_worksheet(workbook, NULL);
_store_defined_name(workbook, "_xlnm.Print_Titles", "Sheet1!$1:$1", 0, 0);
_write_defined_names(workbook);
@ -34,3 +31,36 @@ CTEST(workbook, write_defined_names) {
_free_workbook(workbook);
}
/* Test the _write_defined_name() method. */
CTEST(workbook, write_defined_names_sorted) {
char* got;
char exp[] = "<definedNames><definedName name=\"_Egg\">Sheet1!$A$1</definedName><definedName name=\"_Fog\">Sheet1!$A$1</definedName><definedName name=\"aaa\" localSheetId=\"1\">Sheet2!$A$1</definedName><definedName name=\"Abc\">Sheet1!$A$1</definedName><definedName name=\"Bar\" localSheetId=\"2\">'Sheet 3'!$A$1</definedName><definedName name=\"Bar\" localSheetId=\"0\">Sheet1!$A$1</definedName><definedName name=\"Bar\" localSheetId=\"1\">Sheet2!$A$1</definedName><definedName name=\"Baz\">0.98</definedName><definedName name=\"car\" localSheetId=\"2\">\"Saab 900\"</definedName></definedNames>";
FILE* testfile = tmpfile();
lxw_workbook *workbook = new_workbook(NULL);
workbook->file = testfile;
workbook_add_worksheet(workbook, NULL);
workbook_add_worksheet(workbook, NULL);
workbook_add_worksheet(workbook, "Sheet 3");
workbook_define_name(workbook, "'Sheet 3'!Bar", "='Sheet 3'!$A$1");
workbook_define_name(workbook, "Abc", "=Sheet1!$A$1" );
workbook_define_name(workbook, "Baz", "=0.98" );
workbook_define_name(workbook, "Sheet1!Bar", "=Sheet1!$A$1" );
workbook_define_name(workbook, "Sheet2!Bar", "=Sheet2!$A$1" );
workbook_define_name(workbook, "Sheet2!aaa", "=Sheet2!$A$1" );
workbook_define_name(workbook, "'Sheet 3'!car", "=\"Saab 900\"" );
workbook_define_name(workbook, "_Egg", "=Sheet1!$A$1" );
workbook_define_name(workbook, "_Fog", "=Sheet1!$A$1" );
_write_defined_names(workbook);
RUN_XLSX_STREQ(exp, got);
_free_workbook(workbook);
}