First fully functional chartsheet implementation.

Issue #90
This commit is contained in:
John McNamara 2018-09-08 23:36:09 +01:00
parent 8b35b62d8c
commit 2ea3ba2a71
20 changed files with 781 additions and 117 deletions

1
.indent.pro vendored
View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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,

View File

@ -77,6 +77,7 @@ typedef struct lxw_drawing {
FILE *file;
uint8_t embedded;
uint8_t orientation;
struct lxw_drawing_objects *drawing_objects;

View File

@ -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);

View File

@ -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

View File

@ -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");
}

View File

@ -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 <drawing> 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 <drawing> 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;
}

View File

@ -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.
*/

View File

@ -489,13 +489,40 @@ _drawing_write_client_data(lxw_drawing *self)
lxw_xml_empty_tag(self->file, "xdr:clientData", NULL);
}
/*
* Write the <a:graphicFrameLocks> 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 <xdr:cNvGraphicFramePr> 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 <xdr:ext> 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 <xdr:pos> 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 <xdr:absoluteAnchor> 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");

View File

@ -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);

View File

@ -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 <definedNames> 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;
}

View File

@ -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.
*/

View File

@ -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);
}

View File

@ -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')

Binary file not shown.

View File

@ -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);

View File

@ -17,7 +17,7 @@ CTEST(chartsheet, xml_declaration) {
char exp[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
FILE* testfile = tmpfile();
lxw_chartsheet *chartsheet = lxw_chartsheet_new();
lxw_chartsheet *chartsheet = lxw_chartsheet_new(NULL);
chartsheet->file = testfile;
_chartsheet_xml_declaration(chartsheet);

View File

@ -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);