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