diff --git a/NEWS b/NEWS index ecf990a..c60c11a 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ 1.1 + - Update bundled libxlswriter 0.8.4 - Do not write blank xlsx strings for NA and "" character values - Coerce bit64 vectors to double with warning (xlsx does not have int64) diff --git a/src/Makevars b/src/Makevars index 4833ff0..5169b92 100644 --- a/src/Makevars +++ b/src/Makevars @@ -5,7 +5,7 @@ LIBXLSXWRITER = \ libxlsxwriter/core.o libxlsxwriter/relationships.o libxlsxwriter/worksheet.o \ libxlsxwriter/custom.o libxlsxwriter/shared_strings.o libxlsxwriter/xmlwriter.o \ libxlsxwriter/drawing.o libxlsxwriter/styles.o tmpfileplus/tmpfileplus.o \ - minizip/ioapi.o minizip/zip.o + libxlsxwriter/chartsheet.o minizip/ioapi.o minizip/zip.o STATICLIB=libxlsxwriter/libstatxlsxwriter.a diff --git a/src/include/xlsxwriter.h b/src/include/xlsxwriter.h index 1049075..544e65b 100644 --- a/src/include/xlsxwriter.h +++ b/src/include/xlsxwriter.h @@ -18,6 +18,6 @@ #include "xlsxwriter/format.h" #include "xlsxwriter/utility.h" -#define LXW_VERSION "0.7.6" +#define LXW_VERSION "0.8.4" #endif /* __LXW_XLSXWRITER_H__ */ diff --git a/src/include/xlsxwriter/chart.h b/src/include/xlsxwriter/chart.h index 801dd3a..b35104d 100644 --- a/src/include/xlsxwriter/chart.h +++ b/src/include/xlsxwriter/chart.h @@ -172,11 +172,17 @@ typedef enum lxw_chart_legend_position { /** Chart legend positioned at bottom. */ LXW_CHART_LEGEND_BOTTOM, + /** Chart legend positioned at top right. */ + LXW_CHART_LEGEND_TOP_RIGHT, + /** Chart legend overlaid at right side. */ LXW_CHART_LEGEND_OVERLAY_RIGHT, /** Chart legend overlaid at left side. */ - LXW_CHART_LEGEND_OVERLAY_LEFT + LXW_CHART_LEGEND_OVERLAY_LEFT, + + /** Chart legend overlaid at top right. */ + LXW_CHART_LEGEND_OVERLAY_TOP_RIGHT } lxw_chart_legend_position; /** @@ -523,6 +529,20 @@ typedef enum lxw_chart_axis_label_position { LXW_CHART_AXIS_LABEL_POSITION_NONE } lxw_chart_axis_label_position; +/** + * @brief Axis label alignments. + */ +typedef enum lxw_chart_axis_label_alignment { + /** Chart axis label alignment: center. */ + LXW_CHART_AXIS_LABEL_ALIGN_CENTER, + + /** Chart axis label alignment: left. */ + LXW_CHART_AXIS_LABEL_ALIGN_LEFT, + + /** Chart axis label alignment: right. */ + LXW_CHART_AXIS_LABEL_ALIGN_RIGHT +} lxw_chart_axis_label_alignment; + /** * @brief Display units for chart value axis. */ @@ -981,6 +1001,7 @@ typedef struct lxw_chart_axis { uint8_t axis_position; uint8_t position_axis; uint8_t label_position; + uint8_t label_align; uint8_t hidden; uint8_t reverse; @@ -1048,6 +1069,7 @@ typedef struct lxw_chart { uint8_t in_use; uint8_t chart_group; uint8_t cat_has_num_fmt; + uint8_t is_chartsheet; uint8_t has_horiz_cat_axis; uint8_t has_horiz_val_axis; @@ -1103,6 +1125,7 @@ typedef struct lxw_chart { lxw_chart_fill *down_bar_fill; uint8_t default_label_position; + uint8_t is_protected; STAILQ_ENTRY (lxw_chart) ordered_list_pointers; STAILQ_ENTRY (lxw_chart) list_pointers; @@ -1284,7 +1307,7 @@ void chart_series_set_values(lxw_chart_series *series, const char *sheetname, * @code * lxw_chart_series *series = chart_add_series(chart, NULL, "=Sheet1!$B$2:$B$7"); * - * chart_series_set_name(series, "=Sheet1!$B1$1"); + * chart_series_set_name(series, "=Sheet1!$B$1"); * @endcode * * See also the `chart_series_set_name_range()` function to see how to set the @@ -2275,7 +2298,7 @@ lxw_chart_axis *chart_axis_get(lxw_chart *chart, * a cell in the workbook that contains the name: * * @code - * chart_axis_set_name(chart->x_axis, "=Sheet1!$B1$1"); + * chart_axis_set_name(chart->x_axis, "=Sheet1!$B$1"); * @endcode * * See also the `chart_axis_set_name_range()` function to see how to set the @@ -2567,7 +2590,7 @@ void chart_axis_set_position(lxw_chart_axis *axis, uint8_t position); * * @code * chart_axis_set_label_position(chart->x_axis, LXW_CHART_AXIS_LABEL_POSITION_HIGH); - chart_axis_set_label_position(chart->y_axis, LXW_CHART_AXIS_LABEL_POSITION_HIGH); + * chart_axis_set_label_position(chart->y_axis, LXW_CHART_AXIS_LABEL_POSITION_HIGH); * @endcode * * @image html chart_label_position2.png @@ -2590,6 +2613,31 @@ void chart_axis_set_position(lxw_chart_axis *axis, uint8_t position); */ void chart_axis_set_label_position(lxw_chart_axis *axis, uint8_t position); +/** + * @brief Set the alignment of the axis labels. + * + * @param axis A pointer to a chart #lxw_chart_axis object. + * @param align A #lxw_chart_axis_label_alignment value. + * + * Position the category axis labels for the chart. The labels are the + * numbers, or strings or dates, on the axis that indicate the categories + * of the axis. + * + * The allowable values: + * + * - #LXW_CHART_AXIS_LABEL_ALIGN_CENTER - Align label center (default). + * - #LXW_CHART_AXIS_LABEL_ALIGN_LEFT - Align label left. + * - #LXW_CHART_AXIS_LABEL_ALIGN_RIGHT - Align label right. + * + * @code + * chart_axis_set_label_align(chart->x_axis, LXW_CHART_AXIS_LABEL_ALIGN_RIGHT); + * @endcode + * + * **Axis types**: This function is applicable to category axes only. + * See @ref ww_charts_axes. + */ +void chart_axis_set_label_align(lxw_chart_axis *axis, uint8_t align); + /** * @brief Set the minimum value for a chart axis. * @@ -2958,7 +3006,7 @@ void chart_axis_minor_gridlines_set_line(lxw_chart_axis *axis, * a cell in the workbook that contains the name: * * @code - * chart_title_set_name(chart, "=Sheet1!$B1$1"); + * chart_title_set_name(chart, "=Sheet1!$B$1"); * @endcode * * See also the `chart_title_set_name_range()` function to see how to set the @@ -3040,8 +3088,10 @@ void chart_title_off(lxw_chart *chart); * LXW_CHART_LEGEND_LEFT * LXW_CHART_LEGEND_TOP * LXW_CHART_LEGEND_BOTTOM + * LXW_CHART_LEGEND_TOP_RIGHT * LXW_CHART_LEGEND_OVERLAY_RIGHT * LXW_CHART_LEGEND_OVERLAY_LEFT + * LXW_CHART_LEGEND_OVERLAY_TOP_RIGHT * * For example: * diff --git a/src/include/xlsxwriter/chartsheet.h b/src/include/xlsxwriter/chartsheet.h new file mode 100644 index 0000000..76b49d9 --- /dev/null +++ b/src/include/xlsxwriter/chartsheet.h @@ -0,0 +1,544 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2018, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * chartsheet - A libxlsxwriter library for creating Excel XLSX chartsheet files. + * + */ + +/** + * @page chartsheet_page The Chartsheet object + * + * The Chartsheet object represents an Excel chartsheet, which is a type of + * worksheet that only contains a chart. The Chartsheet object handles + * operations such as adding a chart and setting the page layout. + * + * See @ref chartsheet.h for full details of the functionality. + * + * @file chartsheet.h + * + * @brief Functions related to adding data and formatting to a chartsheet. + * + * The Chartsheet object represents an Excel chartsheet. It handles operations + * such as adding a chart and setting the page layout. + * + * A Chartsheet object isn't created directly. Instead a chartsheet is created + * by calling the workbook_add_chartsheet() function from a Workbook object. A + * chartsheet object functions as a worksheet and not as a chart. In order to + * have it display data a #lxw_chart object must be created and added to the + * chartsheet: + * + * @code + * #include "xlsxwriter.h" + * + * int main() { + * + * lxw_workbook *workbook = new_workbook("chartsheet.xlsx"); + * lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + * lxw_chartsheet *chartsheet = workbook_add_chartsheet(workbook, NULL); + * lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_BAR); + * + * //... Set up the chart. + * + * // Add the chart to the chartsheet. + * return workbook_close(workbook); + * + * } + * @endcode + * + * @image html chartsheet.png + * + * The data for the chartsheet chart must be contained on a separate + * worksheet. That is why it is always created in conjunction with at least + * one data worksheet, as shown above. + */ + +#ifndef __LXW_CHARTSHEET_H__ +#define __LXW_CHARTSHEET_H__ + +#include + +#include "common.h" +#include "worksheet.h" +#include "drawing.h" +#include "utility.h" + +/** + * @brief Struct to represent an Excel chartsheet. + * + * The members of the lxw_chartsheet struct aren't modified directly. Instead + * the chartsheet properties are set by calling the functions shown in + * chartsheet.h. + */ +typedef struct lxw_chartsheet { + + FILE *file; + lxw_worksheet *worksheet; + lxw_chart *chart; + + struct lxw_protection protection; + uint8_t is_protected; + + char *name; + char *quoted_name; + char *tmpdir; + uint32_t index; + uint8_t active; + uint8_t selected; + uint8_t hidden; + uint16_t *active_sheet; + uint16_t *first_sheet; + uint16_t rel_count; + + STAILQ_ENTRY (lxw_chartsheet) list_pointers; + +} lxw_chartsheet; + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +/** + * @brief Insert a chart object into a chartsheet. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param chart A #lxw_chart object created via workbook_add_chart(). + * + * @return A #lxw_error code. + * + * The `%chartsheet_set_chart()` function can be used to insert a chart into a + * chartsheet. The chart object must be created first using the + * `workbook_add_chart()` function and configured using the @ref chart.h + * functions. + * + * @code + * // Create the chartsheet. + * lxw_chartsheet *chartsheet = workbook_add_chartsheet(workbook, NULL); + * + * // Create a chart object. + * lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_LINE); + * + * // Add a data series to the chart. + * chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$6"); + * + * // Insert the chart into the chartsheet. + * chartsheet_set_chart(chartsheet, chart); + * @endcode + * + * @image html chartsheet2.png + * + * **Note:** + * + * A chart may only be inserted once into a chartsheet or a worksheet. If + * several similar charts are required then each one must be created + * separately. + * + */ +lxw_error chartsheet_set_chart(lxw_chartsheet *chartsheet, lxw_chart *chart); + +/* Not currently required since scale options aren't useful in a chartsheet. */ +lxw_error chartsheet_set_chart_opt(lxw_chartsheet *chartsheet, + lxw_chart *chart, + lxw_image_options *user_options); + +/** + * @brief Make a chartsheet the active, i.e., visible chartsheet. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * + * The `%chartsheet_activate()` function is used to specify which chartsheet + * is initially visible in a multi-sheet workbook: + * + * @code + * lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + * lxw_chartsheet *chartsheet1 = workbook_add_chartsheet(workbook, NULL); + * lxw_chartsheet *chartsheet2 = workbook_add_chartsheet(workbook, NULL); + * lxw_chartsheet *chartsheet3 = workbook_add_chartsheet(workbook, NULL); + * + * chartsheet_activate(chartsheet3); + * @endcode + * + * @image html chartsheet_activate.png + * + * More than one chartsheet can be selected via the `chartsheet_select()` + * function, see below, however only one chartsheet can be active. + * + * The default active chartsheet is the first chartsheet. + * + * See also `worksheet_activate()`. + * + */ +void chartsheet_activate(lxw_chartsheet *chartsheet); + +/** + * @brief Set a chartsheet tab as selected. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * + * The `%chartsheet_select()` function is used to indicate that a chartsheet + * is selected in a multi-sheet workbook: + * + * @code + * chartsheet_activate(chartsheet1); + * chartsheet_select(chartsheet2); + * chartsheet_select(chartsheet3); + * + * @endcode + * + * A selected chartsheet has its tab highlighted. Selecting chartsheets is a + * way of grouping them together so that, for example, several chartsheets + * could be printed in one go. A chartsheet that has been activated via the + * `chartsheet_activate()` function will also appear as selected. + * + * See also `worksheet_select()`. + * + */ +void chartsheet_select(lxw_chartsheet *chartsheet); + +/** + * @brief Hide the current chartsheet. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * + * The `%chartsheet_hide()` function is used to hide a chartsheet: + * + * @code + * chartsheet_hide(chartsheet2); + * @endcode + * + * You may wish to hide a chartsheet in order to avoid confusing a user with + * intermediate data or calculations. + * + * @image html hide_sheet.png + * + * A hidden chartsheet can not be activated or selected so this function is + * mutually exclusive with the `chartsheet_activate()` and + * `chartsheet_select()` functions. In addition, since the first chartsheet + * will default to being the active chartsheet, you cannot hide the first + * chartsheet without activating another sheet: + * + * @code + * chartsheet_activate(chartsheet2); + * chartsheet_hide(chartsheet1); + * @endcode + * + * See also `worksheet_hide()`. + * + */ +void chartsheet_hide(lxw_chartsheet *chartsheet); + +/** + * @brief Set current chartsheet as the first visible sheet tab. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * + * The `chartsheet_activate()` function determines which chartsheet is + * initially selected. However, if there are a large number of chartsheets the + * selected chartsheet may not appear on the screen. To avoid this you can + * select the leftmost visible chartsheet tab using + * `%chartsheet_set_first_sheet()`: + * + * @code + * chartsheet_set_first_sheet(chartsheet19); // First visible chartsheet tab. + * chartsheet_activate(chartsheet20); // First visible chartsheet. + * @endcode + * + * This function is not required very often. The default value is the first + * chartsheet. + * + * See also `worksheet_set_first_sheet()`. + * + */ +void chartsheet_set_first_sheet(lxw_chartsheet *chartsheet); + +/** + * @brief Set the color of the chartsheet tab. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param color The tab color. + * + * The `%chartsheet_set_tab_color()` function is used to change the color of + * the chartsheet tab: + * + * @code + * chartsheet_set_tab_color(chartsheet1, LXW_COLOR_RED); + * chartsheet_set_tab_color(chartsheet2, LXW_COLOR_GREEN); + * chartsheet_set_tab_color(chartsheet3, 0xFF9900); // Orange. + * @endcode + * + * The color should be an RGB integer value, see @ref working_with_colors. + * + * See also `worksheet_set_tab_color()`. + */ +void chartsheet_set_tab_color(lxw_chartsheet *chartsheet, lxw_color_t color); + +/** + * @brief Protect elements of a chartsheet from modification. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param password A chartsheet password. + * @param options Chartsheet elements to protect. + * + * The `%chartsheet_protect()` function protects chartsheet elements from + * modification: + * + * @code + * chartsheet_protect(chartsheet, "Some Password", options); + * @endcode + * + * The `password` and lxw_protection pointer are both optional: + * + * @code + * chartsheet_protect(chartsheet2, NULL, my_options); + * chartsheet_protect(chartsheet3, "password", NULL); + * chartsheet_protect(chartsheet4, "password", my_options); + * @endcode + * + * Passing a `NULL` password is the same as turning on protection without a + * password. Passing a `NULL` password and `NULL` options had no effect on + * chartsheets. + * + * You can specify which chartsheet elements you wish to protect by passing a + * lxw_protection pointer in the `options` argument. In Excel chartsheets only + * have two protection options: + * + * no_content + * no_objects + * + * All parameters are off by default. Individual elements can be protected as + * follows: + * + * @code + * lxw_protection options = { + * .no_content = 1, + * .no_objects = 1, + * }; + * + * chartsheet_protect(chartsheet, NULL, &options); + * + * @endcode + * + * See also worksheet_protect(). + * + * **Note:** Sheet level passwords in Excel offer **very** weak + * protection. They don't encrypt your data and are very easy to + * deactivate. Full workbook encryption is not supported by `libxlsxwriter` + * since it requires a completely different file format. + */ +void chartsheet_protect(lxw_chartsheet *chartsheet, const char *password, + lxw_protection *options); + +/** + * @brief Set the chartsheet zoom factor. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param scale Chartsheet zoom factor. + * + * Set the chartsheet zoom factor in the range `10 <= zoom <= 400`: + * + * @code + * chartsheet_set_zoom(chartsheet, 75); + * @endcode + * + * The default zoom factor is 100. It isn't possible to set the zoom to + * "Selection" because it is calculated by Excel at run-time. + * + * See also `worksheet_set_zoom()`. + */ +void chartsheet_set_zoom(lxw_chartsheet *chartsheet, uint16_t scale); + +/** + * @brief Set the page orientation as landscape. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * + * This function is used to set the orientation of a chartsheet's printed page + * to landscape. The default chartsheet orientation is landscape, so this + * function isn't generally required: + * + * @code + * chartsheet_set_landscape(chartsheet); + * @endcode + */ +void chartsheet_set_landscape(lxw_chartsheet *chartsheet); + +/** + * @brief Set the page orientation as portrait. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * + * This function is used to set the orientation of a chartsheet's printed page + * to portrait: + * + * @code + * chartsheet_set_portrait(chartsheet); + * @endcode + */ +void chartsheet_set_portrait(lxw_chartsheet *chartsheet); + +/** + * @brief Set the paper type for printing. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param paper_type The Excel paper format type. + * + * This function is used to set the paper format for the printed output of a + * chartsheet: + * + * @code + * chartsheet_set_paper(chartsheet1, 1); // US Letter + * chartsheet_set_paper(chartsheet2, 9); // A4 + * @endcode + * + * If you do not specify a paper type the chartsheet will print using the + * printer's default paper style. + * + * See `worksheet_set_paper()` for a full list of available paper sizes. + */ +void chartsheet_set_paper(lxw_chartsheet *chartsheet, uint8_t paper_type); + +/** + * @brief Set the chartsheet margins for the printed page. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param left Left margin in inches. Excel default is 0.7. + * @param right Right margin in inches. Excel default is 0.7. + * @param top Top margin in inches. Excel default is 0.75. + * @param bottom Bottom margin in inches. Excel default is 0.75. + * + * The `%chartsheet_set_margins()` function is used to set the margins of the + * chartsheet when it is printed. The units are in inches. Specifying `-1` for + * any parameter will give the default Excel value as shown above. + * + * @code + * chartsheet_set_margins(chartsheet, 1.3, 1.2, -1, -1); + * @endcode + * + */ +void chartsheet_set_margins(lxw_chartsheet *chartsheet, double left, + double right, double top, double bottom); + +/** + * @brief Set the printed page header caption. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param string The header string. + * + * @return A #lxw_error code. + * + * Headers and footers are generated using a string which is a combination of + * plain text and control characters + * + * @code + * chartsheet_set_header(chartsheet, "&LHello"); + * + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | + * + * + * chartsheet_set_header(chartsheet, "&CHello"); + * + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | + * + * + * chartsheet_set_header(chartsheet, "&RHello"); + * + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | + * + * + * @endcode + * + * See `worksheet_set_header()` for a full explanation of the syntax of + * Excel's header formatting and control characters. + * + */ +lxw_error chartsheet_set_header(lxw_chartsheet *chartsheet, + const char *string); + +/** + * @brief Set the printed page footer caption. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param string The footer string. + * + * @return A #lxw_error code. + * + * The syntax of this function is the same as chartsheet_set_header(). + * + */ +lxw_error chartsheet_set_footer(lxw_chartsheet *chartsheet, + const char *string); + +/** + * @brief Set the printed page header caption with additional options. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param string The header string. + * @param options Header options. + * + * @return A #lxw_error code. + * + * The syntax of this function is the same as chartsheet_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 }; + * + * chartsheet_set_header_opt(chartsheet, "Some text", &header_options); + * + * @endcode + * + */ +lxw_error chartsheet_set_header_opt(lxw_chartsheet *chartsheet, + const char *string, + lxw_header_footer_options *options); + +/** + * @brief Set the printed page footer caption with additional options. + * + * @param chartsheet Pointer to a lxw_chartsheet instance to be updated. + * @param string The footer string. + * @param options Footer options. + * + * @return A #lxw_error code. + * + * The syntax of this function is the same as chartsheet_set_header_opt(). + * + */ +lxw_error chartsheet_set_footer_opt(lxw_chartsheet *chartsheet, + const char *string, + lxw_header_footer_options *options); + +lxw_chartsheet *lxw_chartsheet_new(); +void lxw_chartsheet_free(lxw_chartsheet *chartsheet); +void lxw_chartsheet_assemble_xml_file(lxw_chartsheet *chartsheet); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +STATIC void _chartsheet_xml_declaration(lxw_chartsheet *chartsheet); +STATIC void _chartsheet_write_sheet_protection(lxw_chartsheet *chartsheet); +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_CHARTSHEET_H__ */ diff --git a/src/include/xlsxwriter/common.h b/src/include/xlsxwriter/common.h index 337925e..8865240 100644 --- a/src/include/xlsxwriter/common.h +++ b/src/include/xlsxwriter/common.h @@ -65,6 +65,9 @@ typedef enum lxw_error { /** Error encountered when creating a tmpfile during file assembly. */ LXW_ERROR_CREATING_TMPFILE, + /** Error reading a tmpfile. */ + LXW_ERROR_READING_TMPFILE, + /** Zlib error with a file operation while creating xlsx file. */ LXW_ERROR_ZIP_FILE_OPERATION, @@ -177,6 +180,9 @@ enum lxw_custom_property_types { #define LXW_FILENAME_LENGTH 128 #define LXW_IGNORE 1 +#define LXW_PORTRAIT 1 +#define LXW_LANDSCAPE 0 + #define LXW_SCHEMA_MS "http://schemas.microsoft.com/office/2006/relationships" #define LXW_SCHEMA_ROOT "http://schemas.openxmlformats.org" #define LXW_SCHEMA_DRAWING LXW_SCHEMA_ROOT "/drawingml/2006" diff --git a/src/include/xlsxwriter/content_types.h b/src/include/xlsxwriter/content_types.h index 7caa85e..b1fe817 100644 --- a/src/include/xlsxwriter/content_types.h +++ b/src/include/xlsxwriter/content_types.h @@ -46,6 +46,8 @@ 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 *content_types, const char *name); +void lxw_ct_add_chartsheet_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, diff --git a/src/include/xlsxwriter/drawing.h b/src/include/xlsxwriter/drawing.h index 0455fe0..2cd660e 100644 --- a/src/include/xlsxwriter/drawing.h +++ b/src/include/xlsxwriter/drawing.h @@ -77,6 +77,7 @@ typedef struct lxw_drawing { FILE *file; uint8_t embedded; + uint8_t orientation; struct lxw_drawing_objects *drawing_objects; diff --git a/src/include/xlsxwriter/format.h b/src/include/xlsxwriter/format.h index a73c2d7..8f3419c 100644 --- a/src/include/xlsxwriter/format.h +++ b/src/include/xlsxwriter/format.h @@ -691,7 +691,7 @@ void format_set_num_format(lxw_format *format, const char *num_format); * * @code * format = workbook_add_format(workbook); - * format_set_num_format(format, 0x0F); // d-mmm-yy + * format_set_num_format_index(format, 0x0F); // d-mmm-yy * @endcode * * @note diff --git a/src/include/xlsxwriter/shared_strings.h b/src/include/xlsxwriter/shared_strings.h index 049c561..830e403 100644 --- a/src/include/xlsxwriter/shared_strings.h +++ b/src/include/xlsxwriter/shared_strings.h @@ -37,6 +37,7 @@ STAILQ_HEAD(sst_order_list, sst_element); struct sst_element { uint32_t index; char *string; + uint8_t is_rich_string; STAILQ_ENTRY (sst_element) sst_order_pointers; RB_ENTRY (sst_element) sst_tree_pointers; @@ -64,7 +65,8 @@ extern "C" { lxw_sst *lxw_sst_new(); void lxw_sst_free(lxw_sst *sst); -struct sst_element *lxw_get_sst_index(lxw_sst *sst, const char *string); +struct sst_element *lxw_get_sst_index(lxw_sst *sst, const char *string, + uint8_t is_rich_string); void lxw_sst_assemble_xml_file(lxw_sst *self); /* Declarations required for unit testing. */ diff --git a/src/include/xlsxwriter/styles.h b/src/include/xlsxwriter/styles.h index 93edae1..5ed9f43 100644 --- a/src/include/xlsxwriter/styles.h +++ b/src/include/xlsxwriter/styles.h @@ -10,6 +10,7 @@ #define __LXW_STYLES_H__ #include +#include #include "format.h" @@ -40,6 +41,8 @@ extern "C" { lxw_styles *lxw_styles_new(); void lxw_styles_free(lxw_styles *styles); void lxw_styles_assemble_xml_file(lxw_styles *self); +void lxw_styles_write_string_fragment(lxw_styles *self, char *string); +void lxw_styles_write_rich_font(lxw_styles *lxw_styles, lxw_format *format); /* Declarations required for unit testing. */ #ifdef TESTING @@ -48,10 +51,12 @@ STATIC void _styles_xml_declaration(lxw_styles *self); STATIC void _write_style_sheet(lxw_styles *self); STATIC void _write_font_size(lxw_styles *self, double font_size); STATIC void _write_font_color_theme(lxw_styles *self, uint8_t theme); -STATIC void _write_font_name(lxw_styles *self, const char *font_name); +STATIC void _write_font_name(lxw_styles *self, const char *font_name, + uint8_t is_rich_string); STATIC void _write_font_family(lxw_styles *self, uint8_t font_family); STATIC void _write_font_scheme(lxw_styles *self, const char *font_scheme); -STATIC void _write_font(lxw_styles *self, lxw_format *format); +STATIC void _write_font(lxw_styles *self, lxw_format *format, + uint8_t is_rich_string); STATIC void _write_fonts(lxw_styles *self); STATIC void _write_default_fill(lxw_styles *self, const char *pattern); STATIC void _write_fills(lxw_styles *self); diff --git a/src/include/xlsxwriter/utility.h b/src/include/xlsxwriter/utility.h index e9fff84..cb9b3ea 100644 --- a/src/include/xlsxwriter/utility.h +++ b/src/include/xlsxwriter/utility.h @@ -86,6 +86,20 @@ extern "C" { #endif /* *INDENT-ON* */ +/** + * @brief Retrieve the library version. + * + * @return The "X.Y.Z" version string. + * + * Get the library version as a "X.Y.Z" version string + * + * @code + * printf("Libxlsxwriter version = %s\n", lxw_version()); + * @endcode + * + */ +const char *lxw_version(void); + /** * @brief Converts a libxlsxwriter error number to a string. * @@ -163,6 +177,8 @@ int lxw_sprintf_dbl(char *data, double number); lxw_snprintf(data, LXW_ATTR_32, "%.16g", number) #endif +uint16_t lxw_hash_password(const char *password); + /* *INDENT-OFF* */ #ifdef __cplusplus } diff --git a/src/include/xlsxwriter/workbook.h b/src/include/xlsxwriter/workbook.h index a4e721f..8d88a1b 100644 --- a/src/include/xlsxwriter/workbook.h +++ b/src/include/xlsxwriter/workbook.h @@ -46,6 +46,7 @@ #include #include "worksheet.h" +#include "chartsheet.h" #include "chart.h" #include "shared_strings.h" #include "hash_table.h" @@ -55,12 +56,27 @@ /* Define the tree.h RB structs for the red-black head types. */ RB_HEAD(lxw_worksheet_names, lxw_worksheet_name); +RB_HEAD(lxw_chartsheet_names, lxw_chartsheet_name); /* Define the queue.h structs for the workbook lists. */ +STAILQ_HEAD(lxw_sheets, lxw_sheet); STAILQ_HEAD(lxw_worksheets, lxw_worksheet); +STAILQ_HEAD(lxw_chartsheets, lxw_chartsheet); STAILQ_HEAD(lxw_charts, lxw_chart); TAILQ_HEAD(lxw_defined_names, lxw_defined_name); +/* TODO */ +typedef struct lxw_sheet { + uint8_t is_chartsheet; + + union { + lxw_worksheet *worksheet; + lxw_chartsheet *chartsheet; + } u; + + STAILQ_ENTRY (lxw_sheet) list_pointers; +} lxw_sheet; + /* Struct to represent a worksheet name/pointer pair. */ typedef struct lxw_worksheet_name { const char *name; @@ -69,18 +85,37 @@ typedef struct lxw_worksheet_name { RB_ENTRY (lxw_worksheet_name) tree_pointers; } lxw_worksheet_name; +/* Struct to represent a chartsheet name/pointer pair. */ +typedef struct lxw_chartsheet_name { + const char *name; + lxw_chartsheet *chartsheet; + + RB_ENTRY (lxw_chartsheet_name) tree_pointers; +} lxw_chartsheet_name; + /* Wrapper around RB_GENERATE_STATIC from tree.h to avoid unused function * warnings and to avoid portability issues with the _unused attribute. */ -#define LXW_RB_GENERATE_NAMES(name, type, field, cmp) \ - RB_GENERATE_INSERT_COLOR(name, type, field, static) \ - RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ - RB_GENERATE_INSERT(name, type, field, cmp, static) \ - RB_GENERATE_REMOVE(name, type, field, static) \ - RB_GENERATE_FIND(name, type, field, cmp, static) \ - RB_GENERATE_NEXT(name, type, field, static) \ - RB_GENERATE_MINMAX(name, type, field, static) \ - /* Add unused struct to allow adding a semicolon */ \ - struct lxw_rb_generate_names{int unused;} +#define LXW_RB_GENERATE_WORKSHEET_NAMES(name, type, field, cmp) \ + RB_GENERATE_INSERT_COLOR(name, type, field, static) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ + RB_GENERATE_INSERT(name, type, field, cmp, static) \ + RB_GENERATE_REMOVE(name, type, field, static) \ + RB_GENERATE_FIND(name, type, field, cmp, static) \ + RB_GENERATE_NEXT(name, type, field, static) \ + RB_GENERATE_MINMAX(name, type, field, static) \ + /* Add unused struct to allow adding a semicolon */ \ + struct lxw_rb_generate_worksheet_names{int unused;} + +#define LXW_RB_GENERATE_CHARTSHEET_NAMES(name, type, field, cmp) \ + RB_GENERATE_INSERT_COLOR(name, type, field, static) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ + RB_GENERATE_INSERT(name, type, field, cmp, static) \ + RB_GENERATE_REMOVE(name, type, field, static) \ + RB_GENERATE_FIND(name, type, field, cmp, static) \ + RB_GENERATE_NEXT(name, type, field, static) \ + RB_GENERATE_MINMAX(name, type, field, static) \ + /* Add unused struct to allow adding a semicolon */ \ + struct lxw_rb_generate_charsheet_names{int unused;} /** * @brief Macro to loop over all the worksheets in a workbook. @@ -181,7 +216,7 @@ typedef struct lxw_doc_properties { * to assembling the final XLSX file. The temporary files are created in the * system's temp directory. If the default temporary directory isn't * accessible to your application, or doesn't contain enough space, you can - * specify an alternative location using the `tempdir` option. + * specify an alternative location using the `tmpdir` option. */ typedef struct lxw_workbook_options { /** Optimize the workbook to use constant memory for worksheets */ @@ -201,8 +236,11 @@ typedef struct lxw_workbook_options { typedef struct lxw_workbook { FILE *file; + struct lxw_sheets *sheets; struct lxw_worksheets *worksheets; + struct lxw_chartsheets *chartsheets; struct lxw_worksheet_names *worksheet_names; + struct lxw_chartsheet_names *chartsheet_names; struct lxw_charts *charts; struct lxw_charts *ordered_charts; struct lxw_formats *formats; @@ -215,6 +253,8 @@ typedef struct lxw_workbook { lxw_workbook_options options; uint16_t num_sheets; + uint16_t num_worksheets; + uint16_t num_chartsheets; uint16_t first_sheet; uint16_t active_sheet; uint16_t num_xf_formats; @@ -294,7 +334,7 @@ lxw_workbook *workbook_new(const char *filename); * to assembling the final XLSX file. The temporary files are created in the * system's temp directory. If the default temporary directory isn't * accessible to your application, or doesn't contain enough space, you can - * specify an alternative location using the `tempdir` option.* + * specify an alternative location using the `tmpdir` option.* * * See @ref working_with_memory for more details. * @@ -317,7 +357,7 @@ lxw_workbook *new_workbook_opt(const char *filename, * * @return A lxw_worksheet object. * - * The `%workbook_add_worksheet()` function adds a new worksheet to a workbook: + * The `%workbook_add_worksheet()` function adds a new worksheet to a workbook. * * At least one worksheet should be added to a new workbook: The @ref * worksheet.h "Worksheet" object is used to write data and configure a @@ -342,12 +382,52 @@ lxw_workbook *new_workbook_opt(const char *filename, * / \ [ ] : * ? * * In addition, you cannot use the same, case insensitive, `sheetname` for more - * than one worksheet. + * than one worksheet, or chartsheet. * */ lxw_worksheet *workbook_add_worksheet(lxw_workbook *workbook, const char *sheetname); +/** + * @brief Add a new chartsheet to a workbook. + * + * @param workbook Pointer to a lxw_workbook instance. + * @param sheetname Optional chartsheet name, defaults to Chart1, etc. + * + * @return A lxw_chartsheet object. + * + * The `%workbook_add_chartsheet()` function adds a new chartsheet to a + * workbook. The @ref chartsheet.h "Chartsheet" object is like a worksheet + * except it displays a chart instead of cell data. + * + * @image html chartsheet.png + * + * The `sheetname` parameter is optional. If it is `NULL` the default + * Excel convention will be followed, i.e. Chart1, Chart2, etc.: + * + * @code + * chartsheet = workbook_add_chartsheet(workbook, NULL ); // Chart1 + * chartsheet = workbook_add_chartsheet(workbook, "My Chart"); // My Chart + * chartsheet = workbook_add_chartsheet(workbook, NULL ); // Chart3 + * + * @endcode + * + * The chartsheet name must be a valid Excel worksheet name, i.e. it must be + * less than 32 character and it cannot contain any of the characters: + * + * / \ [ ] : * ? + * + * In addition, you cannot use the same, case insensitive, `sheetname` for more + * than one chartsheet, or worksheet. + * + * At least one worksheet should be added to a new workbook when creating a + * chartsheet in order to provide data for the chart. The @ref worksheet.h + * "Worksheet" object is used to write data and configure a worksheet in the + * workbook. + */ +lxw_chartsheet *workbook_add_chartsheet(lxw_workbook *workbook, + const char *sheetname); + /** * @brief Create a new @ref format.h "Format" object to formats cells in * worksheets. @@ -687,31 +767,49 @@ lxw_worksheet *workbook_get_worksheet_by_name(lxw_workbook *workbook, const char *name); /** - * @brief Validate a worksheet name. + * @brief Get a chartsheet object from its name. + * + * @param workbook Pointer to a lxw_workbook instance. + * @param name chartsheet name. + * + * @return A lxw_chartsheet object. + * + * This function returns a lxw_chartsheet object reference based on its name: + * + * @code + * chartsheet = workbook_get_chartsheet_by_name(workbook, "Chart1"); + * @endcode + * + */ +lxw_chartsheet *workbook_get_chartsheet_by_name(lxw_workbook *workbook, + const char *name); + +/** + * @brief Validate a worksheet or chartsheet name. * * @param workbook Pointer to a lxw_workbook instance. - * @param sheetname Worksheet name to validate. + * @param sheetname Sheet name to validate. * * @return A #lxw_error. * - * This function is used to validate a worksheet name according to the rules - * used by Excel: + * This function is used to validate a worksheet or chartsheet name according + * to the rules used by Excel: * * - The name is less than or equal to 31 UTF-8 characters. * - The name doesn't contain any of the characters: ` [ ] : * ? / \ ` * - The name isn't already in use. * * @code - * lxw_error err = workbook_validate_worksheet_name(workbook, "Foglio"); + * lxw_error err = workbook_validate_sheet_name(workbook, "Foglio"); * @endcode * - * This function is called by `workbook_add_worksheet()` but it can be - * explicitly called by the user beforehand to ensure that the worksheet - * name is valid. + * This function is called by `workbook_add_worksheet()` and + * `workbook_add_chartsheet()` but it can be explicitly called by the user + * beforehand to ensure that the sheet name is valid. * */ -lxw_error workbook_validate_worksheet_name(lxw_workbook *workbook, - const char *sheetname); +lxw_error workbook_validate_sheet_name(lxw_workbook *workbook, + const char *sheetname); void lxw_workbook_free(lxw_workbook *workbook); void lxw_workbook_assemble_xml_file(lxw_workbook *workbook); diff --git a/src/include/xlsxwriter/worksheet.h b/src/include/xlsxwriter/worksheet.h index a2b43cd..26d47b0 100644 --- a/src/include/xlsxwriter/worksheet.h +++ b/src/include/xlsxwriter/worksheet.h @@ -53,18 +53,19 @@ #include "drawing.h" #include "common.h" #include "format.h" +#include "styles.h" #include "utility.h" -#define LXW_ROW_MAX 1048576 -#define LXW_COL_MAX 16384 -#define LXW_COL_META_MAX 128 +#define LXW_ROW_MAX 1048576 +#define LXW_COL_MAX 16384 +#define LXW_COL_META_MAX 128 #define LXW_HEADER_FOOTER_MAX 255 -#define LXW_MAX_NUMBER_URLS 65530 -#define LXW_PANE_NAME_LENGTH 12 /* bottomRight + 1 */ +#define LXW_MAX_NUMBER_URLS 65530 +#define LXW_PANE_NAME_LENGTH 12 /* bottomRight + 1 */ /* The Excel 2007 specification says that the maximum number of page * breaks is 1026. However, in practice it is actually 1023. */ -#define LXW_BREAKS_MAX 1023 +#define LXW_BREAKS_MAX 1023 /** Default column width in Excel */ #define LXW_DEF_COL_WIDTH (double)8.43 @@ -205,6 +206,7 @@ enum cell_types { NUMBER_CELL = 1, STRING_CELL, INLINE_STRING_CELL, + INLINE_RICH_STRING_CELL, FORMULA_CELL, ARRAY_FORMULA_CELL, BLANK_CELL, @@ -546,6 +548,7 @@ typedef struct lxw_image_options { lxw_row_t row; lxw_col_t col; char *filename; + char *description; char *url; char *tip; uint8_t anchor; @@ -553,9 +556,11 @@ typedef struct lxw_image_options { /* Internal metadata. */ FILE *stream; uint8_t image_type; + uint8_t is_image_buffer; + unsigned char *image_buffer; + size_t image_buffer_size; double width; double height; - char *short_name; char *extension; double x_dpi; double y_dpi; @@ -623,15 +628,39 @@ typedef struct lxw_protection { /** Protect scenarios. */ uint8_t scenarios; - /** Protect drawing objects. */ + /** Protect drawing objects. Worksheets only. */ uint8_t objects; + /** Turn off chartsheet content protection. */ + uint8_t no_content; + + /** Turn off chartsheet objects. */ + uint8_t no_objects; + uint8_t no_sheet; - uint8_t content; uint8_t is_configured; char hash[5]; } lxw_protection; +/** + * @brief Struct to represent a rich string format/string pair. + * + * Arrays of this struct are used to define "rich" multi-format strings that + * are passed to `worksheet_write_rich_string()`. Each struct represents a + * fragment of the rich multi-format string with a lxw_format to define the + * format for the string part. If the string fragment is unformatted then + * `NULL` can be used for the format. + */ +typedef struct lxw_rich_string_tuple { + + /** The format for a string fragment in a rich string. NULL if the string + * isn't formatted. */ + lxw_format *format; + + /** The string fragment. */ + char *string; +} lxw_rich_string_tuple; + /** * @brief Struct to represent an Excel worksheet. * @@ -668,6 +697,7 @@ typedef struct lxw_worksheet { uint8_t hidden; uint16_t *active_sheet; uint16_t *first_sheet; + uint8_t is_chartsheet; lxw_col_options **col_options; uint16_t col_options_max; @@ -1305,6 +1335,81 @@ lxw_error worksheet_write_formula_num(lxw_worksheet *worksheet, const char *formula, lxw_format *format, double result); +/** + * @brief Write a "Rich" multi-format string to a worksheet cell. + * + * @param worksheet pointer to a lxw_worksheet instance to be updated. + * @param row The zero indexed row number. + * @param col The zero indexed column number. + * @param rich_string An array of format/string lxw_rich_string_tuple fragments. + * @param format A pointer to a Format instance or NULL. + * + * @return A #lxw_error code. + * + * The `%worksheet_write_rich_string()` function is used to write strings with + * multiple formats. For example to write the string 'This is **bold** + * and this is *italic*' you would use the following: + * + * @code + * lxw_format *bold = workbook_add_format(workbook); + * format_set_bold(bold); + * + * lxw_format *italic = workbook_add_format(workbook); + * format_set_italic(italic); + * + * lxw_rich_string_tuple fragment11 = {.format = NULL, .string = "This is " }; + * lxw_rich_string_tuple fragment12 = {.format = bold, .string = "bold" }; + * lxw_rich_string_tuple fragment13 = {.format = NULL, .string = " and this is "}; + * lxw_rich_string_tuple fragment14 = {.format = italic, .string = "italic" }; + * + * lxw_rich_string_tuple *rich_string1[] = {&fragment11, &fragment12, + * &fragment13, &fragment14, NULL}; + * + * worksheet_write_rich_string(worksheet, CELL("A1"), rich_string1, NULL); + * + * @endcode + * + * @image html rich_strings_small.png + * + * The basic rule is to break the string into fragments and put a lxw_format + * object before the fragment that you want to format. So if we look at the + * above example again: + * + * This is **bold** and this is *italic* + * + * The would be broken down into 4 fragments: + * + * default: |This is | + * bold: |bold| + * default: | and this is | + * italic: |italic| + * + * This in then converted to the lxw_rich_string_tuple fragments shown in the + * example above. For the default format we use `NULL`. + * + * The fragments are passed to `%worksheet_write_rich_string()` as a `NULL` + * terminated array: + * + * @code + * lxw_rich_string_tuple *rich_string1[] = {&fragment11, &fragment12, + * &fragment13, &fragment14, NULL}; + * + * worksheet_write_rich_string(worksheet, CELL("A1"), rich_string1, NULL); + * + * @endcode + * + * **Note**: + * Excel doesn't allow the use of two consecutive formats in a rich string or + * an empty string fragment. For either of these conditions a warning is + * raised and the input to `%worksheet_write_rich_string()` is ignored. + * + */ +lxw_error worksheet_write_rich_string(lxw_worksheet *worksheet, + lxw_row_t row, + lxw_col_t col, + lxw_rich_string_tuple *rich_string[], + lxw_format *format); + /** * @brief Set the properties for a row of cells. * @@ -1643,6 +1748,77 @@ lxw_error worksheet_insert_image_opt(lxw_worksheet *worksheet, lxw_row_t row, lxw_col_t col, const char *filename, lxw_image_options *options); + +/** + * @brief Insert an image in a worksheet cell, from a memory buffer. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param row The zero indexed row number. + * @param col The zero indexed column number. + * @param image_buffer Pointer to an array of bytes that holds the image data. + * @param image_size The size of the array of bytes. + * + * @return A #lxw_error code. + * + * This function can be used to insert a image into a worksheet from a memory + * buffer: + * + * @code + * worksheet_insert_image_buffer(worksheet, CELL("B3"), image_buffer, image_size); + * @endcode + * + * @image html image_buffer.png + * + * The buffer should be a pointer to an array of unsigned char data with a + * specified size. + * + * See `worksheet_insert_image()` for details about the supported image + * formats, and other image features. + */ +lxw_error worksheet_insert_image_buffer(lxw_worksheet *worksheet, + lxw_row_t row, + lxw_col_t col, + const unsigned char *image_buffer, + size_t image_size); + +/** + * @brief Insert an image in a worksheet cell, from a memory buffer. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param row The zero indexed row number. + * @param col The zero indexed column number. + * @param image_buffer Pointer to an array of bytes that holds the image data. + * @param image_size The size of the array of bytes. + * @param options Optional image parameters. + * + * @return A #lxw_error code. + * + * The `%worksheet_insert_image_buffer_opt()` function is like + * `worksheet_insert_image_buffer()` function except that it takes an optional + * #lxw_image_options struct to scale and position the image: + * + * @code + * lxw_image_options options = {.x_offset = 32, .y_offset = 4, + * .x_scale = 2, .y_scale = 1}; + * + * worksheet_insert_image_buffer_opt(worksheet, CELL("B3"), image_buffer, image_size, &options); + * @endcode + * + * @image html image_buffer_opt.png + * + * The buffer should be a pointer to an array of unsigned char data with a + * specified size. + * + * See `worksheet_insert_image_buffer_opt()` for details about the supported + * image formats, and other image options. + */ +lxw_error worksheet_insert_image_buffer_opt(lxw_worksheet *worksheet, + lxw_row_t row, + lxw_col_t col, + const unsigned char *image_buffer, + size_t image_size, + lxw_image_options *options); + /** * @brief Insert a chart object into a worksheet. * @@ -1653,8 +1829,8 @@ lxw_error worksheet_insert_image_opt(lxw_worksheet *worksheet, * * @return A #lxw_error code. * - * The `%worksheet_insert_chart()` can be used to insert a chart into a - * worksheet. The chart object must be created first using the + * The `%worksheet_insert_chart()` function can be used to insert a chart into + * a worksheet. The chart object must be created first using the * `workbook_add_chart()` function and configured using the @ref chart.h * functions. * @@ -1665,13 +1841,12 @@ lxw_error worksheet_insert_image_opt(lxw_worksheet *worksheet, * // Add a data series to the chart. * chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$6"); * - * // Insert the chart into the worksheet + * // Insert the chart into the worksheet. * worksheet_insert_chart(worksheet, 0, 2, chart); * @endcode * * @image html chart_working.png * - * * **Note:** * * A chart may only be inserted into a worksheet once. If several similar @@ -2282,26 +2457,26 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * @code * worksheet_set_header(worksheet, "&LHello"); * - * --------------------------------------------------------------- - * | | - * | Hello | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | * * * worksheet_set_header(worksheet, "&CHello"); * - * --------------------------------------------------------------- - * | | - * | Hello | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | * * * worksheet_set_header(worksheet, "&RHello"); * - * --------------------------------------------------------------- - * | | - * | Hello | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | * * * @endcode @@ -2313,10 +2488,10 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * @code * worksheet_set_header(worksheet, "Hello"); * - * --------------------------------------------------------------- - * | | - * | Hello | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Hello | + * // | | * * @endcode * @@ -2325,10 +2500,10 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * @code * worksheet_set_header(worksheet, "&LCiao&CBello&RCielo"); * - * --------------------------------------------------------------- - * | | - * | Ciao Bello Cielo | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Ciao Bello Cielo | + * // | | * * @endcode * @@ -2339,17 +2514,17 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * @code * worksheet_set_header(worksheet, "&CPage &P of &N"); * - * --------------------------------------------------------------- - * | | - * | Page 1 of 6 | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Page 1 of 6 | + * // | | * * worksheet_set_header(worksheet, "&CUpdated at &T"); * - * --------------------------------------------------------------- - * | | - * | Updated at 12:30 PM | - * | | + * // --------------------------------------------------------------- + * // | | + * // | Updated at 12:30 PM | + * // | | * * @endcode * @@ -2889,8 +3064,8 @@ void worksheet_hide_zero(lxw_worksheet *worksheet); * @param worksheet Pointer to a lxw_worksheet instance to be updated. * @param color The tab color. * - * The `%worksheet_set_tab_color()` function is used to change the color of the worksheet - * tab: + * The `%worksheet_set_tab_color()` function is used to change the color of + * the worksheet tab: * * @code * worksheet_set_tab_color(worksheet1, LXW_COLOR_RED); @@ -2973,11 +3148,10 @@ void worksheet_set_tab_color(lxw_worksheet *worksheet, lxw_color_t color); * * See also the format_set_unlocked() and format_set_hidden() format functions. * - * **Note:** Worksheet level passwords in Excel offer **very** weak + * **Note:** Sheet level passwords in Excel offer **very** weak * protection. They don't encrypt your data and are very easy to * deactivate. Full workbook encryption is not supported by `libxlsxwriter` - * since it requires a completely different file format and would take several - * man months to implement. + * since it requires a completely different file format. */ void worksheet_protect(lxw_worksheet *worksheet, const char *password, lxw_protection *options); @@ -3072,11 +3246,24 @@ void lxw_worksheet_prepare_image(lxw_worksheet *worksheet, void lxw_worksheet_prepare_chart(lxw_worksheet *worksheet, uint16_t chart_ref_id, uint16_t drawing_id, - lxw_image_options *image_data); + lxw_image_options *image_data, + uint8_t is_chartsheet); lxw_row *lxw_worksheet_find_row(lxw_worksheet *worksheet, lxw_row_t row_num); lxw_cell *lxw_worksheet_find_cell(lxw_row *row, lxw_col_t col_num); +/* + * External functions to call intern XML methods shared with chartsheet. + */ +void lxw_worksheet_write_sheet_views(lxw_worksheet *worksheet); +void lxw_worksheet_write_page_margins(lxw_worksheet *worksheet); +void lxw_worksheet_write_drawings(lxw_worksheet *worksheet); +void lxw_worksheet_write_sheet_protection(lxw_worksheet *worksheet, + lxw_protection *protect); +void lxw_worksheet_write_sheet_pr(lxw_worksheet *worksheet); +void lxw_worksheet_write_page_setup(lxw_worksheet *worksheet); +void lxw_worksheet_write_header_footer(lxw_worksheet *worksheet); + /* Declarations required for unit testing. */ #ifdef TESTING @@ -3106,7 +3293,8 @@ STATIC void _worksheet_write_header_footer(lxw_worksheet *worksheet); STATIC void _worksheet_write_print_options(lxw_worksheet *worksheet); STATIC void _worksheet_write_sheet_pr(lxw_worksheet *worksheet); STATIC void _worksheet_write_tab_color(lxw_worksheet *worksheet); -STATIC void _worksheet_write_sheet_protection(lxw_worksheet *worksheet); +STATIC void _worksheet_write_sheet_protection(lxw_worksheet *worksheet, + lxw_protection *protect); STATIC void _worksheet_write_data_validations(lxw_worksheet *self); #endif /* TESTING */ diff --git a/src/include/xlsxwriter/xmlwriter.h b/src/include/xlsxwriter/xmlwriter.h index 5e15db7..69afa70 100644 --- a/src/include/xlsxwriter/xmlwriter.h +++ b/src/include/xlsxwriter/xmlwriter.h @@ -165,6 +165,8 @@ void lxw_xml_data_element(FILE * xmlfile, const char *data, struct xml_attribute_list *attributes); +void lxw_xml_rich_si_element(FILE * xmlfile, const char *string); + char *lxw_escape_control_characters(const char *string); char *lxw_escape_data(const char *data); diff --git a/src/libxlsxwriter/chart.c b/src/libxlsxwriter/chart.c index 83bfcc4..9a7ff28 100644 --- a/src/libxlsxwriter/chart.c +++ b/src/libxlsxwriter/chart.c @@ -573,6 +573,15 @@ _chart_xml_declaration(lxw_chart *self) lxw_xml_declaration(self->file); } +/* + * Write the element. + */ +STATIC void +_chart_write_protection(lxw_chart *self) +{ + lxw_xml_empty_tag(self->file, "c:protection", NULL); +} + /* * Write the element. */ @@ -3201,13 +3210,19 @@ _chart_write_auto(lxw_chart *self) * Write the element. */ STATIC void -_chart_write_label_align(lxw_chart *self) +_chart_write_label_align(lxw_chart *self, lxw_chart_axis *axis) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("val", "ctr"); + + if (axis->label_align == LXW_CHART_AXIS_LABEL_ALIGN_LEFT) + LXW_PUSH_ATTRIBUTES_STR("val", "l"); + else if (axis->label_align == LXW_CHART_AXIS_LABEL_ALIGN_RIGHT) + LXW_PUSH_ATTRIBUTES_STR("val", "r"); + else + LXW_PUSH_ATTRIBUTES_STR("val", "ctr"); lxw_xml_empty_tag(self->file, "c:lblAlgn", &attributes); @@ -3585,6 +3600,9 @@ _chart_write_legend(lxw_chart *self) case LXW_CHART_LEGEND_BOTTOM: _chart_write_legend_pos(self, "b"); break; + case LXW_CHART_LEGEND_TOP_RIGHT: + _chart_write_legend_pos(self, "tr"); + break; case LXW_CHART_LEGEND_OVERLAY_RIGHT: _chart_write_legend_pos(self, "r"); has_overlay = LXW_TRUE; @@ -3593,6 +3611,10 @@ _chart_write_legend(lxw_chart *self) _chart_write_legend_pos(self, "l"); has_overlay = LXW_TRUE; break; + case LXW_CHART_LEGEND_OVERLAY_TOP_RIGHT: + _chart_write_legend_pos(self, "tr"); + has_overlay = LXW_TRUE; + break; default: _chart_write_legend_pos(self, "r"); } @@ -4091,7 +4113,7 @@ _chart_write_cat_axis(lxw_chart *self) _chart_write_auto(self); /* Write the c:lblAlgn element. */ - _chart_write_label_align(self); + _chart_write_label_align(self, self->x_axis); /* Write the c:lblOffset element. */ _chart_write_label_offset(self); @@ -4928,6 +4950,10 @@ lxw_chart_assemble_xml_file(lxw_chart *self) /* Write the c:style element. */ _chart_write_style(self); + /* Write the c:protection element. */ + if (self->is_protected) + _chart_write_protection(self); + /* Write the c:chart element. */ _chart_write_chart(self); @@ -4936,7 +4962,8 @@ lxw_chart_assemble_xml_file(lxw_chart *self) self->chartarea_pattern); /* Write the c:printSettings element. */ - _chart_write_print_settings(self); + if (!self->is_chartsheet) + _chart_write_print_settings(self); lxw_xml_end_tag(self->file, "c:chartSpace"); } @@ -6040,6 +6067,15 @@ chart_axis_minor_gridlines_set_line(lxw_chart_axis *axis, axis->minor_gridlines.visible = LXW_TRUE; } +/* + * Set the chart axis label alignment. + */ +void +chart_axis_set_label_align(lxw_chart_axis *axis, uint8_t align) +{ + axis->label_align = align; +} + /* * Set the chart title. */ diff --git a/src/libxlsxwriter/chartsheet.c b/src/libxlsxwriter/chartsheet.c new file mode 100644 index 0000000..ab754ea --- /dev/null +++ b/src/libxlsxwriter/chartsheet.c @@ -0,0 +1,508 @@ +/***************************************************************************** + * chartsheet - A library for creating Excel XLSX chartsheet files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2018, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/chartsheet.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Create a new chartsheet object. + */ +lxw_chartsheet * +lxw_chartsheet_new(lxw_worksheet_init_data *init_data) +{ + lxw_chartsheet *chartsheet = calloc(1, sizeof(lxw_chartsheet)); + GOTO_LABEL_ON_MEM_ERROR(chartsheet, mem_error); + + /* Use an embedded worksheet instance to write XML records that are + * shared with worksheet.c. */ + chartsheet->worksheet = lxw_worksheet_new(NULL); + GOTO_LABEL_ON_MEM_ERROR(chartsheet->worksheet, mem_error); + + if (init_data) { + chartsheet->name = init_data->name; + chartsheet->quoted_name = init_data->quoted_name; + chartsheet->tmpdir = init_data->tmpdir; + chartsheet->index = init_data->index; + chartsheet->hidden = init_data->hidden; + chartsheet->active_sheet = init_data->active_sheet; + chartsheet->first_sheet = init_data->first_sheet; + } + + chartsheet->worksheet->is_chartsheet = LXW_TRUE; + chartsheet->worksheet->zoom_scale_normal = LXW_FALSE; + chartsheet->worksheet->orientation = LXW_LANDSCAPE; + + return chartsheet; + +mem_error: + lxw_chartsheet_free(chartsheet); + return NULL; +} + +/* + * Free a chartsheet object. + */ +void +lxw_chartsheet_free(lxw_chartsheet *chartsheet) +{ + if (!chartsheet) + return; + + lxw_worksheet_free(chartsheet->worksheet); + free(chartsheet->name); + free(chartsheet->quoted_name); + free(chartsheet); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ + +/* + * Write the XML declaration. + */ +STATIC void +_chartsheet_xml_declaration(lxw_chartsheet *self) +{ + lxw_xml_declaration(self->file); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_chartsheet(lxw_chartsheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = "http://schemas.openxmlformats.org/" + "spreadsheetml/2006/main"; + char xmlns_r[] = "http://schemas.openxmlformats.org/" + "officeDocument/2006/relationships"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r); + + lxw_xml_start_tag(self->file, "chartsheet", &attributes); + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_sheet_pr(lxw_chartsheet *self) +{ + lxw_worksheet_write_sheet_pr(self->worksheet); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_sheet_views(lxw_chartsheet *self) +{ + lxw_worksheet_write_sheet_views(self->worksheet); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_page_margins(lxw_chartsheet *self) +{ + lxw_worksheet_write_page_margins(self->worksheet); +} + +/* + * Write the elements. + */ +STATIC void +_chartsheet_write_drawings(lxw_chartsheet *self) +{ + lxw_worksheet_write_drawings(self->worksheet); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_sheet_protection(lxw_chartsheet *self) +{ + lxw_worksheet_write_sheet_protection(self->worksheet, &self->protection); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_page_setup(lxw_chartsheet *self) +{ + lxw_worksheet_write_page_setup(self->worksheet); +} + +/* + * Write the element. + */ +STATIC void +_chartsheet_write_header_footer(lxw_chartsheet *self) +{ + lxw_worksheet_write_header_footer(self->worksheet); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Assemble and write the XML file. + */ +void +lxw_chartsheet_assemble_xml_file(lxw_chartsheet *self) +{ + /* Set the embedded worksheet filehandle to the same as the chartsheet. */ + self->worksheet->file = self->file; + + /* Write the XML declaration. */ + _chartsheet_xml_declaration(self); + + /* Write the chartsheet element. */ + _chartsheet_write_chartsheet(self); + + /* Write the sheetPr element. */ + _chartsheet_write_sheet_pr(self); + + /* Write the sheetViews element. */ + _chartsheet_write_sheet_views(self); + + /* Write the sheetProtection element. */ + _chartsheet_write_sheet_protection(self); + + /* Write the pageMargins element. */ + _chartsheet_write_page_margins(self); + + /* Write the chartsheet page setup. */ + _chartsheet_write_page_setup(self); + + /* Write the headerFooter element. */ + _chartsheet_write_header_footer(self); + + /* Write the drawing element. */ + _chartsheet_write_drawings(self); + + lxw_xml_end_tag(self->file, "chartsheet"); +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ +/* + * Set a chartsheet chart, with options. + */ +lxw_error +chartsheet_set_chart_opt(lxw_chartsheet *self, + lxw_chart *chart, lxw_image_options *user_options) +{ + lxw_image_options *options; + lxw_chart_series *series; + + if (!chart) { + LXW_WARN("chartsheet_set_chart()/_opt(): chart must be non-NULL."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Check that the chart isn't being used more than once. */ + if (chart->in_use) { + LXW_WARN("chartsheet_set_chart()/_opt(): the same chart object " + "cannot be set for a chartsheet more than once."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Check that the chart has a data series. */ + if (STAILQ_EMPTY(chart->series_list)) { + LXW_WARN("chartsheet_set_chart()/_opt(): chart must have a series."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Check that the chart has a 'values' series. */ + STAILQ_FOREACH(series, chart->series_list, list_pointers) { + if (!series->values->formula && !series->values->sheetname) { + LXW_WARN("chartsheet_set_chart()/_opt(): chart must have a " + "'values' series."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + + /* Create a new object to hold the chart image options. */ + options = calloc(1, sizeof(lxw_image_options)); + RETURN_ON_MEM_ERROR(options, LXW_ERROR_MEMORY_MALLOC_FAILED); + + if (user_options) { + options->x_offset = user_options->x_offset; + options->y_offset = user_options->y_offset; + options->x_scale = user_options->x_scale; + options->y_scale = user_options->y_scale; + } + + /* TODO. Read defaults from chart. */ + options->width = 480; + options->height = 288; + + if (!options->x_scale) + options->x_scale = 1; + + if (!options->y_scale) + options->y_scale = 1; + + /* Store chart references so they can be ordered in the workbook. */ + options->chart = chart; + + /* Store the chart data in the embedded worksheet. */ + STAILQ_INSERT_TAIL(self->worksheet->chart_data, options, list_pointers); + + chart->in_use = LXW_TRUE; + chart->is_chartsheet = LXW_TRUE; + + chart->is_protected = self->is_protected; + + self->chart = chart; + + return LXW_NO_ERROR; +} + +/* + * Set a chartsheet charts. + */ +lxw_error +chartsheet_set_chart(lxw_chartsheet *self, lxw_chart *chart) +{ + return chartsheet_set_chart_opt(self, chart, NULL); +} + +/* + * Set this chartsheet as a selected worksheet, i.e. the worksheet has its tab + * highlighted. + */ +void +chartsheet_select(lxw_chartsheet *self) +{ + self->selected = LXW_TRUE; + + /* Selected worksheet can't be hidden. */ + self->hidden = LXW_FALSE; +} + +/* + * Set this chartsheet as the active worksheet, i.e. the worksheet that is + * displayed when the workbook is opened. Also set it as selected. + */ +void +chartsheet_activate(lxw_chartsheet *self) +{ + self->worksheet->selected = LXW_TRUE; + self->worksheet->active = LXW_TRUE; + + /* Active worksheet can't be hidden. */ + self->worksheet->hidden = LXW_FALSE; + + *self->active_sheet = self->index; +} + +/* + * Set this chartsheet as the first visible sheet. This is necessary + * when there are a large number of worksheets and the activated + * worksheet is not visible on the screen. + */ +void +chartsheet_set_first_sheet(lxw_chartsheet *self) +{ + /* Active worksheet can't be hidden. */ + self->hidden = LXW_FALSE; + + *self->first_sheet = self->index; +} + +/* + * Hide this chartsheet. + */ +void +chartsheet_hide(lxw_chartsheet *self) +{ + self->hidden = LXW_TRUE; + + /* A hidden worksheet shouldn't be active or selected. */ + self->selected = LXW_FALSE; + + /* If this is active_sheet or first_sheet reset the workbook value. */ + if (*self->first_sheet == self->index) + *self->first_sheet = 0; + + if (*self->active_sheet == self->index) + *self->active_sheet = 0; +} + +/* + * Set the color of the chartsheet tab. + */ +void +chartsheet_set_tab_color(lxw_chartsheet *self, lxw_color_t color) +{ + self->worksheet->tab_color = color; +} + +/* + * Set the chartsheet protection flags to prevent modification of chartsheet + * objects. + */ +void +chartsheet_protect(lxw_chartsheet *self, const char *password, + lxw_protection *options) +{ + struct lxw_protection *protect = &self->protection; + + /* Copy any user parameters to the internal structure. */ + if (options) { + protect->objects = options->no_objects; + protect->no_content = options->no_content; + } + else { + protect->objects = LXW_FALSE; + protect->no_content = LXW_FALSE; + } + + if (password) { + uint16_t hash = lxw_hash_password(password); + lxw_snprintf(protect->hash, 5, "%X", hash); + } + else { + if (protect->objects && protect->no_content) + return; + } + + protect->no_sheet = LXW_TRUE; + protect->scenarios = LXW_TRUE; + protect->is_configured = LXW_TRUE; + + if (self->chart) + self->chart->is_protected = LXW_TRUE; + else + self->is_protected = LXW_TRUE; +} + +/* + * Set the chartsheet zoom factor. + */ +void +chartsheet_set_zoom(lxw_chartsheet *self, uint16_t scale) +{ + /* Confine the scale to Excel"s range */ + if (scale < 10 || scale > 400) { + LXW_WARN("chartsheet_set_zoom(): " + "Zoom factor scale outside range: 10 <= zoom <= 400."); + return; + } + + self->worksheet->zoom = scale; +} + +/* + * Set the page orientation as portrait. + */ +void +chartsheet_set_portrait(lxw_chartsheet *self) +{ + worksheet_set_portrait(self->worksheet); +} + +/* + * Set the page orientation as landscape. + */ +void +chartsheet_set_landscape(lxw_chartsheet *self) +{ + worksheet_set_landscape(self->worksheet); +} + +/* + * Set the paper type. Example. 1 = US Letter, 9 = A4 + */ +void +chartsheet_set_paper(lxw_chartsheet *self, uint8_t paper_size) +{ + worksheet_set_paper(self->worksheet, paper_size); +} + +/* + * Set all the page margins in inches. + */ +void +chartsheet_set_margins(lxw_chartsheet *self, double left, double right, + double top, double bottom) +{ + worksheet_set_margins(self->worksheet, left, right, top, bottom); +} + +/* + * Set the page header caption and options. + */ +lxw_error +chartsheet_set_header_opt(lxw_chartsheet *self, const char *string, + lxw_header_footer_options *options) +{ + return worksheet_set_header_opt(self->worksheet, string, options); +} + +/* + * Set the page footer caption and options. + */ +lxw_error +chartsheet_set_footer_opt(lxw_chartsheet *self, const char *string, + lxw_header_footer_options *options) +{ + return worksheet_set_footer_opt(self->worksheet, string, options); +} + +/* + * Set the page header caption. + */ +lxw_error +chartsheet_set_header(lxw_chartsheet *self, const char *string) +{ + return chartsheet_set_header_opt(self, string, NULL); +} + +/* + * Set the page footer caption. + */ +lxw_error +chartsheet_set_footer(lxw_chartsheet *self, const char *string) +{ + return chartsheet_set_footer_opt(self, string, NULL); +} diff --git a/src/libxlsxwriter/content_types.c b/src/libxlsxwriter/content_types.c index 73068bd..8f6a5cb 100644 --- a/src/libxlsxwriter/content_types.c +++ b/src/libxlsxwriter/content_types.c @@ -296,6 +296,16 @@ lxw_ct_add_worksheet_name(lxw_content_types *self, const char *name) LXW_APP_DOCUMENT "spreadsheetml.worksheet+xml"); } +/* + * Add the name of a chartsheet to the ContentTypes overrides. + */ +void +lxw_ct_add_chartsheet_name(lxw_content_types *self, const char *name) +{ + lxw_ct_add_override(self, name, + LXW_APP_DOCUMENT "spreadsheetml.chartsheet+xml"); +} + /* * Add the name of a chart to the ContentTypes overrides. */ diff --git a/src/libxlsxwriter/drawing.c b/src/libxlsxwriter/drawing.c index 1cde1d5..c33c587 100644 --- a/src/libxlsxwriter/drawing.c +++ b/src/libxlsxwriter/drawing.c @@ -239,7 +239,7 @@ _drawing_write_c_nv_pr(lxw_drawing *self, char *object_name, uint16_t index, LXW_PUSH_ATTRIBUTES_INT("id", index + 1); LXW_PUSH_ATTRIBUTES_STR("name", name); - if (drawing_object) + if (drawing_object && drawing_object->description) LXW_PUSH_ATTRIBUTES_STR("descr", drawing_object->description); lxw_xml_empty_tag(self->file, "xdr:cNvPr", &attributes); @@ -489,13 +489,40 @@ _drawing_write_client_data(lxw_drawing *self) lxw_xml_empty_tag(self->file, "xdr:clientData", NULL); } +/* + * Write the element. + */ +STATIC void +_drawing_write_a_graphic_frame_locks(lxw_drawing *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("noGrp", 1); + + lxw_xml_empty_tag(self->file, "a:graphicFrameLocks", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the element. */ STATIC void _drawing_write_c_nv_graphic_frame_pr(lxw_drawing *self) { - lxw_xml_empty_tag(self->file, "xdr:cNvGraphicFramePr", NULL); + if (self->embedded) { + lxw_xml_empty_tag(self->file, "xdr:cNvGraphicFramePr", NULL); + } + else { + lxw_xml_start_tag(self->file, "xdr:cNvGraphicFramePr", NULL); + + /* Write the a:graphicFrameLocks element. */ + _drawing_write_a_graphic_frame_locks(self); + + lxw_xml_end_tag(self->file, "xdr:cNvGraphicFramePr"); + } } /* @@ -705,6 +732,73 @@ _drawing_write_two_cell_anchor(lxw_drawing *self, uint16_t index, LXW_FREE_ATTRIBUTES(); } +/* + * Write the element. + */ +STATIC void +_drawing_write_ext(lxw_drawing *self, uint32_t cx, uint32_t cy) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("cx", cx); + LXW_PUSH_ATTRIBUTES_INT("cy", cy); + + lxw_xml_empty_tag(self->file, "xdr:ext", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_drawing_write_pos(lxw_drawing *self, int32_t x, int32_t y) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("x", x); + LXW_PUSH_ATTRIBUTES_INT("y", y); + + lxw_xml_empty_tag(self->file, "xdr:pos", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_drawing_write_absolute_anchor(lxw_drawing *self) +{ + lxw_xml_start_tag(self->file, "xdr:absoluteAnchor", NULL); + + if (self->orientation == LXW_LANDSCAPE) { + /* Write the xdr:pos element. */ + _drawing_write_pos(self, 0, 0); + + /* Write the xdr:ext element. */ + _drawing_write_ext(self, 9308969, 6078325); + } + else { + /* Write the xdr:pos element. */ + _drawing_write_pos(self, 0, -47625); + + /* Write the xdr:ext element. */ + _drawing_write_ext(self, 6162675, 6124575); + } + + _drawing_write_graphic_frame(self, 1); + + /* Write the xdr:clientData element. */ + _drawing_write_client_data(self); + + lxw_xml_end_tag(self->file, "xdr:absoluteAnchor"); +} + /***************************************************************************** * * XML file assembly functions. @@ -733,7 +827,10 @@ lxw_drawing_assemble_xml_file(lxw_drawing *self) _drawing_write_two_cell_anchor(self, index, drawing_object); index++; } - + } + else { + /* Write the xdr:absoluteAnchor element. Mainly for chartsheets. */ + _drawing_write_absolute_anchor(self); } lxw_xml_end_tag(self->file, "xdr:wsDr"); diff --git a/src/libxlsxwriter/packager.c b/src/libxlsxwriter/packager.c index 937c7f1..f054826 100644 --- a/src/libxlsxwriter/packager.c +++ b/src/libxlsxwriter/packager.c @@ -15,6 +15,9 @@ STATIC lxw_error _add_file_to_zip(lxw_packager *self, FILE * file, const char *filename); +STATIC lxw_error _add_buffer_to_zip(lxw_packager *self, unsigned char *buffer, + size_t buffer_size, const char *filename); + /* * Forward declarations. */ @@ -164,12 +167,18 @@ STATIC lxw_error _write_worksheet_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; uint16_t index = 1; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "xl/worksheets/sheet%d.xml", index++); @@ -191,6 +200,43 @@ _write_worksheet_files(lxw_packager *self) return LXW_NO_ERROR; } +/* + * Write the chartsheet files. + */ +STATIC lxw_error +_write_chartsheet_files(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_chartsheet *chartsheet; + char sheetname[LXW_FILENAME_LENGTH] = { 0 }; + uint16_t index = 1; + lxw_error err; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + chartsheet = sheet->u.chartsheet; + else + continue; + + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, + "xl/chartsheets/sheet%d.xml", index++); + + chartsheet->file = lxw_tmpfile(self->tmpdir); + if (!chartsheet->file) + return LXW_ERROR_CREATING_TMPFILE; + + lxw_chartsheet_assemble_xml_file(chartsheet); + + err = _add_file_to_zip(self, chartsheet->file, sheetname); + RETURN_ON_ERROR(err); + + fclose(chartsheet->file); + } + + return LXW_NO_ERROR; +} + /* * Write the /xl/media/image?.xml files. */ @@ -198,6 +244,7 @@ STATIC lxw_error _write_image_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; lxw_image_options *image; lxw_error err; @@ -206,7 +253,11 @@ _write_image_files(lxw_packager *self) char filename[LXW_FILENAME_LENGTH] = { 0 }; uint16_t index = 1; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; if (STAILQ_EMPTY(worksheet->image_data)) continue; @@ -216,17 +267,24 @@ _write_image_files(lxw_packager *self) lxw_snprintf(filename, LXW_FILENAME_LENGTH, "xl/media/image%d.%s", index++, image->extension); - /* Check that the image file exists and can be opened. */ - image_stream = fopen(image->filename, "rb"); - if (!image_stream) { - LXW_WARN_FORMAT1("Error adding image to xlsx file: file " - "doesn't exist or can't be opened: %s.", - image->filename); - return LXW_ERROR_CREATING_TMPFILE; + if (!image->is_image_buffer) { + /* Check that the image file exists and can be opened. */ + image_stream = fopen(image->filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("Error adding image to xlsx file: file " + "doesn't exist or can't be opened: %s.", + image->filename); + return LXW_ERROR_CREATING_TMPFILE; + } + + err = _add_file_to_zip(self, image_stream, filename); + fclose(image_stream); + } + else { + err = _add_buffer_to_zip(self, + image->image_buffer, + image->image_buffer_size, filename); } - - err = _add_file_to_zip(self, image_stream, filename); - fclose(image_stream); RETURN_ON_ERROR(err); } @@ -276,13 +334,19 @@ STATIC lxw_error _write_drawing_files(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; lxw_drawing *drawing; char filename[LXW_FILENAME_LENGTH] = { 0 }; uint16_t index = 1; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + worksheet = sheet->u.worksheet; + drawing = worksheet->drawing; if (drawing) { @@ -340,7 +404,9 @@ STATIC lxw_error _write_app_file(lxw_packager *self) { lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; + lxw_chartsheet *chartsheet; lxw_defined_name *defined_name; lxw_app *app; uint16_t named_range_count = 0; @@ -361,12 +427,30 @@ _write_app_file(lxw_packager *self) goto mem_error; } - lxw_snprintf(number, LXW_ATTR_32, "%d", self->workbook->num_sheets); + if (self->workbook->num_worksheets) { + lxw_snprintf(number, LXW_ATTR_32, "%d", + self->workbook->num_worksheets); + lxw_app_add_heading_pair(app, "Worksheets", number); + } + + if (self->workbook->num_chartsheets) { + lxw_snprintf(number, LXW_ATTR_32, "%d", + self->workbook->num_chartsheets); + lxw_app_add_heading_pair(app, "Charts", number); + } - lxw_app_add_heading_pair(app, "Worksheets", number); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (!sheet->is_chartsheet) { + worksheet = sheet->u.worksheet; + lxw_app_add_part_name(app, worksheet->name); + } + } - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_app_add_part_name(app, worksheet->name); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) { + chartsheet = sheet->u.chartsheet; + lxw_app_add_part_name(app, chartsheet->name); + } } /* Add the Named Ranges parts. */ @@ -568,9 +652,11 @@ _write_content_types_file(lxw_packager *self) { lxw_content_types *content_types = lxw_content_types_new(); lxw_workbook *workbook = self->workbook; - lxw_worksheet *worksheet; + lxw_sheet *sheet; char filename[LXW_MAX_ATTRIBUTE_LENGTH] = { 0 }; uint16_t index = 1; + uint16_t worksheet_index = 1; + uint16_t chartsheet_index = 1; lxw_error err = LXW_NO_ERROR; if (!content_types) { @@ -593,10 +679,17 @@ _write_content_types_file(lxw_packager *self) if (workbook->has_bmp) lxw_ct_add_default(content_types, "bmp", "image/bmp"); - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_snprintf(filename, LXW_FILENAME_LENGTH, - "/xl/worksheets/sheet%d.xml", index++); - lxw_ct_add_worksheet_name(content_types, filename); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) { + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "/xl/chartsheets/sheet%d.xml", chartsheet_index++); + lxw_ct_add_chartsheet_name(content_types, filename); + } + else { + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "/xl/worksheets/sheet%d.xml", worksheet_index++); + lxw_ct_add_worksheet_name(content_types, filename); + } } for (index = 1; index <= self->chart_count; index++) { @@ -637,9 +730,10 @@ _write_workbook_rels_file(lxw_packager *self) { lxw_relationships *rels = lxw_relationships_new(); lxw_workbook *workbook = self->workbook; - lxw_worksheet *worksheet; + lxw_sheet *sheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; - uint16_t index = 1; + uint16_t worksheet_index = 1; + uint16_t chartsheet_index = 1; lxw_error err = LXW_NO_ERROR; if (!rels) { @@ -653,10 +747,19 @@ _write_workbook_rels_file(lxw_packager *self) goto mem_error; } - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { - lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "worksheets/sheet%d.xml", - index++); - lxw_add_document_relationship(rels, "/worksheet", sheetname); + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) { + lxw_snprintf(sheetname, + LXW_FILENAME_LENGTH, + "chartsheets/sheet%d.xml", chartsheet_index++); + lxw_add_document_relationship(rels, "/chartsheet", sheetname); + } + else { + lxw_snprintf(sheetname, + LXW_FILENAME_LENGTH, + "worksheets/sheet%d.xml", worksheet_index++); + lxw_add_document_relationship(rels, "/worksheet", sheetname); + } } lxw_add_document_relationship(rels, "/theme", "theme/theme1.xml"); @@ -688,12 +791,17 @@ _write_worksheet_rels_file(lxw_packager *self) lxw_relationships *rels; lxw_rel_tuple *rel; lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; uint16_t index = 0; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; index++; @@ -735,6 +843,68 @@ _write_worksheet_rels_file(lxw_packager *self) return LXW_NO_ERROR; } +/* + * Write the chartsheet .rels files for chartsheets that contain links to + * external data such as drawings. + */ +STATIC lxw_error +_write_chartsheet_rels_file(lxw_packager *self) +{ + lxw_relationships *rels; + lxw_rel_tuple *rel; + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + char sheetname[LXW_FILENAME_LENGTH] = { 0 }; + uint16_t index = 0; + lxw_error err; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + continue; + + index++; + + /* TODO. This should never be empty. Put check higher up. */ + if (STAILQ_EMPTY(worksheet->external_drawing_links)) + continue; + + rels = lxw_relationships_new(); + + rels->file = lxw_tmpfile(self->tmpdir); + if (!rels->file) { + lxw_free_relationships(rels); + return LXW_ERROR_CREATING_TMPFILE; + } + + STAILQ_FOREACH(rel, worksheet->external_hyperlinks, list_pointers) { + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + } + + STAILQ_FOREACH(rel, worksheet->external_drawing_links, list_pointers) { + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + } + + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, + "xl/chartsheets/_rels/sheet%d.xml.rels", index); + + lxw_relationships_assemble_xml_file(rels); + + err = _add_file_to_zip(self, rels->file, sheetname); + + fclose(rels->file); + lxw_free_relationships(rels); + + RETURN_ON_ERROR(err); + } + + return LXW_NO_ERROR; +} + /* * Write the drawing .rels files for worksheets that contain charts or * drawings. @@ -745,12 +915,17 @@ _write_drawing_rels_file(lxw_packager *self) lxw_relationships *rels; lxw_rel_tuple *rel; lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; lxw_worksheet *worksheet; char sheetname[LXW_FILENAME_LENGTH] = { 0 }; uint16_t index = 1; lxw_error err; - STAILQ_FOREACH(worksheet, workbook->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + worksheet = sheet->u.chartsheet->worksheet; + else + worksheet = sheet->u.worksheet; if (STAILQ_EMPTY(worksheet->drawing_links)) continue; @@ -895,6 +1070,47 @@ _add_file_to_zip(lxw_packager *self, FILE * file, const char *filename) return LXW_NO_ERROR; } +STATIC lxw_error +_add_buffer_to_zip(lxw_packager *self, unsigned char *buffer, + size_t buffer_size, const char *filename) +{ + int16_t error = ZIP_OK; + + error = zipOpenNewFileInZip4_64(self->zipfile, + filename, + &self->zipfile_info, + NULL, 0, NULL, 0, NULL, + Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0, + -MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, NULL, 0, 0, 0, 0); + + if (error != ZIP_OK) { + LXW_ERROR("Error adding member to zipfile"); + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); + } + + error = zipWriteInFileInZip(self->zipfile, + buffer, (unsigned int) buffer_size); + + if (error < 0) { + LXW_ERROR("Error in writing member in the zipfile"); + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); + } + + if (error < 0) { + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); + } + else { + error = zipCloseFileInZip(self->zipfile); + if (error != ZIP_OK) { + LXW_ERROR("Error in closing member in the zipfile"); + RETURN_ON_ZIP_ERROR(error, LXW_ERROR_ZIP_FILE_ADD); + } + } + + return LXW_NO_ERROR; +} + /* * Write the xml files that make up the XLXS OPC package. */ @@ -907,6 +1123,9 @@ lxw_create_package(lxw_packager *self) error = _write_worksheet_files(self); RETURN_ON_ERROR(error); + error = _write_chartsheet_files(self); + RETURN_ON_ERROR(error); + error = _write_workbook_file(self); RETURN_ON_ERROR(error); @@ -943,6 +1162,9 @@ lxw_create_package(lxw_packager *self) error = _write_worksheet_rels_file(self); RETURN_ON_ERROR(error); + error = _write_chartsheet_rels_file(self); + RETURN_ON_ERROR(error); + error = _write_drawing_rels_file(self); RETURN_ON_ERROR(error); diff --git a/src/libxlsxwriter/shared_strings.c b/src/libxlsxwriter/shared_strings.c index 5dc34c8..23bbda1 100644 --- a/src/libxlsxwriter/shared_strings.c +++ b/src/libxlsxwriter/shared_strings.c @@ -161,6 +161,15 @@ _write_si(lxw_sst *self, char *string) free(string); } +/* + * Write the element for rich strings. + */ +STATIC void +_write_rich_si(lxw_sst *self, char *string) +{ + lxw_xml_rich_si_element(self->file, string); +} + /* * Write the element. */ @@ -198,7 +207,11 @@ _write_sst_strings(lxw_sst *self) STAILQ_FOREACH(sst_element, self->order_list, sst_order_pointers) { /* Write the si element. */ - _write_si(self, sst_element->string); + if (sst_element->is_rich_string) + _write_rich_si(self, sst_element->string); + else + _write_si(self, sst_element->string); + } } @@ -230,7 +243,7 @@ lxw_sst_assemble_xml_file(lxw_sst *self) * Add to or find a string in the SST SharedString table and return it's index. */ struct sst_element * -lxw_get_sst_index(lxw_sst *sst, const char *string) +lxw_get_sst_index(lxw_sst *sst, const char *string, uint8_t is_rich_string) { struct sst_element *element; struct sst_element *existing_element; @@ -243,6 +256,7 @@ lxw_get_sst_index(lxw_sst *sst, const char *string) /* Create potential new element with the string and its index. */ element->index = sst->unique_count; element->string = lxw_strdup(string); + element->is_rich_string = is_rich_string; /* Try to insert it and see whether we already have that string. */ existing_element = RB_INSERT(sst_rb_tree, sst->rb_tree, element); diff --git a/src/libxlsxwriter/styles.c b/src/libxlsxwriter/styles.c index 326e27c..cdd4a95 100644 --- a/src/libxlsxwriter/styles.c +++ b/src/libxlsxwriter/styles.c @@ -14,6 +14,8 @@ /* * Forward declarations. */ +STATIC void _write_font(lxw_styles *self, lxw_format *format, + uint8_t is_rich_string); /***************************************************************************** * @@ -66,6 +68,34 @@ lxw_styles_free(lxw_styles *styles) free(styles); } +/* + * Write the element for rich strings. + */ +void +lxw_styles_write_string_fragment(lxw_styles *self, char *string) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + /* Add attribute to preserve leading or trailing whitespace. */ + if (isspace((unsigned char) string[0]) + || isspace((unsigned char) string[strlen(string) - 1])) + LXW_PUSH_ATTRIBUTES_STR("xml:space", "preserve"); + + lxw_xml_data_element(self->file, "t", string, &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +void +lxw_styles_write_rich_font(lxw_styles *self, lxw_format *format) +{ + + _write_font(self, format, LXW_TRUE); +} + /***************************************************************************** * * XML functions. @@ -125,6 +155,7 @@ _write_num_fmts(lxw_styles *self) struct xml_attribute_list attributes; struct xml_attribute *attribute; lxw_format *format; + uint16_t last_format_index = 0; if (!self->num_format_count) return; @@ -141,7 +172,13 @@ _write_num_fmts(lxw_styles *self) if (format->num_format_index < 164) continue; + /* Ignore duplicates which have an already used index. */ + if (format->num_format_index <= last_format_index) + continue; + _write_num_fmt(self, format->num_format_index, format->num_format); + + last_format_index = format->num_format_index; } lxw_xml_end_tag(self->file, "numFmts"); @@ -207,7 +244,8 @@ _write_font_color_rgb(lxw_styles *self, int32_t rgb) * Write the element. */ STATIC void -_write_font_name(lxw_styles *self, const char *font_name) +_write_font_name(lxw_styles *self, const char *font_name, + uint8_t is_rich_string) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -219,7 +257,10 @@ _write_font_name(lxw_styles *self, const char *font_name) else LXW_PUSH_ATTRIBUTES_STR("val", LXW_DEFAULT_FONT_NAME); - lxw_xml_empty_tag(self->file, "name", &attributes); + if (is_rich_string) + lxw_xml_empty_tag(self->file, "rFont", &attributes); + else + lxw_xml_empty_tag(self->file, "name", &attributes); LXW_FREE_ATTRIBUTES(); } @@ -309,9 +350,12 @@ _write_vert_align(lxw_styles *self, const char *align) * Write the element. */ STATIC void -_write_font(lxw_styles *self, lxw_format *format) +_write_font(lxw_styles *self, lxw_format *format, uint8_t is_rich_string) { - lxw_xml_start_tag(self->file, "font", NULL); + if (is_rich_string) + lxw_xml_start_tag(self->file, "rPr", NULL); + else + lxw_xml_start_tag(self->file, "font", NULL); if (format->bold) lxw_xml_empty_tag(self->file, "b", NULL); @@ -347,7 +391,7 @@ _write_font(lxw_styles *self, lxw_format *format) else _write_font_color_theme(self, LXW_DEFAULT_FONT_THEME); - _write_font_name(self, format->font_name); + _write_font_name(self, format->font_name, is_rich_string); _write_font_family(self, format->font_family); /* Only write the scheme element for the default font type if it @@ -358,7 +402,10 @@ _write_font(lxw_styles *self, lxw_format *format) _write_font_scheme(self, format->font_scheme); } - lxw_xml_end_tag(self->file, "font"); + if (is_rich_string) + lxw_xml_end_tag(self->file, "rPr"); + else + lxw_xml_end_tag(self->file, "font"); } /* @@ -378,7 +425,7 @@ _write_fonts(lxw_styles *self) STAILQ_FOREACH(format, self->xf_formats, list_pointers) { if (format->has_font) - _write_font(self, format); + _write_font(self, format, LXW_FALSE); } lxw_xml_end_tag(self->file, "fonts"); diff --git a/src/libxlsxwriter/utility.c b/src/libxlsxwriter/utility.c index 445f12a..9ca8591 100644 --- a/src/libxlsxwriter/utility.c +++ b/src/libxlsxwriter/utility.c @@ -12,7 +12,7 @@ #include #include #include -#include "xlsxwriter/utility.h" +#include "xlsxwriter.h" #include "xlsxwriter/third_party/tmpfileplus.h" char *error_strings[LXW_MAX_ERRNO + 1] = { @@ -20,6 +20,7 @@ char *error_strings[LXW_MAX_ERRNO + 1] = { "Memory error, failed to malloc() required memory.", "Error creating output xlsx file. Usually a permissions error.", "Error encountered when creating a tmpfile during file assembly.", + "Error reading a tmpfile.", "Zlib error with a file operation while creating xlsx file.", "Zlib error when adding sub file to xlsx file.", "Zlib error when closing xlsx file.", @@ -553,3 +554,44 @@ lxw_sprintf_dbl(char *data, double number) return 0; } #endif + +/* + * Retrieve runtime library version + */ +const char * +lxw_version(void) +{ + return LXW_VERSION; +} + +/* + * Hash a worksheet password. Based on the algorithm provided by Daniel Rentz + * of OpenOffice. + */ +uint16_t +lxw_hash_password(const char *password) +{ + size_t count; + uint8_t i; + uint16_t hash = 0x0000; + + count = strlen(password); + + for (i = 0; i < count; i++) { + uint32_t low_15; + uint32_t high_15; + uint32_t letter = password[i] << (i + 1); + + low_15 = letter & 0x7fff; + high_15 = letter & (0x7fff << 15); + high_15 = high_15 >> 15; + letter = low_15 | high_15; + + hash ^= letter; + } + + hash ^= count; + hash ^= 0xCE4B; + + return hash; +} diff --git a/src/libxlsxwriter/workbook.c b/src/libxlsxwriter/workbook.c index 34c0a83..f30e342 100644 --- a/src/libxlsxwriter/workbook.c +++ b/src/libxlsxwriter/workbook.c @@ -13,10 +13,15 @@ #include "xlsxwriter/packager.h" #include "xlsxwriter/hash_table.h" -STATIC int _name_cmp(lxw_worksheet_name *name1, lxw_worksheet_name *name2); +STATIC int _worksheet_name_cmp(lxw_worksheet_name *name1, + lxw_worksheet_name *name2); +STATIC int _chartsheet_name_cmp(lxw_chartsheet_name *name1, + lxw_chartsheet_name *name2); #ifndef __clang_analyzer__ -LXW_RB_GENERATE_NAMES(lxw_worksheet_names, lxw_worksheet_name, tree_pointers, - _name_cmp); +LXW_RB_GENERATE_WORKSHEET_NAMES(lxw_worksheet_names, lxw_worksheet_name, + tree_pointers, _worksheet_name_cmp); +LXW_RB_GENERATE_CHARTSHEET_NAMES(lxw_chartsheet_names, lxw_chartsheet_name, + tree_pointers, _chartsheet_name_cmp); #endif /* @@ -30,10 +35,16 @@ LXW_RB_GENERATE_NAMES(lxw_worksheet_names, lxw_worksheet_name, tree_pointers, ****************************************************************************/ /* - * Comparator for the worksheet names structure red/black tree. + * Comparators for the sheet names structure red/black tree. */ STATIC int -_name_cmp(lxw_worksheet_name *name1, lxw_worksheet_name *name2) +_worksheet_name_cmp(lxw_worksheet_name *name1, lxw_worksheet_name *name2) +{ + return strcmp(name1->name, name2->name); +} + +STATIC int +_chartsheet_name_cmp(lxw_chartsheet_name *name1, lxw_chartsheet_name *name2) { return strcmp(name1->name, name2->name); } @@ -81,9 +92,11 @@ _free_custom_doc_property(lxw_custom_property *custom_property) void lxw_workbook_free(lxw_workbook *workbook) { - lxw_worksheet *worksheet; + lxw_sheet *sheet; struct lxw_worksheet_name *worksheet_name; - struct lxw_worksheet_name *next_name; + struct lxw_worksheet_name *next_worksheet_name; + struct lxw_chartsheet_name *chartsheet_name; + struct lxw_chartsheet_name *next_chartsheet_name; lxw_chart *chart; lxw_format *format; lxw_defined_name *defined_name; @@ -97,17 +110,26 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->filename); - /* Free the worksheets in the workbook. */ - if (workbook->worksheets) { - while (!STAILQ_EMPTY(workbook->worksheets)) { - worksheet = STAILQ_FIRST(workbook->worksheets); - STAILQ_REMOVE_HEAD(workbook->worksheets, list_pointers); - lxw_worksheet_free(worksheet); - } - free(workbook->worksheets); + /* Free the sheets in the workbook. */ + if (workbook->sheets) { + while (!STAILQ_EMPTY(workbook->sheets)) { + sheet = STAILQ_FIRST(workbook->sheets); + if (sheet->is_chartsheet) + lxw_chartsheet_free(sheet->u.chartsheet); + else + lxw_worksheet_free(sheet->u.worksheet); + + STAILQ_REMOVE_HEAD(workbook->sheets, list_pointers); + free(sheet); + } + free(workbook->sheets); } + /* Free the sheet lists. The worksheet objects are freed above. */ + free(workbook->worksheets); + free(workbook->chartsheets); + /* Free the charts in the workbook. */ if (workbook->charts) { while (!STAILQ_EMPTY(workbook->charts)) { @@ -153,18 +175,35 @@ lxw_workbook_free(lxw_workbook *workbook) if (workbook->worksheet_names) { for (worksheet_name = RB_MIN(lxw_worksheet_names, workbook->worksheet_names); - worksheet_name; worksheet_name = next_name) { + worksheet_name; worksheet_name = next_worksheet_name) { - next_name = RB_NEXT(lxw_worksheet_names, - workbook->worksheet_name, worksheet_name); - RB_REMOVE(lxw_worksheet_names, - workbook->worksheet_names, worksheet_name); + next_worksheet_name = RB_NEXT(lxw_worksheet_names, + workbook->worksheet_name, + worksheet_name); + RB_REMOVE(lxw_worksheet_names, workbook->worksheet_names, + worksheet_name); free(worksheet_name); } free(workbook->worksheet_names); } + if (workbook->chartsheet_names) { + for (chartsheet_name = + RB_MIN(lxw_chartsheet_names, workbook->chartsheet_names); + chartsheet_name; chartsheet_name = next_chartsheet_name) { + + next_chartsheet_name = RB_NEXT(lxw_chartsheet_names, + workbook->chartsheet_name, + chartsheet_name); + RB_REMOVE(lxw_chartsheet_names, workbook->chartsheet_names, + chartsheet_name); + free(chartsheet_name); + } + + free(workbook->chartsheet_names); + } + lxw_hash_free(workbook->used_xf_formats); lxw_sst_free(workbook->sst); free(workbook->options.tmpdir); @@ -432,7 +471,7 @@ _prepare_num_formats(lxw_workbook *self) num_format_index = calloc(1, sizeof(uint16_t)); *num_format_index = index; format->num_format_index = index; - lxw_insert_hash_element(num_formats, num_format, + lxw_insert_hash_element(num_formats, format->num_format, num_format_index, LXW_FORMAT_FIELD_LEN); index++; @@ -496,6 +535,7 @@ _store_defined_name(lxw_workbook *self, const char *name, const char *app_name, const char *formula, int16_t index, uint8_t hidden) { + lxw_sheet *sheet; lxw_worksheet *worksheet; lxw_defined_name *defined_name; lxw_defined_name *list_defined_name; @@ -546,7 +586,12 @@ _store_defined_name(lxw_workbook *self, const char *name, worksheet_name[strlen(worksheet_name) - 1] = '\0'; /* Search for worksheet name to get the equivalent worksheet index. */ - STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, self->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + if (strcmp(worksheet_name, worksheet->name) == 0) { defined_name->index = worksheet->index; lxw_strcpy(defined_name->normalised_sheetname, @@ -837,13 +882,23 @@ _add_chart_cache_data(lxw_workbook *self) STATIC void _prepare_drawings(lxw_workbook *self) { + lxw_sheet *sheet; lxw_worksheet *worksheet; lxw_image_options *image_options; uint16_t chart_ref_id = 0; uint16_t image_ref_id = 0; uint16_t drawing_id = 0; + uint8_t is_chartsheet; - STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, self->sheets, list_pointers) { + if (sheet->is_chartsheet) { + worksheet = sheet->u.chartsheet->worksheet; + is_chartsheet = LXW_TRUE; + } + else { + worksheet = sheet->u.worksheet; + is_chartsheet = LXW_FALSE; + } if (STAILQ_EMPTY(worksheet->image_data) && STAILQ_EMPTY(worksheet->chart_data)) @@ -854,7 +909,7 @@ _prepare_drawings(lxw_workbook *self) STAILQ_FOREACH(image_options, worksheet->chart_data, list_pointers) { chart_ref_id++; lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id, - image_options); + image_options, is_chartsheet); if (image_options->chart) STAILQ_INSERT_TAIL(self->ordered_charts, image_options->chart, ordered_list_pointers); @@ -889,14 +944,18 @@ STATIC void _prepare_defined_names(lxw_workbook *self) { lxw_worksheet *worksheet; + lxw_sheet *sheet; char app_name[LXW_DEFINED_NAME_LENGTH]; char range[LXW_DEFINED_NAME_LENGTH]; char area[LXW_MAX_CELL_RANGE_LENGTH]; char first_col[8]; char last_col[8]; - STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { - + STAILQ_FOREACH(sheet, self->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; /* * Check for autofilter settings and store them. */ @@ -1170,13 +1229,23 @@ _write_sheet(lxw_workbook *self, const char *name, uint32_t sheet_id, STATIC void _write_sheets(lxw_workbook *self) { + lxw_sheet *sheet; lxw_worksheet *worksheet; + lxw_chartsheet *chartsheet; lxw_xml_start_tag(self->file, "sheets", NULL); - STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { - _write_sheet(self, worksheet->name, worksheet->index + 1, - worksheet->hidden); + STAILQ_FOREACH(sheet, self->sheets, list_pointers) { + if (sheet->is_chartsheet) { + chartsheet = sheet->u.chartsheet; + _write_sheet(self, chartsheet->name, chartsheet->index + 1, + chartsheet->hidden); + } + else { + worksheet = sheet->u.worksheet; + _write_sheet(self, worksheet->name, worksheet->index + 1, + worksheet->hidden); + } } lxw_xml_end_tag(self->file, "sheets"); @@ -1224,9 +1293,6 @@ _write_defined_name(lxw_workbook *self, lxw_defined_name *defined_name) LXW_FREE_ATTRIBUTES(); } -/* - * Write the element. - */ STATIC void _write_defined_names(lxw_workbook *self) { @@ -1330,16 +1396,32 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) GOTO_LABEL_ON_MEM_ERROR(workbook, mem_error); workbook->filename = lxw_strdup(filename); + /* Add the sheets list. */ + workbook->sheets = calloc(1, sizeof(struct lxw_sheets)); + GOTO_LABEL_ON_MEM_ERROR(workbook->sheets, mem_error); + STAILQ_INIT(workbook->sheets); + /* Add the worksheets list. */ workbook->worksheets = calloc(1, sizeof(struct lxw_worksheets)); GOTO_LABEL_ON_MEM_ERROR(workbook->worksheets, mem_error); STAILQ_INIT(workbook->worksheets); + /* Add the chartsheets list. */ + workbook->chartsheets = calloc(1, sizeof(struct lxw_chartsheets)); + GOTO_LABEL_ON_MEM_ERROR(workbook->chartsheets, mem_error); + STAILQ_INIT(workbook->chartsheets); + /* Add the worksheet names tree. */ workbook->worksheet_names = calloc(1, sizeof(struct lxw_worksheet_names)); GOTO_LABEL_ON_MEM_ERROR(workbook->worksheet_names, mem_error); RB_INIT(workbook->worksheet_names); + /* Add the chartsheet names tree. */ + workbook->chartsheet_names = calloc(1, + sizeof(struct lxw_chartsheet_names)); + GOTO_LABEL_ON_MEM_ERROR(workbook->chartsheet_names, mem_error); + RB_INIT(workbook->chartsheet_names); + /* Add the charts list. */ workbook->charts = calloc(1, sizeof(struct lxw_charts)); GOTO_LABEL_ON_MEM_ERROR(workbook->charts, mem_error); @@ -1404,7 +1486,8 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) lxw_worksheet * workbook_add_worksheet(lxw_workbook *self, const char *sheetname) { - lxw_worksheet *worksheet; + lxw_sheet *sheet = NULL; + lxw_worksheet *worksheet = NULL; lxw_worksheet_name *worksheet_name = NULL; lxw_error error; lxw_worksheet_init_data init_data = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -1421,13 +1504,13 @@ workbook_add_worksheet(lxw_workbook *self, const char *sheetname) GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error); lxw_snprintf(new_name, LXW_MAX_SHEETNAME_LENGTH, "Sheet%d", - self->num_sheets + 1); + self->num_worksheets + 1); init_data.name = new_name; init_data.quoted_name = lxw_strdup(new_name); } /* Check that the worksheet name is valid. */ - error = workbook_validate_worksheet_name(self, init_data.name); + error = workbook_validate_sheet_name(self, init_data.name); if (error) { LXW_WARN_FORMAT2("workbook_add_worksheet(): worksheet name '%s' has " "error: %s", init_data.name, lxw_strerror(error)); @@ -1451,9 +1534,19 @@ workbook_add_worksheet(lxw_workbook *self, const char *sheetname) worksheet = lxw_worksheet_new(&init_data); GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error); - self->num_sheets++; + /* Add it to the worksheet list. */ + self->num_worksheets++; STAILQ_INSERT_TAIL(self->worksheets, worksheet, list_pointers); + /* Create a new sheet object. */ + sheet = calloc(1, sizeof(lxw_sheet)); + GOTO_LABEL_ON_MEM_ERROR(sheet, mem_error); + sheet->u.worksheet = worksheet; + + /* Add it to the worksheet list. */ + self->num_sheets++; + STAILQ_INSERT_TAIL(self->sheets, sheet, list_pointers); + /* Store the worksheet so we can look it up by name. */ worksheet_name->name = init_data.name; worksheet_name->worksheet = worksheet; @@ -1465,6 +1558,91 @@ workbook_add_worksheet(lxw_workbook *self, const char *sheetname) free(init_data.name); free(init_data.quoted_name); free(worksheet_name); + free(worksheet); + return NULL; +} + +/* + * Add a new chartsheet to the Excel workbook. + */ +lxw_chartsheet * +workbook_add_chartsheet(lxw_workbook *self, const char *sheetname) +{ + lxw_sheet *sheet = NULL; + lxw_chartsheet *chartsheet = NULL; + lxw_chartsheet_name *chartsheet_name = NULL; + lxw_error error; + lxw_worksheet_init_data init_data = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + char *new_name = NULL; + + if (sheetname) { + /* Use the user supplied name. */ + init_data.name = lxw_strdup(sheetname); + init_data.quoted_name = lxw_quote_sheetname((char *) sheetname); + } + else { + /* Use the default SheetN name. */ + new_name = malloc(LXW_MAX_SHEETNAME_LENGTH); + GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error); + + lxw_snprintf(new_name, LXW_MAX_SHEETNAME_LENGTH, "Chart%d", + self->num_chartsheets + 1); + init_data.name = new_name; + init_data.quoted_name = lxw_strdup(new_name); + } + + /* Check that the chartsheet name is valid. */ + error = workbook_validate_sheet_name(self, init_data.name); + if (error) { + LXW_WARN_FORMAT2 + ("workbook_add_chartsheet(): chartsheet name '%s' has " + "error: %s", init_data.name, lxw_strerror(error)); + goto mem_error; + } + + /* Create a struct to find/store the chartsheet name/pointer. */ + chartsheet_name = calloc(1, sizeof(struct lxw_chartsheet_name)); + GOTO_LABEL_ON_MEM_ERROR(chartsheet_name, mem_error); + + /* Initialize the metadata to pass to the chartsheet. */ + init_data.hidden = 0; + init_data.index = self->num_sheets; + init_data.sst = self->sst; + init_data.optimize = self->options.constant_memory; + init_data.active_sheet = &self->active_sheet; + init_data.first_sheet = &self->first_sheet; + init_data.tmpdir = self->options.tmpdir; + + /* Create a new chartsheet object. */ + chartsheet = lxw_chartsheet_new(&init_data); + GOTO_LABEL_ON_MEM_ERROR(chartsheet, mem_error); + + /* Add it to the chartsheet list. */ + self->num_chartsheets++; + STAILQ_INSERT_TAIL(self->chartsheets, chartsheet, list_pointers); + + /* Create a new sheet object. */ + sheet = calloc(1, sizeof(lxw_sheet)); + GOTO_LABEL_ON_MEM_ERROR(sheet, mem_error); + sheet->is_chartsheet = LXW_TRUE; + sheet->u.chartsheet = chartsheet; + + /* Add it to the chartsheet list. */ + self->num_sheets++; + STAILQ_INSERT_TAIL(self->sheets, sheet, list_pointers); + + /* Store the chartsheet so we can look it up by name. */ + chartsheet_name->name = init_data.name; + chartsheet_name->chartsheet = chartsheet; + RB_INSERT(lxw_chartsheet_names, self->chartsheet_names, chartsheet_name); + + return chartsheet; + +mem_error: + free(init_data.name); + free(init_data.quoted_name); + free(chartsheet_name); + free(chartsheet); return NULL; } @@ -1509,6 +1687,7 @@ workbook_add_format(lxw_workbook *self) lxw_error workbook_close(lxw_workbook *self) { + lxw_sheet *sheet = NULL; lxw_worksheet *worksheet = NULL; lxw_packager *packager = NULL; lxw_error error = LXW_NO_ERROR; @@ -1519,13 +1698,21 @@ workbook_close(lxw_workbook *self) /* Ensure that at least one worksheet has been selected. */ if (self->active_sheet == 0) { - worksheet = STAILQ_FIRST(self->worksheets); - worksheet->selected = 1; - worksheet->hidden = 0; + sheet = STAILQ_FIRST(self->sheets); + if (!sheet->is_chartsheet) { + worksheet = sheet->u.worksheet; + worksheet->selected = 1; + worksheet->hidden = 0; + } } /* Set the active sheet. */ - STAILQ_FOREACH(worksheet, self->worksheets, list_pointers) { + STAILQ_FOREACH(sheet, self->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + if (worksheet->index == self->active_sheet) worksheet->active = 1; } @@ -1891,11 +2078,33 @@ workbook_get_worksheet_by_name(lxw_workbook *self, const char *name) return NULL; } +/* + * Get a chartsheet object from its name. + */ +lxw_chartsheet * +workbook_get_chartsheet_by_name(lxw_workbook *self, const char *name) +{ + lxw_chartsheet_name chartsheet_name; + lxw_chartsheet_name *found; + + if (!name) + return NULL; + + chartsheet_name.name = name; + found = RB_FIND(lxw_chartsheet_names, + self->chartsheet_names, &chartsheet_name); + + if (found) + return found->chartsheet; + else + return NULL; +} + /* * Validate the worksheet name based on Excel's rules. */ lxw_error -workbook_validate_worksheet_name(lxw_workbook *self, const char *sheetname) +workbook_validate_sheet_name(lxw_workbook *self, const char *sheetname) { /* Check the UTF-8 length of the worksheet name. */ if (lxw_utf8_strlen(sheetname) > LXW_SHEETNAME_MAX) @@ -1909,5 +2118,9 @@ workbook_validate_worksheet_name(lxw_workbook *self, const char *sheetname) if (workbook_get_worksheet_by_name(self, sheetname)) return LXW_ERROR_SHEETNAME_ALREADY_USED; + /* Check if the chartsheet name is already in use. */ + if (workbook_get_chartsheet_by_name(self, sheetname)) + return LXW_ERROR_SHEETNAME_ALREADY_USED; + return LXW_NO_ERROR; } diff --git a/src/libxlsxwriter/worksheet.c b/src/libxlsxwriter/worksheet.c index c032950..d8e8752 100644 --- a/src/libxlsxwriter/worksheet.c +++ b/src/libxlsxwriter/worksheet.c @@ -17,8 +17,6 @@ #define LXW_STR_MAX 32767 #define LXW_BUFFER_SIZE 4096 -#define LXW_PORTRAIT 1 -#define LXW_LANDSCAPE 0 #define LXW_PRINT_ACROSS 1 #define LXW_VALIDATION_MAX_TITLE_LENGTH 32 #define LXW_VALIDATION_MAX_STRING_LENGTH 255 @@ -271,10 +269,11 @@ _free_image_options(lxw_image_options *image) return; free(image->filename); - free(image->short_name); + free(image->description); free(image->extension); free(image->url); free(image->tip); + free(image->image_buffer); free(image); } @@ -538,6 +537,26 @@ _new_inline_string_cell(lxw_row_t row_num, return cell; } +/* + * Create a new worksheet inline_string cell object for rich strings. + */ +STATIC lxw_cell * +_new_inline_rich_string_cell(lxw_row_t row_num, + lxw_col_t col_num, char *string, + lxw_format *format) +{ + lxw_cell *cell = calloc(1, sizeof(lxw_cell)); + RETURN_ON_MEM_ERROR(cell, cell); + + cell->row_num = row_num; + cell->col_num = col_num; + cell->type = INLINE_RICH_STRING_CELL; + cell->format = format; + cell->u.string = string; + + return cell; +} + /* * Create a new worksheet formula cell object. */ @@ -841,38 +860,6 @@ _cell_cmp(lxw_cell *cell1, lxw_cell *cell2) return 0; } -/* - * Hash a worksheet password. Based on the algorithm provided by Daniel Rentz - * of OpenOffice. - */ -STATIC uint16_t -_hash_password(const char *password) -{ - size_t count; - uint8_t i; - uint16_t hash = 0x0000; - - count = strlen(password); - - for (i = 0; i < count; i++) { - uint32_t low_15; - uint32_t high_15; - uint32_t letter = password[i] << (i + 1); - - low_15 = letter & 0x7fff; - high_15 = letter & (0x7fff << 15); - high_15 = high_15 >> 15; - letter = low_15 | high_15; - - hash ^= letter; - } - - hash ^= count; - hash ^= 0xCE4B; - - return hash; -} - /* * Simple replacement for libgen.h basename() for compatibility with MSVC. It * handles forward and back slashes. It doesn't copy exactly the return @@ -2027,7 +2014,7 @@ lxw_worksheet_prepare_image(lxw_worksheet *self, drawing_object->anchor_type = LXW_ANCHOR_TYPE_IMAGE; drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL; - drawing_object->description = lxw_strdup(image_data->short_name); + drawing_object->description = lxw_strdup(image_data->description); /* Scale to user scale. */ width = image_data->width * image_data->x_scale; @@ -2079,8 +2066,10 @@ lxw_worksheet_prepare_image(lxw_worksheet *self, */ void lxw_worksheet_prepare_chart(lxw_worksheet *self, - uint16_t chart_ref_id, uint16_t drawing_id, - lxw_image_options *image_data) + uint16_t chart_ref_id, + uint16_t drawing_id, + lxw_image_options *image_data, + uint8_t is_chartsheet) { lxw_drawing_object *drawing_object; lxw_rel_tuple *relationship; @@ -2090,9 +2079,16 @@ lxw_worksheet_prepare_chart(lxw_worksheet *self, if (!self->drawing) { self->drawing = lxw_drawing_new(); - self->drawing->embedded = LXW_TRUE; RETURN_VOID_ON_MEM_ERROR(self->drawing); + if (is_chartsheet) { + self->drawing->embedded = LXW_FALSE; + self->drawing->orientation = self->orientation; + } + else { + self->drawing->embedded = LXW_TRUE; + } + relationship = calloc(1, sizeof(lxw_rel_tuple)); GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); @@ -2259,8 +2255,7 @@ _process_png(lxw_image_options *image_options) file_error: LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "no size data found in file: %s.", - image_options->filename); + "no size data found in: %s.", image_options->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } @@ -2386,8 +2381,7 @@ _process_jpeg(lxw_image_options *image_options) file_error: LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "no size data found in file: %s.", - image_options->filename); + "no size data found in: %s.", image_options->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } @@ -2436,8 +2430,7 @@ _process_bmp(lxw_image_options *image_options) file_error: LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "no size data found in file: %s.", - image_options->filename); + "no size data found in: %s.", image_options->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } @@ -2454,7 +2447,7 @@ _get_image_properties(lxw_image_options *image_options) /* Read 4 bytes to look for the file header/signature. */ if (fread(signature, 1, 4, image_options->stream) < 4) { LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "couldn't read file type for file: %s.", + "couldn't read image type for: %s.", image_options->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } @@ -2473,7 +2466,7 @@ _get_image_properties(lxw_image_options *image_options) } else { LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "unsupported image format for file: %s.", + "unsupported image format for: %s.", image_options->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } @@ -2576,6 +2569,41 @@ _write_inline_string_cell(lxw_worksheet *self, char *range, free(string); } +/* + * Write out an inline rich string. Doesn't use the xml functions as an + * optimization in the inner cell writing loop. + */ +STATIC void +_write_inline_rich_string_cell(lxw_worksheet *self, char *range, + int32_t style_index, lxw_cell *cell) +{ + char *string = cell->u.string; + + /* Add attribute to preserve leading or trailing whitespace. */ + if (isspace((unsigned char) string[0]) + || isspace((unsigned char) string[strlen(string) - 1])) { + + if (style_index) + fprintf(self->file, + "%s", + range, style_index, string); + else + fprintf(self->file, + "%s", + range, string); + } + else { + if (style_index) + fprintf(self->file, + "" + "%s", range, style_index, string); + else + fprintf(self->file, + "" + "%s", range, string); + } +} + /* * Write out a formula worksheet cell with a numeric result. */ @@ -2712,6 +2740,11 @@ _write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format) return; } + if (cell->type == INLINE_RICH_STRING_CELL) { + _write_inline_rich_string_cell(self, range, style_index, cell); + return; + } + /* For other cell types use the general functions. */ LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("r", range); @@ -3093,7 +3126,8 @@ _worksheet_write_sheet_pr(lxw_worksheet *self) if (!self->fit_page && !self->filter_on && self->tab_color == LXW_COLOR_UNSET - && !self->outline_changed && !self->vba_codename) { + && !self->outline_changed + && !self->vba_codename && !self->is_chartsheet) { return; } @@ -3364,13 +3398,12 @@ _worksheet_write_hyperlinks(lxw_worksheet *self) * Write the element. */ STATIC void -_worksheet_write_sheet_protection(lxw_worksheet *self) +_worksheet_write_sheet_protection(lxw_worksheet *self, + lxw_protection *protect) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - struct lxw_protection *protect = &self->protection; - if (!protect->is_configured) return; @@ -3382,7 +3415,7 @@ _worksheet_write_sheet_protection(lxw_worksheet *self) if (!protect->no_sheet) LXW_PUSH_ATTRIBUTES_INT("sheet", 1); - if (protect->content) + if (!protect->no_content) LXW_PUSH_ATTRIBUTES_INT("content", 1); if (!protect->objects) @@ -3439,7 +3472,7 @@ _worksheet_write_sheet_protection(lxw_worksheet *self) * Write the element. */ STATIC void -_write_drawing(lxw_worksheet *self, uint16_t id) +_worksheet_write_drawing(lxw_worksheet *self, uint16_t id) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -3461,14 +3494,14 @@ _write_drawing(lxw_worksheet *self, uint16_t id) * Write the elements. */ STATIC void -_write_drawings(lxw_worksheet *self) +_worksheet_write_drawings(lxw_worksheet *self) { if (!self->drawing) return; self->rel_count++; - _write_drawing(self, self->rel_count); + _worksheet_write_drawing(self, self->rel_count); } /* @@ -3689,6 +3722,52 @@ _worksheet_write_data_validations(lxw_worksheet *self) LXW_FREE_ATTRIBUTES(); } +/* + * External functions to call intern XML methods shared with chartsheet. + */ +void +lxw_worksheet_write_sheet_views(lxw_worksheet *self) +{ + _worksheet_write_sheet_views(self); +} + +void +lxw_worksheet_write_page_margins(lxw_worksheet *self) +{ + _worksheet_write_page_margins(self); +} + +void +lxw_worksheet_write_drawings(lxw_worksheet *self) +{ + _worksheet_write_drawings(self); +} + +void +lxw_worksheet_write_sheet_protection(lxw_worksheet *self, + lxw_protection *protect) +{ + _worksheet_write_sheet_protection(self, protect); +} + +void +lxw_worksheet_write_sheet_pr(lxw_worksheet *self) +{ + _worksheet_write_sheet_pr(self); +} + +void +lxw_worksheet_write_page_setup(lxw_worksheet *self) +{ + _worksheet_write_page_setup(self); +} + +void +lxw_worksheet_write_header_footer(lxw_worksheet *self) +{ + _worksheet_write_header_footer(self); +} + /* * Assemble and write the XML file. */ @@ -3723,7 +3802,7 @@ lxw_worksheet_assemble_xml_file(lxw_worksheet *self) _worksheet_write_optimized_sheet_data(self); /* Write the sheetProtection element. */ - _worksheet_write_sheet_protection(self); + _worksheet_write_sheet_protection(self, &self->protection); /* Write the autoFilter element. */ _worksheet_write_auto_filter(self); @@ -3756,7 +3835,7 @@ lxw_worksheet_assemble_xml_file(lxw_worksheet *self) _worksheet_write_col_breaks(self); /* Write the drawing element. */ - _write_drawings(self); + _worksheet_write_drawings(self); /* Close the worksheet tag. */ lxw_xml_end_tag(self->file, "worksheet"); @@ -3823,7 +3902,7 @@ worksheet_write_string(lxw_worksheet *self, if (!self->optimize) { /* Get the SST element and string id. */ - sst_element = lxw_get_sst_index(self->sst, string); + sst_element = lxw_get_sst_index(self->sst, string, LXW_FALSE); if (!sst_element) return LXW_ERROR_SHARED_STRING_INDEX_NOT_FOUND; @@ -4298,6 +4377,156 @@ worksheet_write_url(lxw_worksheet *self, NULL); } +/* + * Write a rich string to an Excel file. + * + * Rather than duplicate several of the styles.c font xml methods of styles.c + * and write the data to a memory buffer this function creates a temporary + * styles object and uses it to write the data to a file. It then reads that + * data back into memory and closes the file. + */ +lxw_error +worksheet_write_rich_string(lxw_worksheet *self, + lxw_row_t row_num, + lxw_col_t col_num, + lxw_rich_string_tuple *rich_strings[], + lxw_format *format) +{ + lxw_cell *cell; + int32_t string_id; + struct sst_element *sst_element; + lxw_error err; + uint8_t i; + long file_size; + char *rich_string = NULL; + char *string_copy = NULL; + lxw_styles *styles = NULL; + lxw_format *default_format = NULL; + lxw_rich_string_tuple *rich_string_tuple = NULL; + FILE *tmpfile; + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + /* Iterate through rich string fragments to check for input errors. */ + i = 0; + err = LXW_NO_ERROR; + while ((rich_string_tuple = rich_strings[i++]) != NULL) { + + /* Check for NULL or empty strings. */ + if (!rich_string_tuple->string || !*rich_string_tuple->string) { + err = LXW_ERROR_PARAMETER_VALIDATION; + } + } + + /* If there are less than 2 fragments it isn't a rich string. */ + if (i <= 2) + err = LXW_ERROR_PARAMETER_VALIDATION; + + if (err) + return err; + + /* Create a tmp file for the styles object. */ + tmpfile = lxw_tmpfile(self->tmpdir); + if (!tmpfile) + return LXW_ERROR_CREATING_TMPFILE; + + /* Create a temp styles object for writing the font data. */ + styles = lxw_styles_new(); + GOTO_LABEL_ON_MEM_ERROR(styles, mem_error); + styles->file = tmpfile; + + /* Create a default format for non-formatted text. */ + default_format = lxw_format_new(); + GOTO_LABEL_ON_MEM_ERROR(default_format, mem_error); + + /* Iterate through the rich string fragments and write each one out. */ + i = 0; + while ((rich_string_tuple = rich_strings[i++]) != NULL) { + lxw_xml_start_tag(tmpfile, "r", NULL); + + if (rich_string_tuple->format) { + /* Write the user defined font format. */ + lxw_styles_write_rich_font(styles, rich_string_tuple->format); + } + else { + /* Write a default font format. Except for the first fragment. */ + if (i > 1) + lxw_styles_write_rich_font(styles, default_format); + } + + lxw_styles_write_string_fragment(styles, rich_string_tuple->string); + lxw_xml_end_tag(tmpfile, "r"); + } + + /* Free the temp objects. */ + lxw_styles_free(styles); + lxw_format_free(default_format); + + /* Flush the file and read the size to calculate the required memory. */ + fflush(tmpfile); + file_size = ftell(tmpfile); + + /* Allocate a buffer for the rich string xml data. */ + rich_string = calloc(file_size + 1, 1); + GOTO_LABEL_ON_MEM_ERROR(rich_string, mem_error); + + /* Rewind the file and read the data into the memory buffer. */ + rewind(tmpfile); + if (fread(rich_string, file_size, 1, tmpfile) < 1) { + fclose(tmpfile); + free(rich_string); + return LXW_ERROR_READING_TMPFILE; + } + + /* Close the temp file. */ + fclose(tmpfile); + + if (lxw_utf8_strlen(rich_string) > LXW_STR_MAX) { + free(rich_string); + return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED; + } + + if (!self->optimize) { + /* Get the SST element and string id. */ + sst_element = lxw_get_sst_index(self->sst, rich_string, LXW_TRUE); + free(rich_string); + + if (!sst_element) + return LXW_ERROR_SHARED_STRING_INDEX_NOT_FOUND; + + string_id = sst_element->index; + cell = _new_string_cell(row_num, col_num, string_id, + sst_element->string, format); + } + else { + /* Look for and escape control chars in the string. */ + if (strpbrk(rich_string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C" + "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16" + "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) { + string_copy = lxw_escape_control_characters(rich_string); + free(rich_string); + } + else { + string_copy = rich_string; + } + cell = _new_inline_rich_string_cell(row_num, col_num, string_copy, + format); + } + + _insert_cell(self, row_num, col_num, cell); + + return LXW_NO_ERROR; + +mem_error: + lxw_styles_free(styles); + lxw_format_free(default_format); + fclose(tmpfile); + + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + /* * Set the properties of a single column or a range of columns with options. */ @@ -5234,10 +5463,11 @@ worksheet_protect(lxw_worksheet *self, const char *password, } if (password) { - uint16_t hash = _hash_password(password); + uint16_t hash = lxw_hash_password(password); lxw_snprintf(protect->hash, 5, "%X", hash); } + protect->no_content = LXW_TRUE; protect->is_configured = LXW_TRUE; } @@ -5289,7 +5519,7 @@ worksheet_insert_image_opt(lxw_worksheet *self, lxw_image_options *user_options) { FILE *image_stream; - char *short_name; + char *description; lxw_image_options *options; if (!filename) { @@ -5307,9 +5537,9 @@ worksheet_insert_image_opt(lxw_worksheet *self, return LXW_ERROR_PARAMETER_VALIDATION; } - /* Get the filename from the full path to add to the Drawing object. */ - short_name = lxw_basename(filename); - if (!short_name) { + /* Use the filename as the default description, like Excel. */ + description = lxw_basename(filename); + if (!description) { LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " "couldn't get basename for file: %s.", filename); fclose(image_stream); @@ -5332,7 +5562,7 @@ worksheet_insert_image_opt(lxw_worksheet *self, /* Copy other options or set defaults. */ options->filename = lxw_strdup(filename); - options->short_name = lxw_strdup(short_name); + options->description = lxw_strdup(description); options->stream = image_stream; options->row = row_num; options->col = col_num; @@ -5349,7 +5579,7 @@ worksheet_insert_image_opt(lxw_worksheet *self, return LXW_NO_ERROR; } else { - free(options); + _free_image_options(options); fclose(image_stream); return LXW_ERROR_IMAGE_DIMENSIONS; } @@ -5366,6 +5596,92 @@ worksheet_insert_image(lxw_worksheet *self, return worksheet_insert_image_opt(self, row_num, col_num, filename, NULL); } +lxw_error +worksheet_insert_image_buffer_opt(lxw_worksheet *self, + lxw_row_t row_num, + lxw_col_t col_num, + const unsigned char *image_buffer, + size_t image_size, + lxw_image_options *user_options) +{ + FILE *image_stream; + lxw_image_options *options; + + if (!image_size) { + LXW_WARN("worksheet_insert_image_buffer()/_opt(): " + "size must be non-zero."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Write the image buffer to a temporary file so we can read the + * dimensions like an ordinary file. */ + image_stream = lxw_tmpfile(self->tmpdir); + fwrite(image_buffer, 1, image_size, image_stream); + rewind(image_stream); + + /* Create a new object to hold the image options. */ + options = calloc(1, sizeof(lxw_image_options)); + if (!options) { + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + /* Store the image data in the options structure. */ + options->image_buffer = calloc(1, image_size); + if (!options->image_buffer) { + _free_image_options(options); + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + else { + memcpy(options->image_buffer, image_buffer, image_size); + options->image_buffer_size = image_size; + options->is_image_buffer = LXW_TRUE; + } + + if (user_options) { + options->x_offset = user_options->x_offset; + options->y_offset = user_options->y_offset; + options->x_scale = user_options->x_scale; + options->y_scale = user_options->y_scale; + options->description = lxw_strdup(user_options->description); + } + + /* Copy other options or set defaults. */ + options->filename = lxw_strdup("image_buffer"); + options->stream = image_stream; + options->row = row_num; + options->col = col_num; + + if (!options->x_scale) + options->x_scale = 1; + + if (!options->y_scale) + options->y_scale = 1; + + if (_get_image_properties(options) == LXW_NO_ERROR) { + STAILQ_INSERT_TAIL(self->image_data, options, list_pointers); + fclose(image_stream); + return LXW_NO_ERROR; + } + else { + _free_image_options(options); + fclose(image_stream); + return LXW_ERROR_IMAGE_DIMENSIONS; + } +} + +lxw_error +worksheet_insert_image_buffer(lxw_worksheet *self, + lxw_row_t row_num, + lxw_col_t col_num, + const unsigned char *image_buffer, + size_t image_size) +{ + return worksheet_insert_image_buffer_opt(self, row_num, col_num, + image_buffer, image_size, NULL); +} + /* * Insert an chart into the worksheet. */ diff --git a/src/libxlsxwriter/xmlwriter.c b/src/libxlsxwriter/xmlwriter.c index 3b70dcd..0ad3b1b 100644 --- a/src/libxlsxwriter/xmlwriter.c +++ b/src/libxlsxwriter/xmlwriter.c @@ -140,6 +140,15 @@ lxw_xml_data_element(FILE * xmlfile, fprintf(xmlfile, "", tag); } +/* + * Write an XML element for rich strings, without encoding. + */ +void +lxw_xml_rich_si_element(FILE * xmlfile, const char *string) +{ + fprintf(xmlfile, "%s", string); +} + /* * Escape XML characters in attributes. */ @@ -153,19 +162,19 @@ _escape_attributes(struct xml_attribute *attribute) while (*p_attr) { switch (*p_attr) { case '&': - strncat(p_encoded, LXW_AMP, sizeof(LXW_AMP) - 1); + memcpy(p_encoded, LXW_AMP, sizeof(LXW_AMP) - 1); p_encoded += sizeof(LXW_AMP) - 1; break; case '<': - strncat(p_encoded, LXW_LT, sizeof(LXW_LT) - 1); + memcpy(p_encoded, LXW_LT, sizeof(LXW_LT) - 1); p_encoded += sizeof(LXW_LT) - 1; break; case '>': - strncat(p_encoded, LXW_GT, sizeof(LXW_GT) - 1); + memcpy(p_encoded, LXW_GT, sizeof(LXW_GT) - 1); p_encoded += sizeof(LXW_GT) - 1; break; case '"': - strncat(p_encoded, LXW_QUOT, sizeof(LXW_QUOT) - 1); + memcpy(p_encoded, LXW_QUOT, sizeof(LXW_QUOT) - 1); p_encoded += sizeof(LXW_QUOT) - 1; break; default: @@ -195,15 +204,15 @@ lxw_escape_data(const char *data) while (*data) { switch (*data) { case '&': - strncat(p_encoded, LXW_AMP, sizeof(LXW_AMP) - 1); + memcpy(p_encoded, LXW_AMP, sizeof(LXW_AMP) - 1); p_encoded += sizeof(LXW_AMP) - 1; break; case '<': - strncat(p_encoded, LXW_LT, sizeof(LXW_LT) - 1); + memcpy(p_encoded, LXW_LT, sizeof(LXW_LT) - 1); p_encoded += sizeof(LXW_LT) - 1; break; case '>': - strncat(p_encoded, LXW_GT, sizeof(LXW_GT) - 1); + memcpy(p_encoded, LXW_GT, sizeof(LXW_GT) - 1); p_encoded += sizeof(LXW_GT) - 1; break; default: diff --git a/src/minizip/ioapi.c b/src/minizip/ioapi.c index 7f5c191..b72d580 100644 --- a/src/minizip/ioapi.c +++ b/src/minizip/ioapi.c @@ -96,6 +96,7 @@ static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, in { FILE* file = NULL; const char* mode_fopen = NULL; + (void) opaque; if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) mode_fopen = "rb"; else @@ -114,6 +115,7 @@ static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, { FILE* file = NULL; const char* mode_fopen = NULL; + (void) opaque; if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) mode_fopen = "rb"; else @@ -132,6 +134,7 @@ static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) { uLong ret; + (void) opaque; ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); return ret; } @@ -139,6 +142,7 @@ static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) { uLong ret; + (void) opaque; ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); return ret; } @@ -146,6 +150,7 @@ static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const voi static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) { long ret; + (void) opaque; ret = ftell((FILE *)stream); return ret; } @@ -154,6 +159,7 @@ static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) { ZPOS64_T ret; + (void) opaque; ret = FTELLO_FUNC((FILE *)stream); return ret; } @@ -162,6 +168,7 @@ static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offs { int fseek_origin=0; long ret; + (void) opaque; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR : @@ -185,6 +192,7 @@ static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T { int fseek_origin=0; long ret; + (void) opaque; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR : @@ -210,6 +218,7 @@ static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) { int ret; + (void) opaque; ret = fclose((FILE *)stream); return ret; } @@ -217,6 +226,7 @@ static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) { int ret; + (void) opaque; ret = ferror((FILE *)stream); return ret; } diff --git a/src/minizip/zip.c b/src/minizip/zip.c index 337bcd5..ebe70f4 100644 --- a/src/minizip/zip.c +++ b/src/minizip/zip.c @@ -519,12 +519,14 @@ local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_f break; for (i=(int)uReadSize-3; (i--)>0;) + { if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } + } if (uPosFound!=0) break; diff --git a/src/tmpfileplus/tmpfileplus.c b/src/tmpfileplus/tmpfileplus.c index e1ff7c7..28ae9c8 100644 --- a/src/tmpfileplus/tmpfileplus.c +++ b/src/tmpfileplus/tmpfileplus.c @@ -169,7 +169,7 @@ static char *getenv_save(const char *varname, char *buf, size_t bufsize) buf[0] = '\0'; if (ptr) { - strncpy(buf, ptr, bufsize); + strncpy(buf, ptr, bufsize-1); buf[bufsize-1] = '\0'; return buf; } @@ -189,7 +189,7 @@ static FILE *mktempfile_internal(const char *tmpdir, const char *pfx, char **tmp */ { FILE *fp; - int fd; + int fd = 0; char randpart[] = "1234567890"; size_t lentempname; int i;