libxlsxwriter/src/workbook.c in fast_excel-0.4.1 vs libxlsxwriter/src/workbook.c in fast_excel-0.5.0
- old
+ new
@@ -1,11 +1,11 @@
/*****************************************************************************
* workbook - A library for creating Excel XLSX workbook files.
*
* Used in conjunction with the libxlsxwriter library.
*
- * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
+ * Copyright 2014-2022, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
*
*/
#include "xlsxwriter/xmlwriter.h"
#include "xlsxwriter/workbook.h"
@@ -15,15 +15,19 @@
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);
+STATIC int _image_md5_cmp(lxw_image_md5 *tuple1, lxw_image_md5 *tuple2);
+
#ifndef __clang_analyzer__
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);
+LXW_RB_GENERATE_IMAGE_MD5S(lxw_image_md5s, lxw_image_md5,
+ tree_pointers, _image_md5_cmp);
#endif
/*
* Forward declarations.
*/
@@ -47,27 +51,33 @@
_chartsheet_name_cmp(lxw_chartsheet_name *name1, lxw_chartsheet_name *name2)
{
return lxw_strcasecmp(name1->name, name2->name);
}
+STATIC int
+_image_md5_cmp(lxw_image_md5 *tuple1, lxw_image_md5 *tuple2)
+{
+ return strcmp(tuple1->md5, tuple2->md5);
+}
+
/*
* Free workbook properties.
*/
STATIC void
_free_doc_properties(lxw_doc_properties *properties)
{
if (properties) {
- free(properties->title);
- free(properties->subject);
- free(properties->author);
- free(properties->manager);
- free(properties->company);
- free(properties->category);
- free(properties->keywords);
- free(properties->comments);
- free(properties->status);
- free(properties->hyperlink_base);
+ free((void *) properties->title);
+ free((void *) properties->subject);
+ free((void *) properties->author);
+ free((void *) properties->manager);
+ free((void *) properties->company);
+ free((void *) properties->category);
+ free((void *) properties->keywords);
+ free((void *) properties->comments);
+ free((void *) properties->status);
+ free((void *) properties->hyperlink_base);
}
free(properties);
}
@@ -95,10 +105,12 @@
lxw_sheet *sheet;
struct lxw_worksheet_name *worksheet_name;
struct lxw_worksheet_name *next_worksheet_name;
struct lxw_chartsheet_name *chartsheet_name;
struct lxw_chartsheet_name *next_chartsheet_name;
+ struct lxw_image_md5 *image_md5;
+ struct lxw_image_md5 *next_image_md5;
lxw_chart *chart;
lxw_format *format;
lxw_defined_name *defined_name;
lxw_defined_name *defined_name_tmp;
lxw_custom_property *custom_property;
@@ -202,15 +214,59 @@
}
free(workbook->chartsheet_names);
}
+ if (workbook->image_md5s) {
+ for (image_md5 = RB_MIN(lxw_image_md5s, workbook->image_md5s);
+ image_md5; image_md5 = next_image_md5) {
+
+ next_image_md5 =
+ RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5);
+ RB_REMOVE(lxw_image_md5s, workbook->image_md5s, image_md5);
+ free(image_md5->md5);
+ free(image_md5);
+ }
+
+ free(workbook->image_md5s);
+ }
+
+ if (workbook->header_image_md5s) {
+ for (image_md5 = RB_MIN(lxw_image_md5s, workbook->header_image_md5s);
+ image_md5; image_md5 = next_image_md5) {
+
+ next_image_md5 =
+ RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5);
+ RB_REMOVE(lxw_image_md5s, workbook->header_image_md5s, image_md5);
+ free(image_md5->md5);
+ free(image_md5);
+ }
+
+ free(workbook->header_image_md5s);
+ }
+
+ if (workbook->background_md5s) {
+ for (image_md5 = RB_MIN(lxw_image_md5s, workbook->background_md5s);
+ image_md5; image_md5 = next_image_md5) {
+
+ next_image_md5 =
+ RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5);
+ RB_REMOVE(lxw_image_md5s, workbook->background_md5s, image_md5);
+ free(image_md5->md5);
+ free(image_md5);
+ }
+
+ free(workbook->background_md5s);
+ }
+
lxw_hash_free(workbook->used_xf_formats);
+ lxw_hash_free(workbook->used_dxf_formats);
lxw_sst_free(workbook->sst);
- free(workbook->options.tmpdir);
+ free((void *) workbook->options.tmpdir);
free(workbook->ordered_charts);
free(workbook->vba_project);
+ free(workbook->vba_project_signature);
free(workbook->vba_codename);
free(workbook);
}
/*
@@ -218,13 +274,19 @@
*/
void
lxw_workbook_set_default_xf_indices(lxw_workbook *self)
{
lxw_format *format;
+ int32_t index = 0;
STAILQ_FOREACH(format, self->formats, list_pointers) {
- lxw_format_get_xf_index(format);
+
+ /* Skip the hyperlink format. */
+ if (index != 1)
+ lxw_format_get_xf_index(format);
+
+ index++;
}
}
/*
* Iterate through the XF Format objects and give them an index to non-default
@@ -256,20 +318,32 @@
else {
/* This is a new font. */
uint16_t *font_index = calloc(1, sizeof(uint16_t));
*font_index = index;
format->font_index = index;
- format->has_font = 1;
+ format->has_font = LXW_TRUE;
lxw_insert_hash_element(fonts, key, font_index,
sizeof(lxw_font));
index++;
}
}
}
lxw_hash_free(fonts);
+ /* For DXF formats we only need to check if the properties have changed. */
+ LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
+ lxw_format *format = (lxw_format *) used_format_element->value;
+
+ /* The only font properties that can change for a DXF format are:
+ * color, bold, italic, underline and strikethrough. */
+ if (format->font_color || format->bold || format->italic
+ || format->underline || format->font_strikeout) {
+ format->has_dxf_font = LXW_TRUE;
+ }
+ }
+
self->font_count = index;
}
/*
* Iterate through the XF Format objects and give them an index to non-default
@@ -310,10 +384,19 @@
index++;
}
}
}
+ /* For DXF formats we only need to check if the properties have changed. */
+ LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
+ lxw_format *format = (lxw_format *) used_format_element->value;
+
+ if (format->left || format->right || format->top || format->bottom) {
+ format->has_dxf_border = LXW_TRUE;
+ }
+ }
+
lxw_hash_free(borders);
self->border_count = index;
}
@@ -359,10 +442,21 @@
default_fill_2->bg_color = LXW_COLOR_UNSET;
*fill_index2 = 1;
lxw_insert_hash_element(fills, default_fill_2, fill_index2,
sizeof(lxw_fill));
+ /* For DXF formats we only need to check if the properties have changed. */
+ LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
+ lxw_format *format = (lxw_format *) used_format_element->value;
+
+ if (format->pattern || format->bg_color || format->fg_color) {
+ format->has_dxf_fill = LXW_TRUE;
+ format->dxf_bg_color = format->bg_color;
+ format->dxf_fg_color = format->fg_color;
+ }
+ }
+
LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
lxw_format *format = (lxw_format *) used_format_element->value;
lxw_fill *key = lxw_format_get_fill_key(format);
/* The following logical statements jointly take care of special */
@@ -389,11 +483,10 @@
}
if (format->pattern <= LXW_PATTERN_SOLID
&& format->bg_color == LXW_COLOR_UNSET
&& format->fg_color != LXW_COLOR_UNSET) {
- format->bg_color = LXW_COLOR_UNSET;
format->pattern = LXW_PATTERN_SOLID;
}
if (key) {
/* Look up the format in the hash table. */
@@ -480,10 +573,45 @@
num_format_count++;
}
}
}
+ LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
+ lxw_format *format = (lxw_format *) used_format_element->value;
+
+ /* Format already has a number format index. */
+ if (format->num_format_index)
+ continue;
+
+ /* Check if there is a user defined number format string. */
+ if (*format->num_format) {
+ char num_format[LXW_FORMAT_FIELD_LEN] = { 0 };
+ lxw_snprintf(num_format, LXW_FORMAT_FIELD_LEN, "%s",
+ format->num_format);
+
+ /* Look up the num_format in the hash table. */
+ hash_element = lxw_hash_key_exists(num_formats, num_format,
+ LXW_FORMAT_FIELD_LEN);
+
+ if (hash_element) {
+ /* Num_Format has already been used. */
+ format->num_format_index = *(uint16_t *) hash_element->value;
+ }
+ else {
+ /* This is a new num_format. */
+ num_format_index = calloc(1, sizeof(uint16_t));
+ *num_format_index = index;
+ format->num_format_index = index;
+ lxw_insert_hash_element(num_formats, format->num_format,
+ num_format_index,
+ LXW_FORMAT_FIELD_LEN);
+ index++;
+ /* Don't update num_format_count for DXF formats. */
+ }
+ }
+ }
+
lxw_hash_free(num_formats);
self->num_format_count = num_format_count;
}
@@ -733,11 +861,11 @@
if (!data_point) {
range->ignore_cache = LXW_TRUE;
return;
}
- cell_obj = lxw_worksheet_find_cell(row_obj, col_num);
+ cell_obj = lxw_worksheet_find_cell_in_row(row_obj, col_num);
if (cell_obj) {
if (cell_obj->type == NUMBER_CELL) {
data_point->number = cell_obj->u.number;
}
@@ -794,11 +922,11 @@
}
/* Create a copy of the formula to modify and parse into parts. */
lxw_snprintf(formula, LXW_MAX_FORMULA_RANGE_LENGTH, "%s", range->formula);
- /* Check for valid formula. TODO. This needs stronger validation. */
+ /* Check for valid formula. Note, This needs stronger validation. */
tmp_str = strchr(formula, '!');
if (tmp_str == NULL) {
range->ignore_cache = LXW_TRUE;
return;
@@ -845,10 +973,13 @@
/* Set the range dimensions and set the data cache.
*/
STATIC void
_populate_range(lxw_workbook *self, lxw_series_range *range)
{
+ if (!range)
+ return;
+
_populate_range_dimensions(self, range);
_populate_range_data_cache(self, range);
}
/*
@@ -858,10 +989,11 @@
STATIC void
_add_chart_cache_data(lxw_workbook *self)
{
lxw_chart *chart;
lxw_chart_series *series;
+ uint16_t i;
STAILQ_FOREACH(chart, self->ordered_charts, ordered_list_pointers) {
_populate_range(self, chart->title.range);
_populate_range(self, chart->x_axis->title.range);
@@ -872,27 +1004,56 @@
STAILQ_FOREACH(series, chart->series_list, list_pointers) {
_populate_range(self, series->categories);
_populate_range(self, series->values);
_populate_range(self, series->title.range);
+
+ for (i = 0; i < series->data_label_count; i++) {
+ lxw_chart_custom_label *data_label = &series->data_labels[i];
+ _populate_range(self, data_label->range);
+ }
}
}
}
/*
+ * Store the image types used in the workbook to update the content types.
+ */
+STATIC void
+_store_image_type(lxw_workbook *self, uint8_t image_type)
+{
+ if (image_type == LXW_IMAGE_PNG)
+ self->has_png = LXW_TRUE;
+
+ if (image_type == LXW_IMAGE_JPEG)
+ self->has_jpeg = LXW_TRUE;
+
+ if (image_type == LXW_IMAGE_BMP)
+ self->has_bmp = LXW_TRUE;
+
+ if (image_type == LXW_IMAGE_GIF)
+ self->has_gif = LXW_TRUE;
+}
+
+/*
* Iterate through the worksheets and set up any chart or image drawings.
*/
STATIC void
_prepare_drawings(lxw_workbook *self)
{
lxw_sheet *sheet;
lxw_worksheet *worksheet;
- lxw_image_options *image_options;
+ lxw_object_properties *object_props;
uint32_t chart_ref_id = 0;
uint32_t image_ref_id = 0;
+ uint32_t ref_id = 0;
uint32_t drawing_id = 0;
uint8_t is_chartsheet;
+ lxw_image_md5 tmp_image_md5;
+ lxw_image_md5 *new_image_md5 = NULL;
+ lxw_image_md5 *found_duplicate_image = NULL;
+ uint8_t i;
STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
if (sheet->is_chartsheet) {
worksheet = sheet->u.chartsheet->worksheet;
is_chartsheet = LXW_TRUE;
@@ -900,47 +1061,210 @@
else {
worksheet = sheet->u.worksheet;
is_chartsheet = LXW_FALSE;
}
- if (STAILQ_EMPTY(worksheet->image_data)
- && STAILQ_EMPTY(worksheet->chart_data))
+ if (STAILQ_EMPTY(worksheet->image_props)
+ && STAILQ_EMPTY(worksheet->chart_data)
+ && !worksheet->has_header_vml && !worksheet->has_background_image) {
continue;
+ }
drawing_id++;
- STAILQ_FOREACH(image_options, worksheet->chart_data, list_pointers) {
+ /* Prepare background images. */
+ if (worksheet->has_background_image) {
+
+ object_props = worksheet->background_image;
+
+ _store_image_type(self, object_props->image_type);
+
+ /* Check for duplicate images and only store the first instance. */
+ if (object_props->md5) {
+ tmp_image_md5.md5 = object_props->md5;
+ found_duplicate_image = RB_FIND(lxw_image_md5s,
+ self->background_md5s,
+ &tmp_image_md5);
+ }
+
+ if (found_duplicate_image) {
+ ref_id = found_duplicate_image->id;
+ object_props->is_duplicate = LXW_TRUE;
+ }
+ else {
+ image_ref_id++;
+ ref_id = image_ref_id;
+
+#ifndef USE_NO_MD5
+ new_image_md5 = calloc(1, sizeof(lxw_image_md5));
+#endif
+ if (new_image_md5 && object_props->md5) {
+ new_image_md5->id = ref_id;
+ new_image_md5->md5 = lxw_strdup(object_props->md5);
+
+ RB_INSERT(lxw_image_md5s, self->background_md5s,
+ new_image_md5);
+ }
+ }
+
+ lxw_worksheet_prepare_background(worksheet, ref_id, object_props);
+ }
+
+ /* Prepare worksheet images. */
+ STAILQ_FOREACH(object_props, worksheet->image_props, list_pointers) {
+
+ /* Ignore background image added above. */
+ if (object_props->is_background)
+ continue;
+
+ _store_image_type(self, object_props->image_type);
+
+ /* Check for duplicate images and only store the first instance. */
+ if (object_props->md5) {
+ tmp_image_md5.md5 = object_props->md5;
+ found_duplicate_image = RB_FIND(lxw_image_md5s,
+ self->image_md5s,
+ &tmp_image_md5);
+ }
+
+ if (found_duplicate_image) {
+ ref_id = found_duplicate_image->id;
+ object_props->is_duplicate = LXW_TRUE;
+ }
+ else {
+ image_ref_id++;
+ ref_id = image_ref_id;
+
+#ifndef USE_NO_MD5
+ new_image_md5 = calloc(1, sizeof(lxw_image_md5));
+#endif
+ if (new_image_md5 && object_props->md5) {
+ new_image_md5->id = ref_id;
+ new_image_md5->md5 = lxw_strdup(object_props->md5);
+
+ RB_INSERT(lxw_image_md5s, self->image_md5s,
+ new_image_md5);
+ }
+ }
+
+ lxw_worksheet_prepare_image(worksheet, ref_id, drawing_id,
+ object_props);
+ }
+
+ /* Prepare worksheet charts. */
+ STAILQ_FOREACH(object_props, worksheet->chart_data, list_pointers) {
chart_ref_id++;
lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id,
- image_options, is_chartsheet);
- if (image_options->chart)
- STAILQ_INSERT_TAIL(self->ordered_charts, image_options->chart,
+ object_props, is_chartsheet);
+ if (object_props->chart)
+ STAILQ_INSERT_TAIL(self->ordered_charts, object_props->chart,
ordered_list_pointers);
}
- STAILQ_FOREACH(image_options, worksheet->image_data, list_pointers) {
+ /* Prepare worksheet header/footer images. */
+ for (i = 0; i < LXW_HEADER_FOOTER_OBJS_MAX; i++) {
- if (image_options->image_type == LXW_IMAGE_PNG)
- self->has_png = LXW_TRUE;
+ object_props = *worksheet->header_footer_objs[i];
+ if (!object_props)
+ continue;
- if (image_options->image_type == LXW_IMAGE_JPEG)
- self->has_jpeg = LXW_TRUE;
+ _store_image_type(self, object_props->image_type);
- if (image_options->image_type == LXW_IMAGE_BMP)
- self->has_bmp = LXW_TRUE;
+ /* Check for duplicate images and only store the first instance. */
+ if (object_props->md5) {
+ tmp_image_md5.md5 = object_props->md5;
+ found_duplicate_image = RB_FIND(lxw_image_md5s,
+ self->header_image_md5s,
+ &tmp_image_md5);
+ }
- image_ref_id++;
+ if (found_duplicate_image) {
+ ref_id = found_duplicate_image->id;
+ object_props->is_duplicate = LXW_TRUE;
+ }
+ else {
+ image_ref_id++;
+ ref_id = image_ref_id;
- lxw_worksheet_prepare_image(worksheet, image_ref_id, drawing_id,
- image_options);
+#ifndef USE_NO_MD5
+ new_image_md5 = calloc(1, sizeof(lxw_image_md5));
+#endif
+ if (new_image_md5 && object_props->md5) {
+ new_image_md5->id = ref_id;
+ new_image_md5->md5 = lxw_strdup(object_props->md5);
+
+ RB_INSERT(lxw_image_md5s, self->header_image_md5s,
+ new_image_md5);
+ }
+ }
+
+ lxw_worksheet_prepare_header_image(worksheet, ref_id,
+ object_props);
}
+
}
self->drawing_count = drawing_id;
}
/*
+ * Iterate through the worksheets and set up the VML objects.
+ */
+STATIC void
+_prepare_vml(lxw_workbook *self)
+{
+ lxw_worksheet *worksheet;
+ lxw_sheet *sheet;
+ uint32_t comment_id = 0;
+ uint32_t vml_drawing_id = 0;
+ uint32_t vml_data_id = 1;
+ uint32_t vml_header_id = 0;
+ uint32_t vml_shape_id = 1024;
+ uint32_t comment_count = 0;
+
+ STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
+ if (sheet->is_chartsheet)
+ continue;
+ else
+ worksheet = sheet->u.worksheet;
+
+ if (!worksheet->has_vml && !worksheet->has_header_vml)
+ continue;
+
+ if (worksheet->has_vml) {
+ self->has_vml = LXW_TRUE;
+ if (worksheet->has_comments) {
+ self->comment_count++;
+ comment_id++;
+ self->has_comments = LXW_TRUE;
+ }
+
+ vml_drawing_id++;
+
+ comment_count = lxw_worksheet_prepare_vml_objects(worksheet,
+ vml_data_id,
+ vml_shape_id,
+ vml_drawing_id,
+ comment_id);
+
+ /* Each VML should start with a shape id incremented by 1024. */
+ vml_data_id += 1 * ((1024 + comment_count) / 1024);
+ vml_shape_id += 1024 * ((1024 + comment_count) / 1024);
+ }
+
+ if (worksheet->has_header_vml) {
+ self->has_vml = LXW_TRUE;
+ vml_drawing_id++;
+ vml_header_id++;
+ lxw_worksheet_prepare_header_vml_objects(worksheet,
+ vml_header_id,
+ vml_drawing_id);
+ }
+ }
+}
+
+/*
* Iterate through the worksheets and store any defined names used for print
* ranges or repeat rows/columns.
*/
STATIC void
_prepare_defined_names(lxw_workbook *self)
@@ -1083,10 +1407,38 @@
}
}
}
}
+/*
+ * Iterate through the worksheets and set up the table objects.
+ */
+STATIC void
+_prepare_tables(lxw_workbook *self)
+{
+ lxw_worksheet *worksheet;
+ lxw_sheet *sheet;
+ uint32_t table_id = 0;
+ uint32_t table_count = 0;
+
+ STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
+ if (sheet->is_chartsheet)
+ continue;
+ else
+ worksheet = sheet->u.worksheet;
+
+ table_count = worksheet->table_count;
+
+ if (table_count == 0)
+ continue;
+
+ lxw_worksheet_prepare_tables(worksheet, table_id + 1);
+
+ table_id += table_count;
+ }
+}
+
/*****************************************************************************
*
* XML functions.
*
****************************************************************************/
@@ -1145,10 +1497,30 @@
LXW_FREE_ATTRIBUTES();
}
/*
+ * Write the <fileSharing> element.
+ */
+STATIC void
+_workbook_write_file_sharing(lxw_workbook *self)
+{
+ struct xml_attribute_list attributes;
+ struct xml_attribute *attribute;
+
+ if (self->read_only == 0)
+ return;
+
+ LXW_INIT_ATTRIBUTES();
+ LXW_PUSH_ATTRIBUTES_STR("readOnlyRecommended", "1");
+
+ lxw_xml_empty_tag(self->file, "fileSharing", &attributes);
+
+ LXW_FREE_ATTRIBUTES();
+}
+
+/*
* Write the <workbookPr> element.
*/
STATIC void
_write_workbook_pr(lxw_workbook *self)
{
@@ -1342,10 +1714,13 @@
_write_workbook(self);
/* Write the XLSX file version. */
_write_file_version(self);
+ /* Write the fileSharing element. */
+ _workbook_write_file_sharing(self);
+
/* Write the workbook properties. */
_write_workbook_pr(self);
/* Write the workbook view properties. */
_write_book_views(self);
@@ -1430,10 +1805,25 @@
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 image MD5 tree. */
+ workbook->image_md5s = calloc(1, sizeof(struct lxw_image_md5s));
+ GOTO_LABEL_ON_MEM_ERROR(workbook->image_md5s, mem_error);
+ RB_INIT(workbook->image_md5s);
+
+ /* Add the header image MD5 tree. */
+ workbook->header_image_md5s = calloc(1, sizeof(struct lxw_image_md5s));
+ GOTO_LABEL_ON_MEM_ERROR(workbook->header_image_md5s, mem_error);
+ RB_INIT(workbook->header_image_md5s);
+
+ /* Add the background image MD5 tree. */
+ workbook->background_md5s = calloc(1, sizeof(struct lxw_image_md5s));
+ GOTO_LABEL_ON_MEM_ERROR(workbook->background_md5s, mem_error);
+ RB_INIT(workbook->background_md5s);
+
/* Add the charts list. */
workbook->charts = calloc(1, sizeof(struct lxw_charts));
GOTO_LABEL_ON_MEM_ERROR(workbook->charts, mem_error);
STAILQ_INIT(workbook->charts);
@@ -1462,10 +1852,14 @@
/* Add a hash table to track format indices. */
workbook->used_xf_formats = lxw_hash_new(128, 1, 0);
GOTO_LABEL_ON_MEM_ERROR(workbook->used_xf_formats, mem_error);
+ /* Add a hash table to track format indices. */
+ workbook->used_dxf_formats = lxw_hash_new(128, 1, 0);
+ GOTO_LABEL_ON_MEM_ERROR(workbook->used_dxf_formats, mem_error);
+
/* Add the worksheets list. */
workbook->custom_properties =
calloc(1, sizeof(struct lxw_custom_properties));
GOTO_LABEL_ON_MEM_ERROR(workbook->custom_properties, mem_error);
STAILQ_INIT(workbook->custom_properties);
@@ -1475,16 +1869,26 @@
GOTO_LABEL_ON_MEM_ERROR(format, mem_error);
/* Initialize its index. */
lxw_format_get_xf_index(format);
+ /* Add the default hyperlink format. */
+ format = workbook_add_format(workbook);
+ GOTO_LABEL_ON_MEM_ERROR(format, mem_error);
+ format_set_hyperlink(format);
+ workbook->default_url_format = format;
+
if (options) {
workbook->options.constant_memory = options->constant_memory;
workbook->options.tmpdir = lxw_strdup(options->tmpdir);
workbook->options.use_zip64 = options->use_zip64;
+ workbook->options.output_buffer = options->output_buffer;
+ workbook->options.output_buffer_size = options->output_buffer_size;
}
+ workbook->max_url_length = 2079;
+
return workbook;
mem_error:
lxw_workbook_free(workbook);
workbook = NULL;
@@ -1499,17 +1903,17 @@
{
lxw_sheet *sheet = NULL;
lxw_worksheet *worksheet = NULL;
lxw_worksheet_name *worksheet_name = NULL;
lxw_error error;
- lxw_worksheet_init_data init_data = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+ lxw_worksheet_init_data init_data = { 0, 0, 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);
+ init_data.quoted_name = lxw_quote_sheetname(sheetname);
}
else {
/* Use the default SheetN name. */
new_name = malloc(LXW_MAX_SHEETNAME_LENGTH);
GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error);
@@ -1538,10 +1942,12 @@
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;
+ init_data.default_url_format = self->default_url_format;
+ init_data.max_url_length = self->max_url_length;
/* Create a new worksheet object. */
worksheet = lxw_worksheet_new(&init_data);
GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error);
@@ -1564,12 +1970,12 @@
RB_INSERT(lxw_worksheet_names, self->worksheet_names, worksheet_name);
return worksheet;
mem_error:
- free(init_data.name);
- free(init_data.quoted_name);
+ free((void *) init_data.name);
+ free((void *) init_data.quoted_name);
free(worksheet_name);
free(worksheet);
return NULL;
}
@@ -1581,17 +1987,17 @@
{
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 };
+ lxw_worksheet_init_data init_data = { 0, 0, 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);
+ init_data.quoted_name = lxw_quote_sheetname(sheetname);
}
else {
/* Use the default SheetN name. */
new_name = malloc(LXW_MAX_SHEETNAME_LENGTH);
GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error);
@@ -1648,12 +2054,12 @@
RB_INSERT(lxw_chartsheet_names, self->chartsheet_names, chartsheet_name);
return chartsheet;
mem_error:
- free(init_data.name);
- free(init_data.quoted_name);
+ free((void *) init_data.name);
+ free((void *) init_data.quoted_name);
free(chartsheet_name);
free(chartsheet);
return NULL;
}
@@ -1692,10 +2098,11 @@
format->xf_index = LXW_PROPERTY_UNSET;
format->dxf_index = LXW_PROPERTY_UNSET;
}
format->xf_format_indices = self->used_xf_formats;
+ format->dxf_format_indices = self->used_dxf_formats;
format->num_xf_formats = &self->num_xf_formats;
STAILQ_INSERT_TAIL(self->formats, format, list_pointers);
return format;
@@ -1719,34 +2126,38 @@
{
lxw_sheet *sheet = NULL;
lxw_worksheet *worksheet = NULL;
lxw_packager *packager = NULL;
lxw_error error = LXW_NO_ERROR;
+ char codename[LXW_MAX_SHEETNAME_LENGTH] = { 0 };
/* Add a default worksheet if non have been added. */
if (!self->num_sheets)
workbook_add_worksheet(self, NULL);
/* Ensure that at least one worksheet has been selected. */
if (self->active_sheet == 0) {
sheet = STAILQ_FIRST(self->sheets);
if (!sheet->is_chartsheet) {
worksheet = sheet->u.worksheet;
- worksheet->selected = 1;
+ worksheet->selected = LXW_TRUE;
worksheet->hidden = 0;
}
}
- /* Set the active sheet. */
+ /* Set the active sheet and check if a metadata file is needed. */
STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
if (sheet->is_chartsheet)
continue;
else
worksheet = sheet->u.worksheet;
if (worksheet->index == self->active_sheet)
- worksheet->active = 1;
+ worksheet->active = LXW_TRUE;
+
+ if (worksheet->has_dynamic_arrays)
+ self->has_metadata = LXW_TRUE;
}
/* Set workbook and worksheet VBA codenames if a macro has been added. */
if (self->vba_project) {
if (!self->vba_codename)
@@ -1756,34 +2167,44 @@
if (sheet->is_chartsheet)
continue;
else
worksheet = sheet->u.worksheet;
- if (!worksheet->vba_codename)
- worksheet_set_vba_name(worksheet, worksheet->name);
+ if (!worksheet->vba_codename) {
+ lxw_snprintf(codename, LXW_MAX_SHEETNAME_LENGTH, "Sheet%d",
+ worksheet->index + 1);
+
+ worksheet_set_vba_name(worksheet, codename);
+ }
}
}
+ /* Prepare the worksheet VML elements such as comments. */
+ _prepare_vml(self);
+
/* Set the defined names for the worksheets such as Print Titles. */
_prepare_defined_names(self);
/* Prepare the drawings, charts and images. */
_prepare_drawings(self);
/* Add cached data to charts. */
_add_chart_cache_data(self);
+ /* Set the table ids for the worksheet tables. */
+ _prepare_tables(self);
+
/* Create a packager object to assemble sub-elements into a zip file. */
packager = lxw_packager_new(self->filename,
self->options.tmpdir,
self->options.use_zip64);
/* If the packager fails it is generally due to a zip permission error. */
if (packager == NULL) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Error creating '%s'. "
- "System error = %s\n", self->filename, strerror(errno));
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Error creating '%s'. "
+ "System error = %s\n", self->filename, strerror(errno));
error = LXW_ERROR_CREATING_XLSX_FILE;
goto mem_error;
}
@@ -1791,55 +2212,61 @@
packager->workbook = self;
/* Assemble all the sub-files in the xlsx package. */
error = lxw_create_package(packager);
+ if (!self->filename) {
+ *self->options.output_buffer = packager->output_buffer;
+ *self->options.output_buffer_size = packager->output_buffer_size;
+ }
+
/* Error and non-error conditions fall through to the cleanup code. */
if (error == LXW_ERROR_CREATING_TMPFILE) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Error creating tmpfile(s) to assemble '%s'. "
- "System error = %s\n", self->filename, strerror(errno));
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Error creating tmpfile(s) to assemble '%s'. "
+ "System error = %s\n", self->filename, strerror(errno));
}
/* If LXW_ERROR_ZIP_FILE_OPERATION then errno is set by zip. */
if (error == LXW_ERROR_ZIP_FILE_OPERATION) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Zip ZIP_ERRNO error while creating xlsx file '%s'. "
- "System error = %s\n", self->filename, strerror(errno));
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Zip ZIP_ERRNO error while creating xlsx file '%s'. "
+ "System error = %s\n", self->filename, strerror(errno));
}
/* If LXW_ERROR_ZIP_PARAMETER_ERROR then errno is set by zip. */
if (error == LXW_ERROR_ZIP_PARAMETER_ERROR) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Zip ZIP_PARAMERROR error while creating xlsx file '%s'. "
- "System error = %s\n", self->filename, strerror(errno));
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Zip ZIP_PARAMERROR error while creating xlsx file '%s'. "
+ "System error = %s\n", self->filename, strerror(errno));
}
/* If LXW_ERROR_ZIP_BAD_ZIP_FILE then errno is set by zip. */
if (error == LXW_ERROR_ZIP_BAD_ZIP_FILE) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Zip ZIP_BADZIPFILE error while creating xlsx file '%s'. "
- "This may require the use_zip64 option for large files. "
- "System error = %s\n", self->filename, strerror(errno));
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Zip ZIP_BADZIPFILE error while creating xlsx file '%s'. "
+ "This may require the use_zip64 option for large files. "
+ "System error = %s\n", self->filename, strerror(errno));
}
/* If LXW_ERROR_ZIP_INTERNAL_ERROR then errno is set by zip. */
if (error == LXW_ERROR_ZIP_INTERNAL_ERROR) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Zip ZIP_INTERNALERROR error while creating xlsx file '%s'. "
- "System error = %s\n", self->filename, strerror(errno));
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Zip ZIP_INTERNALERROR error while creating xlsx file '%s'. "
+ "System error = %s\n", self->filename, strerror(errno));
}
/* The next 2 error conditions don't set errno. */
if (error == LXW_ERROR_ZIP_FILE_ADD) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Zip error adding file to xlsx file '%s'.\n", self->filename);
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Zip error adding file to xlsx file '%s'.\n",
+ self->filename);
}
if (error == LXW_ERROR_ZIP_CLOSE) {
- fprintf(stderr, "[ERROR] workbook_close(): "
- "Zip error closing xlsx file '%s'.\n", self->filename);
+ LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
+ "Zip error closing xlsx file '%s'.\n", self->filename);
}
mem_error:
lxw_packager_free(packager);
lxw_workbook_free(self);
@@ -1920,10 +2347,12 @@
if (user_props->hyperlink_base) {
doc_props->hyperlink_base = lxw_strdup(user_props->hyperlink_base);
GOTO_LABEL_ON_MEM_ERROR(doc_props->hyperlink_base, mem_error);
}
+ doc_props->created = user_props->created;
+
self->properties = doc_props;
return LXW_NO_ERROR;
mem_error:
@@ -2168,10 +2597,31 @@
else
return NULL;
}
/*
+ * Get the default URL format.
+ */
+lxw_format *
+workbook_get_default_url_format(lxw_workbook *self)
+{
+ return self->default_url_format;
+}
+
+/*
+ * Unset the default URL format.
+ */
+void
+workbook_unset_default_url_format(lxw_workbook *self)
+{
+ self->default_url_format->hyperlink = LXW_FALSE;
+ self->default_url_format->xf_id = 0;
+ self->default_url_format->underline = LXW_UNDERLINE_NONE;
+ self->default_url_format->theme = 0;
+}
+
+/*
* Validate the worksheet name based on Excel's rules.
*/
lxw_error
workbook_validate_sheet_name(lxw_workbook *self, const char *sheetname)
{
@@ -2185,14 +2635,10 @@
/* Check that the worksheet doesn't start or end with an apostrophe. */
if (sheetname[0] == '\'' || sheetname[strlen(sheetname) - 1] == '\'')
return LXW_ERROR_SHEETNAME_START_END_APOSTROPHE;
- /* Check that the worksheet name isn't the reserved work "History". */
- if (lxw_strcasecmp(sheetname, "history") == 0)
- return LXW_ERROR_SHEETNAME_RESERVED;
-
/* Check if the worksheet name is already in use. */
if (workbook_get_worksheet_by_name(self, sheetname))
return LXW_ERROR_SHEETNAME_ALREADY_USED;
/* Check if the chartsheet name is already in use. */
@@ -2210,19 +2656,19 @@
{
FILE *filehandle;
if (!filename) {
LXW_WARN("workbook_add_vba_project(): "
- "filename must be specified.");
+ "project filename must be specified.");
return LXW_ERROR_NULL_PARAMETER_IGNORED;
}
/* Check that the vbaProject file exists and can be opened. */
- filehandle = fopen(filename, "rb");
+ filehandle = lxw_fopen(filename, "rb");
if (!filehandle) {
LXW_WARN_FORMAT1("workbook_add_vba_project(): "
- "file doesn't exist or can't be opened: %s.",
+ "project file doesn't exist or can't be opened: %s.",
filename);
return LXW_ERROR_PARAMETER_VALIDATION;
}
fclose(filehandle);
@@ -2230,10 +2676,45 @@
return LXW_NO_ERROR;
}
/*
+ * Add a vbaProject binary and a vbaProjectSignature binary to the Excel workbook.
+ */
+lxw_error
+workbook_add_signed_vba_project(lxw_workbook *self,
+ const char *vba_project,
+ const char *signature)
+{
+ FILE *filehandle;
+
+ lxw_error error = workbook_add_vba_project(self, vba_project);
+ if (error != LXW_NO_ERROR)
+ return error;
+
+ if (!signature) {
+ LXW_WARN("workbook_add_signed_vba_project(): "
+ "signature filename must be specified.");
+ return LXW_ERROR_NULL_PARAMETER_IGNORED;
+ }
+
+ /* Check that the vbaProjectSignature file exists and can be opened. */
+ filehandle = lxw_fopen(signature, "rb");
+ if (!filehandle) {
+ LXW_WARN_FORMAT1("workbook_add_signed_vba_project(): "
+ "signature file doesn't exist or can't be opened: %s.",
+ signature);
+ return LXW_ERROR_PARAMETER_VALIDATION;
+ }
+ fclose(filehandle);
+
+ self->vba_project_signature = lxw_strdup(signature);
+
+ return LXW_NO_ERROR;
+}
+
+/*
* Set the VBA name for the workbook.
*/
lxw_error
workbook_set_vba_name(lxw_workbook *self, const char *name)
{
@@ -2243,6 +2724,15 @@
}
self->vba_codename = lxw_strdup(name);
return LXW_NO_ERROR;
+}
+
+/*
+ * Set the Excel "Read-only recommended" save option.
+ */
+void
+workbook_read_only_recommended(lxw_workbook *self)
+{
+ self->read_only = 2;
}