diff --git a/.indent.pro b/.indent.pro index e58bf0c6..b09c6396 100644 --- a/.indent.pro +++ b/.indent.pro @@ -59,6 +59,7 @@ -T lxw_content_types -T lxw_core -T lxw_custom +-T lxw_custom_property -T lxw_datetime -T lxw_defined_name -T lxw_doc_properties diff --git a/include/xlsxwriter/common.h b/include/xlsxwriter/common.h index 92e83339..6cd76c42 100644 --- a/include/xlsxwriter/common.h +++ b/include/xlsxwriter/common.h @@ -190,6 +190,7 @@ STAILQ_HEAD(lxw_formats, lxw_format); /* Define the queue.h structs for the generic data structs. */ STAILQ_HEAD(lxw_tuples, lxw_tuple); +STAILQ_HEAD(lxw_custom_properties, lxw_custom_property); typedef struct lxw_tuple { char *key; @@ -198,6 +199,17 @@ typedef struct lxw_tuple { STAILQ_ENTRY (lxw_tuple) list_pointers; } lxw_tuple; +/* Define custom property used in workbook.c and custom.c. */ +typedef struct lxw_custom_property { + + char *name; + char *value; + + STAILQ_ENTRY (lxw_custom_property) list_pointers; + +} lxw_custom_property; + + /* *INDENT-OFF* */ #ifdef __cplusplus diff --git a/include/xlsxwriter/content_types.h b/include/xlsxwriter/content_types.h index 3697eddc..39aa8e16 100644 --- a/include/xlsxwriter/content_types.h +++ b/include/xlsxwriter/content_types.h @@ -39,16 +39,20 @@ extern "C" { lxw_content_types *lxw_content_types_new(); void lxw_content_types_free(lxw_content_types *content_types); -void lxw_content_types_assemble_xml_file(lxw_content_types *self); -void lxw_ct_add_default(lxw_content_types *self, const char *key, +void lxw_content_types_assemble_xml_file(lxw_content_types *content_types); +void lxw_ct_add_default(lxw_content_types *content_types, const char *key, const char *value); -void lxw_ct_add_override(lxw_content_types *self, const char *key, +void lxw_ct_add_override(lxw_content_types *content_types, const char *key, const char *value); -void lxw_ct_add_worksheet_name(lxw_content_types *self, const char *name); -void lxw_ct_add_chart_name(lxw_content_types *self, const char *name); -void lxw_ct_add_drawing_name(lxw_content_types *self, const char *name); -void lxw_ct_add_shared_strings(lxw_content_types *self); -void lxw_ct_add_calc_chain(lxw_content_types *self); +void lxw_ct_add_worksheet_name(lxw_content_types *content_types, + const char *name); +void lxw_ct_add_chart_name(lxw_content_types *content_types, + const char *name); +void lxw_ct_add_drawing_name(lxw_content_types *content_types, + const char *name); +void lxw_ct_add_shared_strings(lxw_content_types *content_types); +void lxw_ct_add_calc_chain(lxw_content_types *content_types); +void lxw_ct_add_custom_properties(lxw_content_types *content_types); /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/include/xlsxwriter/custom.h b/include/xlsxwriter/custom.h index 62e86857..7483c07e 100644 --- a/include/xlsxwriter/custom.h +++ b/include/xlsxwriter/custom.h @@ -3,7 +3,7 @@ * * Copyright 2014-2016, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. * - * custom - A libxlsxwriter library for creating Excel XLSX custom files. + * custom - A libxlsxwriter library for creating Excel custom property files. * */ #ifndef __LXW_CUSTOM_H__ @@ -14,12 +14,15 @@ #include "common.h" /* - * Struct to represent a custom object. + * Struct to represent a custom property file object. */ typedef struct lxw_custom { FILE *file; + struct lxw_custom_properties *custom_properties; + uint32_t pid; + } lxw_custom; diff --git a/include/xlsxwriter/packager.h b/include/xlsxwriter/packager.h index babbaab9..76788c55 100644 --- a/include/xlsxwriter/packager.h +++ b/include/xlsxwriter/packager.h @@ -18,6 +18,7 @@ #include "shared_strings.h" #include "app.h" #include "core.h" +#include "custom.h" #include "theme.h" #include "styles.h" #include "format.h" diff --git a/include/xlsxwriter/workbook.h b/include/xlsxwriter/workbook.h index ebb4ed36..67938b3b 100644 --- a/include/xlsxwriter/workbook.h +++ b/include/xlsxwriter/workbook.h @@ -193,6 +193,8 @@ typedef struct lxw_workbook { struct lxw_defined_names *defined_names; lxw_sst *sst; lxw_doc_properties *properties; + struct lxw_custom_properties *custom_properties; + char *filename; lxw_workbook_options options; @@ -481,6 +483,9 @@ uint8_t workbook_close(lxw_workbook *workbook); uint8_t workbook_set_properties(lxw_workbook *workbook, lxw_doc_properties *properties); +uint8_t workbook_set_custom_property_string(lxw_workbook *workbook, + char *name, char *value); + /** * @brief Create a defined name in the workbook to use as a variable. * diff --git a/src/content_types.c b/src/content_types.c index 1803ea98..ec954928 100644 --- a/src/content_types.c +++ b/src/content_types.c @@ -329,3 +329,13 @@ lxw_ct_add_calc_chain(lxw_content_types *self) lxw_ct_add_override(self, "/xl/calcChain.xml", LXW_APP_DOCUMENT "spreadsheetml.calcChain+xml"); } + +/* + * Add the custom properties to the ContentTypes overrides. + */ +void +lxw_ct_add_custom_properties(lxw_content_types *self) +{ + lxw_ct_add_override(self, "/docProps/custom.xml", + LXW_APP_DOCUMENT "custom-properties+xml"); +} diff --git a/src/custom.c b/src/custom.c index 467227a2..5ff0a798 100644 --- a/src/custom.c +++ b/src/custom.c @@ -1,5 +1,5 @@ /***************************************************************************** - * custom - A library for creating Excel XLSX custom files. + * custom - A library for creating Excel custom property files. * * Used in conjunction with the libxlsxwriter library. * @@ -49,7 +49,6 @@ lxw_custom_free(lxw_custom *custom) free(custom); } - /***************************************************************************** * * XML functions. @@ -65,6 +64,67 @@ _custom_xml_declaration(lxw_custom *self) lxw_xml_declaration(self->file); } +/* + * Write the element. + */ +STATIC void +_chart_write_vt_lpwstr(lxw_custom *self, char *value) +{ + lxw_xml_data_element(self->file, "vt:lpwstr", value, NULL); +} + +/* + * Write the element. + */ +STATIC void +_chart_write_custom_property(lxw_custom *self, + lxw_custom_property *custom_property) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char fmtid[] = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"; + + self->pid++; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("fmtid", fmtid); + LXW_PUSH_ATTRIBUTES_INT("pid", self->pid + 1); + LXW_PUSH_ATTRIBUTES_STR("name", custom_property->name); + + lxw_xml_start_tag(self->file, "property", &attributes); + + /* Write the vt:lpwstr element. */ + _chart_write_vt_lpwstr(self, custom_property->value); + + lxw_xml_end_tag(self->file, "property"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_write_custom_properties(lxw_custom *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = LXW_SCHEMA_OFFICEDOC "/custom-properties"; + char xmlns_vt[] = LXW_SCHEMA_OFFICEDOC "/docPropsVTypes"; + lxw_custom_property *custom_property; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + LXW_PUSH_ATTRIBUTES_STR("xmlns:vt", xmlns_vt); + + lxw_xml_start_tag(self->file, "Properties", &attributes); + + STAILQ_FOREACH(custom_property, self->custom_properties, list_pointers) { + _chart_write_custom_property(self, custom_property); + } + + LXW_FREE_ATTRIBUTES(); +} /***************************************************************************** * @@ -81,7 +141,9 @@ lxw_custom_assemble_xml_file(lxw_custom *self) /* Write the XML declaration. */ _custom_xml_declaration(self); - lxw_xml_end_tag(self->file, "custom"); + _write_custom_properties(self); + + lxw_xml_end_tag(self->file, "Properties"); } /***************************************************************************** diff --git a/src/packager.c b/src/packager.c index c33ee720..ef174870 100644 --- a/src/packager.c +++ b/src/packager.c @@ -400,6 +400,38 @@ _write_core_file(lxw_packager *self) return 0; } +/* + * Write the custom.xml file. + */ +STATIC uint8_t +_write_custom_file(lxw_packager *self) +{ + lxw_custom *custom; + int err; + + if (STAILQ_EMPTY(self->workbook->custom_properties)) + return 0; + + custom = lxw_custom_new(); + + custom->file = lxw_tmpfile(); + if (!custom->file) + return LXW_ERROR_CREATING_TMPFILE; + + custom->custom_properties = self->workbook->custom_properties; + + lxw_custom_assemble_xml_file(custom); + + err = _add_file_to_zip(self, custom->file, "docProps/custom.xml"); + RETURN_ON_ERROR(err); + + fclose(custom->file); + + lxw_custom_free(custom); + + return 0; +} + /* * Write the theme.xml file. */ @@ -513,6 +545,9 @@ _write_content_types_file(lxw_packager *self) if (workbook->sst->string_count) lxw_ct_add_shared_strings(content_types); + if (!STAILQ_EMPTY(self->workbook->custom_properties)) + lxw_ct_add_custom_properties(content_types); + lxw_content_types_assemble_xml_file(content_types); err = _add_file_to_zip(self, content_types->file, "[Content_Types].xml"); @@ -679,10 +714,18 @@ _write_root_rels_file(lxw_packager *self) return LXW_ERROR_CREATING_TMPFILE; lxw_add_document_relationship(rels, "/officeDocument", "xl/workbook.xml"); - lxw_add_package_relationship(rels, "/metadata/core-properties", + + lxw_add_package_relationship(rels, + "/metadata/core-properties", "docProps/core.xml"); - lxw_add_document_relationship(rels, "/extended-properties", - "docProps/app.xml"); + + lxw_add_document_relationship(rels, + "/extended-properties", "docProps/app.xml"); + + if (!STAILQ_EMPTY(self->workbook->custom_properties)) + lxw_add_document_relationship(rels, + "/custom-properties", + "docProps/custom.xml"); lxw_relationships_assemble_xml_file(rels); @@ -789,6 +832,9 @@ lxw_create_package(lxw_packager *self) error = _write_core_file(self); RETURN_ON_ERROR(error); + error = _write_custom_file(self); + RETURN_ON_ERROR(error); + error = _write_theme_file(self); RETURN_ON_ERROR(error); diff --git a/src/workbook.c b/src/workbook.c index 2fcf9ea5..87208f3e 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -58,6 +58,20 @@ _free_doc_properties(lxw_doc_properties *properties) free(properties); } +/* + * Free workbook custom property. + */ +STATIC void +_free_custom_doc_property(lxw_custom_property *custom_property) +{ + if (custom_property) { + free(custom_property->name); + free(custom_property->value); + } + + free(custom_property); +} + /* * Free a workbook object. */ @@ -70,6 +84,7 @@ lxw_workbook_free(lxw_workbook *workbook) lxw_chart *chart; lxw_format *format; lxw_defined_name *defined_name; + lxw_custom_property *custom_property; if (!workbook) return; @@ -106,6 +121,13 @@ lxw_workbook_free(lxw_workbook *workbook) free(defined_name); } + /* Free the custom_properties in the workbook. */ + while (!STAILQ_EMPTY(workbook->custom_properties)) { + custom_property = STAILQ_FIRST(workbook->custom_properties); + STAILQ_REMOVE_HEAD(workbook->custom_properties, list_pointers); + _free_custom_doc_property(custom_property); + } + if (workbook->worksheet_names) { for (worksheet_name = RB_MIN(lxw_worksheet_names, workbook->worksheet_names); @@ -128,6 +150,7 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->ordered_charts); free(workbook->formats); free(workbook->defined_names); + free(workbook->custom_properties); free(workbook); } @@ -471,7 +494,7 @@ _store_defined_name(lxw_workbook *self, const char *name, /* Allocate a new defined_name to be added to the linked list of names. */ defined_name = calloc(1, sizeof(struct lxw_defined_name)); - RETURN_ON_MEM_ERROR(defined_name, 1); + RETURN_ON_MEM_ERROR(defined_name, LXW_ERROR_MEMORY_MALLOC_FAILED); /* Copy the user input string. */ strcpy(name_copy, name); @@ -1328,6 +1351,12 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) workbook->used_xf_formats = lxw_hash_new(128, 1, 0); GOTO_LABEL_ON_MEM_ERROR(workbook->used_xf_formats, mem_error); + /* Add the worksheets list. */ + workbook->custom_properties = + calloc(1, sizeof(struct lxw_custom_properties)); + GOTO_LABEL_ON_MEM_ERROR(workbook->custom_properties, mem_error); + STAILQ_INIT(workbook->custom_properties); + /* Add the default cell format. */ format = workbook_add_format(workbook); GOTO_LABEL_ON_MEM_ERROR(format, mem_error); @@ -1384,8 +1413,8 @@ workbook_add_worksheet(lxw_workbook *self, const char *sheetname) /* Check if the worksheet name is already in use. */ if (workbook_get_worksheet_by_name(self, init_data.name)) { - LXW_WARN_FORMAT("workbook_add_worksheet(): worksheet name " - "'%s' already exists.", init_data.name); + LXW_WARN_FORMAT2("%s(): worksheet name '%s' already exists.", + __func__, init_data.name); goto mem_error; } @@ -1625,6 +1654,38 @@ mem_error: return -1; } +/* + * Set the document properties such as Title, Author etc. + */ +uint8_t +workbook_set_custom_property_string(lxw_workbook *self, char *name, + char *value) +{ + lxw_custom_property *custom_property; + + if (!name) { + LXW_WARN_FORMAT("%s(): parameter 'name' cannot be NULL.", __func__); + return LXW_ERROR_NULL_STRING_IGNORED; + } + + if (!value) { + LXW_WARN_FORMAT("%s(): parameter 'value' cannot be NULL.", __func__); + return LXW_ERROR_NULL_STRING_IGNORED; + } + + /* Create a struct to hold the custom property. */ + custom_property = calloc(1, sizeof(struct lxw_custom_property)); + RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED); + + custom_property->name = lxw_strdup(name); + custom_property->value = lxw_strdup(value); + + STAILQ_INSERT_TAIL(self->custom_properties, custom_property, + list_pointers); + + return 0; +} + lxw_worksheet * workbook_get_worksheet_by_name(lxw_workbook *self, char *name) { diff --git a/test/functional/src/test_properties03.c b/test/functional/src/test_properties03.c new file mode 100644 index 00000000..cd418da0 --- /dev/null +++ b/test/functional/src/test_properties03.c @@ -0,0 +1,23 @@ +/***************************************************************************** + * 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_properties03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + workbook_set_custom_property_string(workbook, "Checked by", "Adam"); + + worksheet_set_column(worksheet, 0, 0, 70, NULL); + worksheet_write_string(worksheet, CELL("A1"), "Select 'Office Button -> Prepare -> Properties' to see the file properties." , NULL); + + return workbook_close(workbook); +} diff --git a/test/functional/test_properties.py b/test/functional/test_properties.py index 42c9c0c3..e85541db 100644 --- a/test/functional/test_properties.py +++ b/test/functional/test_properties.py @@ -18,3 +18,6 @@ class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): def test_properties02(self): self.run_exe_test('test_properties02') + + def test_properties03(self): + self.run_exe_test('test_properties03') diff --git a/test/functional/xlsx_files/properties03.xlsx b/test/functional/xlsx_files/properties03.xlsx new file mode 100644 index 00000000..391e196e Binary files /dev/null and b/test/functional/xlsx_files/properties03.xlsx differ