mirror of
https://github.com/jmcnamara/libxlsxwriter
synced 2025-03-28 21:13:14 +00:00
parent
8b35b62d8c
commit
2ea3ba2a71
1
.indent.pro
vendored
1
.indent.pro
vendored
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -77,6 +77,7 @@ typedef struct lxw_drawing {
|
||||
FILE *file;
|
||||
|
||||
uint8_t embedded;
|
||||
uint8_t orientation;
|
||||
|
||||
struct lxw_drawing_objects *drawing_objects;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
190
src/chartsheet.c
190
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 <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;
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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");
|
||||
|
173
src/packager.c
173
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);
|
||||
|
||||
|
214
src/workbook.c
214
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 <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;
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
44
test/functional/src/test_chartsheet01.c
Normal file
44
test/functional/src/test_chartsheet01.c
Normal 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);
|
||||
}
|
17
test/functional/test_chartsheet.py
Normal file
17
test/functional/test_chartsheet.py
Normal 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')
|
BIN
test/functional/xlsx_files/chartsheet01.xlsx
Normal file
BIN
test/functional/xlsx_files/chartsheet01.xlsx
Normal file
Binary file not shown.
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user