From 2ea3ba2a71797466e7eb50f01cc21cbb89f562a0 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Sat, 8 Sep 2018 23:36:09 +0100 Subject: [PATCH] First fully functional chartsheet implementation. Issue #90 --- .indent.pro | 1 + include/xlsxwriter/chart.h | 1 + include/xlsxwriter/chartsheet.h | 23 +- include/xlsxwriter/content_types.h | 2 + include/xlsxwriter/drawing.h | 1 + include/xlsxwriter/workbook.h | 85 +++++-- include/xlsxwriter/worksheet.h | 4 +- src/chart.c | 3 +- src/chartsheet.c | 190 +++++++++++++--- src/content_types.c | 10 + src/drawing.c | 99 +++++++- src/packager.c | 173 ++++++++++++-- src/workbook.c | 214 +++++++++++++++--- src/worksheet.c | 18 +- test/functional/src/test_chartsheet01.c | 44 ++++ test/functional/test_chartsheet.py | 17 ++ test/functional/xlsx_files/chartsheet01.xlsx | Bin 0 -> 9606 bytes test/unit/chartsheet/test_chartsheet.c | 3 +- .../test_chartsheet_xml_declaration.c | 2 +- .../test_workbook_validate_worksheet_name.c | 8 +- 20 files changed, 781 insertions(+), 117 deletions(-) create mode 100644 test/functional/src/test_chartsheet01.c create mode 100644 test/functional/test_chartsheet.py create mode 100644 test/functional/xlsx_files/chartsheet01.xlsx diff --git a/.indent.pro b/.indent.pro index 14fed76b..26de6bb8 100644 --- a/.indent.pro +++ b/.indent.pro @@ -79,6 +79,7 @@ -T lxw_chart_trendline_type -T lxw_chart_type -T lxw_chartsheet +-T lxw_chartsheet_name -T lxw_col_options -T lxw_col_t -T lxw_color_t diff --git a/include/xlsxwriter/chart.h b/include/xlsxwriter/chart.h index 37ff3822..a9abb019 100644 --- a/include/xlsxwriter/chart.h +++ b/include/xlsxwriter/chart.h @@ -1069,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; diff --git a/include/xlsxwriter/chartsheet.h b/include/xlsxwriter/chartsheet.h index a0a4a21e..61ec65a1 100644 --- a/include/xlsxwriter/chartsheet.h +++ b/include/xlsxwriter/chartsheet.h @@ -60,10 +60,20 @@ typedef struct lxw_chartsheet { FILE *file; lxw_worksheet *worksheet; - lxw_drawing *drawing; + 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; @@ -73,6 +83,17 @@ extern "C" { #endif /* *INDENT-ON* */ +lxw_error chartsheet_set_chart_opt(lxw_chartsheet *chartsheet, + lxw_chart *chart, + lxw_image_options *user_options); + +lxw_error chartsheet_set_chart(lxw_chartsheet *chartsheet, lxw_chart *chart); + +void chartsheet_select(lxw_chartsheet *chartsheet); +void chartsheet_activate(lxw_chartsheet *chartsheet); +void chartsheet_set_first_sheet(lxw_chartsheet *chartsheet); +void chartsheet_hide(lxw_chartsheet *chartsheet); + lxw_chartsheet *lxw_chartsheet_new(); void lxw_chartsheet_free(lxw_chartsheet *chartsheet); void lxw_chartsheet_assemble_xml_file(lxw_chartsheet *self); diff --git a/include/xlsxwriter/content_types.h b/include/xlsxwriter/content_types.h index 7caa85e3..b1fe817b 100644 --- a/include/xlsxwriter/content_types.h +++ b/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/include/xlsxwriter/drawing.h b/include/xlsxwriter/drawing.h index 0455fe04..2cd660ef 100644 --- a/include/xlsxwriter/drawing.h +++ b/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/include/xlsxwriter/workbook.h b/include/xlsxwriter/workbook.h index 96737dad..5223b136 100644 --- a/include/xlsxwriter/workbook.h +++ b/include/xlsxwriter/workbook.h @@ -56,10 +56,12 @@ /* 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); @@ -83,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. @@ -217,7 +238,9 @@ 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; @@ -231,6 +254,7 @@ typedef struct lxw_workbook { 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; @@ -364,6 +388,9 @@ lxw_workbook *new_workbook_opt(const char *filename, lxw_worksheet *workbook_add_worksheet(lxw_workbook *workbook, const char *sheetname); +lxw_chartsheet *workbook_add_chartsheet(lxw_workbook *chartsheet, + const char *sheetname); + /** * @brief Create a new @ref format.h "Format" object to formats cells in * worksheets. @@ -703,31 +730,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/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 9cdb33f0..d4a194cd 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -3146,7 +3146,8 @@ 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); @@ -3156,6 +3157,7 @@ lxw_cell *lxw_worksheet_find_cell(lxw_row *row, lxw_col_t col_num); */ 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); /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/src/chart.c b/src/chart.c index ca2a2412..4f8becfe 100644 --- a/src/chart.c +++ b/src/chart.c @@ -4949,7 +4949,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"); } diff --git a/src/chartsheet.c b/src/chartsheet.c index e21d3431..77450a98 100644 --- a/src/chartsheet.c +++ b/src/chartsheet.c @@ -25,7 +25,7 @@ * Create a new chartsheet object. */ lxw_chartsheet * -lxw_chartsheet_new() +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); @@ -35,6 +35,16 @@ lxw_chartsheet_new() 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; + } + return chartsheet; mem_error: @@ -52,10 +62,8 @@ lxw_chartsheet_free(lxw_chartsheet *chartsheet) return; lxw_worksheet_free(chartsheet->worksheet); - - if (chartsheet->drawing) - lxw_drawing_free(chartsheet->drawing); - + free(chartsheet->name); + free(chartsheet->quoted_name); free(chartsheet); } @@ -122,40 +130,13 @@ _chartsheet_write_page_margins(lxw_chartsheet *self) lxw_worksheet_write_page_margins(self->worksheet); } -/* - * Write the element. - */ -STATIC void -_chartsheet_write_drawing(lxw_chartsheet *self, uint16_t id) -{ - struct xml_attribute_list attributes; - struct xml_attribute *attribute; - char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; - - lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id); - - LXW_INIT_ATTRIBUTES(); - - LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); - - lxw_xml_empty_tag(self->file, "drawing", &attributes); - - LXW_FREE_ATTRIBUTES(); - -} - /* * Write the elements. */ STATIC void _chartsheet_write_drawings(lxw_chartsheet *self) { - if (!self->drawing) - return; - - self->rel_count++; - - _chartsheet_write_drawing(self, self->rel_count); + lxw_worksheet_write_drawings(self->worksheet); } /***************************************************************************** @@ -199,3 +180,146 @@ lxw_chartsheet_assemble_xml_file(lxw_chartsheet *self) * 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; + + 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; +} diff --git a/src/content_types.c b/src/content_types.c index 73068bd4..8f6a5cb2 100644 --- a/src/content_types.c +++ b/src/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/drawing.c b/src/drawing.c index 1a16eb1f..c15f0acd 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -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,71 @@ _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); + + /* Horizontal == 0. Vertial == 1. */ + if (self->orientation == 0) { + /* 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); + } + + _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 +825,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/packager.c b/src/packager.c index 36662f88..4362a155 100644 --- a/src/packager.c +++ b/src/packager.c @@ -200,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. */ @@ -306,7 +343,7 @@ _write_drawing_files(lxw_packager *self) STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { if (sheet->is_chartsheet) - continue; + worksheet = sheet->u.chartsheet->worksheet; else worksheet = sheet->u.worksheet; @@ -369,6 +406,7 @@ _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; @@ -389,17 +427,27 @@ _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); + } - 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); + } STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { - if (sheet->is_chartsheet) - continue; - else + if (sheet->is_chartsheet) { + chartsheet = sheet->u.chartsheet; + lxw_app_add_part_name(app, chartsheet->name); + } + else { worksheet = sheet->u.worksheet; - - lxw_app_add_part_name(app, worksheet->name); + lxw_app_add_part_name(app, worksheet->name); + } } /* Add the Named Ranges parts. */ @@ -604,6 +652,8 @@ _write_content_types_file(lxw_packager *self) 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) { @@ -627,12 +677,16 @@ _write_content_types_file(lxw_packager *self) lxw_ct_add_default(content_types, "bmp", "image/bmp"); STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { - if (sheet->is_chartsheet) - continue; - - lxw_snprintf(filename, LXW_FILENAME_LENGTH, - "/xl/worksheets/sheet%d.xml", index++); - lxw_ct_add_worksheet_name(content_types, filename); + 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++) { @@ -675,7 +729,8 @@ _write_workbook_rels_file(lxw_packager *self) lxw_workbook *workbook = self->workbook; 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) { @@ -690,12 +745,18 @@ _write_workbook_rels_file(lxw_packager *self) } STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { - if (sheet->is_chartsheet) - continue; - - lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "worksheets/sheet%d.xml", - index++); - lxw_add_document_relationship(rels, "/worksheet", sheetname); + 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"); @@ -779,6 +840,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. @@ -797,7 +920,7 @@ _write_drawing_rels_file(lxw_packager *self) STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { if (sheet->is_chartsheet) - continue; + worksheet = sheet->u.chartsheet->worksheet; else worksheet = sheet->u.worksheet; @@ -997,6 +1120,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); @@ -1033,6 +1159,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/workbook.c b/src/workbook.c index de42f5bf..2cfca6dd 100644 --- a/src/workbook.c +++ b/src/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); } @@ -83,7 +94,9 @@ lxw_workbook_free(lxw_workbook *workbook) { 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; @@ -113,8 +126,9 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->sheets); } - /* Free the worksheets list. The worksheet objects are freed above. */ + /* 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) { @@ -161,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); @@ -857,12 +888,17 @@ _prepare_drawings(lxw_workbook *self) uint16_t chart_ref_id = 0; uint16_t image_ref_id = 0; uint16_t drawing_id = 0; + uint8_t is_chartsheet; STAILQ_FOREACH(sheet, self->sheets, list_pointers) { - if (sheet->is_chartsheet) - continue; - else + 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)) @@ -873,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); @@ -1195,17 +1231,21 @@ _write_sheets(lxw_workbook *self) { lxw_sheet *sheet; lxw_worksheet *worksheet; + lxw_chartsheet *chartsheet; lxw_xml_start_tag(self->file, "sheets", NULL); STAILQ_FOREACH(sheet, self->sheets, list_pointers) { - if (sheet->is_chartsheet) - continue; - else + 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); + _write_sheet(self, worksheet->name, worksheet->index + 1, + worksheet->hidden); + } } lxw_xml_end_tag(self->file, "sheets"); @@ -1253,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) { @@ -1369,11 +1406,22 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) 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); @@ -1456,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)); @@ -1514,6 +1562,90 @@ mem_error: 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; +} + /* * Add a new chart to the Excel workbook. */ @@ -1946,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) @@ -1964,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/worksheet.c b/src/worksheet.c index f2427a81..e542cb97 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -2080,8 +2080,10 @@ mem_error: */ 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; @@ -2091,9 +2093,13 @@ 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; + else + self->drawing->embedded = LXW_TRUE; + relationship = calloc(1, sizeof(lxw_rel_tuple)); GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); @@ -3702,6 +3708,12 @@ 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); +} + /* * Assemble and write the XML file. */ diff --git a/test/functional/src/test_chartsheet01.c b/test/functional/src/test_chartsheet01.c new file mode 100644 index 00000000..18e9d9e0 --- /dev/null +++ b/test/functional/src/test_chartsheet01.c @@ -0,0 +1,44 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2018, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_chartsheet01.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); + + /* For testing, copy the randomly generated axis ids in the target file. */ + chart->axis_id_1 = 79858304; + chart->axis_id_2 = 79860096; + + uint8_t data[5][3] = { + {1, 2, 3}, + {2, 4, 6}, + {3, 6, 9}, + {4, 8, 12}, + {5, 10, 15} + }; + + int row, col; + for (row = 0; row < 5; row++) + for (col = 0; col < 3; col++) + worksheet_write_number(worksheet, row, col, data[row][col], NULL); + + chart_add_series(chart, NULL, "=Sheet1!$A$1:$A$5"); + chart_add_series(chart, NULL, "=Sheet1!$B$1:$B$5"); + chart_add_series(chart, NULL, "=Sheet1!$C$1:$C$5"); + + chartsheet_set_chart(chartsheet, chart); + chartsheet_activate(chartsheet); + + return workbook_close(workbook); +} diff --git a/test/functional/test_chartsheet.py b/test/functional/test_chartsheet.py new file mode 100644 index 00000000..701c9ad5 --- /dev/null +++ b/test/functional/test_chartsheet.py @@ -0,0 +1,17 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2018, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_chartsheet01(self): + self.run_exe_test('test_chartsheet01') diff --git a/test/functional/xlsx_files/chartsheet01.xlsx b/test/functional/xlsx_files/chartsheet01.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a7872717d6db42a25ef049d62dab5468cc964a70 GIT binary patch literal 9606 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 TY7GFuL+Ga*YEZ4@Kkohq*!^d{ literal 0 HcmV?d00001 diff --git a/test/unit/chartsheet/test_chartsheet.c b/test/unit/chartsheet/test_chartsheet.c index ea8a89d8..71a39b63 100644 --- a/test/unit/chartsheet/test_chartsheet.c +++ b/test/unit/chartsheet/test_chartsheet.c @@ -9,6 +9,7 @@ #include "../helper.h" #include "xlsxwriter/chartsheet.h" +#include "xlsxwriter/drawing.h" // Test assembling a complete Chartsheet file. CTEST(chartsheet, chartsheet) { @@ -29,7 +30,7 @@ CTEST(chartsheet, chartsheet) { lxw_chartsheet *chartsheet = lxw_chartsheet_new(NULL); chartsheet->file = testfile; - chartsheet->drawing = lxw_drawing_new(NULL); + chartsheet->worksheet->drawing = lxw_drawing_new(NULL); lxw_chartsheet_assemble_xml_file(chartsheet); diff --git a/test/unit/chartsheet/test_chartsheet_xml_declaration.c b/test/unit/chartsheet/test_chartsheet_xml_declaration.c index 212dc51c..cd75bb6d 100644 --- a/test/unit/chartsheet/test_chartsheet_xml_declaration.c +++ b/test/unit/chartsheet/test_chartsheet_xml_declaration.c @@ -17,7 +17,7 @@ CTEST(chartsheet, xml_declaration) { char exp[] = "\n"; FILE* testfile = tmpfile(); - lxw_chartsheet *chartsheet = lxw_chartsheet_new(); + lxw_chartsheet *chartsheet = lxw_chartsheet_new(NULL); chartsheet->file = testfile; _chartsheet_xml_declaration(chartsheet); diff --git a/test/unit/workbook/test_workbook_validate_worksheet_name.c b/test/unit/workbook/test_workbook_validate_worksheet_name.c index 3ed45dcc..4aefbf79 100644 --- a/test/unit/workbook/test_workbook_validate_worksheet_name.c +++ b/test/unit/workbook/test_workbook_validate_worksheet_name.c @@ -18,7 +18,7 @@ CTEST(workbook, validate_worksheet_name01) { lxw_workbook *workbook = workbook_new(NULL); lxw_error exp = LXW_NO_ERROR; - lxw_error got = workbook_validate_worksheet_name(workbook, sheetname); + lxw_error got = workbook_validate_sheet_name(workbook, sheetname); ASSERT_EQUAL(got, exp); @@ -32,7 +32,7 @@ CTEST(workbook, validate_worksheet_name02) { lxw_workbook *workbook = workbook_new(NULL); lxw_error exp = LXW_ERROR_SHEETNAME_LENGTH_EXCEEDED; - lxw_error got = workbook_validate_worksheet_name(workbook, sheetname); + lxw_error got = workbook_validate_sheet_name(workbook, sheetname); ASSERT_EQUAL(got, exp); @@ -46,7 +46,7 @@ CTEST(workbook, validate_worksheet_name03) { lxw_workbook *workbook = workbook_new(NULL); lxw_error exp = LXW_ERROR_INVALID_SHEETNAME_CHARACTER; - lxw_error got = workbook_validate_worksheet_name(workbook, sheetname); + lxw_error got = workbook_validate_sheet_name(workbook, sheetname); ASSERT_EQUAL(got, exp); @@ -62,7 +62,7 @@ CTEST(workbook, validate_worksheet_name04) { workbook_add_worksheet(workbook, sheetname); lxw_error exp = LXW_ERROR_SHEETNAME_ALREADY_USED; - lxw_error got = workbook_validate_worksheet_name(workbook, sheetname); + lxw_error got = workbook_validate_sheet_name(workbook, sheetname); ASSERT_EQUAL(got, exp);