diff --git a/dev/release/fix_example_docs.pl b/dev/release/fix_example_docs.pl index c64ad06c..09d3d369 100644 --- a/dev/release/fix_example_docs.pl +++ b/dev/release/fix_example_docs.pl @@ -25,6 +25,7 @@ my @examples = ( [ 'utf8.c', 'A example of some UTF-8 text' ], [ 'constant_memory.c', 'Write a large file with constant memory usage' ], [ 'merge1.c', 'Create a merged range of cells' ], + [ 'headers_footers.c', 'Example of adding worksheet headers/footers' ], [ 'defined_name.c', 'Example of how to create defined names' ], ); diff --git a/docs/images/headers_footers.png b/docs/images/headers_footers.png new file mode 100644 index 00000000..d6ad0490 Binary files /dev/null and b/docs/images/headers_footers.png differ diff --git a/docs/src/examples.dox b/docs/src/examples.dox index 7200c4b1..74df0eb2 100644 --- a/docs/src/examples.dox +++ b/docs/src/examples.dox @@ -104,10 +104,17 @@ Next example: @ref merge1.c @example merge1.c Example of merging cells in a worksheet. -Next example: @ref defined_name.c +Next example: @ref headers_footers.c @image html merge1.png +@example headers_footers.c +Example of adding worksheet headers and footers to worksheets. + +Next example: @ref defined_name.c +@image html headers_footers.png + + @example defined_name.c Example of how to create defined names (named ranges) using libxlsxwriter. diff --git a/examples/headers_footers.c b/examples/headers_footers.c new file mode 100644 index 00000000..187e4556 --- /dev/null +++ b/examples/headers_footers.c @@ -0,0 +1,101 @@ +/* + * This program shows several examples of how to set up headers and + * footers with libxlsxwriter. + * + * The control characters used in the header/footer strings are: + * + * Control Category Description + * ======= ======== =========== + * &L Justification Left + * &C Center + * &R Right + * &P Information Page number + * &N Total number of pages + * &D Date + * &T Time + * &F File name + * &A Worksheet name + * &fontsize Font Font size + * &"font,style" Font name and style + * &U Single underline + * &E Double underline + * &S Strikethrough + * &X Superscript + * &Y Subscript + * &[Picture] Images Image placeholder + * &G Same as &[Picture] + * && Miscellaneous Literal ampersand & + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + + +int main() { + + lxw_workbook *workbook = new_workbook("headers_footers.xlsx"); + + char preview[] = "Select Print Preview to see the header and footer"; + + /* A simple example to start */ + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, "Simple"); + char header1[] = "&CHere is some centred text."; + char footer1[] = "&LHere is some left aligned text."; + + worksheet_set_header(worksheet1, header1); + worksheet_set_footer(worksheet1, footer1); + + worksheet_set_column(worksheet1, 0, 0, 50, NULL, NULL); + worksheet_write_string(worksheet1, 0, 0, preview, NULL); + + + /* This is an example of some of the header/footer variables. */ + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, "Variables"); + char header2[] = "&LPage &P of &N" "&CFilename: &F" "&RSheetname: &A"; + char footer2[] = "&LCurrent date: &D" "&RCurrent time: &T"; + + worksheet_set_header(worksheet2, header2); + worksheet_set_footer(worksheet2, footer2); + + worksheet_set_column(worksheet2, 0, 0, 50, NULL, NULL); + worksheet_write_string(worksheet2, 0, 0, preview, NULL); + + + /* This example shows how to use more than one font. */ + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, "Mixed fonts"); + char header3[] = "&C&\"Courier New,Bold\"Hello &\"Arial,Italic\"World"; + char footer3[] = "&C&\"Symbol\"e&\"Arial\" = mc&X2"; + + worksheet_set_header(worksheet3, header3); + worksheet_set_footer(worksheet3, footer3); + + worksheet_set_column(worksheet3, 0, 0, 50, NULL, NULL); + worksheet_write_string(worksheet3, 0, 0, preview, NULL); + + + /*Example of line wrapping. */ + lxw_worksheet *worksheet4 = workbook_add_worksheet(workbook, "Word wrap"); + char header4[] = "&CHeading 1\nHeading 2"; + + worksheet_set_header(worksheet4, header4); + + worksheet_set_column(worksheet4, 0, 0, 50, NULL, NULL); + worksheet_write_string(worksheet4, 0, 0, preview, NULL); + + + /* Example of inserting a literal ampersand & */ + lxw_worksheet *worksheet5 = workbook_add_worksheet(workbook, "Ampersand"); + char header5[] = "&CCuriouser && Curiouser - Attorneys at Law"; + + worksheet_set_header(worksheet5, header5); + + worksheet_set_column(worksheet5, 0, 0, 50, NULL, NULL); + worksheet_write_string(worksheet5, 0, 0, preview, NULL); + + + workbook_close(workbook); + + return 0; +} diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 54094ecc..d1953476 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -54,6 +54,7 @@ #include "utility.h" #define LXW_COL_META_MAX 128 +#define LXW_HEADER_FOOTER_MAX 255 /** Default column width in Excel */ #define LXW_DEF_COL_WIDTH 8.43 @@ -134,6 +135,18 @@ typedef struct lxw_merged_range { STAILQ_ENTRY (lxw_merged_range) list_pointers; } lxw_merged_range; +/** + * @brief Header and footer options. + * + * Optional parameters used in the worksheet_set_header_opt() and + * worksheet_set_footer_opt() functions. + * + */ +typedef struct lxw_header_footer_options { + /** Header or footer margin in inches. Excel default is 0.3. */ + double margin; +} lxw_header_footer_options; + /** * @brief Struct to represent an Excel worksheet. * @@ -195,6 +208,10 @@ typedef struct lxw_worksheet { double margin_header; double margin_footer; + uint8_t header_footer_changed; + char header[LXW_HEADER_FOOTER_MAX]; + char footer[LXW_HEADER_FOOTER_MAX]; + uint16_t merged_range_count; STAILQ_ENTRY (lxw_worksheet) list_pointers; @@ -938,6 +955,242 @@ void worksheet_set_paper(lxw_worksheet *worksheet, uint8_t paper_type); void worksheet_set_margins(lxw_worksheet *worksheet, double left, double right, double top, double bottom); +/** + * @brief Set the printed page header caption. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param string The header string. + * + * @return 0 for success, non-zero on error. + * + * Headers and footers are generated using a string which is a combination of + * plain text and control characters. + * + * The available control character are: + * + * + * | Control | Category | Description | + * | --------------- | ------------- | --------------------- | + * | `&L` | Justification | Left | + * | `&C` | | Center | + * | `&R` | | Right | + * | `&P` | Information | Page number | + * | `&N` | | Total number of pages | + * | `&D` | | Date | + * | `&T` | | Time | + * | `&F` | | File name | + * | `&A` | | Worksheet name | + * | `&Z` | | Workbook path | + * | `&fontsize` | Font | Font size | + * | `&"font,style"` | | Font name and style | + * | `&U` | | Single underline | + * | `&E` | | Double underline | + * | `&S` | | Strikethrough | + * | `&X` | | Superscript | + * | `&Y` | | Subscript | + * + * + * Text in headers and footers can be justified (aligned) to the left, center + * and right by prefixing the text with the control characters `&L`, `&C` and + * `&R`. + * + * For example (with ASCII art representation of the results): + * + * @code + * worksheet_set_header(worksheet, "&LHello"); + * + * --------------------------------------------------------------- + * | | + * | Hello | + * | | + * + * + * worksheet_set_header(worksheet, "&CHello"); + * + * --------------------------------------------------------------- + * | | + * | Hello | + * | | + * + * + * worksheet_set_header(worksheet, "&RHello"); + * + * --------------------------------------------------------------- + * | | + * | Hello | + * | | + * + * + * @endcode + * + * For simple text, if you do not specify any justification the text will be + * centred. However, you must prefix the text with `&C` if you specify a font + * name or any other formatting: + * + * @code + * worksheet_set_header(worksheet, "Hello"); + * + * --------------------------------------------------------------- + * | | + * | Hello | + * | | + * + * @endcode + * + * You can have text in each of the justification regions: + * + * @code + * worksheet_set_header(worksheet, "&LCiao&CBello&RCielo"); + * + * --------------------------------------------------------------- + * | | + * | Ciao Bello Cielo | + * | | + * + * @endcode + * + * The information control characters act as variables that Excel will update + * as the workbook or worksheet changes. Times and dates are in the users + * default format: + * + * @code + * worksheet_set_header(worksheet, "&CPage &P of &N"); + * + * --------------------------------------------------------------- + * | | + * | Page 1 of 6 | + * | | + * + * worksheet_set_header(worksheet, "&CUpdated at &T"); + * + * --------------------------------------------------------------- + * | | + * | Updated at 12:30 PM | + * | | + * + * @endcode + * + * You can specify the font size of a section of the text by prefixing it with + * the control character `&n` where `n` is the font size: + * + * @code + * worksheet_set_header(worksheet1, "&C&30Hello Big"); + * worksheet_set_header(worksheet2, "&C&10Hello Small"); + * + * @endcode + * + * You can specify the font of a section of the text by prefixing it with the + * control sequence `&"font,style"` where `fontname` is a font name such as + * Windows font descriptions: "Regular", "Italic", "Bold" or "Bold Italic": + * "Courier New" or "Times New Roman" and `style` is one of the standard + * + * @code + * worksheet_set_header(worksheet1, "&C&\"Courier New,Italic\"Hello"); + * worksheet_set_header(worksheet2, "&C&\"Courier New,Bold Italic\"Hello"); + * worksheet_set_header(worksheet3, "&C&\"Times New Roman,Regular\"Hello"); + * + * @endcode + * + * It is possible to combine all of these features together to create + * sophisticated headers and footers. As an aid to setting up complicated + * headers and footers you can record a page set-up as a macro in Excel and + * look at the format strings that VBA produces. Remember however that VBA + * uses two double quotes `""` to indicate a single double quote. For the last + * example above the equivalent VBA code looks like this: + * + * @code + * .LeftHeader = "" + * .CenterHeader = "&""Times New Roman,Regular""Hello" + * .RightHeader = "" + * + * @endcode + * + * Alternatively you can inspect the header and footer strings in an Excel + * file by unzipping it and grepping the XML sub-files. The following shows + * how to do that using libxml's xmllint to format the XML for clarity: + * + * @code + * + * $ unzip myfile.xlsm -d myfile + * $ xmllint --format `find myfile -name "*.xml" | xargs` \ + * | egrep "Header|Footer" + * + * + * &L&P + * + * + * @endcode + * + * Note that in this case you need to unescape the Html. In the above example + * the header string would be `&L&P`. + * + * To include a single literal ampersand `&` in a header or footer you should + * use a double ampersand `&&`: + * + * @code + * worksheet_set_header(worksheet, "&CCuriouser && Curiouser - Attorneys at Law"); + * @endcode + * + * Note, the header or footer string must be less than 255 characters. Strings + * longer than this will not be written. + * + */ +uint8_t worksheet_set_header(lxw_worksheet *worksheet, char *string); + +/** + * @brief Set the printed page footer caption. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param string The footer string. + * + * @return 0 for success, non-zero on error. + * + * The syntax of this function is the same as worksheet_set_header(). + * + */ +uint8_t worksheet_set_footer(lxw_worksheet *worksheet, char *string); + +/** + * @brief Set the printed page header caption with additional options. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param string The header string. + * @param options Header options. + * + * @return 0 for success, non-zero on error. + * + * The syntax of this function is the same as worksheet_set_header() with an + * additional parameter to specify options for the header. + * + * Currently, the only available option is the header margin: + * + * @code + * + * lxw_header_footer_options header_options = { 0.2 }; + * + * worksheet_set_header_opt(worksheet, "Some text", &header_options); + * + * @endcode + * + */ +uint8_t worksheet_set_header_opt(lxw_worksheet *worksheet, char *string, + lxw_header_footer_options * options); + +/** + * @brief Set the printed page footer caption with additional options. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param string The footer string. + * @param options Footer options. + * + * @return 0 for success, non-zero on error. + * + * The syntax of this function is the same as worksheet_set_header_opt(). + * + */ +uint8_t worksheet_set_footer_opt(lxw_worksheet *worksheet, char *string, + lxw_header_footer_options * options); + /** * @brief Set the order in which pages are printed. * @@ -991,6 +1244,10 @@ STATIC void _write_merge_cell(lxw_worksheet *worksheet, lxw_merged_range * merged_range); STATIC void _write_merge_cells(lxw_worksheet *worksheet); +STATIC void _worksheet_write_odd_header(lxw_worksheet *worksheet); +STATIC void _worksheet_write_odd_footer(lxw_worksheet *worksheet); +STATIC void _worksheet_write_header_footer(lxw_worksheet *worksheet); + #endif /* TESTING */ /* *INDENT-OFF* */ diff --git a/src/worksheet.c b/src/worksheet.c index 69df42a8..9f5078d5 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -1235,6 +1235,44 @@ _write_merge_cells(lxw_worksheet *self) } } +/* + * Write the element. + */ +STATIC void +_worksheet_write_odd_header(lxw_worksheet *self) +{ + _xml_data_element(self->file, "oddHeader", self->header, NULL); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_odd_footer(lxw_worksheet *self) +{ + _xml_data_element(self->file, "oddFooter", self->footer, NULL); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_header_footer(lxw_worksheet *self) +{ + if (!self->header_footer_changed) + return; + + _xml_start_tag(self->file, "headerFooter", NULL); + + if (self->header[0] != '\0') + _worksheet_write_odd_header(self); + + if (self->footer[0] != '\0') + _worksheet_write_odd_footer(self); + + _xml_end_tag(self->file, "headerFooter"); +} + /* * Check that row and col are within the allowed Excel range and store max * and min values for use in other methods/elements. @@ -1316,6 +1354,9 @@ _worksheet_assemble_xml_file(lxw_worksheet *self) /* Write the worksheet page setup. */ _worksheet_write_page_setup(self); + /* Write the headerFooter element. */ + _worksheet_write_header_footer(self); + /* Close the worksheet tag. */ _xml_end_tag(self->file, "worksheet"); } @@ -1830,3 +1871,69 @@ worksheet_set_margins(lxw_worksheet *self, double left, double right, if (bottom >= 0) self->margin_bottom = bottom; } + +/* + * Set the page header caption and options. + */ +uint8_t +worksheet_set_header_opt(lxw_worksheet *self, char *string, + lxw_header_footer_options * options) +{ + if (options) { + if (options->margin > 0) + self->margin_header = options->margin; + } + + if (!string) + return 1; + + if (strlen(string) >= LXW_HEADER_FOOTER_MAX) + return 1; + + strcpy(self->header, string); + self->header_footer_changed = 1; + + return 0; +} + +/* + * Set the page footer caption and options. + */ +uint8_t +worksheet_set_footer_opt(lxw_worksheet *self, char *string, + lxw_header_footer_options * options) +{ + if (options) { + if (options->margin > 0) + self->margin_footer = options->margin; + } + + if (!string) + return 1; + + if (strlen(string) >= LXW_HEADER_FOOTER_MAX) + return 1; + + strcpy(self->footer, string); + self->header_footer_changed = 1; + + return 0; +} + +/* + * Set the page header caption. + */ +uint8_t +worksheet_set_header(lxw_worksheet *self, char *string) +{ + return worksheet_set_header_opt(self, string, NULL); +} + +/* + * Set the page footer caption. + */ +uint8_t +worksheet_set_footer(lxw_worksheet *self, char *string) +{ + return worksheet_set_footer_opt(self, string, NULL); +} diff --git a/test/unit/worksheet/test_worksheet_write_header_footer.c b/test/unit/worksheet/test_worksheet_write_header_footer.c new file mode 100644 index 00000000..2c4c2173 --- /dev/null +++ b/test/unit/worksheet/test_worksheet_write_header_footer.c @@ -0,0 +1,108 @@ +/* + * Tests for the lib_xlsx_writer library. + * + * Copyright 2014, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "xlsxwriter/worksheet.h" + +// Test the header and footer functions. +CTEST(worksheet, write_odd_header) { + + char* got; + char exp[] = "Page &P of &N"; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_set_header(worksheet, "Page &P of &N"); + + _worksheet_write_odd_header(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + +// Test the header and footer functions. +CTEST(worksheet, write_odd_footer) { + + char* got; + char exp[] = "&F"; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_set_footer(worksheet, "&F"); + + _worksheet_write_odd_footer(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + + +// Test the header and footer functions. +CTEST(worksheet, _worksheet_write_header_footer1) { + + char* got; + char exp[] = "Page &P of &N"; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_set_header(worksheet, "Page &P of &N"); + + _worksheet_write_header_footer(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + +// Test the header and footer functions. +CTEST(worksheet, _worksheet_write_header_footer2) { + + char* got; + char exp[] = "&F"; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_set_footer(worksheet, "&F"); + + _worksheet_write_header_footer(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} + +// Test the header and footer functions. +CTEST(worksheet, _worksheet_write_header_footer3) { + + char* got; + char exp[] = "Page &P of &N&F"; + FILE* testfile = tmpfile(); + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_set_header(worksheet, "Page &P of &N"); + worksheet_set_footer(worksheet, "&F"); + + _worksheet_write_header_footer(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} diff --git a/test/unit/worksheet/test_worksheet_write_page_margin.c b/test/unit/worksheet/test_worksheet_write_page_margin.c index 25728b02..4e374621 100644 --- a/test/unit/worksheet/test_worksheet_write_page_margin.c +++ b/test/unit/worksheet/test_worksheet_write_page_margin.c @@ -116,3 +116,26 @@ CTEST(worksheet, write_page_margin06) { _free_worksheet(worksheet); } + +/* Test the _write_page_margins() method. */ +CTEST(worksheet, write_page_margin07) { + char* got; + char exp[] = ""; + FILE* testfile = tmpfile(); + lxw_header_footer_options header_options = {0.2}; + lxw_header_footer_options footer_options = {0.4}; + + lxw_worksheet *worksheet = _new_worksheet(NULL); + worksheet->file = testfile; + + worksheet_set_header_opt(worksheet, "", &header_options); + worksheet_set_footer_opt(worksheet, "", &footer_options); + + _worksheet_write_page_margins(worksheet); + + RUN_XLSX_STREQ(exp, got); + + _free_worksheet(worksheet); +} +