From 2fa33fe58a84a4e7b7589234e6e4019eb3259d60 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Fri, 10 Jun 2016 00:03:38 +0100 Subject: [PATCH] Initial working custom properties. --- .indent.pro | 1 + include/xlsxwriter/common.h | 12 ++++ include/xlsxwriter/content_types.h | 20 +++--- include/xlsxwriter/custom.h | 7 +- include/xlsxwriter/packager.h | 1 + include/xlsxwriter/workbook.h | 5 ++ src/content_types.c | 10 +++ src/custom.c | 68 ++++++++++++++++++- src/packager.c | 52 +++++++++++++- src/workbook.c | 67 +++++++++++++++++- test/functional/src/test_properties03.c | 23 +++++++ test/functional/test_properties.py | 3 + test/functional/xlsx_files/properties03.xlsx | Bin 0 -> 8028 bytes 13 files changed, 250 insertions(+), 19 deletions(-) create mode 100644 test/functional/src/test_properties03.c create mode 100644 test/functional/xlsx_files/properties03.xlsx 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 0000000000000000000000000000000000000000..391e196e171142e88d314a89f5f27bdc3f9be06a GIT binary patch literal 8028 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 literal 0 HcmV?d00001