libxlsxwriter/src/worksheet.c in fast_excel-0.4.1 vs libxlsxwriter/src/worksheet.c in fast_excel-0.5.0

- old
+ new

@@ -1,38 +1,59 @@ /***************************************************************************** * worksheet - A library for creating Excel XLSX worksheet 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 <ctype.h> +#ifdef USE_FMEMOPEN +#define _POSIX_C_SOURCE 200809L +#endif #include "xlsxwriter/xmlwriter.h" #include "xlsxwriter/worksheet.h" #include "xlsxwriter/format.h" #include "xlsxwriter/utility.h" -#include "xlsxwriter/relationships.h" +#ifdef USE_OPENSSL_MD5 +#include <openssl/md5.h> +#else +#ifndef USE_NO_MD5 +#include "xlsxwriter/third_party/md5.h" +#endif +#endif + #define LXW_STR_MAX 32767 #define LXW_BUFFER_SIZE 4096 #define LXW_PRINT_ACROSS 1 #define LXW_VALIDATION_MAX_TITLE_LENGTH 32 #define LXW_VALIDATION_MAX_STRING_LENGTH 255 - +#define LXW_THIS_ROW "[#This Row]," /* * Forward declarations. */ STATIC void _worksheet_write_rows(lxw_worksheet *self); STATIC int _row_cmp(lxw_row *row1, lxw_row *row2); STATIC int _cell_cmp(lxw_cell *cell1, lxw_cell *cell2); +STATIC int _drawing_rel_id_cmp(lxw_drawing_rel_id *tuple1, + lxw_drawing_rel_id *tuple2); +STATIC int _cond_format_hash_cmp(lxw_cond_format_hash_element *elem_1, + lxw_cond_format_hash_element *elem_2); #ifndef __clang_analyzer__ LXW_RB_GENERATE_ROW(lxw_table_rows, lxw_row, tree_pointers, _row_cmp); LXW_RB_GENERATE_CELL(lxw_table_cells, lxw_cell, tree_pointers, _cell_cmp); +LXW_RB_GENERATE_DRAWING_REL_IDS(lxw_drawing_rel_ids, lxw_drawing_rel_id, + tree_pointers, _drawing_rel_id_cmp); +LXW_RB_GENERATE_VML_DRAWING_REL_IDS(lxw_vml_drawing_rel_ids, + lxw_drawing_rel_id, tree_pointers, + _drawing_rel_id_cmp); +LXW_RB_GENERATE_COND_FORMAT_HASH(lxw_cond_format_hash, + lxw_cond_format_hash_element, tree_pointers, + _cond_format_hash_cmp); #endif /***************************************************************************** * * Private functions. @@ -43,31 +64,31 @@ * Find but don't create a row object for a given row number. */ lxw_row * lxw_worksheet_find_row(lxw_worksheet *self, lxw_row_t row_num) { - lxw_row row; + lxw_row tmp_row; - row.row_num = row_num; + tmp_row.row_num = row_num; - return RB_FIND(lxw_table_rows, self->table, &row); + return RB_FIND(lxw_table_rows, self->table, &tmp_row); } /* * Find but don't create a cell object for a given row object and col number. */ lxw_cell * -lxw_worksheet_find_cell(lxw_row *row, lxw_col_t col_num) +lxw_worksheet_find_cell_in_row(lxw_row *row, lxw_col_t col_num) { - lxw_cell cell; + lxw_cell tmp_cell; if (!row) return NULL; - cell.col_num = col_num; + tmp_cell.col_num = col_num; - return RB_FIND(lxw_table_cells, row->cells, &cell); + return RB_FIND(lxw_table_cells, row->cells, &tmp_cell); } /* * Create a new worksheet object. */ @@ -83,13 +104,18 @@ worksheet->hyperlinks = calloc(1, sizeof(struct lxw_table_rows)); GOTO_LABEL_ON_MEM_ERROR(worksheet->hyperlinks, mem_error); RB_INIT(worksheet->hyperlinks); + worksheet->comments = calloc(1, sizeof(struct lxw_table_rows)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->comments, mem_error); + RB_INIT(worksheet->comments); + /* Initialize the cached rows. */ worksheet->table->cached_row_num = LXW_ROW_MAX + 1; worksheet->hyperlinks->cached_row_num = LXW_ROW_MAX + 1; + worksheet->comments->cached_row_num = LXW_ROW_MAX + 1; if (init_data && init_data->optimize) { worksheet->array = calloc(LXW_COL_MAX, sizeof(struct lxw_cell *)); GOTO_LABEL_ON_MEM_ERROR(worksheet->array, mem_error); } @@ -109,27 +135,43 @@ worksheet->merged_ranges = calloc(1, sizeof(struct lxw_merged_ranges)); GOTO_LABEL_ON_MEM_ERROR(worksheet->merged_ranges, mem_error); STAILQ_INIT(worksheet->merged_ranges); - worksheet->image_data = calloc(1, sizeof(struct lxw_image_data)); - GOTO_LABEL_ON_MEM_ERROR(worksheet->image_data, mem_error); - STAILQ_INIT(worksheet->image_data); + worksheet->image_props = calloc(1, sizeof(struct lxw_image_props)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->image_props, mem_error); + STAILQ_INIT(worksheet->image_props); - worksheet->chart_data = calloc(1, sizeof(struct lxw_chart_data)); + worksheet->chart_data = calloc(1, sizeof(struct lxw_chart_props)); GOTO_LABEL_ON_MEM_ERROR(worksheet->chart_data, mem_error); STAILQ_INIT(worksheet->chart_data); + worksheet->comment_objs = calloc(1, sizeof(struct lxw_comment_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->comment_objs, mem_error); + STAILQ_INIT(worksheet->comment_objs); + + worksheet->header_image_objs = calloc(1, sizeof(struct lxw_comment_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->header_image_objs, mem_error); + STAILQ_INIT(worksheet->header_image_objs); + + worksheet->button_objs = calloc(1, sizeof(struct lxw_comment_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->button_objs, mem_error); + STAILQ_INIT(worksheet->button_objs); + worksheet->selections = calloc(1, sizeof(struct lxw_selections)); GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error); STAILQ_INIT(worksheet->selections); worksheet->data_validations = calloc(1, sizeof(struct lxw_data_validations)); GOTO_LABEL_ON_MEM_ERROR(worksheet->data_validations, mem_error); STAILQ_INIT(worksheet->data_validations); + worksheet->table_objs = calloc(1, sizeof(struct lxw_table_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->table_objs, mem_error); + STAILQ_INIT(worksheet->table_objs); + worksheet->external_hyperlinks = calloc(1, sizeof(struct lxw_rel_tuples)); GOTO_LABEL_ON_MEM_ERROR(worksheet->external_hyperlinks, mem_error); STAILQ_INIT(worksheet->external_hyperlinks); worksheet->external_drawing_links = @@ -139,14 +181,27 @@ worksheet->drawing_links = calloc(1, sizeof(struct lxw_rel_tuples)); GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_links, mem_error); STAILQ_INIT(worksheet->drawing_links); + worksheet->vml_drawing_links = calloc(1, sizeof(struct lxw_rel_tuples)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->vml_drawing_links, mem_error); + STAILQ_INIT(worksheet->vml_drawing_links); + + worksheet->external_table_links = + calloc(1, sizeof(struct lxw_rel_tuples)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->external_table_links, mem_error); + STAILQ_INIT(worksheet->external_table_links); + if (init_data && init_data->optimize) { FILE *tmpfile; - tmpfile = lxw_tmpfile(init_data->tmpdir); + worksheet->optimize_buffer = NULL; + worksheet->optimize_buffer_size = 0; + tmpfile = lxw_get_filehandle(&worksheet->optimize_buffer, + &worksheet->optimize_buffer_size, + init_data->tmpdir); if (!tmpfile) { LXW_ERROR("Error creating tmpfile() for worksheet in " "'constant_memory' mode."); goto mem_error; } @@ -154,10 +209,25 @@ worksheet->optimize_tmpfile = tmpfile; GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_tmpfile, mem_error); worksheet->file = worksheet->optimize_tmpfile; } + worksheet->drawing_rel_ids = + calloc(1, sizeof(struct lxw_drawing_rel_ids)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_rel_ids, mem_error); + RB_INIT(worksheet->drawing_rel_ids); + + worksheet->vml_drawing_rel_ids = + calloc(1, sizeof(struct lxw_vml_drawing_rel_ids)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->vml_drawing_rel_ids, mem_error); + RB_INIT(worksheet->vml_drawing_rel_ids); + + worksheet->conditional_formats = + calloc(1, sizeof(struct lxw_cond_format_hash)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->conditional_formats, mem_error); + RB_INIT(worksheet->conditional_formats); + /* Initialize the worksheet dimensions. */ worksheet->dim_rowmax = 0; worksheet->dim_colmax = 0; worksheet->dim_rowmin = LXW_ROW_MAX; worksheet->dim_colmin = LXW_COL_MAX; @@ -194,31 +264,102 @@ worksheet->outline_on = LXW_TRUE; worksheet->outline_style = LXW_TRUE; worksheet->outline_below = LXW_TRUE; worksheet->outline_right = LXW_FALSE; worksheet->tab_color = LXW_COLOR_UNSET; + worksheet->max_url_length = 2079; + worksheet->comment_display_default = LXW_COMMENT_DISPLAY_HIDDEN; + worksheet->header_footer_objs[0] = &worksheet->header_left_object_props; + worksheet->header_footer_objs[1] = &worksheet->header_center_object_props; + worksheet->header_footer_objs[2] = &worksheet->header_right_object_props; + worksheet->header_footer_objs[3] = &worksheet->footer_left_object_props; + worksheet->header_footer_objs[4] = &worksheet->footer_center_object_props; + worksheet->header_footer_objs[5] = &worksheet->footer_right_object_props; + if (init_data) { worksheet->name = init_data->name; worksheet->quoted_name = init_data->quoted_name; worksheet->tmpdir = init_data->tmpdir; worksheet->index = init_data->index; worksheet->hidden = init_data->hidden; worksheet->sst = init_data->sst; worksheet->optimize = init_data->optimize; worksheet->active_sheet = init_data->active_sheet; worksheet->first_sheet = init_data->first_sheet; + worksheet->default_url_format = init_data->default_url_format; + worksheet->max_url_length = init_data->max_url_length; } return worksheet; mem_error: lxw_worksheet_free(worksheet); return NULL; } /* + * Free vml object. + */ +STATIC void +_free_vml_object(lxw_vml_obj *vml_obj) +{ + if (!vml_obj) + return; + + free(vml_obj->author); + free(vml_obj->font_name); + free(vml_obj->text); + free(vml_obj->image_position); + free(vml_obj->name); + free(vml_obj->macro); + + free(vml_obj); +} + +/* + * Free autofilter rule object. + */ +STATIC void +_free_filter_rule(lxw_filter_rule_obj *rule_obj) +{ + uint16_t i; + + if (!rule_obj) + return; + + free(rule_obj->value1_string); + free(rule_obj->value2_string); + + if (rule_obj->list) { + for (i = 0; i < rule_obj->num_list_filters; i++) + free(rule_obj->list[i]); + + free(rule_obj->list); + } + + free(rule_obj); +} + +/* + * Free autofilter rules. + */ +STATIC void +_free_filter_rules(lxw_worksheet *worksheet) +{ + uint16_t i; + + if (!worksheet->filter_rules) + return; + + for (i = 0; i < worksheet->num_filter_rules; i++) + _free_filter_rule(worksheet->filter_rules[i]); + + free(worksheet->filter_rules); +} + +/* * Free a worksheet cell. */ STATIC void _free_cell(lxw_cell *cell) { @@ -226,16 +367,18 @@ return; if (cell->type != NUMBER_CELL && cell->type != STRING_CELL && cell->type != BLANK_CELL && cell->type != BOOLEAN_CELL) { - free(cell->u.string); + free((void *) cell->u.string); } free(cell->user_data1); free(cell->user_data2); + _free_vml_object(cell->comment); + free(cell); } /* * Free a worksheet row. @@ -261,29 +404,32 @@ /* * Free a worksheet image_options. */ STATIC void -_free_image_options(lxw_image_options *image) +_free_object_properties(lxw_object_properties *object_property) { - if (!image) + if (!object_property) return; - free(image->filename); - free(image->description); - free(image->extension); - free(image->url); - free(image->tip); - free(image->image_buffer); - free(image); + free(object_property->filename); + free(object_property->description); + free(object_property->extension); + free(object_property->url); + free(object_property->tip); + free(object_property->image_buffer); + free(object_property->md5); + free(object_property->image_position); + free(object_property); + object_property = NULL; } /* * Free a worksheet data_validation. */ STATIC void -_free_data_validation(lxw_data_validation *data_validation) +_free_data_validation(lxw_data_val_obj *data_validation) { if (!data_validation) return; free(data_validation->value_formula); @@ -296,23 +442,101 @@ free(data_validation); } /* + * Free a worksheet conditional format obj. + */ +STATIC void +_free_cond_format(lxw_cond_format_obj *cond_format) +{ + if (!cond_format) + return; + + free(cond_format->min_value_string); + free(cond_format->mid_value_string); + free(cond_format->max_value_string); + free(cond_format->type_string); + free(cond_format->guid); + + free(cond_format); +} + +/* + * Free a relationship structure. + */ +STATIC void +_free_relationship(lxw_rel_tuple *relationship) +{ + if (!relationship) + return; + + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + + free(relationship); +} + +/* + * Free a worksheet table column object. + */ +STATIC void +_free_worksheet_table_column(lxw_table_column *column) +{ + if (!column) + return; + + free((void *) column->header); + free((void *) column->formula); + free((void *) column->total_string); + + free(column); +} + +/* + * Free a worksheet table object. + */ +STATIC void +_free_worksheet_table(lxw_table_obj *table) +{ + uint16_t i; + + if (!table) + return; + + for (i = 0; i < table->num_cols; i++) + _free_worksheet_table_column(table->columns[i]); + + free(table->name); + free(table->total_string); + free(table->columns); + + free(table); +} + +/* * Free a worksheet object. */ void lxw_worksheet_free(lxw_worksheet *worksheet) { lxw_row *row; lxw_row *next_row; lxw_col_t col; lxw_merged_range *merged_range; - lxw_image_options *image_options; + lxw_object_properties *object_props; + lxw_vml_obj *vml_obj; lxw_selection *selection; - lxw_data_validation *data_validation; + lxw_data_val_obj *data_validation; lxw_rel_tuple *relationship; + lxw_cond_format_obj *cond_format; + lxw_table_obj *table_obj; + struct lxw_drawing_rel_id *drawing_rel_id; + struct lxw_drawing_rel_id *next_drawing_rel_id; + struct lxw_cond_format_hash_element *cond_format_elem; + struct lxw_cond_format_hash_element *next_cond_format_elem; if (!worksheet) return; if (worksheet->col_options) { @@ -348,91 +572,206 @@ } free(worksheet->hyperlinks); } + if (worksheet->comments) { + for (row = RB_MIN(lxw_table_rows, worksheet->comments); row; + row = next_row) { + + next_row = RB_NEXT(lxw_table_rows, worksheet->comments, row); + RB_REMOVE(lxw_table_rows, worksheet->comments, row); + _free_row(row); + } + + free(worksheet->comments); + } + if (worksheet->merged_ranges) { while (!STAILQ_EMPTY(worksheet->merged_ranges)) { merged_range = STAILQ_FIRST(worksheet->merged_ranges); STAILQ_REMOVE_HEAD(worksheet->merged_ranges, list_pointers); free(merged_range); } free(worksheet->merged_ranges); } - if (worksheet->image_data) { - while (!STAILQ_EMPTY(worksheet->image_data)) { - image_options = STAILQ_FIRST(worksheet->image_data); - STAILQ_REMOVE_HEAD(worksheet->image_data, list_pointers); - _free_image_options(image_options); + if (worksheet->image_props) { + while (!STAILQ_EMPTY(worksheet->image_props)) { + object_props = STAILQ_FIRST(worksheet->image_props); + STAILQ_REMOVE_HEAD(worksheet->image_props, list_pointers); + _free_object_properties(object_props); } - free(worksheet->image_data); + free(worksheet->image_props); } if (worksheet->chart_data) { while (!STAILQ_EMPTY(worksheet->chart_data)) { - image_options = STAILQ_FIRST(worksheet->chart_data); + object_props = STAILQ_FIRST(worksheet->chart_data); STAILQ_REMOVE_HEAD(worksheet->chart_data, list_pointers); - _free_image_options(image_options); + _free_object_properties(object_props); } free(worksheet->chart_data); } + /* Just free the list. The list objects are freed from the RB tree. */ + free(worksheet->comment_objs); + + if (worksheet->header_image_objs) { + while (!STAILQ_EMPTY(worksheet->header_image_objs)) { + vml_obj = STAILQ_FIRST(worksheet->header_image_objs); + STAILQ_REMOVE_HEAD(worksheet->header_image_objs, list_pointers); + _free_vml_object(vml_obj); + } + + free(worksheet->header_image_objs); + } + + if (worksheet->button_objs) { + while (!STAILQ_EMPTY(worksheet->button_objs)) { + vml_obj = STAILQ_FIRST(worksheet->button_objs); + STAILQ_REMOVE_HEAD(worksheet->button_objs, list_pointers); + _free_vml_object(vml_obj); + } + + free(worksheet->button_objs); + } + if (worksheet->selections) { while (!STAILQ_EMPTY(worksheet->selections)) { selection = STAILQ_FIRST(worksheet->selections); STAILQ_REMOVE_HEAD(worksheet->selections, list_pointers); free(selection); } free(worksheet->selections); } + if (worksheet->table_objs) { + while (!STAILQ_EMPTY(worksheet->table_objs)) { + table_obj = STAILQ_FIRST(worksheet->table_objs); + STAILQ_REMOVE_HEAD(worksheet->table_objs, list_pointers); + _free_worksheet_table(table_obj); + } + + free(worksheet->table_objs); + } + if (worksheet->data_validations) { while (!STAILQ_EMPTY(worksheet->data_validations)) { data_validation = STAILQ_FIRST(worksheet->data_validations); STAILQ_REMOVE_HEAD(worksheet->data_validations, list_pointers); _free_data_validation(data_validation); } free(worksheet->data_validations); } - /* TODO. Add function for freeing the relationship lists. */ while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) { relationship = STAILQ_FIRST(worksheet->external_hyperlinks); STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers); - free(relationship->type); - free(relationship->target); - free(relationship->target_mode); - free(relationship); + _free_relationship(relationship); } free(worksheet->external_hyperlinks); while (!STAILQ_EMPTY(worksheet->external_drawing_links)) { relationship = STAILQ_FIRST(worksheet->external_drawing_links); STAILQ_REMOVE_HEAD(worksheet->external_drawing_links, list_pointers); - free(relationship->type); - free(relationship->target); - free(relationship->target_mode); - free(relationship); + _free_relationship(relationship); } free(worksheet->external_drawing_links); while (!STAILQ_EMPTY(worksheet->drawing_links)) { relationship = STAILQ_FIRST(worksheet->drawing_links); STAILQ_REMOVE_HEAD(worksheet->drawing_links, list_pointers); - free(relationship->type); - free(relationship->target); - free(relationship->target_mode); - free(relationship); + _free_relationship(relationship); } free(worksheet->drawing_links); + while (!STAILQ_EMPTY(worksheet->vml_drawing_links)) { + relationship = STAILQ_FIRST(worksheet->vml_drawing_links); + STAILQ_REMOVE_HEAD(worksheet->vml_drawing_links, list_pointers); + _free_relationship(relationship); + } + free(worksheet->vml_drawing_links); + + while (!STAILQ_EMPTY(worksheet->external_table_links)) { + relationship = STAILQ_FIRST(worksheet->external_table_links); + STAILQ_REMOVE_HEAD(worksheet->external_table_links, list_pointers); + _free_relationship(relationship); + } + free(worksheet->external_table_links); + + if (worksheet->drawing_rel_ids) { + for (drawing_rel_id = + RB_MIN(lxw_drawing_rel_ids, worksheet->drawing_rel_ids); + drawing_rel_id; drawing_rel_id = next_drawing_rel_id) { + + next_drawing_rel_id = + RB_NEXT(lxw_drawing_rel_ids, worksheet->drawing_rel_id, + drawing_rel_id); + RB_REMOVE(lxw_drawing_rel_ids, worksheet->drawing_rel_ids, + drawing_rel_id); + free(drawing_rel_id->target); + free(drawing_rel_id); + } + + free(worksheet->drawing_rel_ids); + } + + if (worksheet->vml_drawing_rel_ids) { + for (drawing_rel_id = + RB_MIN(lxw_vml_drawing_rel_ids, worksheet->vml_drawing_rel_ids); + drawing_rel_id; drawing_rel_id = next_drawing_rel_id) { + + next_drawing_rel_id = + RB_NEXT(lxw_vml_drawing_rel_ids, worksheet->drawing_rel_id, + drawing_rel_id); + RB_REMOVE(lxw_vml_drawing_rel_ids, worksheet->vml_drawing_rel_ids, + drawing_rel_id); + free(drawing_rel_id->target); + free(drawing_rel_id); + } + + free(worksheet->vml_drawing_rel_ids); + } + + if (worksheet->conditional_formats) { + for (cond_format_elem = + RB_MIN(lxw_cond_format_hash, worksheet->conditional_formats); + cond_format_elem; cond_format_elem = next_cond_format_elem) { + + next_cond_format_elem = RB_NEXT(lxw_cond_format_hash, + worksheet->conditional_formats, + cond_format_elem); + RB_REMOVE(lxw_cond_format_hash, + worksheet->conditional_formats, cond_format_elem); + + while (!STAILQ_EMPTY(cond_format_elem->cond_formats)) { + cond_format = STAILQ_FIRST(cond_format_elem->cond_formats); + STAILQ_REMOVE_HEAD(cond_format_elem->cond_formats, + list_pointers); + _free_cond_format(cond_format); + } + + free(cond_format_elem->cond_formats); + free(cond_format_elem); + } + + free(worksheet->conditional_formats); + } + + _free_relationship(worksheet->external_vml_comment_link); + _free_relationship(worksheet->external_comment_link); + _free_relationship(worksheet->external_vml_header_link); + _free_relationship(worksheet->external_background_link); + + _free_filter_rules(worksheet); + if (worksheet->array) { for (col = 0; col < LXW_COL_MAX; col++) { _free_cell(worksheet->array[col]); } free(worksheet->array); @@ -444,13 +783,27 @@ if (worksheet->drawing) lxw_drawing_free(worksheet->drawing); free(worksheet->hbreaks); free(worksheet->vbreaks); - free(worksheet->name); - free(worksheet->quoted_name); + free((void *) worksheet->name); + free((void *) worksheet->quoted_name); free(worksheet->vba_codename); + free(worksheet->vml_data_id_str); + free(worksheet->vml_header_id_str); + free(worksheet->comment_author); + free(worksheet->ignore_number_stored_as_text); + free(worksheet->ignore_eval_error); + free(worksheet->ignore_formula_differs); + free(worksheet->ignore_formula_range); + free(worksheet->ignore_formula_unlocked); + free(worksheet->ignore_empty_cell_reference); + free(worksheet->ignore_list_data_validation); + free(worksheet->ignore_calculated_column); + free(worksheet->ignore_two_digit_text_year); + free(worksheet->header); + free(worksheet->footer); free(worksheet); worksheet = NULL; } @@ -541,11 +894,11 @@ /* * Create a new worksheet inline_string cell object for rich strings. */ STATIC lxw_cell * _new_inline_rich_string_cell(lxw_row_t row_num, - lxw_col_t col_num, char *string, + lxw_col_t col_num, const char *string, lxw_format *format) { lxw_cell *cell = calloc(1, sizeof(lxw_cell)); RETURN_ON_MEM_ERROR(cell, cell); @@ -580,22 +933,26 @@ /* * Create a new worksheet array formula cell object. */ STATIC lxw_cell * _new_array_formula_cell(lxw_row_t row_num, lxw_col_t col_num, char *formula, - char *range, lxw_format *format) + char *range, lxw_format *format, uint8_t is_dynamic) { lxw_cell *cell = calloc(1, sizeof(lxw_cell)); RETURN_ON_MEM_ERROR(cell, cell); cell->row_num = row_num; cell->col_num = col_num; - cell->type = ARRAY_FORMULA_CELL; cell->format = format; cell->u.string = formula; cell->user_data1 = range; + if (is_dynamic) + cell->type = DYNAMIC_ARRAY_FORMULA_CELL; + else + cell->type = ARRAY_FORMULA_CELL; + return cell; } /* * Create a new worksheet blank cell object. @@ -632,10 +989,28 @@ return cell; } /* + * Create a new comment cell object. + */ +STATIC lxw_cell * +_new_comment_cell(lxw_row_t row_num, lxw_col_t col_num, + lxw_vml_obj *comment_obj) +{ + lxw_cell *cell = calloc(1, sizeof(lxw_cell)); + RETURN_ON_MEM_ERROR(cell, cell); + + cell->row_num = row_num; + cell->col_num = col_num; + cell->type = COMMENT; + cell->comment = comment_obj; + + return cell; +} + +/* * Create a new worksheet hyperlink cell object. */ STATIC lxw_cell * _new_hyperlink_cell(lxw_row_t row_num, lxw_col_t col_num, enum cell_types link_type, char *url, char *string, @@ -763,22 +1138,65 @@ } } } /* - * Insert a hyperlink object into the hyperlink list. + * Insert a blank placeholder cell in the cells RB tree in the same position + * as a comment so that the rows "spans" calculation is correct. Since the + * blank cell doesn't have a format it is ignored when writing. If there is + * already a cell in the required position we don't have add a new cell. */ STATIC void +_insert_cell_placeholder(lxw_worksheet *self, lxw_row_t row_num, + lxw_col_t col_num) +{ + lxw_row *row; + lxw_cell *cell; + + /* The spans calculation isn't required in constant_memory mode. */ + if (self->optimize) + return; + + cell = _new_blank_cell(row_num, col_num, NULL); + if (!cell) + return; + + /* Only add a cell if one doesn't already exist. */ + row = _get_row(self, row_num); + if (!RB_FIND(lxw_table_cells, row->cells, cell)) { + _insert_cell_list(row->cells, cell, col_num); + } + else { + _free_cell(cell); + } +} + +/* + * Insert a hyperlink object into the hyperlink RB tree. + */ +STATIC void _insert_hyperlink(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, lxw_cell *link) { lxw_row *row = _get_row_list(self->hyperlinks, row_num); _insert_cell_list(row->cells, link, col_num); } /* + * Insert a comment into the comment RB tree. + */ +STATIC void +_insert_comment(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, + lxw_cell *link) +{ + lxw_row *row = _get_row_list(self->comments, row_num); + + _insert_cell_list(row->cells, link, col_num); +} + +/* * Next power of two for column reallocs. Taken from bithacks in the public * domain. */ STATIC lxw_col_t _next_power_of_two(uint16_t col) @@ -860,48 +1278,191 @@ return -1; return 0; } /* + * Comparator for the image/hyperlink relationship ids. + */ +STATIC int +_drawing_rel_id_cmp(lxw_drawing_rel_id *rel_id1, lxw_drawing_rel_id *rel_id2) +{ + return strcmp(rel_id1->target, rel_id2->target); +} + +/* + * Comparator for the conditional format RB hash elements. + */ +STATIC int +_cond_format_hash_cmp(lxw_cond_format_hash_element *elem_1, + lxw_cond_format_hash_element *elem_2) +{ + return strcmp(elem_1->sqref, elem_2->sqref); +} + +/* + * Get the index used to address a drawing rel link. + */ +STATIC uint32_t +_get_drawing_rel_index(lxw_worksheet *self, char *target) +{ + lxw_drawing_rel_id tmp_drawing_rel_id; + lxw_drawing_rel_id *found_duplicate_target = NULL; + lxw_drawing_rel_id *new_drawing_rel_id = NULL; + + if (target) { + tmp_drawing_rel_id.target = target; + found_duplicate_target = RB_FIND(lxw_drawing_rel_ids, + self->drawing_rel_ids, + &tmp_drawing_rel_id); + } + + if (found_duplicate_target) { + return found_duplicate_target->id; + } + else { + self->drawing_rel_id++; + + if (target) { + new_drawing_rel_id = calloc(1, sizeof(lxw_drawing_rel_id)); + + if (new_drawing_rel_id) { + new_drawing_rel_id->id = self->drawing_rel_id; + new_drawing_rel_id->target = lxw_strdup(target); + + RB_INSERT(lxw_drawing_rel_ids, self->drawing_rel_ids, + new_drawing_rel_id); + } + } + + return self->drawing_rel_id; + } +} + +/* + * find the index used to address a drawing rel link. + */ +STATIC uint32_t +_find_drawing_rel_index(lxw_worksheet *self, char *target) +{ + lxw_drawing_rel_id tmp_drawing_rel_id; + lxw_drawing_rel_id *found_duplicate_target = NULL; + + if (!target) + return 0; + + tmp_drawing_rel_id.target = target; + found_duplicate_target = RB_FIND(lxw_drawing_rel_ids, + self->drawing_rel_ids, + &tmp_drawing_rel_id); + + if (found_duplicate_target) + return found_duplicate_target->id; + else + return 0; +} + +/* + * Get the index used to address a VMLdrawing rel link. + */ +STATIC uint32_t +_get_vml_drawing_rel_index(lxw_worksheet *self, char *target) +{ + lxw_drawing_rel_id tmp_drawing_rel_id; + lxw_drawing_rel_id *found_duplicate_target = NULL; + lxw_drawing_rel_id *new_drawing_rel_id = NULL; + + if (target) { + tmp_drawing_rel_id.target = target; + found_duplicate_target = RB_FIND(lxw_vml_drawing_rel_ids, + self->vml_drawing_rel_ids, + &tmp_drawing_rel_id); + } + + if (found_duplicate_target) { + return found_duplicate_target->id; + } + else { + self->vml_drawing_rel_id++; + + if (target) { + new_drawing_rel_id = calloc(1, sizeof(lxw_drawing_rel_id)); + + if (new_drawing_rel_id) { + new_drawing_rel_id->id = self->vml_drawing_rel_id; + new_drawing_rel_id->target = lxw_strdup(target); + + RB_INSERT(lxw_vml_drawing_rel_ids, self->vml_drawing_rel_ids, + new_drawing_rel_id); + } + } + + return self->vml_drawing_rel_id; + } +} + +/* + * find the index used to address a VML drawing rel link. + */ +STATIC uint32_t +_find_vml_drawing_rel_index(lxw_worksheet *self, char *target) +{ + lxw_drawing_rel_id tmp_drawing_rel_id; + lxw_drawing_rel_id *found_duplicate_target = NULL; + + if (!target) + return 0; + + tmp_drawing_rel_id.target = target; + found_duplicate_target = RB_FIND(lxw_vml_drawing_rel_ids, + self->vml_drawing_rel_ids, + &tmp_drawing_rel_id); + + if (found_duplicate_target) + return found_duplicate_target->id; + else + return 0; +} + +/* * Simple replacement for libgen.h basename() for compatibility with MSVC. It * handles forward and back slashes. It doesn't copy exactly the return * format of basename(). */ -char * +const char * lxw_basename(const char *path) { - char *forward_slash; - char *back_slash; + const char *forward_slash; + const char *back_slash; if (!path) return NULL; forward_slash = strrchr(path, '/'); back_slash = strrchr(path, '\\'); if (!forward_slash && !back_slash) - return (char *) path; + return path; if (forward_slash > back_slash) return forward_slash + 1; else return back_slash + 1; } /* Function to count the total concatenated length of the strings in a * validation list array, including commas. */ size_t -_validation_list_length(char **list) +_validation_list_length(const char **list) { uint8_t i = 0; size_t length = 0; if (!list || !list[0]) return 0; - while (list[i] && length <= LXW_VALIDATION_MAX_STRING_LENGTH) { + while (list[i] && length < LXW_VALIDATION_MAX_STRING_LENGTH) { /* Include commas in the length. */ length += 1 + lxw_utf8_strlen(list[i]); i++; } @@ -912,18 +1473,18 @@ } /* Function to convert an array of strings into a CSV string for data * validation lists. */ char * -_validation_list_to_csv(char **list) +_validation_list_to_csv(const char **list) { uint8_t i = 0; char *str; /* Create a buffer for the concatenated, and quoted, string. */ - /* Add +3 for quotes and EOL. */ - str = calloc(1, LXW_VALIDATION_MAX_STRING_LENGTH + 3); + /* Allow for 4 byte UTF-8 chars and add 3 bytes for quotes and EOL. */ + str = calloc(1, LXW_VALIDATION_MAX_STRING_LENGTH * 4 + 3); if (!str) return NULL; /* Add the start quote and first element. */ strcat(str, "\""); @@ -941,10 +1502,464 @@ strcat(str, "\""); return str; } +STATIC double +_pixels_to_width(double pixels) +{ + double max_digit_width = 7.0; + double padding = 5.0; + double width; + + if (pixels == LXW_DEF_COL_WIDTH_PIXELS) + width = LXW_DEF_COL_WIDTH; + else if (pixels <= 12.0) + width = pixels / (max_digit_width + padding); + else + width = (pixels - padding) / max_digit_width; + + return width; +} + +STATIC double +_pixels_to_height(double pixels) +{ + if (pixels == LXW_DEF_ROW_HEIGHT_PIXELS) + return LXW_DEF_ROW_HEIGHT; + else + return pixels * 0.75; +} + +/* Check and set if an autofilter is a standard or custom filter. */ +void +_set_custom_filter(lxw_filter_rule_obj *rule_obj) +{ + rule_obj->is_custom = LXW_TRUE; + + if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_EQUAL_TO) + rule_obj->is_custom = LXW_FALSE; + + if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS) + rule_obj->is_custom = LXW_FALSE; + + if (rule_obj->criteria2 != LXW_FILTER_CRITERIA_NONE) { + if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_EQUAL_TO) + rule_obj->is_custom = LXW_FALSE; + + if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS) + rule_obj->is_custom = LXW_FALSE; + + if (rule_obj->type == LXW_FILTER_TYPE_AND) + rule_obj->is_custom = LXW_TRUE; + } + + if (rule_obj->value1_string && strpbrk(rule_obj->value1_string, "*?")) + rule_obj->is_custom = LXW_TRUE; + + if (rule_obj->value2_string && strpbrk(rule_obj->value2_string, "*?")) + rule_obj->is_custom = LXW_TRUE; +} + +/* Check and copy user input for table styles in worksheet_add_table(). */ +void +_check_and_copy_table_style(lxw_table_obj *table_obj, + lxw_table_options *user_options) +{ + if (!user_options) + return; + + /* Set the defaults. */ + table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM; + table_obj->style_type_number = 9; + + if (user_options->style_type > LXW_TABLE_STYLE_TYPE_DARK) { + LXW_WARN_FORMAT1 + ("worksheet_add_table(): invalid style_type = %d. " + "Using default TableStyleMedium9", user_options->style_type); + + table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM; + table_obj->style_type_number = 9; + } + else { + table_obj->style_type = user_options->style_type; + } + + /* Each type (light, medium and dark) has a different number of styles. */ + if (user_options->style_type == LXW_TABLE_STYLE_TYPE_LIGHT) { + if (user_options->style_type_number > 21) { + LXW_WARN_FORMAT1("worksheet_add_table(): " + "invalid style_type_number = %d for style type " + "LXW_TABLE_STYLE_TYPE_LIGHT. " + "Using default TableStyleMedium9", + user_options->style_type); + + table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM; + table_obj->style_type_number = 9; + } + else { + table_obj->style_type_number = user_options->style_type_number; + } + } + + if (user_options->style_type == LXW_TABLE_STYLE_TYPE_MEDIUM) { + if (user_options->style_type_number < 1 + || user_options->style_type_number > 28) { + LXW_WARN_FORMAT1("worksheet_add_table(): " + "invalid style_type_number = %d for style type " + "LXW_TABLE_STYLE_TYPE_MEDIUM. " + "Using default TableStyleMedium9", + user_options->style_type_number); + + table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM; + table_obj->style_type_number = 9; + } + else { + table_obj->style_type_number = user_options->style_type_number; + } + } + + if (user_options->style_type == LXW_TABLE_STYLE_TYPE_DARK) { + if (user_options->style_type_number < 1 + || user_options->style_type_number > 11) { + LXW_WARN_FORMAT1("worksheet_add_table(): " + "invalid style_type_number = %d for style type " + "LXW_TABLE_STYLE_TYPE_DARK. " + "Using default TableStyleMedium9", + user_options->style_type_number); + + table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM; + table_obj->style_type_number = 9; + } + else { + table_obj->style_type_number = user_options->style_type_number; + } + } +} + +/* Set the defaults for table columns in worksheet_add_table(). */ +lxw_error +_set_default_table_columns(lxw_table_obj *table_obj) +{ + + char col_name[LXW_ATTR_32]; + char *header; + uint16_t i; + lxw_table_column *column; + uint16_t num_cols = table_obj->num_cols; + lxw_table_column **columns = table_obj->columns; + + for (i = 0; i < num_cols; i++) { + lxw_snprintf(col_name, LXW_ATTR_32, "Column%d", i + 1); + + column = calloc(num_cols, sizeof(lxw_table_column)); + RETURN_ON_MEM_ERROR(column, LXW_ERROR_MEMORY_MALLOC_FAILED); + + header = lxw_strdup(col_name); + if (!header) { + free(column); + RETURN_ON_MEM_ERROR(header, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + columns[i] = column; + columns[i]->header = header; + } + + return LXW_NO_ERROR; +} + +/* Convert Excel 2010 style "@" structural references to the Excel 2007 style + * "[#This Row]" in table formulas. This is the format that Excel uses to + * store the references. */ +char * +_expand_table_formula(const char *formula) +{ + char *expanded; + const char *ptr; + size_t i; + size_t ref_count = 0; + size_t expanded_len; + + ptr = formula; + + while (*ptr++) { + if (*ptr == '@') + ref_count++; + } + + if (ref_count == 0) { + /* String doesn't need to be expanded. Just copy it. */ + expanded = lxw_strdup_formula(formula); + } + else { + /* Convert "@" in the formula string to "[#This Row],". */ + expanded_len = strlen(formula) + (sizeof(LXW_THIS_ROW) * ref_count); + expanded = calloc(1, expanded_len); + + if (!expanded) + return NULL; + + i = 0; + ptr = formula; + /* Ignore the = in the formula. */ + if (*ptr == '=') + ptr++; + + /* Do the "@" expansion. */ + while (*ptr) { + if (*ptr == '@') { + strcat(&expanded[i], LXW_THIS_ROW); + i += sizeof(LXW_THIS_ROW) - 1; + } + else { + expanded[i] = *ptr; + i++; + } + + ptr++; + } + } + + return expanded; +} + +/* Set user values for table columns in worksheet_add_table(). */ +lxw_error +_set_custom_table_columns(lxw_table_obj *table_obj, + lxw_table_options *user_options) +{ + char *str; + uint16_t i; + lxw_table_column *table_column; + lxw_table_column *user_column; + uint16_t num_cols = table_obj->num_cols; + lxw_table_column **user_columns = user_options->columns; + + for (i = 0; i < num_cols; i++) { + + user_column = user_columns[i]; + table_column = table_obj->columns[i]; + + /* NULL indicates end of user input array. */ + if (user_column == NULL) + return LXW_NO_ERROR; + + if (user_column->header) { + if (lxw_utf8_strlen(user_column->header) > 255) { + LXW_WARN_FORMAT("worksheet_add_table(): column parameter " + "'header' exceeds Excel length limit of 255."); + return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } + + str = lxw_strdup(user_column->header); + RETURN_ON_MEM_ERROR(str, LXW_ERROR_MEMORY_MALLOC_FAILED); + + /* Free the default column header. */ + free((void *) table_column->header); + table_column->header = str; + } + + if (user_column->total_string) { + str = lxw_strdup(user_column->total_string); + RETURN_ON_MEM_ERROR(str, LXW_ERROR_MEMORY_MALLOC_FAILED); + + table_column->total_string = str; + } + + if (user_column->formula) { + str = _expand_table_formula(user_column->formula); + RETURN_ON_MEM_ERROR(str, LXW_ERROR_MEMORY_MALLOC_FAILED); + + table_column->formula = str; + } + + table_column->format = user_column->format; + table_column->total_value = user_column->total_value; + table_column->header_format = user_column->header_format; + table_column->total_function = user_column->total_function; + } + + return LXW_NO_ERROR; +} + +/* Write a worksheet table column formula like SUBTOTAL(109,[Column1]). */ +void +_write_column_function(lxw_worksheet *self, lxw_row_t row, lxw_col_t col, + lxw_table_column *column) +{ + size_t offset; + char formula[LXW_MAX_ATTRIBUTE_LENGTH]; + lxw_format *format = column->format; + uint8_t total_function = column->total_function; + double value = column->total_value; + const char *header = column->header; + + /* Write the subtotal formula number. */ + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, "SUBTOTAL(%d,[", + total_function); + + /* Copy the header string but escape any special characters. Note, this is + * guaranteed to fit in the 2k buffer since the header is max 255 + * characters, checked in _set_custom_table_columns(). */ + offset = strlen(formula); + while (*header) { + switch (*header) { + case '\'': + case '#': + case '[': + case ']': + formula[offset++] = '\''; + formula[offset] = *header; + break; + default: + formula[offset] = *header; + break; + } + offset++; + header++; + } + + /* Write the end of the string. */ + memcpy(&formula[offset], "])\0", sizeof("])\0")); + + worksheet_write_formula_num(self, row, col, formula, format, value); +} + +/* Write a worksheet table column formula. */ +void +_write_column_formula(lxw_worksheet *self, lxw_row_t first_row, + lxw_row_t last_row, lxw_col_t col, + lxw_table_column *column) +{ + lxw_row_t row; + const char *formula = column->formula; + lxw_format *format = column->format; + + for (row = first_row; row <= last_row; row++) + worksheet_write_formula(self, row, col, formula, format); +} + +/* Set the defaults for table columns in worksheet_add_table(). */ +void +_write_table_column_data(lxw_worksheet *self, lxw_table_obj *table_obj) +{ + uint16_t i; + lxw_table_column *column; + lxw_table_column **columns = table_obj->columns; + + lxw_col_t col; + lxw_row_t first_row = table_obj->first_row; + lxw_col_t first_col = table_obj->first_col; + lxw_row_t last_row = table_obj->last_row; + lxw_row_t first_data_row = first_row; + lxw_row_t last_data_row = last_row; + + if (!table_obj->no_header_row) + first_data_row++; + + if (table_obj->total_row) + last_data_row--; + + for (i = 0; i < table_obj->num_cols; i++) { + col = first_col + i; + column = columns[i]; + + if (table_obj->no_header_row == LXW_FALSE) + worksheet_write_string(self, first_row, col, column->header, + column->header_format); + + if (column->total_string) + worksheet_write_string(self, last_row, col, column->total_string, + NULL); + + if (column->total_function) + _write_column_function(self, last_row, col, column); + + if (column->formula) + _write_column_formula(self, first_data_row, last_data_row, col, + column); + } +} + +/* + * Check that there are sufficient data rows in a worksheet table. + */ +lxw_error +_check_table_rows(lxw_row_t first_row, lxw_row_t last_row, + lxw_table_options *user_options) +{ + lxw_row_t num_non_header_rows = last_row - first_row; + + if (user_options && user_options->no_header_row == LXW_TRUE) + num_non_header_rows++; + + if (num_non_header_rows == 0) { + LXW_WARN_FORMAT("worksheet_add_table(): " + "table must have at least 1 non-header row."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + return LXW_NO_ERROR; +} + +/* + * Check that the the table name is valid. + */ +lxw_error +_check_table_name(lxw_table_options *user_options) +{ + const char *name; + char *ptr; + char first[2] = { 0, 0 }; + + if (!user_options) + return LXW_NO_ERROR; + + if (!user_options->name) + return LXW_NO_ERROR; + + name = user_options->name; + + /* Check table name length. */ + if (lxw_utf8_strlen(name) > 255) { + LXW_WARN_FORMAT("worksheet_add_table(): " + "Table name exceeds Excel's limit of 255."); + return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } + + /* Check some short invalid names. */ + if (strlen(name) == 1 + && (name[0] == 'C' || name[0] == 'c' || name[0] == 'R' + || name[0] == 'r')) { + LXW_WARN_FORMAT1("worksheet_add_table(): " + "invalid table name \"%s\".", name); + return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } + + /* Check for invalid characters in Table name, while trying to allow + * for utf8 strings. */ + ptr = strpbrk(name, " !\"#$%&'()*+,-/:;<=>?@[\\]^`{|}~"); + if (ptr) { + LXW_WARN_FORMAT2("worksheet_add_table(): " + "invalid character '%c' in table name \"%s\".", + *ptr, name); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Check for invalid initial character in Table name, while trying to allow + * for utf8 strings. */ + first[0] = name[0]; + ptr = strpbrk(first, " !\"#$%&'()*+,-./0123456789:;<=>?@[\\]^`{|}~"); + if (ptr) { + LXW_WARN_FORMAT2("worksheet_add_table(): " + "invalid first character '%c' in table name \"%s\".", + *ptr, name); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + return LXW_NO_ERROR; +} + /***************************************************************************** * * XML functions. * ****************************************************************************/ @@ -967,15 +1982,25 @@ struct xml_attribute *attribute; char xmlns[] = "http://schemas.openxmlformats.org/" "spreadsheetml/2006/main"; char xmlns_r[] = "http://schemas.openxmlformats.org/" "officeDocument/2006/relationships"; + char xmlns_mc[] = "http://schemas.openxmlformats.org/" + "markup-compatibility/2006"; + char xmlns_x14ac[] = "http://schemas.microsoft.com/" + "office/spreadsheetml/2009/9/ac"; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r); + if (self->excel_version == 2010) { + LXW_PUSH_ATTRIBUTES_STR("xmlns:mc", xmlns_mc); + LXW_PUSH_ATTRIBUTES_STR("xmlns:x14ac", xmlns_x14ac); + LXW_PUSH_ATTRIBUTES_STR("mc:Ignorable", "x14ac"); + } + lxw_xml_start_tag(self->file, "worksheet", &attributes); LXW_FREE_ATTRIBUTES(); } /* @@ -1374,40 +2399,39 @@ /* Hide screen gridlines if required */ if (!self->screen_gridlines) LXW_PUSH_ATTRIBUTES_STR("showGridLines", "0"); /* Hide zeroes in cells. */ - if (!self->show_zeros) { + if (!self->show_zeros) LXW_PUSH_ATTRIBUTES_STR("showZeros", "0"); - } /* Display worksheet right to left for Hebrew, Arabic and others. */ - if (self->right_to_left) { + if (self->right_to_left) LXW_PUSH_ATTRIBUTES_STR("rightToLeft", "1"); - } /* Show that the sheet tab is selected. */ if (self->selected) LXW_PUSH_ATTRIBUTES_STR("tabSelected", "1"); /* Turn outlines off. Also required in the outlinePr element. */ - if (!self->outline_on) { + if (!self->outline_on) LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0"); - } /* Set the page view/layout mode if required. */ if (self->page_view) LXW_PUSH_ATTRIBUTES_STR("view", "pageLayout"); + /* Set the top left cell if required. */ + if (self->top_left_cell[0]) + LXW_PUSH_ATTRIBUTES_STR("topLeftCell", self->top_left_cell); + /* Set the zoom level. */ - if (self->zoom != 100) { - if (!self->page_view) { - LXW_PUSH_ATTRIBUTES_INT("zoomScale", self->zoom); + if (self->zoom != 100 && !self->page_view) { + LXW_PUSH_ATTRIBUTES_INT("zoomScale", self->zoom); - if (self->zoom_scale_normal) - LXW_PUSH_ATTRIBUTES_INT("zoomScaleNormal", self->zoom); - } + if (self->zoom_scale_normal) + LXW_PUSH_ATTRIBUTES_INT("zoomScaleNormal", self->zoom); } LXW_PUSH_ATTRIBUTES_STR("workbookViewId", "0"); if (self->panes.type != NO_PANES || !STAILQ_EMPTY(self->selections)) { @@ -1459,10 +2483,13 @@ LXW_PUSH_ATTRIBUTES_INT("outlineLevelRow", self->outline_row_level); if (self->outline_col_level) LXW_PUSH_ATTRIBUTES_INT("outlineLevelCol", self->outline_col_level); + if (self->excel_version == 2010) + LXW_PUSH_ATTRIBUTES_STR("x14ac:dyDescent", "0.25"); + lxw_xml_empty_tag(self->file, "sheetFormatPr", &attributes); LXW_FREE_ATTRIBUTES(); } @@ -1499,21 +2526,31 @@ } else { lxw_xml_start_tag(self->file, "sheetData", NULL); - /* Flush and rewind the temp file. */ + /* Flush the temp file. */ fflush(self->optimize_tmpfile); - rewind(self->optimize_tmpfile); - while (read_size) { - read_size = - fread(buffer, 1, LXW_BUFFER_SIZE, self->optimize_tmpfile); - fwrite(buffer, 1, read_size, self->file); + if (self->optimize_buffer) { + /* Ignore return value. There is no easy way to raise error. */ + (void) fwrite(self->optimize_buffer, self->optimize_buffer_size, + 1, self->file); } + else { + /* Rewind the temp file. */ + rewind(self->optimize_tmpfile); + while (read_size) { + read_size = + fread(buffer, 1, LXW_BUFFER_SIZE, self->optimize_tmpfile); + /* Ignore return value. There is no easy way to raise error. */ + (void) fwrite(buffer, 1, read_size, self->file); + } + } fclose(self->optimize_tmpfile); + free(self->optimize_buffer); lxw_xml_end_tag(self->file, "sheetData"); } } @@ -1600,10 +2637,13 @@ if (self->orientation) LXW_PUSH_ATTRIBUTES_STR("orientation", "portrait"); else LXW_PUSH_ATTRIBUTES_STR("orientation", "landscape"); + if (self->black_white) + LXW_PUSH_ATTRIBUTES_STR("blackAndWhite", "1"); + /* Set start page active flag. */ if (self->page_start) LXW_PUSH_ATTRIBUTES_INT("useFirstPageNumber", 1); /* Set the DPI. Mainly only for testing. */ @@ -1701,10 +2741,13 @@ LXW_PUSH_ATTRIBUTES_INT("outlineLevel", row->level); if (row->collapsed) LXW_PUSH_ATTRIBUTES_STR("collapsed", "1"); + if (self->excel_version == 2010) + LXW_PUSH_ATTRIBUTES_STR("x14ac:dyDescent", "0.25"); + if (!row->data_changed) lxw_xml_empty_tag(self->file, "row", &attributes); else lxw_xml_start_tag(self->file, "row", &attributes); @@ -1715,11 +2758,11 @@ * Convert the width of a cell from user's units to pixels. Excel rounds the * column width to the nearest pixel. If the width hasn't been set by the user * we use the default value. If the column is hidden it has a value of zero. */ STATIC int32_t -_worksheet_size_col(lxw_worksheet *self, lxw_col_t col_num) +_worksheet_size_col(lxw_worksheet *self, lxw_col_t col_num, uint8_t anchor) { lxw_col_options *col_opt = NULL; uint32_t pixels; double width; double max_digit_width = 7.0; /* For Calabri 11. */ @@ -1739,17 +2782,14 @@ col_opt = NULL; } } if (col_opt) { - if (col_opt->hidden) - return 0; - width = col_opt->width; /* Convert to pixels. */ - if (width == 0) { + if (col_opt->hidden && anchor != LXW_OBJECT_MOVE_AND_SIZE_AFTER) { pixels = 0; } else if (width < 1.0) { pixels = (uint32_t) (width * (max_digit_width + padding) + 0.5); } @@ -1768,31 +2808,26 @@ * Convert the height of a cell from user's units to pixels. If the height * hasn't been set by the user we use the default value. If the row is hidden * it has a value of zero. */ STATIC int32_t -_worksheet_size_row(lxw_worksheet *self, lxw_row_t row_num) +_worksheet_size_row(lxw_worksheet *self, lxw_row_t row_num, uint8_t anchor) { lxw_row *row; uint32_t pixels; - double height; row = lxw_worksheet_find_row(self, row_num); + /* Note, the 0.75 below is due to the difference between 72/96 DPI. */ if (row) { - if (row->hidden) - return 0; - - height = row->height; - - if (height == 0) + if (row->hidden && anchor != LXW_OBJECT_MOVE_AND_SIZE_AFTER) pixels = 0; else - pixels = (uint32_t) (4.0 / 3.0 * height); + pixels = (uint32_t) (row->height / 0.75); } else { - pixels = (uint32_t) (4.0 / 3.0 * self->default_row_height); + pixels = (uint32_t) (self->default_row_height / 0.75); } return pixels; } @@ -1831,11 +2866,11 @@ * the width and height of the object from the width and height of the * underlying cells. */ STATIC void _worksheet_position_object_pixels(lxw_worksheet *self, - lxw_image_options *image, + lxw_object_properties *object_props, lxw_drawing_object *drawing_object) { lxw_col_t col_start; /* Column containing upper left corner. */ int32_t x1; /* Distance to left side of object. */ @@ -1853,27 +2888,29 @@ uint32_t x_abs = 0; /* Abs. distance to left side of object. */ uint32_t y_abs = 0; /* Abs. distance to top side of object. */ uint32_t i; + uint8_t anchor = drawing_object->anchor; + uint8_t ignore_anchor = LXW_OBJECT_POSITION_DEFAULT; - col_start = image->col; - row_start = image->row; - x1 = image->x_offset; - y1 = image->y_offset; - width = image->width; - height = image->height; + col_start = object_props->col; + row_start = object_props->row; + x1 = object_props->x_offset; + y1 = object_props->y_offset; + width = object_props->width; + height = object_props->height; /* Adjust start column for negative offsets. */ while (x1 < 0 && col_start > 0) { - x1 += _worksheet_size_col(self, col_start - 1); + x1 += _worksheet_size_col(self, col_start - 1, ignore_anchor); col_start--; } /* Adjust start row for negative offsets. */ while (y1 < 0 && row_start > 0) { - y1 += _worksheet_size_row(self, row_start - 1); + y1 += _worksheet_size_row(self, row_start - 1, ignore_anchor); row_start--; } /* Ensure that the image isn't shifted off the page at top left. */ if (x1 < 0) @@ -1883,11 +2920,11 @@ y1 = 0; /* Calculate the absolute x offset of the top-left vertex. */ if (self->col_size_changed) { for (i = 0; i < col_start; i++) - x_abs += _worksheet_size_col(self, i); + x_abs += _worksheet_size_col(self, i, ignore_anchor); } else { /* Optimization for when the column widths haven't changed. */ x_abs += self->default_col_pixels * col_start; } @@ -1896,54 +2933,50 @@ /* Calculate the absolute y offset of the top-left vertex. */ /* Store the column change to allow optimizations. */ if (self->row_size_changed) { for (i = 0; i < row_start; i++) - y_abs += _worksheet_size_row(self, i); + y_abs += _worksheet_size_row(self, i, ignore_anchor); } else { /* Optimization for when the row heights haven"t changed. */ y_abs += self->default_row_pixels * row_start; } y_abs += y1; /* Adjust start col for offsets that are greater than the col width. */ - if (_worksheet_size_col(self, col_start) > 0) { - while (x1 >= _worksheet_size_col(self, col_start)) { - x1 -= _worksheet_size_col(self, col_start); - col_start++; - } + while (x1 >= _worksheet_size_col(self, col_start, anchor)) { + x1 -= _worksheet_size_col(self, col_start, ignore_anchor); + col_start++; } /* Adjust start row for offsets that are greater than the row height. */ - if (_worksheet_size_row(self, row_start) > 0) { - while (y1 >= _worksheet_size_row(self, row_start)) { - y1 -= _worksheet_size_row(self, row_start); - row_start++; - } + while (y1 >= _worksheet_size_row(self, row_start, anchor)) { + y1 -= _worksheet_size_row(self, row_start, ignore_anchor); + row_start++; } /* Initialize end cell to the same as the start cell. */ col_end = col_start; row_end = row_start; - /* Only offset the image in the cell if the row/col isn't hidden. */ - if (_worksheet_size_col(self, col_start) > 0) + /* Only offset the image in the cell if the row/col is hidden. */ + if (_worksheet_size_col(self, col_start, anchor) > 0) width = width + x1; - if (_worksheet_size_row(self, row_start) > 0) + if (_worksheet_size_row(self, row_start, anchor) > 0) height = height + y1; /* Subtract the underlying cell widths to find the end cell. */ - while (width >= _worksheet_size_col(self, col_end)) { - width -= _worksheet_size_col(self, col_end); + while (width >= _worksheet_size_col(self, col_end, anchor)) { + width -= _worksheet_size_col(self, col_end, anchor); col_end++; } /* Subtract the underlying cell heights to find the end cell. */ - while (height >= _worksheet_size_row(self, row_end)) { - height -= _worksheet_size_row(self, row_end); + while (height >= _worksheet_size_row(self, row_end, anchor)) { + height -= _worksheet_size_row(self, row_end, anchor); row_end++; } /* The end vertices are whatever is left from the width and height. */ x2 = width; @@ -1958,22 +2991,21 @@ drawing_object->to.row = row_end; drawing_object->to.col_offset = x2; drawing_object->to.row_offset = y2; drawing_object->col_absolute = x_abs; drawing_object->row_absolute = y_abs; - } /* * Calculate the vertices that define the position of a graphical object * within the worksheet in EMUs. The vertices are expressed as English * Metric Units (EMUs). There are 12,700 EMUs per point. * Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel. */ STATIC void _worksheet_position_object_emus(lxw_worksheet *self, - lxw_image_options *image, + lxw_object_properties *image, lxw_drawing_object *drawing_object) { _worksheet_position_object_pixels(self, image, drawing_object); @@ -1987,22 +3019,272 @@ drawing_object->col_absolute *= 9525; drawing_object->row_absolute *= 9525; } /* + * This function handles the additional optional parameters to + * worksheet_write_comment_opt() as well as calculating the comment object + * position and vertices. + */ +void +_get_comment_params(lxw_vml_obj *comment, lxw_comment_options *options) +{ + + lxw_row_t start_row; + lxw_col_t start_col; + int32_t x_offset; + int32_t y_offset; + uint32_t height = 74; + uint32_t width = 128; + double x_scale = 1.0; + double y_scale = 1.0; + lxw_row_t row = comment->row; + lxw_col_t col = comment->col;; + + /* Set the default start cell and offsets for the comment. These are + * generally fixed in relation to the parent cell. However there are some + * edge cases for cells at the, well yes, edges. */ + if (row == 0) + y_offset = 2; + else if (row == LXW_ROW_MAX - 3) + y_offset = 16; + else if (row == LXW_ROW_MAX - 2) + y_offset = 16; + else if (row == LXW_ROW_MAX - 1) + y_offset = 14; + else + y_offset = 10; + + if (col == LXW_COL_MAX - 3) + x_offset = 49; + else if (col == LXW_COL_MAX - 2) + x_offset = 49; + else if (col == LXW_COL_MAX - 1) + x_offset = 49; + else + x_offset = 15; + + if (row == 0) + start_row = 0; + else if (row == LXW_ROW_MAX - 3) + start_row = LXW_ROW_MAX - 7; + else if (row == LXW_ROW_MAX - 2) + start_row = LXW_ROW_MAX - 6; + else if (row == LXW_ROW_MAX - 1) + start_row = LXW_ROW_MAX - 5; + else + start_row = row - 1; + + if (col == LXW_COL_MAX - 3) + start_col = LXW_COL_MAX - 6; + else if (col == LXW_COL_MAX - 2) + start_col = LXW_COL_MAX - 5; + else if (col == LXW_COL_MAX - 1) + start_col = LXW_COL_MAX - 4; + else + start_col = col + 1; + + /* Set the default font properties. */ + comment->font_size = 8; + comment->font_family = 2; + + /* Set any user defined options. */ + if (options) { + + if (options->width > 0.0) + width = options->width; + + if (options->height > 0.0) + height = options->height; + + if (options->x_scale > 0.0) + x_scale = options->x_scale; + + if (options->y_scale > 0.0) + y_scale = options->y_scale; + + if (options->x_offset != 0) + x_offset = options->x_offset; + + if (options->y_offset != 0) + y_offset = options->y_offset; + + if (options->start_row > 0 || options->start_col > 0) { + start_row = options->start_row; + start_col = options->start_col; + } + + if (options->font_size > 0.0) + comment->font_size = options->font_size; + + if (options->font_family > 0) + comment->font_family = options->font_family; + + comment->visible = options->visible; + comment->color = options->color; + comment->author = lxw_strdup(options->author); + comment->font_name = lxw_strdup(options->font_name); + } + + /* Scale the width/height to the default/user scale and round to the + * nearest pixel. */ + width = (uint32_t) (0.5 + x_scale * width); + height = (uint32_t) (0.5 + y_scale * height); + + comment->width = width; + comment->height = height; + comment->start_col = start_col; + comment->start_row = start_row; + comment->x_offset = x_offset; + comment->y_offset = y_offset; +} + +/* + * This function handles the additional optional parameters to + * worksheet_insert_button() as well as calculating the button object + * position and vertices. + */ +lxw_error +_get_button_params(lxw_vml_obj *button, uint16_t button_number, + lxw_button_options *options) +{ + + int32_t x_offset = 0; + int32_t y_offset = 0; + uint32_t height = LXW_DEF_ROW_HEIGHT_PIXELS; + uint32_t width = LXW_DEF_COL_WIDTH_PIXELS; + double x_scale = 1.0; + double y_scale = 1.0; + lxw_row_t row = button->row; + lxw_col_t col = button->col; + char buffer[LXW_ATTR_32]; + uint8_t has_caption = LXW_FALSE; + uint8_t has_macro = LXW_FALSE; + size_t len; + + /* Set any user defined options. */ + if (options) { + + if (options->width > 0.0) + width = options->width; + + if (options->height > 0.0) + height = options->height; + + if (options->x_scale > 0.0) + x_scale = options->x_scale; + + if (options->y_scale > 0.0) + y_scale = options->y_scale; + + if (options->x_offset != 0) + x_offset = options->x_offset; + + if (options->y_offset != 0) + y_offset = options->y_offset; + + if (options->caption) { + button->name = lxw_strdup(options->caption); + RETURN_ON_MEM_ERROR(button->name, LXW_ERROR_MEMORY_MALLOC_FAILED); + has_caption = LXW_TRUE; + } + + if (options->macro) { + len = sizeof("[0]!") + strlen(options->macro); + button->macro = calloc(1, len); + RETURN_ON_MEM_ERROR(button->macro, + LXW_ERROR_MEMORY_MALLOC_FAILED); + + if (button->macro) + lxw_snprintf(button->macro, len, "[0]!%s", options->macro); + + has_macro = LXW_TRUE; + } + + if (options->description) { + button->text = lxw_strdup(options->description); + RETURN_ON_MEM_ERROR(button->text, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + } + + if (!has_caption) { + lxw_snprintf(buffer, LXW_ATTR_32, "Button %d", button_number); + button->name = lxw_strdup(buffer); + RETURN_ON_MEM_ERROR(button->name, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + + if (!has_macro) { + lxw_snprintf(buffer, LXW_ATTR_32, "[0]!Button%d_Click", + button_number); + button->macro = lxw_strdup(buffer); + RETURN_ON_MEM_ERROR(button->macro, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + + /* Scale the width/height to the default/user scale and round to the + * nearest pixel. */ + width = (uint32_t) (0.5 + x_scale * width); + height = (uint32_t) (0.5 + y_scale * height); + + button->width = width; + button->height = height; + button->start_col = col; + button->start_row = row; + button->x_offset = x_offset; + button->y_offset = y_offset; + + return LXW_NO_ERROR; +} + +/* + * Calculate the vml_obj object position and vertices. + */ +void +_worksheet_position_vml_object(lxw_worksheet *self, lxw_vml_obj *vml_obj) +{ + lxw_object_properties object_props; + lxw_drawing_object drawing_object; + + object_props.col = vml_obj->start_col; + object_props.row = vml_obj->start_row; + object_props.x_offset = vml_obj->x_offset; + object_props.y_offset = vml_obj->y_offset; + object_props.width = vml_obj->width; + object_props.height = vml_obj->height; + + drawing_object.anchor = LXW_OBJECT_DONT_MOVE_DONT_SIZE; + + _worksheet_position_object_pixels(self, &object_props, &drawing_object); + + vml_obj->from.col = drawing_object.from.col; + vml_obj->from.row = drawing_object.from.row; + vml_obj->from.col_offset = drawing_object.from.col_offset; + vml_obj->from.row_offset = drawing_object.from.row_offset; + vml_obj->to.col = drawing_object.to.col; + vml_obj->to.row = drawing_object.to.row; + vml_obj->to.col_offset = drawing_object.to.col_offset; + vml_obj->to.row_offset = drawing_object.to.row_offset; + vml_obj->col_absolute = drawing_object.col_absolute; + vml_obj->row_absolute = drawing_object.row_absolute; +} + +/* * Set up image/drawings. */ void lxw_worksheet_prepare_image(lxw_worksheet *self, uint32_t image_ref_id, uint32_t drawing_id, - lxw_image_options *image_data) + lxw_object_properties *object_props) { lxw_drawing_object *drawing_object; lxw_rel_tuple *relationship; double width; double height; + char *url; + char *found_string; + size_t i; char filename[LXW_FILENAME_LENGTH]; + enum cell_types link_type = HYPERLINK_URL; if (!self->drawing) { self->drawing = lxw_drawing_new(); self->drawing->embedded = LXW_TRUE; RETURN_VOID_ON_MEM_ERROR(self->drawing); @@ -2024,47 +3306,255 @@ } drawing_object = calloc(1, sizeof(lxw_drawing_object)); RETURN_VOID_ON_MEM_ERROR(drawing_object); - drawing_object->anchor_type = LXW_ANCHOR_TYPE_IMAGE; - drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL; - drawing_object->description = lxw_strdup(image_data->description); + drawing_object->anchor = LXW_OBJECT_MOVE_DONT_SIZE; + if (object_props->object_position) + drawing_object->anchor = object_props->object_position; + drawing_object->type = LXW_DRAWING_IMAGE; + drawing_object->description = lxw_strdup(object_props->description); + drawing_object->tip = lxw_strdup(object_props->tip); + drawing_object->rel_index = 0; + drawing_object->url_rel_index = 0; + drawing_object->decorative = object_props->decorative; + /* Scale to user scale. */ - width = image_data->width * image_data->x_scale; - height = image_data->height * image_data->y_scale; + width = object_props->width * object_props->x_scale; + height = object_props->height * object_props->y_scale; /* Scale by non 96dpi resolutions. */ - width *= 96.0 / image_data->x_dpi; - height *= 96.0 / image_data->y_dpi; + width *= 96.0 / object_props->x_dpi; + height *= 96.0 / object_props->y_dpi; - /* Convert to the nearest pixel. */ - image_data->width = width; - image_data->height = height; + object_props->width = width; + object_props->height = height; - _worksheet_position_object_emus(self, image_data, drawing_object); + _worksheet_position_object_emus(self, object_props, drawing_object); /* Convert from pixels to emus. */ drawing_object->width = (uint32_t) (0.5 + width * 9525); drawing_object->height = (uint32_t) (0.5 + height * 9525); lxw_add_drawing_object(self->drawing, drawing_object); + if (object_props->url) { + url = object_props->url; + + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/hyperlink"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + /* Check the link type. Default to external hyperlinks. */ + if (strstr(url, "internal:")) + link_type = HYPERLINK_INTERNAL; + else if (strstr(url, "external:")) + link_type = HYPERLINK_EXTERNAL; + else + link_type = HYPERLINK_URL; + + /* Set the relationship object for each type of link. */ + if (link_type == HYPERLINK_INTERNAL) { + relationship->target_mode = NULL; + relationship->target = lxw_strdup(url + sizeof("internal") - 1); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + /* We need to prefix the internal link/range with #. */ + relationship->target[0] = '#'; + } + else if (link_type == HYPERLINK_EXTERNAL) { + relationship->target_mode = lxw_strdup("External"); + GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error); + + /* Look for Windows style "C:/" link or Windows share "\\" link. */ + found_string = strchr(url + sizeof("external:") - 1, ':'); + if (!found_string) + found_string = strstr(url, "\\\\"); + + if (found_string) { + /* Copy the url with some space at the start to overwrite + * "external:" with "file:///". */ + relationship->target = lxw_escape_url_characters(url + 1, + LXW_TRUE); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + /* Add the file:/// URI to the url if absolute path. */ + memcpy(relationship->target, "file:///", + sizeof("file:///") - 1); + } + else { + /* Copy the relative url without "external:". */ + relationship->target = + lxw_escape_url_characters(url + sizeof("external:") - 1, + LXW_TRUE); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + /* Switch backslash to forward slash. */ + for (i = 0; i <= strlen(relationship->target); i++) + if (relationship->target[i] == '\\') + relationship->target[i] = '/'; + } + + } + else { + relationship->target_mode = lxw_strdup("External"); + GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error); + + relationship->target = + lxw_escape_url_characters(object_props->url, LXW_FALSE); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + } + + /* Check if URL exceeds Excel's length limit. */ + if (lxw_utf8_strlen(url) > self->max_url_length) { + LXW_WARN_FORMAT2("worksheet_insert_image()/_opt(): URL exceeds " + "Excel's allowable length of %d characters: %s", + self->max_url_length, url); + goto mem_error; + } + + if (!_find_drawing_rel_index(self, url)) { + STAILQ_INSERT_TAIL(self->drawing_links, relationship, + list_pointers); + } + else { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + + drawing_object->url_rel_index = _get_drawing_rel_index(self, url); + + } + + if (!_find_drawing_rel_index(self, object_props->md5)) { + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/image"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id, + object_props->extension); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers); + } + + drawing_object->rel_index = + _get_drawing_rel_index(self, object_props->md5); + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } +} + +/* + * Set up image/drawings for header/footer images. + */ +void +lxw_worksheet_prepare_header_image(lxw_worksheet *self, + uint32_t image_ref_id, + lxw_object_properties *object_props) +{ + lxw_rel_tuple *relationship = NULL; + char filename[LXW_FILENAME_LENGTH]; + lxw_vml_obj *header_image_vml; + char *extension; + + STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers); + + if (!_find_vml_drawing_rel_index(self, object_props->md5)) { + relationship = calloc(1, sizeof(lxw_rel_tuple)); + RETURN_VOID_ON_MEM_ERROR(relationship); + + relationship->type = lxw_strdup("/image"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id, + object_props->extension); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + STAILQ_INSERT_TAIL(self->vml_drawing_links, relationship, + list_pointers); + } + + header_image_vml = calloc(1, sizeof(lxw_vml_obj)); + GOTO_LABEL_ON_MEM_ERROR(header_image_vml, mem_error); + + header_image_vml->width = (uint32_t) object_props->width; + header_image_vml->height = (uint32_t) object_props->height; + header_image_vml->x_dpi = object_props->x_dpi; + header_image_vml->y_dpi = object_props->y_dpi; + header_image_vml->rel_index = 1; + + header_image_vml->image_position = + lxw_strdup(object_props->image_position); + header_image_vml->name = lxw_strdup(object_props->description); + + /* Strip the extension from the filename. */ + extension = strchr(header_image_vml->name, '.'); + if (extension) + *extension = '\0'; + + header_image_vml->rel_index = + _get_vml_drawing_rel_index(self, object_props->md5); + + STAILQ_INSERT_TAIL(self->header_image_objs, header_image_vml, + list_pointers); + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } +} + +/* + * Set up background image. + */ +void +lxw_worksheet_prepare_background(lxw_worksheet *self, + uint32_t image_ref_id, + lxw_object_properties *object_props) +{ + lxw_rel_tuple *relationship = NULL; + char filename[LXW_FILENAME_LENGTH]; + + STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers); + relationship = calloc(1, sizeof(lxw_rel_tuple)); - GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + RETURN_VOID_ON_MEM_ERROR(relationship); relationship->type = lxw_strdup("/image"); GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id, - image_data->extension); + object_props->extension); relationship->target = lxw_strdup(filename); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); - STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers); + self->external_background_link = relationship; return; mem_error: if (relationship) { @@ -2080,11 +3570,11 @@ */ void lxw_worksheet_prepare_chart(lxw_worksheet *self, uint32_t chart_ref_id, uint32_t drawing_id, - lxw_image_options *image_data, + lxw_object_properties *object_props, uint8_t is_chartsheet) { lxw_drawing_object *drawing_object; lxw_rel_tuple *relationship; double width; @@ -2120,23 +3610,30 @@ } drawing_object = calloc(1, sizeof(lxw_drawing_object)); RETURN_VOID_ON_MEM_ERROR(drawing_object); - drawing_object->anchor_type = LXW_ANCHOR_TYPE_CHART; - drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL; - drawing_object->description = lxw_strdup("TODO_DESC"); + drawing_object->anchor = LXW_OBJECT_MOVE_AND_SIZE; + if (object_props->object_position) + drawing_object->anchor = object_props->object_position; + drawing_object->type = LXW_DRAWING_CHART; + drawing_object->description = lxw_strdup(object_props->description); + drawing_object->tip = NULL; + drawing_object->rel_index = _get_drawing_rel_index(self, NULL); + drawing_object->url_rel_index = 0; + drawing_object->decorative = object_props->decorative; + /* Scale to user scale. */ - width = image_data->width * image_data->x_scale; - height = image_data->height * image_data->y_scale; + width = object_props->width * object_props->x_scale; + height = object_props->height * object_props->y_scale; /* Convert to the nearest pixel. */ - image_data->width = width; - image_data->height = height; + object_props->width = width; + object_props->height = height; - _worksheet_position_object_emus(self, image_data, drawing_object); + _worksheet_position_object_emus(self, object_props, drawing_object); /* Convert from pixels to emus. */ drawing_object->width = (uint32_t) (0.5 + width * 9525); drawing_object->height = (uint32_t) (0.5 + height * 9525); @@ -2165,25 +3662,240 @@ free(relationship); } } /* + * Set up VML objects, such as comments, in the worksheet. + */ +uint32_t +lxw_worksheet_prepare_vml_objects(lxw_worksheet *self, + uint32_t vml_data_id, + uint32_t vml_shape_id, + uint32_t vml_drawing_id, + uint32_t comment_id) +{ + lxw_row *row; + lxw_cell *cell; + lxw_rel_tuple *relationship; + char filename[LXW_FILENAME_LENGTH]; + uint32_t comment_count = 0; + uint32_t i; + uint32_t tmp_data_id; + size_t data_str_len = 0; + size_t used = 0; + char *vml_data_id_str; + + RB_FOREACH(row, lxw_table_rows, self->comments) { + + RB_FOREACH(cell, lxw_table_cells, row->cells) { + /* Calculate the worksheet position of the comment. */ + _worksheet_position_vml_object(self, cell->comment); + + /* Store comment in a simple list for use by packager. */ + STAILQ_INSERT_TAIL(self->comment_objs, cell->comment, + list_pointers); + comment_count++; + } + } + + /* Set up the VML relationship for comments/buttons/header images. */ + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/vmlDrawing"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../drawings/vmlDrawing%d.vml", + vml_drawing_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + self->external_vml_comment_link = relationship; + + if (self->has_comments) { + /* Only need this relationship object for comment VMLs. */ + + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/comments"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../comments%d.xml", comment_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + self->external_comment_link = relationship; + } + + /* The vml.c <o:idmap> element data id contains a comma separated range + * when there is more than one 1024 block of comments, like this: + * data="1,2,3". Since this could potentially (but unlikely) exceed + * LXW_MAX_ATTRIBUTE_LENGTH we need to allocate space dynamically. */ + + /* Calculate the total space required for the ID for each 1024 block. */ + for (i = 0; i <= comment_count / 1024; i++) { + tmp_data_id = vml_data_id + i; + + /* Calculate the space required for the digits in the id. */ + while (tmp_data_id) { + data_str_len++; + tmp_data_id /= 10; + } + + /* Add an extra char for comma separator or '\O'. */ + data_str_len++; + }; + + /* If this allocation fails it will be dealt with in packager.c. */ + vml_data_id_str = calloc(1, data_str_len + 2); + GOTO_LABEL_ON_MEM_ERROR(vml_data_id_str, mem_error); + + /* Create the CSV list in the allocated space. */ + for (i = 0; i <= comment_count / 1024; i++) { + tmp_data_id = vml_data_id + i; + lxw_snprintf(vml_data_id_str + used, data_str_len - used, "%d,", + tmp_data_id); + + used = strlen(vml_data_id_str); + }; + + self->vml_shape_id = vml_shape_id; + self->vml_data_id_str = vml_data_id_str; + + return comment_count; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + + return 0; +} + +/* + * Set up external linkage for VML header/footer images. + */ +void +lxw_worksheet_prepare_header_vml_objects(lxw_worksheet *self, + uint32_t vml_header_id, + uint32_t vml_drawing_id) +{ + + lxw_rel_tuple *relationship; + char filename[LXW_FILENAME_LENGTH]; + char *vml_data_id_str; + + self->vml_header_id = vml_header_id; + + /* Set up the VML relationship for header images. */ + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/vmlDrawing"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../drawings/vmlDrawing%d.vml", + vml_drawing_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + self->external_vml_header_link = relationship; + + /* If this allocation fails it will be dealt with in packager.c. */ + vml_data_id_str = calloc(1, sizeof("4294967295")); + GOTO_LABEL_ON_MEM_ERROR(vml_data_id_str, mem_error); + + lxw_snprintf(vml_data_id_str, sizeof("4294967295"), "%d", vml_header_id); + + self->vml_header_id_str = vml_data_id_str; + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + + return; +} + +/* + * Set up external linkage for VML header/footer images. + */ +void +lxw_worksheet_prepare_tables(lxw_worksheet *self, uint32_t table_id) +{ + lxw_table_obj *table_obj; + lxw_rel_tuple *relationship; + char name[LXW_ATTR_32]; + char filename[LXW_FILENAME_LENGTH]; + + STAILQ_FOREACH(table_obj, self->table_objs, list_pointers) { + + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/table"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "../tables/table%d.xml", table_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + STAILQ_INSERT_TAIL(self->external_table_links, relationship, + list_pointers); + + if (!table_obj->name) { + lxw_snprintf(name, LXW_ATTR_32, "Table%d", table_id); + table_obj->name = lxw_strdup(name); + GOTO_LABEL_ON_MEM_ERROR(table_obj->name, mem_error); + } + table_obj->id = table_id; + table_id++; + } + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + + return; +} + +/* * Extract width and height information from a PNG file. */ STATIC lxw_error -_process_png(lxw_image_options *image_options) +_process_png(lxw_object_properties *object_props) { uint32_t length; uint32_t offset; char type[4]; uint32_t width = 0; uint32_t height = 0; double x_dpi = 96; double y_dpi = 96; int fseek_err; - FILE *stream = image_options->stream; + FILE *stream = object_props->stream; /* Skip another 4 bytes to the end of the PNG header. */ fseek_err = fseek(stream, 4, SEEK_CUR); if (fseek_err) goto file_error; @@ -2256,42 +3968,42 @@ /* Ensure that we read some valid data from the file. */ if (width == 0) goto file_error; /* Set the image metadata. */ - image_options->image_type = LXW_IMAGE_PNG; - image_options->width = width; - image_options->height = height; - image_options->x_dpi = x_dpi ? x_dpi : 96; - image_options->y_dpi = y_dpi ? x_dpi : 96; - image_options->extension = lxw_strdup("png"); + object_props->image_type = LXW_IMAGE_PNG; + object_props->width = width; + object_props->height = height; + object_props->x_dpi = x_dpi ? x_dpi : 96; + object_props->y_dpi = y_dpi ? y_dpi : 96; + object_props->extension = lxw_strdup("png"); return LXW_NO_ERROR; file_error: - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "no size data found in: %s.", image_options->filename); + LXW_WARN_FORMAT1("worksheet image insertion: " + "no size data found in: %s.", object_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } /* * Extract width and height information from a JPEG file. */ STATIC lxw_error -_process_jpeg(lxw_image_options *image_options) +_process_jpeg(lxw_object_properties *image_props) { uint16_t length; uint16_t marker; uint32_t offset; uint16_t width = 0; uint16_t height = 0; double x_dpi = 96; double y_dpi = 96; int fseek_err; - FILE *stream = image_options->stream; + FILE *stream = image_props->stream; /* Read back 2 bytes to the end of the initial 0xFFD8 marker. */ fseek_err = fseek(stream, -2, SEEK_CUR); if (fseek_err) goto file_error; @@ -2373,48 +4085,48 @@ break; if (!feof(stream)) { fseek_err = fseek(stream, offset, SEEK_CUR); if (fseek_err) - goto file_error; + break; } } /* Ensure that we read some valid data from the file. */ if (width == 0) goto file_error; /* Set the image metadata. */ - image_options->image_type = LXW_IMAGE_JPEG; - image_options->width = width; - image_options->height = height; - image_options->x_dpi = x_dpi ? x_dpi : 96; - image_options->y_dpi = y_dpi ? x_dpi : 96; - image_options->extension = lxw_strdup("jpeg"); + image_props->image_type = LXW_IMAGE_JPEG; + image_props->width = width; + image_props->height = height; + image_props->x_dpi = x_dpi ? x_dpi : 96; + image_props->y_dpi = y_dpi ? y_dpi : 96; + image_props->extension = lxw_strdup("jpeg"); return LXW_NO_ERROR; file_error: - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "no size data found in: %s.", image_options->filename); + LXW_WARN_FORMAT1("worksheet image insertion: " + "no size data found in: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } /* * Extract width and height information from a BMP file. */ STATIC lxw_error -_process_bmp(lxw_image_options *image_options) +_process_bmp(lxw_object_properties *image_props) { uint32_t width = 0; uint32_t height = 0; double x_dpi = 96; double y_dpi = 96; int fseek_err; - FILE *stream = image_options->stream; + FILE *stream = image_props->stream; /* Skip another 14 bytes to the start of the BMP height/width. */ fseek_err = fseek(stream, 14, SEEK_CUR); if (fseek_err) goto file_error; @@ -2431,65 +4143,208 @@ height = LXW_UINT32_HOST(height); width = LXW_UINT32_HOST(width); /* Set the image metadata. */ - image_options->image_type = LXW_IMAGE_BMP; - image_options->width = width; - image_options->height = height; - image_options->x_dpi = x_dpi; - image_options->y_dpi = y_dpi; - image_options->extension = lxw_strdup("bmp"); + image_props->image_type = LXW_IMAGE_BMP; + image_props->width = width; + image_props->height = height; + image_props->x_dpi = x_dpi; + image_props->y_dpi = y_dpi; + image_props->extension = lxw_strdup("bmp"); return LXW_NO_ERROR; file_error: - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " - "no size data found in: %s.", image_options->filename); + LXW_WARN_FORMAT1("worksheet image insertion: " + "no size data found in: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } /* + * Extract width and height information from a GIF file. + */ +STATIC lxw_error +_process_gif(lxw_object_properties *image_props) +{ + uint16_t width = 0; + uint16_t height = 0; + double x_dpi = 96; + double y_dpi = 96; + int fseek_err; + + FILE *stream = image_props->stream; + + /* Skip another 2 bytes to the start of the GIF height/width. */ + fseek_err = fseek(stream, 2, SEEK_CUR); + if (fseek_err) + goto file_error; + + if (fread(&width, sizeof(width), 1, stream) < 1) + width = 0; + + if (fread(&height, sizeof(height), 1, stream) < 1) + height = 0; + + /* Ensure that we read some valid data from the file. */ + if (width == 0) + goto file_error; + + height = LXW_UINT16_HOST(height); + width = LXW_UINT16_HOST(width); + + /* Set the image metadata. */ + image_props->image_type = LXW_IMAGE_GIF; + image_props->width = width; + image_props->height = height; + image_props->x_dpi = x_dpi; + image_props->y_dpi = y_dpi; + image_props->extension = lxw_strdup("gif"); + + return LXW_NO_ERROR; + +file_error: + LXW_WARN_FORMAT1("worksheet image insertion: " + "no size data found in: %s.", image_props->filename); + + return LXW_ERROR_IMAGE_DIMENSIONS; +} + +/* * Extract information from the image file such as dimension, type, filename, * and extension. */ STATIC lxw_error -_get_image_properties(lxw_image_options *image_options) +_get_image_properties(lxw_object_properties *image_props) { unsigned char signature[4]; +#ifndef USE_NO_MD5 + uint8_t i; + MD5_CTX md5_context; + size_t size_read; + char buffer[LXW_IMAGE_BUFFER_SIZE]; + unsigned char md5_checksum[LXW_MD5_SIZE]; +#endif /* Read 4 bytes to look for the file header/signature. */ - if (fread(signature, 1, 4, image_options->stream) < 4) { - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + if (fread(signature, 1, 4, image_props->stream) < 4) { + LXW_WARN_FORMAT1("worksheet image insertion: " "couldn't read image type for: %s.", - image_options->filename); + image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } if (memcmp(&signature[1], "PNG", 3) == 0) { - if (_process_png(image_options) != LXW_NO_ERROR) + if (_process_png(image_props) != LXW_NO_ERROR) return LXW_ERROR_IMAGE_DIMENSIONS; } else if (signature[0] == 0xFF && signature[1] == 0xD8) { - if (_process_jpeg(image_options) != LXW_NO_ERROR) + if (_process_jpeg(image_props) != LXW_NO_ERROR) return LXW_ERROR_IMAGE_DIMENSIONS; } else if (memcmp(signature, "BM", 2) == 0) { - if (_process_bmp(image_options) != LXW_NO_ERROR) + if (_process_bmp(image_props) != LXW_NO_ERROR) return LXW_ERROR_IMAGE_DIMENSIONS; } + else if (memcmp(signature, "GIF8", 4) == 0) { + if (_process_gif(image_props) != LXW_NO_ERROR) + return LXW_ERROR_IMAGE_DIMENSIONS; + } else { - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + LXW_WARN_FORMAT1("worksheet image insertion: " "unsupported image format for: %s.", - image_options->filename); + image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } +#ifndef USE_NO_MD5 + /* Calculate an MD5 checksum for the image so that we can remove duplicate + * images to reduce the xlsx file size.*/ + rewind(image_props->stream); + + MD5_Init(&md5_context); + + size_read = fread(buffer, 1, LXW_IMAGE_BUFFER_SIZE, image_props->stream); + while (size_read) { + MD5_Update(&md5_context, buffer, (unsigned long) size_read); + size_read = + fread(buffer, 1, LXW_IMAGE_BUFFER_SIZE, image_props->stream); + } + + MD5_Final(md5_checksum, &md5_context); + + /* Create a 32 char hex string buffer for the MD5 checksum. */ + image_props->md5 = calloc(1, LXW_MD5_SIZE * 2 + 1); + + /* If this calloc fails we just return and don't remove duplicates. */ + RETURN_ON_MEM_ERROR(image_props->md5, LXW_NO_ERROR); + + /* Convert the 16 byte MD5 buffer to a 32 char hex string. */ + for (i = 0; i < LXW_MD5_SIZE; i++) { + lxw_snprintf(&image_props->md5[2 * i], 3, "%02x", md5_checksum[i]); + } +#endif + return LXW_NO_ERROR; } +/* Conditional formats that refer to the same cell sqref range, like A or + * B1:B9, need to be written as part of one xml structure. Therefore we need + * to store them in a RB hash/tree keyed by sqref. Within the RB hash element + * we then store conditional formats that refer to sqref in a STAILQ list. */ +lxw_error +_store_conditional_format_object(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + lxw_cond_format_hash_element tmp_hash_element; + lxw_cond_format_hash_element *found_hash_element = NULL; + lxw_cond_format_hash_element *new_hash_element = NULL; + + /* Create a temp hash element to do the lookup. */ + LXW_ATTRIBUTE_COPY(tmp_hash_element.sqref, cond_format->sqref); + found_hash_element = RB_FIND(lxw_cond_format_hash, + self->conditional_formats, + &tmp_hash_element); + + if (found_hash_element) { + /* If the RB element exists then add the conditional format to the + * list for the sqref range.*/ + STAILQ_INSERT_TAIL(found_hash_element->cond_formats, cond_format, + list_pointers); + } + else { + /* Create a new RB hash element. */ + new_hash_element = calloc(1, sizeof(lxw_cond_format_hash_element)); + GOTO_LABEL_ON_MEM_ERROR(new_hash_element, mem_error); + + /* Use the sqref as the key. */ + LXW_ATTRIBUTE_COPY(new_hash_element->sqref, cond_format->sqref); + + /* Also create the list where we store the cond format objects. */ + new_hash_element->cond_formats = + calloc(1, sizeof(struct lxw_cond_format_list)); + GOTO_LABEL_ON_MEM_ERROR(new_hash_element->cond_formats, mem_error); + + /* Initialize the list and add the conditional format object. */ + STAILQ_INIT(new_hash_element->cond_formats); + STAILQ_INSERT_TAIL(new_hash_element->cond_formats, cond_format, + list_pointers); + + /* Now insert the RB hash element into the tree. */ + RB_INSERT(lxw_cond_format_hash, self->conditional_formats, + new_hash_element); + + } + + return LXW_NO_ERROR; + +mem_error: + free(new_hash_element); + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + /***************************************************************************** * * XML file assembly functions. * ****************************************************************************/ @@ -2500,11 +4355,11 @@ */ STATIC void _write_number_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { -#ifdef USE_DOUBLE_FUNCTION +#ifdef USE_DTOA_LIBRARY char data[LXW_ATTR_32]; lxw_sprintf_dbl(data, cell->u.number); if (style_index) @@ -2514,15 +4369,15 @@ else fprintf(self->file, "<c r=\"%s\"><v>%s</v></c>", range, data); #else if (style_index) fprintf(self->file, - "<c r=\"%s\" s=\"%d\"><v>%.16g</v></c>", + "<c r=\"%s\" s=\"%d\"><v>%.16G</v></c>", range, style_index, cell->u.number); else fprintf(self->file, - "<c r=\"%s\"><v>%.16g</v></c>", range, cell->u.number); + "<c r=\"%s\"><v>%.16G</v></c>", range, cell->u.number); #endif } /* @@ -2589,35 +4444,20 @@ */ STATIC void _write_inline_rich_string_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { - char *string = cell->u.string; + const char *string = cell->u.string; - /* Add attribute to preserve leading or trailing whitespace. */ - if (isspace((unsigned char) string[0]) - || isspace((unsigned char) string[strlen(string) - 1])) { - - if (style_index) - fprintf(self->file, - "<c r=\"%s\" s=\"%d\" t=\"inlineStr\"><is>%s</is></c>", - range, style_index, string); - else - fprintf(self->file, - "<c r=\"%s\" t=\"inlineStr\"><is>%s</is></c>", - range, string); - } - else { - if (style_index) - fprintf(self->file, - "<c r=\"%s\" s=\"%d\" t=\"inlineStr\">" - "<is>%s</is></c>", range, style_index, string); - else - fprintf(self->file, - "<c r=\"%s\" t=\"inlineStr\">" - "<is>%s</is></c>", range, string); - } + if (style_index) + fprintf(self->file, + "<c r=\"%s\" s=\"%d\" t=\"inlineStr\">" + "<is>%s</is></c>", range, style_index, string); + else + fprintf(self->file, + "<c r=\"%s\" t=\"inlineStr\">" + "<is>%s</is></c>", range, string); } /* * Write out a formula worksheet cell with a numeric result. */ @@ -2630,10 +4470,20 @@ lxw_xml_data_element(self->file, "f", cell->u.string, NULL); lxw_xml_data_element(self->file, "v", data, NULL); } /* + * Write out a formula worksheet cell with a numeric result. + */ +STATIC void +_write_formula_str_cell(lxw_worksheet *self, lxw_cell *cell) +{ + lxw_xml_data_element(self->file, "f", cell->u.string, NULL); + lxw_xml_data_element(self->file, "v", cell->user_data2, NULL); +} + +/* * Write out an array formula worksheet cell with a numeric result. */ STATIC void _write_array_formula_num_cell(lxw_worksheet *self, lxw_cell *cell) { @@ -2659,14 +4509,14 @@ STATIC void _write_boolean_cell(lxw_worksheet *self, lxw_cell *cell) { char data[LXW_ATTR_32]; - if (cell->u.number) - data[0] = '1'; - else + if (cell->u.number == 0.0) data[0] = '0'; + else + data[0] = '1'; data[1] = '\0'; lxw_xml_data_element(self->file, "v", data, NULL); } @@ -2765,16 +4615,26 @@ if (style_index) LXW_PUSH_ATTRIBUTES_INT("s", style_index); if (cell->type == FORMULA_CELL) { + /* If user_data2 is set then the formula has a string result. */ + if (cell->user_data2) + LXW_PUSH_ATTRIBUTES_STR("t", "str"); + lxw_xml_start_tag(self->file, "c", &attributes); - _write_formula_num_cell(self, cell); + + if (cell->user_data2) + _write_formula_str_cell(self, cell); + else + _write_formula_num_cell(self, cell); + lxw_xml_end_tag(self->file, "c"); } else if (cell->type == BLANK_CELL) { - lxw_xml_empty_tag(self->file, "c", &attributes); + if (cell->format) + lxw_xml_empty_tag(self->file, "c", &attributes); } else if (cell->type == BOOLEAN_CELL) { LXW_PUSH_ATTRIBUTES_STR("t", "b"); lxw_xml_start_tag(self->file, "c", &attributes); _write_boolean_cell(self, cell); @@ -2783,10 +4643,16 @@ else if (cell->type == ARRAY_FORMULA_CELL) { lxw_xml_start_tag(self->file, "c", &attributes); _write_array_formula_num_cell(self, cell); lxw_xml_end_tag(self->file, "c"); } + else if (cell->type == DYNAMIC_ARRAY_FORMULA_CELL) { + LXW_PUSH_ATTRIBUTES_STR("cm", "1"); + lxw_xml_start_tag(self->file, "c", &attributes); + _write_array_formula_num_cell(self, cell); + lxw_xml_end_tag(self->file, "c"); + } LXW_FREE_ATTRIBUTES(); } /* @@ -2816,14 +4682,17 @@ if ((int32_t) row->row_num / 16 > block_num) _calculate_spans(row, spans, &block_num); _write_row(self, row, spans); - RB_FOREACH(cell, lxw_table_cells, row->cells) { - _write_cell(self, cell, row->format); + if (row->data_changed) { + RB_FOREACH(cell, lxw_table_cells, row->cells) { + _write_cell(self, cell, row->format); + } + + lxw_xml_end_tag(self->file, "row"); } - lxw_xml_end_tag(self->file, "row"); } } } /* @@ -2870,10 +4739,70 @@ row->collapsed = LXW_FALSE; row->data_changed = LXW_FALSE; row->row_changed = LXW_FALSE; } +/* Process a header/footer image and store it in the correct slot. */ +lxw_error +_worksheet_set_header_footer_image(lxw_worksheet *self, const char *filename, + uint8_t image_position) +{ + FILE *image_stream; + const char *description; + lxw_object_properties *object_props; + char *image_strings[] = { "LH", "CH", "RH", "LF", "CF", "RF" }; + + /* Not all slots will have image files. */ + if (!filename) + return LXW_NO_ERROR; + + /* Check that the image file exists and can be opened. */ + image_stream = lxw_fopen(filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "file doesn't exist or can't be opened: %s.", + filename); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Use the filename as the default description, like Excel. */ + description = lxw_basename(filename); + if (!description) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "couldn't get basename for file: %s.", filename); + fclose(image_stream); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + /* Copy other options or set defaults. */ + object_props->filename = lxw_strdup(filename); + object_props->description = lxw_strdup(description); + object_props->stream = image_stream; + + /* Set VML image position string based on the header/footer/position. */ + object_props->image_position = lxw_strdup(image_strings[image_position]); + + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + *self->header_footer_objs[image_position] = object_props; + self->has_header_vml = LXW_TRUE; + fclose(image_stream); + return LXW_NO_ERROR; + } + else { + _free_object_properties(object_props); + fclose(image_stream); + return LXW_ERROR_IMAGE_DIMENSIONS; + } +} + /* * Write the <col> element. */ STATIC void _worksheet_write_col_info(lxw_worksheet *self, lxw_col_options *options) @@ -3041,14 +4970,14 @@ if (!self->header_footer_changed) return; lxw_xml_start_tag(self->file, "headerFooter", NULL); - if (self->header[0] != '\0') + if (self->header) _worksheet_write_odd_header(self); - if (self->footer[0] != '\0') + if (self->footer) _worksheet_write_odd_footer(self); lxw_xml_end_tag(self->file, "headerFooter"); } @@ -3243,18 +5172,211 @@ LXW_FREE_ATTRIBUTES(); } /* + * Write the <filter> element. + */ +STATIC void +_worksheet_write_filter(lxw_worksheet *self, const char *str, double num, + uint8_t criteria) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + if (criteria == LXW_FILTER_CRITERIA_BLANKS) + return; + + LXW_INIT_ATTRIBUTES(); + + if (str) + LXW_PUSH_ATTRIBUTES_STR("val", str); + else + LXW_PUSH_ATTRIBUTES_DBL("val", num); + + lxw_xml_empty_tag(self->file, "filter", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <filterColumn> element as simple equality. + */ +STATIC void +_worksheet_write_filter_standard(lxw_worksheet *self, + lxw_filter_rule_obj *filter) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (filter->has_blanks) { + LXW_PUSH_ATTRIBUTES_STR("blank", "1"); + } + + if (filter->type == LXW_FILTER_TYPE_SINGLE && filter->has_blanks) { + lxw_xml_empty_tag(self->file, "filters", &attributes); + + } + else { + lxw_xml_start_tag(self->file, "filters", &attributes); + + /* Write the filter element. */ + if (filter->type == LXW_FILTER_TYPE_SINGLE) { + _worksheet_write_filter(self, filter->value1_string, + filter->value1, filter->criteria1); + } + else if (filter->type == LXW_FILTER_TYPE_AND + || filter->type == LXW_FILTER_TYPE_OR) { + _worksheet_write_filter(self, filter->value1_string, + filter->value1, filter->criteria1); + _worksheet_write_filter(self, filter->value2_string, + filter->value2, filter->criteria2); + } + + lxw_xml_end_tag(self->file, "filters"); + } + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <customFilter> element. + */ +STATIC void +_worksheet_write_custom_filter(lxw_worksheet *self, const char *str, + double num, uint8_t criteria) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (criteria == LXW_FILTER_CRITERIA_NOT_EQUAL_TO) + LXW_PUSH_ATTRIBUTES_STR("operator", "notEqual"); + if (criteria == LXW_FILTER_CRITERIA_GREATER_THAN) + LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThan"); + else if (criteria == LXW_FILTER_CRITERIA_GREATER_THAN_OR_EQUAL_TO) + LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThanOrEqual"); + else if (criteria == LXW_FILTER_CRITERIA_LESS_THAN) + LXW_PUSH_ATTRIBUTES_STR("operator", "lessThan"); + else if (criteria == LXW_FILTER_CRITERIA_LESS_THAN_OR_EQUAL_TO) + LXW_PUSH_ATTRIBUTES_STR("operator", "lessThanOrEqual"); + + if (str) + LXW_PUSH_ATTRIBUTES_STR("val", str); + else + LXW_PUSH_ATTRIBUTES_DBL("val", num); + + lxw_xml_empty_tag(self->file, "customFilter", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <filterColumn> element as a list. + */ +STATIC void +_worksheet_write_filter_list(lxw_worksheet *self, lxw_filter_rule_obj *filter) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint16_t i; + + LXW_INIT_ATTRIBUTES(); + + if (filter->has_blanks) { + LXW_PUSH_ATTRIBUTES_STR("blank", "1"); + } + + lxw_xml_start_tag(self->file, "filters", &attributes); + + for (i = 0; i < filter->num_list_filters; i++) { + _worksheet_write_filter(self, filter->list[i], 0, 0); + } + + lxw_xml_end_tag(self->file, "filters"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <filterColumn> element. + */ +STATIC void +_worksheet_write_filter_custom(lxw_worksheet *self, + lxw_filter_rule_obj *filter) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (filter->type == LXW_FILTER_TYPE_AND) + LXW_PUSH_ATTRIBUTES_STR("and", "1"); + + lxw_xml_start_tag(self->file, "customFilters", &attributes); + + /* Write the filter element. */ + if (filter->type == LXW_FILTER_TYPE_SINGLE) { + _worksheet_write_custom_filter(self, filter->value1_string, + filter->value1, filter->criteria1); + } + else if (filter->type == LXW_FILTER_TYPE_AND + || filter->type == LXW_FILTER_TYPE_OR) { + _worksheet_write_custom_filter(self, filter->value1_string, + filter->value1, filter->criteria1); + _worksheet_write_custom_filter(self, filter->value2_string, + filter->value2, filter->criteria2); + } + + lxw_xml_end_tag(self->file, "customFilters"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <filterColumn> element. + */ +STATIC void +_worksheet_write_filter_column(lxw_worksheet *self, + lxw_filter_rule_obj *filter) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + if (!filter) + return; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("colId", filter->col_num); + + lxw_xml_start_tag(self->file, "filterColumn", &attributes); + + if (filter->list) + _worksheet_write_filter_list(self, filter); + else if (filter->is_custom) + _worksheet_write_filter_custom(self, filter); + else + _worksheet_write_filter_standard(self, filter); + + lxw_xml_end_tag(self->file, "filterColumn"); + + LXW_FREE_ATTRIBUTES(); +} + +/* * Write the <autoFilter> element. */ STATIC void _worksheet_write_auto_filter(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char range[LXW_MAX_CELL_RANGE_LENGTH]; + uint16_t i; if (!self->autofilter.in_use) return; lxw_rowcol_to_range(range, @@ -3263,12 +5385,23 @@ self->autofilter.last_row, self->autofilter.last_col); LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("ref", range); - lxw_xml_empty_tag(self->file, "autoFilter", &attributes); + if (self->autofilter.has_rules) { + lxw_xml_start_tag(self->file, "autoFilter", &attributes); + for (i = 0; i < self->num_filter_rules; i++) + _worksheet_write_filter_column(self, self->filter_rules[i]); + + lxw_xml_end_tag(self->file, "autoFilter"); + + } + else { + lxw_xml_empty_tag(self->file, "autoFilter", &attributes); + } + LXW_FREE_ATTRIBUTES(); } /* * Write the <hyperlink> element for external links. @@ -3411,11 +5544,11 @@ /* * Write the <sheetProtection> element. */ STATIC void _worksheet_write_sheet_protection(lxw_worksheet *self, - lxw_protection *protect) + lxw_protection_obj *protect) { struct xml_attribute_list attributes; struct xml_attribute *attribute; if (!protect->is_configured) @@ -3481,23 +5614,95 @@ LXW_FREE_ATTRIBUTES(); } /* + * Write the <legacyDrawing> element. + */ +STATIC void +_worksheet_write_legacy_drawing(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; + + if (!self->has_vml) + return; + else + self->rel_count++; + + lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count); + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); + + lxw_xml_empty_tag(self->file, "legacyDrawing", &attributes); + + LXW_FREE_ATTRIBUTES(); + +} + +/* + * Write the <legacyDrawingHF> element. + */ +STATIC void +_worksheet_write_legacy_drawing_hf(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; + + if (!self->has_header_vml) + return; + else + self->rel_count++; + + lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count); + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); + + lxw_xml_empty_tag(self->file, "legacyDrawingHF", &attributes); + + LXW_FREE_ATTRIBUTES(); + +} + +/* + * Write the <picture> element. + */ +STATIC void +_worksheet_write_picture(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; + + if (!self->has_background_image) + return; + else + self->rel_count++; + + lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count); + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); + + lxw_xml_empty_tag(self->file, "picture", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* * Write the <drawing> element. */ STATIC void _worksheet_write_drawing(lxw_worksheet *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(); @@ -3565,11 +5770,11 @@ /* * Write the <dataValidation> element. */ STATIC void _worksheet_write_data_validation(lxw_worksheet *self, - lxw_data_validation *validation) + lxw_data_val_obj *validation) { struct xml_attribute_list attributes; struct xml_attribute *attribute; uint8_t is_between = 0; @@ -3714,11 +5919,11 @@ STATIC void _worksheet_write_data_validations(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - lxw_data_validation *data_validation; + lxw_data_val_obj *data_validation; if (self->num_validations == 0) return; LXW_INIT_ATTRIBUTES(); @@ -3735,10 +5940,1670 @@ LXW_FREE_ATTRIBUTES(); } /* + * Write the <formula> element for strings. + */ +STATIC void +_worksheet_write_formula_str(lxw_worksheet *self, char *data) +{ + lxw_xml_data_element(self->file, "formula", data, NULL); +} + +/* + * Write the <formula> element for numbers. + */ +STATIC void +_worksheet_write_formula_num(lxw_worksheet *self, double num) +{ + char data[LXW_ATTR_32]; + + lxw_sprintf_dbl(data, num); + lxw_xml_data_element(self->file, "formula", data, NULL); +} + +/* + * Write the <ext> element. + */ +STATIC void +_worksheet_write_ext(lxw_worksheet *self, char *uri) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns_x_14[] = + "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns:x14", xmlns_x_14); + LXW_PUSH_ATTRIBUTES_STR("uri", uri); + + lxw_xml_start_tag(self->file, "ext", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <extLst> dataBar extension element. + */ +STATIC void +_worksheet_write_data_bar_ext(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + /* Create a pseudo GUID for each unique Excel 2010 data bar. */ + cond_format->guid = calloc(1, LXW_GUID_LENGTH); + lxw_snprintf(cond_format->guid, LXW_GUID_LENGTH, + "{DA7ABA51-AAAA-BBBB-%04X-%012X}", + self->index + 1, ++self->data_bar_2010_index); + + lxw_xml_start_tag(self->file, "extLst", NULL); + + _worksheet_write_ext(self, "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"); + + lxw_xml_data_element(self->file, "x14:id", cond_format->guid, NULL); + + lxw_xml_end_tag(self->file, "ext"); + lxw_xml_end_tag(self->file, "extLst"); +} + +/* + * Write the <color> element. + */ +STATIC void +_worksheet_write_color(lxw_worksheet *self, lxw_color_t color) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char rgb[LXW_ATTR_32]; + + lxw_snprintf(rgb, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("rgb", rgb); + + lxw_xml_empty_tag(self->file, "color", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfvo> element for strings. + */ +STATIC void +_worksheet_write_cfvo_str(lxw_worksheet *self, uint8_t rule_type, + char *value, uint8_t data_bar_2010) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "min"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) + LXW_PUSH_ATTRIBUTES_STR("type", "num"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) + LXW_PUSH_ATTRIBUTES_STR("type", "percent"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) + LXW_PUSH_ATTRIBUTES_STR("type", "percentile"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) + LXW_PUSH_ATTRIBUTES_STR("type", "formula"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "max"); + + if (!data_bar_2010 || (rule_type != LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && rule_type != LXW_CONDITIONAL_RULE_TYPE_MAXIMUM)) + LXW_PUSH_ATTRIBUTES_STR("val", value); + + lxw_xml_empty_tag(self->file, "cfvo", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfvo> element for numbers. + */ +STATIC void +_worksheet_write_cfvo_num(lxw_worksheet *self, uint8_t rule_type, + double value, uint8_t data_bar_2010) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "min"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) + LXW_PUSH_ATTRIBUTES_STR("type", "num"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) + LXW_PUSH_ATTRIBUTES_STR("type", "percent"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) + LXW_PUSH_ATTRIBUTES_STR("type", "percentile"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) + LXW_PUSH_ATTRIBUTES_STR("type", "formula"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "max"); + + if (!data_bar_2010 || (rule_type != LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && rule_type != LXW_CONDITIONAL_RULE_TYPE_MAXIMUM)) + LXW_PUSH_ATTRIBUTES_DBL("val", value); + + lxw_xml_empty_tag(self->file, "cfvo", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <iconSet> element. + */ +STATIC void +_worksheet_write_icon_set(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char *icon_set[] = { + "3Arrows", + "3ArrowsGray", + "3Flags", + "3TrafficLights", + "3TrafficLights2", + "3Signs", + "3Symbols", + "3Symbols2", + "4Arrows", + "4ArrowsGray", + "4RedToBlack", + "4Rating", + "4TrafficLights", + "5Arrows", + "5ArrowsGray", + "5Rating", + "5Quarters", + }; + uint8_t percent = LXW_CONDITIONAL_RULE_TYPE_PERCENT; + uint8_t style = cond_format->icon_style; + + LXW_INIT_ATTRIBUTES(); + + if (style != LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED) + LXW_PUSH_ATTRIBUTES_STR("iconSet", icon_set[style]); + + if (cond_format->reverse_icons == LXW_TRUE) + LXW_PUSH_ATTRIBUTES_STR("reverse", "1"); + + if (cond_format->icons_only == LXW_TRUE) + LXW_PUSH_ATTRIBUTES_STR("showValue", "0"); + + lxw_xml_start_tag(self->file, "iconSet", &attributes); + + if (style < LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED) { + _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 33, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 67, LXW_FALSE); + } + + if (style >= LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED + && style < LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED) { + _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 25, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 50, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 75, LXW_FALSE); + } + + if (style >= LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED + && style <= LXW_CONDITIONAL_ICONS_5_QUARTERS) { + _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 20, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 40, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 60, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 80, LXW_FALSE); + } + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for data bar rules. + */ +STATIC void +_worksheet_write_cf_rule_icons(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + _worksheet_write_icon_set(self, cond_format); + + lxw_xml_end_tag(self->file, "iconSet"); + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <dataBar> element. + */ +STATIC void +_worksheet_write_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (cond_format->bar_only) + LXW_PUSH_ATTRIBUTES_STR("showValue", "0"); + + lxw_xml_start_tag(self->file, "dataBar", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for data bar rules. + */ +STATIC void +_worksheet_write_cf_rule_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + _worksheet_write_data_bar(self, cond_format); + + if (cond_format->min_value_string) { + _worksheet_write_cfvo_str(self, cond_format->min_rule_type, + cond_format->min_value_string, + cond_format->data_bar_2010); + } + else { + _worksheet_write_cfvo_num(self, cond_format->min_rule_type, + cond_format->min_value, + cond_format->data_bar_2010); + } + + if (cond_format->max_value_string) { + _worksheet_write_cfvo_str(self, cond_format->max_rule_type, + cond_format->max_value_string, + cond_format->data_bar_2010); + } + else { + _worksheet_write_cfvo_num(self, cond_format->max_rule_type, + cond_format->max_value, + cond_format->data_bar_2010); + } + + _worksheet_write_color(self, cond_format->bar_color); + + lxw_xml_end_tag(self->file, "dataBar"); + + if (cond_format->data_bar_2010) + _worksheet_write_data_bar_ext(self, cond_format); + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for 2 and 3 color scale rules. + */ +STATIC void +_worksheet_write_cf_rule_color_scale(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + lxw_xml_start_tag(self->file, "colorScale", NULL); + + if (cond_format->min_value_string) { + _worksheet_write_cfvo_str(self, cond_format->min_rule_type, + cond_format->min_value_string, LXW_FALSE); + } + else { + _worksheet_write_cfvo_num(self, cond_format->min_rule_type, + cond_format->min_value, LXW_FALSE); + } + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + if (cond_format->mid_value_string) { + _worksheet_write_cfvo_str(self, cond_format->mid_rule_type, + cond_format->mid_value_string, + LXW_FALSE); + } + else { + _worksheet_write_cfvo_num(self, cond_format->mid_rule_type, + cond_format->mid_value, LXW_FALSE); + } + } + + if (cond_format->max_value_string) { + _worksheet_write_cfvo_str(self, cond_format->max_rule_type, + cond_format->max_value_string, LXW_FALSE); + } + else { + _worksheet_write_cfvo_num(self, cond_format->max_rule_type, + cond_format->max_value, LXW_FALSE); + } + + _worksheet_write_color(self, cond_format->min_color); + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) + _worksheet_write_color(self, cond_format->mid_color); + + _worksheet_write_color(self, cond_format->max_color); + + lxw_xml_end_tag(self->file, "colorScale"); + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for formula rules. + */ +STATIC void +_worksheet_write_cf_rule_formula(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + _worksheet_write_formula_str(self, cond_format->min_value_string); + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for top and bottom rules. + */ +STATIC void +_worksheet_write_cf_rule_top(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + if (cond_format->criteria == + LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT) + LXW_PUSH_ATTRIBUTES_INT("percent", 1); + + if (cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) + LXW_PUSH_ATTRIBUTES_INT("bottom", 1); + + /* Rank must be an int in the range 1-1000 . */ + if (cond_format->min_value < 1.0 || cond_format->min_value > 1000.0) + LXW_PUSH_ATTRIBUTES_DBL("rank", 10); + else + LXW_PUSH_ATTRIBUTES_DBL("rank", (uint16_t) cond_format->min_value); + + lxw_xml_empty_tag(self->file, "cfRule", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for unique/duplicate rules. + */ +STATIC void +_worksheet_write_cf_rule_duplicate(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + /* Set the attributes common to all rule types. */ + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + lxw_xml_empty_tag(self->file, "cfRule", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for averages rules. + */ +STATIC void +_worksheet_write_cf_rule_average(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint8_t criteria = cond_format->criteria; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("aboveAverage", 0); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE_OR_EQUAL + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL) + LXW_PUSH_ATTRIBUTES_INT("equalAverage", 1); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_ABOVE + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("stdDev", 1); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_ABOVE + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("stdDev", 2); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_ABOVE + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("stdDev", 3); + + lxw_xml_empty_tag(self->file, "cfRule", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for time_period rules. + */ +STATIC void +_worksheet_write_cf_rule_time_period(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char formula[LXW_MAX_ATTRIBUTE_LENGTH]; + uint8_t pos; + uint8_t criteria = cond_format->criteria; + char *first_cell = cond_format->first_cell; + char *time_periods[] = { + "yesterday", + "today", + "tomorrow", + "last7Days", + "lastWeek", + "thisWeek", + "nextWeek", + "lastMonth", + "thisMonth", + "nextMonth", + }; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + pos = criteria - LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY; + LXW_PUSH_ATTRIBUTES_STR("timePeriod", time_periods[pos]); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "FLOOR(%s,1)=TODAY()-1", first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TODAY) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "FLOOR(%s,1)=TODAY()", first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TOMORROW) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "FLOOR(%s,1)=TODAY()+1", first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_7_DAYS) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_WEEK) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY()))," + "TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_WEEK) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1," + "ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_WEEK) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY()))," + "ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_MONTH) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(MONTH(%s)=MONTH(TODAY())-1,OR(YEAR(%s)=YEAR(" + "TODAY()),AND(MONTH(%s)=1,YEAR(A1)=YEAR(TODAY())-1)))", + first_cell, first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_MONTH) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(MONTH(%s)=MONTH(TODAY())+1,OR(YEAR(%s)=YEAR(" + "TODAY()),AND(MONTH(%s)=12,YEAR(%s)=YEAR(TODAY())+1)))", + first_cell, first_cell, first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for blanks/no_blanks, errors/no_errors rules. + */ +STATIC void +_worksheet_write_cf_rule_blanks(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char formula[LXW_ATTR_32]; + uint8_t type = cond_format->type; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (type == LXW_CONDITIONAL_TYPE_BLANKS) { + lxw_snprintf(formula, LXW_ATTR_32, "LEN(TRIM(%s))=0", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (type == LXW_CONDITIONAL_TYPE_NO_BLANKS) { + lxw_snprintf(formula, LXW_ATTR_32, "LEN(TRIM(%s))>0", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (type == LXW_CONDITIONAL_TYPE_ERRORS) { + lxw_snprintf(formula, LXW_ATTR_32, "ISERROR(%s)", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (type == LXW_CONDITIONAL_TYPE_NO_ERRORS) { + lxw_snprintf(formula, LXW_ATTR_32, "NOT(ISERROR(%s))", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for text rules. + */ +STATIC void +_worksheet_write_cf_rule_text(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint8_t pos; + char formula[LXW_ATTR_32 * 2]; + char *operators[] = { + "containsText", + "notContains", + "beginsWith", + "endsWith", + }; + uint8_t criteria = cond_format->criteria; + + LXW_INIT_ATTRIBUTES(); + + if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING) + LXW_PUSH_ATTRIBUTES_STR("type", "containsText"); + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING) + LXW_PUSH_ATTRIBUTES_STR("type", "notContainsText"); + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH) + LXW_PUSH_ATTRIBUTES_STR("type", "beginsWith"); + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) + LXW_PUSH_ATTRIBUTES_STR("type", "endsWith"); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + pos = criteria - LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING; + LXW_PUSH_ATTRIBUTES_STR("operator", operators[pos]); + + LXW_PUSH_ATTRIBUTES_STR("text", cond_format->min_value_string); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "NOT(ISERROR(SEARCH(\"%s\",%s)))", + cond_format->min_value_string, cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "ISERROR(SEARCH(\"%s\",%s))", + cond_format->min_value_string, cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "LEFT(%s,%d)=\"%s\"", + cond_format->first_cell, + (uint16_t) strlen(cond_format->min_value_string), + cond_format->min_value_string); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "RIGHT(%s,%d)=\"%s\"", + cond_format->first_cell, + (uint16_t) strlen(cond_format->min_value_string), + cond_format->min_value_string); + _worksheet_write_formula_str(self, formula); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element for cell rules. + */ +STATIC void +_worksheet_write_cf_rule_cell(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char *operators[] = { + "none", + "equal", + "notEqual", + "greaterThan", + "lessThan", + "greaterThanOrEqual", + "lessThanOrEqual", + "between", + "notBetween", + }; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + LXW_PUSH_ATTRIBUTES_STR("operator", operators[cond_format->criteria]); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (cond_format->min_value_string) + _worksheet_write_formula_str(self, cond_format->min_value_string); + else + _worksheet_write_formula_num(self, cond_format->min_value); + + if (cond_format->has_max) { + if (cond_format->max_value_string) + _worksheet_write_formula_str(self, cond_format->max_value_string); + else + _worksheet_write_formula_num(self, cond_format->max_value); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <cfRule> element. + */ +STATIC void +_worksheet_write_cf_rule(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + if (cond_format->type == LXW_CONDITIONAL_TYPE_CELL) { + + _worksheet_write_cf_rule_cell(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TEXT) { + + _worksheet_write_cf_rule_text(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TIME_PERIOD) { + + _worksheet_write_cf_rule_time_period(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_DUPLICATE + || cond_format->type == LXW_CONDITIONAL_TYPE_UNIQUE) { + + _worksheet_write_cf_rule_duplicate(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_AVERAGE) { + + _worksheet_write_cf_rule_average(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TOP + || cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) { + + _worksheet_write_cf_rule_top(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_BLANKS + || cond_format->type == LXW_CONDITIONAL_TYPE_NO_BLANKS + || cond_format->type == LXW_CONDITIONAL_TYPE_ERRORS + || cond_format->type == LXW_CONDITIONAL_TYPE_NO_ERRORS) { + + _worksheet_write_cf_rule_blanks(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_FORMULA) { + + _worksheet_write_cf_rule_formula(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_2_COLOR_SCALE + || cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + + _worksheet_write_cf_rule_color_scale(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_DATA_BAR) { + + _worksheet_write_cf_rule_data_bar(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_ICON_SETS) { + + _worksheet_write_cf_rule_icons(self, cond_format); + } + +} + +/* + * Write the <conditionalFormatting> element. + */ +STATIC void +_worksheet_write_conditional_formatting(lxw_worksheet *self, + lxw_cond_format_hash_element *element) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_cond_format_obj *cond_format; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("sqref", element->sqref); + + lxw_xml_start_tag(self->file, "conditionalFormatting", &attributes); + + STAILQ_FOREACH(cond_format, element->cond_formats, list_pointers) { + /* Write the cfRule element. */ + _worksheet_write_cf_rule(self, cond_format); + } + + lxw_xml_end_tag(self->file, "conditionalFormatting"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the conditional formatting> elements. + */ +STATIC void +_worksheet_write_conditional_formats(lxw_worksheet *self) +{ + lxw_cond_format_hash_element *element; + lxw_cond_format_hash_element *next_element; + + for (element = RB_MIN(lxw_cond_format_hash, self->conditional_formats); + element; element = next_element) { + + _worksheet_write_conditional_formatting(self, element); + + next_element = + RB_NEXT(lxw_cond_format_hash, self->conditional_formats, element); + } +} + +/* + * Write the <x14:xxxColor> elements for data bar conditional formats. + */ +STATIC void +_worksheet_write_x14_color(lxw_worksheet *self, char *type, lxw_color_t color) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char rgb[LXW_ATTR_32]; + + lxw_snprintf(rgb, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("rgb", rgb); + lxw_xml_empty_tag(self->file, type, &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <x14:cfvo> element. + */ +STATIC void +_worksheet_write_x14_cfvo(lxw_worksheet *self, uint8_t rule_type, + double number, char *string) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char data[LXW_ATTR_32]; + uint8_t has_value = LXW_FALSE; + + LXW_INIT_ATTRIBUTES(); + + if (!string) + lxw_sprintf_dbl(data, number); + + if (rule_type == LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN) { + LXW_PUSH_ATTRIBUTES_STR("type", "autoMin"); + has_value = LXW_FALSE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) { + LXW_PUSH_ATTRIBUTES_STR("type", "min"); + has_value = LXW_FALSE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) { + LXW_PUSH_ATTRIBUTES_STR("type", "num"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) { + LXW_PUSH_ATTRIBUTES_STR("type", "percent"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) { + LXW_PUSH_ATTRIBUTES_STR("type", "percentile"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) { + LXW_PUSH_ATTRIBUTES_STR("type", "formula"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + LXW_PUSH_ATTRIBUTES_STR("type", "max"); + has_value = LXW_FALSE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX) { + LXW_PUSH_ATTRIBUTES_STR("type", "autoMax"); + has_value = LXW_FALSE; + } + + if (has_value) { + lxw_xml_start_tag(self->file, "x14:cfvo", &attributes); + + if (string) + lxw_xml_data_element(self->file, "xm:f", string, NULL); + else + lxw_xml_data_element(self->file, "xm:f", data, NULL); + + lxw_xml_end_tag(self->file, "x14:cfvo"); + } + else { + lxw_xml_empty_tag(self->file, "x14:cfvo", &attributes); + } + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <x14:dataBar> element. + */ +STATIC void +_worksheet_write_x14_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char min_length[] = "0"; + char max_length[] = "100"; + char border[] = "1"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("minLength", min_length); + LXW_PUSH_ATTRIBUTES_STR("maxLength", max_length); + + if (!cond_format->bar_no_border) + LXW_PUSH_ATTRIBUTES_STR("border", border); + + if (cond_format->bar_solid) + LXW_PUSH_ATTRIBUTES_STR("gradient", "0"); + + if (cond_format->bar_direction == + LXW_CONDITIONAL_BAR_DIRECTION_RIGHT_TO_LEFT) + LXW_PUSH_ATTRIBUTES_STR("direction", "rightToLeft"); + + if (cond_format->bar_direction == + LXW_CONDITIONAL_BAR_DIRECTION_LEFT_TO_RIGHT) + LXW_PUSH_ATTRIBUTES_STR("direction", "leftToRight"); + + if (cond_format->bar_negative_color_same) + LXW_PUSH_ATTRIBUTES_STR("negativeBarColorSameAsPositive", "1"); + + if (!cond_format->bar_no_border + && !cond_format->bar_negative_border_color_same) + LXW_PUSH_ATTRIBUTES_STR("negativeBarBorderColorSameAsPositive", "0"); + + if (cond_format->bar_axis_position == LXW_CONDITIONAL_BAR_AXIS_MIDPOINT) + LXW_PUSH_ATTRIBUTES_STR("axisPosition", "middle"); + + if (cond_format->bar_axis_position == LXW_CONDITIONAL_BAR_AXIS_NONE) + LXW_PUSH_ATTRIBUTES_STR("axisPosition", "none"); + + lxw_xml_start_tag(self->file, "x14:dataBar", &attributes); + + if (cond_format->auto_min) + cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN; + + _worksheet_write_x14_cfvo(self, cond_format->min_rule_type, + cond_format->min_value, + cond_format->min_value_string); + + if (cond_format->auto_max) + cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX; + + _worksheet_write_x14_cfvo(self, cond_format->max_rule_type, + cond_format->max_value, + cond_format->max_value_string); + + if (!cond_format->bar_no_border) + _worksheet_write_x14_color(self, "x14:borderColor", + cond_format->bar_border_color); + + if (!cond_format->bar_negative_color_same) + _worksheet_write_x14_color(self, "x14:negativeFillColor", + cond_format->bar_negative_color); + + if (!cond_format->bar_no_border + && !cond_format->bar_negative_border_color_same) + _worksheet_write_x14_color(self, "x14:negativeBorderColor", + cond_format->bar_negative_border_color); + + if (cond_format->bar_axis_position != LXW_CONDITIONAL_BAR_AXIS_NONE) + _worksheet_write_x14_color(self, "x14:axisColor", + cond_format->bar_axis_color); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <x14:cfRule> element. + */ +STATIC void +_worksheet_write_x14_cf_rule(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("type", "dataBar"); + LXW_PUSH_ATTRIBUTES_STR("id", cond_format->guid); + + lxw_xml_start_tag(self->file, "x14:cfRule", &attributes); + + /* Write the x14:dataBar element. */ + _worksheet_write_x14_data_bar(self, cond_format); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <xm:sqref> element. + */ +STATIC void +_worksheet_write_xm_sqref(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + lxw_xml_data_element(self->file, "xm:sqref", cond_format->sqref, NULL); +} + +/* + * Write the <conditionalFormatting> element. + */ +STATIC void +_worksheet_write_conditional_formatting_2010(lxw_worksheet *self, lxw_cond_format_hash_element + *element) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_cond_format_obj *cond_format; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns:xm", + "http://schemas.microsoft.com/office/excel/2006/main"); + + STAILQ_FOREACH(cond_format, element->cond_formats, list_pointers) { + if (!cond_format->data_bar_2010) + continue; + + lxw_xml_start_tag(self->file, "x14:conditionalFormatting", + &attributes); + + _worksheet_write_x14_cf_rule(self, cond_format); + + lxw_xml_end_tag(self->file, "x14:dataBar"); + lxw_xml_end_tag(self->file, "x14:cfRule"); + _worksheet_write_xm_sqref(self, cond_format); + lxw_xml_end_tag(self->file, "x14:conditionalFormatting"); + } + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <extLst> element for Excel 2010 conditional formatting data bars. + */ +STATIC void +_worksheet_write_ext_list_data_bars(lxw_worksheet *self) +{ + lxw_cond_format_hash_element *element; + lxw_cond_format_hash_element *next_element; + + _worksheet_write_ext(self, "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"); + lxw_xml_start_tag(self->file, "x14:conditionalFormattings", NULL); + + for (element = RB_MIN(lxw_cond_format_hash, self->conditional_formats); + element; element = next_element) { + + _worksheet_write_conditional_formatting_2010(self, element); + + next_element = + RB_NEXT(lxw_cond_format_hash, self->conditional_formats, element); + } + + lxw_xml_end_tag(self->file, "x14:conditionalFormattings"); + lxw_xml_end_tag(self->file, "ext"); +} + +/* + * Write the <extLst> element. + */ +STATIC void +_worksheet_write_ext_list(lxw_worksheet *self) +{ + if (self->data_bar_2010_index == 0) + return; + + lxw_xml_start_tag(self->file, "extLst", NULL); + + _worksheet_write_ext_list_data_bars(self); + + lxw_xml_end_tag(self->file, "extLst"); +} + +/* + * Write the <ignoredError> element. + */ +STATIC void +_worksheet_write_ignored_error(lxw_worksheet *self, char *ignore_error, + char *range) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("sqref", range); + LXW_PUSH_ATTRIBUTES_STR(ignore_error, "1"); + + lxw_xml_empty_tag(self->file, "ignoredError", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +lxw_error +_validate_conditional_icons(lxw_conditional_format *user) +{ + if (user->icon_style > LXW_CONDITIONAL_ICONS_5_QUARTERS) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_ICON_SETS, " + "invalid icon_style (%d).", user->icon_style); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + else { + return LXW_NO_ERROR; + } +} + +lxw_error +_validate_conditional_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + uint8_t min_rule_type = user_options->min_rule_type; + uint8_t max_rule_type = user_options->max_rule_type; + + if (user_options->data_bar_2010 + || user_options->bar_solid + || user_options->bar_no_border + || user_options->bar_direction + || user_options->bar_axis_position + || user_options->bar_negative_color_same + || user_options->bar_negative_border_color_same + || user_options->bar_negative_color + || user_options->bar_border_color + || user_options->bar_negative_border_color + || user_options->bar_axis_color) { + + cond_format->data_bar_2010 = LXW_TRUE; + self->excel_version = 2010; + } + + if (min_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && min_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->min_rule_type = min_rule_type; + cond_format->min_value = user_options->min_value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->min_value_string); + } + else { + cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_MINIMUM; + cond_format->min_value = 0; + } + + if (max_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && max_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->max_rule_type = max_rule_type; + cond_format->max_value = user_options->max_value; + cond_format->max_value_string = + lxw_strdup_formula(user_options->max_value_string); + } + else { + cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_MAXIMUM; + cond_format->max_value = 0; + } + + if (cond_format->data_bar_2010) { + if (min_rule_type == LXW_CONDITIONAL_RULE_TYPE_NONE) + cond_format->auto_min = LXW_TRUE; + if (max_rule_type == LXW_CONDITIONAL_RULE_TYPE_NONE) + cond_format->auto_max = LXW_TRUE; + } + + cond_format->bar_only = user_options->bar_only; + cond_format->bar_solid = user_options->bar_solid; + cond_format->bar_no_border = user_options->bar_no_border; + cond_format->bar_direction = user_options->bar_direction; + cond_format->bar_axis_position = user_options->bar_axis_position; + cond_format->bar_negative_color_same = + user_options->bar_negative_color_same; + cond_format->bar_negative_border_color_same = + user_options->bar_negative_border_color_same; + + if (user_options->bar_color != LXW_COLOR_UNSET) + cond_format->bar_color = user_options->bar_color; + else + cond_format->bar_color = 0x638EC6; + + if (user_options->bar_negative_color != LXW_COLOR_UNSET) + cond_format->bar_negative_color = user_options->bar_negative_color; + else + cond_format->bar_negative_color = 0xFF0000; + + if (user_options->bar_border_color != LXW_COLOR_UNSET) + cond_format->bar_border_color = user_options->bar_border_color; + else + cond_format->bar_border_color = cond_format->bar_color; + + if (user_options->bar_negative_border_color != LXW_COLOR_UNSET) + cond_format->bar_negative_border_color = + user_options->bar_negative_border_color; + else + cond_format->bar_negative_border_color = 0xFF0000; + + if (user_options->bar_axis_color != LXW_COLOR_UNSET) + cond_format->bar_axis_color = user_options->bar_axis_color; + else + cond_format->bar_axis_color = 0x000000; + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_scale(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + uint8_t min_rule_type = user_options->min_rule_type; + uint8_t mid_rule_type = user_options->mid_rule_type; + uint8_t max_rule_type = user_options->max_rule_type; + + if (min_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && min_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->min_rule_type = min_rule_type; + cond_format->min_value = user_options->min_value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->min_value_string); + } + else { + cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_MINIMUM; + cond_format->min_value = 0; + } + + if (max_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && max_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->max_rule_type = max_rule_type; + cond_format->max_value = user_options->max_value; + cond_format->max_value_string = + lxw_strdup_formula(user_options->max_value_string); + } + else { + cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_MAXIMUM; + cond_format->max_value = 0; + } + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + if (mid_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && mid_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->mid_rule_type = mid_rule_type; + cond_format->mid_value = user_options->mid_value; + cond_format->mid_value_string = + lxw_strdup_formula(user_options->mid_value_string); + } + else { + cond_format->mid_rule_type = LXW_CONDITIONAL_RULE_TYPE_PERCENTILE; + cond_format->mid_value = 50; + } + } + + if (user_options->min_color != LXW_COLOR_UNSET) + cond_format->min_color = user_options->min_color; + else + cond_format->min_color = 0xFF7128; + + if (user_options->max_color != LXW_COLOR_UNSET) + cond_format->max_color = user_options->max_color; + else + cond_format->max_color = 0xFFEF9C; + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + if (user_options->min_color == LXW_COLOR_UNSET) + cond_format->min_color = 0xF8696B; + + if (user_options->mid_color != LXW_COLOR_UNSET) + cond_format->mid_color = user_options->mid_color; + else + cond_format->mid_color = 0xFFEB84; + + if (user_options->max_color == LXW_COLOR_UNSET) + cond_format->max_color = 0x63BE7B; + } + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_top(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + /* Restrict the range of rank values to Excel's allowed range. */ + if (user_options->criteria == + LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT) { + if (user_options->value < 0.0 || user_options->value > 100.0) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TOP/BOTTOM, " + "top/bottom percent (%g%%) must by in range 0-100", + user_options->value); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + else { + if (user_options->value < 1.0 || user_options->value > 1000.0) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TOP/BOTTOM, " + "top/bottom items (%g) must by in range 1-1000", + user_options->value); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + + cond_format->min_value = (uint16_t) user_options->value; + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_average(lxw_conditional_format *user) +{ + if (user->criteria < LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE || + user->criteria > LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_AVERAGE, " + "invalid criteria value (%d).", user->criteria); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + else { + return LXW_NO_ERROR; + } +} + +lxw_error +_validate_conditional_time_period(lxw_conditional_format *user) +{ + if (user->criteria < LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY || + user->criteria > LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TIME_PERIOD, " + "invalid criteria value (%d).", user->criteria); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + else { + return LXW_NO_ERROR; + } +} + +lxw_error +_validate_conditional_text(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + if (!user_options->value_string) { + + LXW_WARN_FORMAT("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TEXT, " + "value_string can not be NULL. " + "Text must be specified."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (strlen(user_options->value_string) >= LXW_MAX_ATTRIBUTE_LENGTH) { + + LXW_WARN_FORMAT2("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TEXT, " + "value_string length (%d) must be less than %d.", + (uint16_t) strlen(user_options->value_string), + LXW_MAX_ATTRIBUTE_LENGTH); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (user_options->criteria < LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING || + user_options->criteria > LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TEXT, " + "invalid criteria value (%d).", + user_options->criteria); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + cond_format->min_value_string = + lxw_strdup_formula(user_options->value_string); + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_formula(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + if (!user_options->value_string) { + + LXW_WARN_FORMAT("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_FORMULA, " + "value_string can not be NULL. " + "Formula must be specified."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + cond_format->min_value_string = + lxw_strdup_formula(user_options->value_string); + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_cell(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + cond_format->min_value = user_options->value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->value_string); + + if (cond_format->criteria == LXW_CONDITIONAL_CRITERIA_BETWEEN + || cond_format->criteria == LXW_CONDITIONAL_CRITERIA_NOT_BETWEEN) { + cond_format->has_max = LXW_TRUE; + cond_format->min_value = user_options->min_value; + cond_format->max_value = user_options->max_value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->min_value_string); + cond_format->max_value_string = + lxw_strdup_formula(user_options->max_value_string); + } + + return LXW_NO_ERROR; +} + +/* + * Write the <ignoredErrors> element. + */ +STATIC void +_worksheet_write_ignored_errors(lxw_worksheet *self) +{ + if (!self->has_ignore_errors) + return; + + lxw_xml_start_tag(self->file, "ignoredErrors", NULL); + + if (self->ignore_number_stored_as_text) { + _worksheet_write_ignored_error(self, "numberStoredAsText", + self->ignore_number_stored_as_text); + } + + if (self->ignore_eval_error) { + _worksheet_write_ignored_error(self, "evalError", + self->ignore_eval_error); + } + + if (self->ignore_formula_differs) { + _worksheet_write_ignored_error(self, "formula", + self->ignore_formula_differs); + } + + if (self->ignore_formula_range) { + _worksheet_write_ignored_error(self, "formulaRange", + self->ignore_formula_range); + } + + if (self->ignore_formula_unlocked) { + _worksheet_write_ignored_error(self, "unlockedFormula", + self->ignore_formula_unlocked); + } + + if (self->ignore_empty_cell_reference) { + _worksheet_write_ignored_error(self, "emptyCellReference", + self->ignore_empty_cell_reference); + } + + if (self->ignore_list_data_validation) { + _worksheet_write_ignored_error(self, "listDataValidation", + self->ignore_list_data_validation); + } + + if (self->ignore_calculated_column) { + _worksheet_write_ignored_error(self, "calculatedColumn", + self->ignore_calculated_column); + } + + if (self->ignore_two_digit_text_year) { + _worksheet_write_ignored_error(self, "twoDigitTextYear", + self->ignore_two_digit_text_year); + } + + lxw_xml_end_tag(self->file, "ignoredErrors"); +} + +/* + * Write the <tablePart> element. + */ +STATIC void +_worksheet_write_table_part(lxw_worksheet *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, "tablePart", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <tableParts> element. + */ +STATIC void +_worksheet_write_table_parts(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_table_obj *table_obj; + + if (!self->table_count) + return; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("count", self->table_count); + + lxw_xml_start_tag(self->file, "tableParts", &attributes); + + STAILQ_FOREACH(table_obj, self->table_objs, list_pointers) { + self->rel_count++; + + /* Write the tablePart element. */ + _worksheet_write_table_part(self, self->rel_count); + } + + lxw_xml_end_tag(self->file, "tableParts"); + + LXW_FREE_ATTRIBUTES(); +} + +/* * External functions to call intern XML methods shared with chartsheet. */ void lxw_worksheet_write_sheet_views(lxw_worksheet *self) { @@ -3757,11 +7622,11 @@ _worksheet_write_drawings(self); } void lxw_worksheet_write_sheet_protection(lxw_worksheet *self, - lxw_protection *protect) + lxw_protection_obj *protect) { _worksheet_write_sheet_protection(self, protect); } void @@ -3822,10 +7687,13 @@ _worksheet_write_auto_filter(self); /* Write the mergeCells element. */ _worksheet_write_merge_cells(self); + /* Write the conditionalFormatting elements. */ + _worksheet_write_conditional_formats(self); + /* Write the dataValidations element. */ _worksheet_write_data_validations(self); /* Write the hyperlink element. */ _worksheet_write_hyperlinks(self); @@ -3846,13 +7714,31 @@ _worksheet_write_row_breaks(self); /* Write the colBreaks element. */ _worksheet_write_col_breaks(self); + /* Write the ignoredErrors element. */ + _worksheet_write_ignored_errors(self); + /* Write the drawing element. */ _worksheet_write_drawings(self); + /* Write the legacyDrawing element. */ + _worksheet_write_legacy_drawing(self); + + /* Write the legacyDrawingHF element. */ + _worksheet_write_legacy_drawing_hf(self); + + /* Write the picture element. */ + _worksheet_write_picture(self); + + /* Write the tableParts element. */ + _worksheet_write_table_parts(self); + + /* Write the extLst element. */ + _worksheet_write_ext_list(self); + /* Close the worksheet tag. */ lxw_xml_end_tag(self->file, "worksheet"); } /***************************************************************************** @@ -3925,13 +7811,11 @@ cell = _new_string_cell(row_num, col_num, string_id, sst_element->string, format); } else { /* Look for and escape control chars in the string. */ - if (strpbrk(string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C" - "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16" - "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) { + if (lxw_has_control_characters(string)) { string_copy = lxw_escape_control_characters(string); } else { string_copy = lxw_strdup(string); } @@ -3977,10 +7861,45 @@ return LXW_NO_ERROR; } /* + * Write a formula with a string result to a cell in Excel. + */ +lxw_error +worksheet_write_formula_str(lxw_worksheet *self, + lxw_row_t row_num, + lxw_col_t col_num, + const char *formula, + lxw_format *format, const char *result) +{ + lxw_cell *cell; + char *formula_copy; + lxw_error err; + + if (!formula) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + /* Strip leading "=" from formula. */ + if (formula[0] == '=') + formula_copy = lxw_strdup(formula + 1); + else + formula_copy = lxw_strdup(formula); + + cell = _new_formula_cell(row_num, col_num, formula_copy, format); + cell->user_data2 = lxw_strdup(result); + + _insert_cell(self, row_num, col_num, cell); + + return LXW_NO_ERROR; +} + +/* * Write a formula with a default result to a cell in Excel . */ lxw_error worksheet_write_formula(lxw_worksheet *self, lxw_row_t row_num, @@ -3990,20 +7909,20 @@ return worksheet_write_formula_num(self, row_num, col_num, formula, format, 0); } /* - * Write a formula with a numerical result to a cell in Excel. + * Internal shared function for various array formula functions. */ lxw_error -worksheet_write_array_formula_num(lxw_worksheet *self, - lxw_row_t first_row, - lxw_col_t first_col, - lxw_row_t last_row, - lxw_col_t last_col, - const char *formula, - lxw_format *format, double result) +_store_array_formula(lxw_worksheet *self, + lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + const char *formula, lxw_format *format, double result, + uint8_t is_dynamic) { lxw_cell *cell; lxw_row_t tmp_row; lxw_col_t tmp_col; char *formula_copy; @@ -4023,45 +7942,52 @@ } if (!formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; - /* Check that column number is valid and store the max value */ + /* Check that row and col are valid and store max and min values. */ + err = _check_dimensions(self, first_row, first_col, LXW_FALSE, LXW_FALSE); + if (err) + return err; + err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE); if (err) return err; /* Define the array range. */ range = calloc(1, LXW_MAX_CELL_RANGE_LENGTH); RETURN_ON_MEM_ERROR(range, LXW_ERROR_MEMORY_MALLOC_FAILED); if (first_row == last_row && first_col == last_col) - lxw_rowcol_to_cell(range, first_row, last_col); + lxw_rowcol_to_cell(range, first_row, first_col); else lxw_rowcol_to_range(range, first_row, first_col, last_row, last_col); /* Copy and trip leading "{=" from formula. */ if (formula[0] == '{') if (formula[1] == '=') formula_copy = lxw_strdup(formula + 2); else formula_copy = lxw_strdup(formula + 1); else - formula_copy = lxw_strdup(formula); + formula_copy = lxw_strdup_formula(formula); /* Strip trailing "}" from formula. */ if (formula_copy[strlen(formula_copy) - 1] == '}') formula_copy[strlen(formula_copy) - 1] = '\0'; /* Create a new array formula cell object. */ cell = _new_array_formula_cell(first_row, first_col, - formula_copy, range, format); + formula_copy, range, format, is_dynamic); cell->formula_result = result; _insert_cell(self, first_row, first_col, cell); + if (is_dynamic) + self->has_dynamic_arrays = LXW_TRUE; + /* Pad out the rest of the area with formatted zeroes. */ if (!self->optimize) { for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) { for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) { if (tmp_row == first_row && tmp_col == first_col) @@ -4074,26 +8000,103 @@ return LXW_NO_ERROR; } /* - * Write an array formula with a default result to a cell in Excel . + * Write an array formula with a numerical result to a cell in Excel. */ lxw_error +worksheet_write_array_formula_num(lxw_worksheet *self, + lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + const char *formula, + lxw_format *format, double result) +{ + return _store_array_formula(self, first_row, first_col, + last_row, last_col, formula, format, result, + LXW_FALSE); +} + +/* + * Write an array formula with a default result to a cell in Excel. + */ +lxw_error worksheet_write_array_formula(lxw_worksheet *self, lxw_row_t first_row, lxw_col_t first_col, lxw_row_t last_row, lxw_col_t last_col, const char *formula, lxw_format *format) { - return worksheet_write_array_formula_num(self, first_row, first_col, - last_row, last_col, formula, - format, 0); + return _store_array_formula(self, first_row, first_col, + last_row, last_col, formula, format, 0, + LXW_FALSE); } /* + * Write a single cell dynamic array formula with a default result to a cell. + */ +lxw_error +worksheet_write_dynamic_formula(lxw_worksheet *self, + lxw_row_t row, + lxw_col_t col, + const char *formula, lxw_format *format) +{ + return _store_array_formula(self, row, col, row, col, formula, format, 0, + LXW_TRUE); +} + +/* + * Write a single cell dynamic array formula with a numerical result to a cell. + */ +lxw_error +worksheet_write_dynamic_formula_num(lxw_worksheet *self, + lxw_row_t row, + lxw_col_t col, + const char *formula, + lxw_format *format, double result) +{ + return _store_array_formula(self, row, col, row, col, formula, format, + result, LXW_TRUE); +} + +/* + * Write a dynamic array formula with a numerical result to a cell in Excel. + */ +lxw_error +worksheet_write_dynamic_array_formula_num(lxw_worksheet *self, + lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + const char *formula, + lxw_format *format, double result) +{ + return _store_array_formula(self, first_row, first_col, + last_row, last_col, formula, format, result, + LXW_TRUE); +} + +/* + * Write a dynamic array formula with a default result to a cell in Excel. + */ +lxw_error +worksheet_write_dynamic_array_formula(lxw_worksheet *self, + lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + const char *formula, lxw_format *format) +{ + return _store_array_formula(self, first_row, first_col, + last_row, last_col, formula, format, 0, + LXW_TRUE); +} + +/* * Write a blank cell with a format to a cell in Excel. */ lxw_error worksheet_write_blank(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, @@ -4127,11 +8130,10 @@ { lxw_cell *cell; lxw_error err; err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); - if (err) return err; cell = _new_boolean_cell(row_num, col_num, value, format); @@ -4155,52 +8157,86 @@ err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; - excel_date = lxw_datetime_to_excel_date(datetime, LXW_EPOCH_1900); + excel_date = lxw_datetime_to_excel_date_epoch(datetime, LXW_EPOCH_1900); cell = _new_number_cell(row_num, col_num, excel_date, format); _insert_cell(self, row_num, col_num, cell); return LXW_NO_ERROR; } /* + * Write a date and or time to a cell in Excel. + */ +lxw_error +worksheet_write_unixtime(lxw_worksheet *self, + lxw_row_t row_num, + lxw_col_t col_num, + int64_t unixtime, lxw_format *format) +{ + lxw_cell *cell; + double excel_date; + lxw_error err; + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + excel_date = lxw_unixtime_to_excel_date_epoch(unixtime, LXW_EPOCH_1900); + + cell = _new_number_cell(row_num, col_num, excel_date, format); + + _insert_cell(self, row_num, col_num, cell); + + return LXW_NO_ERROR; +} + +/* * Write a hyperlink/url to an Excel file. */ lxw_error worksheet_write_url_opt(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *url, - lxw_format *format, const char *string, + lxw_format *user_format, const char *string, const char *tooltip) { lxw_cell *link; char *string_copy = NULL; char *url_copy = NULL; char *url_external = NULL; char *url_string = NULL; char *tooltip_copy = NULL; char *found_string; - lxw_error err; + char *tmp_string = NULL; + lxw_format *format = NULL; size_t string_size; size_t i; + lxw_error err = LXW_ERROR_MEMORY_MALLOC_FAILED; enum cell_types link_type = HYPERLINK_URL; if (!url || !*url) return LXW_ERROR_NULL_PARAMETER_IGNORED; /* Check the Excel limit of URLS per worksheet. */ - if (self->hlink_count > LXW_MAX_NUMBER_URLS) + if (self->hlink_count > LXW_MAX_NUMBER_URLS) { + LXW_WARN("worksheet_write_url()/_opt(): URL ignored since it exceeds " + "the maximum number of allowed worksheet URLs (65530)."); return LXW_ERROR_WORKSHEET_MAX_NUMBER_URLS_EXCEEDED; + } err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; + /* Reset default error condition. */ + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + /* Set the URI scheme from internal links. */ found_string = strstr(url, "internal:"); if (found_string) link_type = HYPERLINK_INTERNAL; @@ -4245,84 +8281,43 @@ if (link_type == HYPERLINK_INTERNAL) { url_string = lxw_strdup(string_copy); GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error); } - /* Escape the URL. */ - if (link_type == HYPERLINK_URL && strlen(url_copy) >= 3) { - uint8_t not_escaped = 1; + /* Split url into the link and optional anchor/location. */ + found_string = strchr(url_copy, '#'); - /* First check if the URL is already escaped by the user. */ - for (i = 0; i <= strlen(url_copy) - 3; i++) { - if (url_copy[i] == '%' && isxdigit(url_copy[i + 1]) - && isxdigit(url_copy[i + 2])) { + if (found_string) { + free(url_string); + url_string = lxw_strdup(found_string + 1); + GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error); - not_escaped = 0; - break; - } - } + *found_string = '\0'; + } - if (not_escaped) { - url_external = calloc(1, strlen(url_copy) * 3 + 1); - GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error); + /* Escape the URL. */ + if (link_type == HYPERLINK_URL || link_type == HYPERLINK_EXTERNAL) { + tmp_string = lxw_escape_url_characters(url_copy, LXW_FALSE); + GOTO_LABEL_ON_MEM_ERROR(tmp_string, mem_error); - for (i = 0; i <= strlen(url_copy); i++) { - switch (url_copy[i]) { - case (' '): - case ('"'): - case ('%'): - case ('<'): - case ('>'): - case ('['): - case (']'): - case ('`'): - case ('^'): - case ('{'): - case ('}'): - lxw_snprintf(url_external + strlen(url_external), - sizeof("%xx"), "%%%2x", url_copy[i]); - break; - default: - url_external[strlen(url_external)] = url_copy[i]; - } - - } - - free(url_copy); - url_copy = lxw_strdup(url_external); - GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error); - - free(url_external); - url_external = NULL; - } + free(url_copy); + url_copy = tmp_string; } if (link_type == HYPERLINK_EXTERNAL) { /* External Workbook links need to be modified into the right format. - * The URL will look something like "c:\temp\file.xlsx#Sheet!A1". - * We need the part to the left of the # as the URL and the part to - * the right as the "location" string (if it exists). - */ + * The URL will look something like "c:\temp\file.xlsx#Sheet!A1". */ /* For external links change the dir separator from Unix to DOS. */ for (i = 0; i <= strlen(url_copy); i++) if (url_copy[i] == '/') url_copy[i] = '\\'; for (i = 0; i <= strlen(string_copy); i++) if (string_copy[i] == '/') string_copy[i] = '\\'; - found_string = strchr(url_copy, '#'); - - if (found_string) { - url_string = lxw_strdup(found_string + 1); - GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error); - - *found_string = '\0'; - } - /* Look for Windows style "C:/" link or Windows share "\\" link. */ found_string = strchr(url_copy, ':'); if (!found_string) found_string = strstr(url_copy, "\\\\"); @@ -4350,18 +8345,32 @@ url_external = NULL; } } - /* Excel limits escaped URL to 255 characters. */ - if (lxw_utf8_strlen(url_copy) > 255) + /* Check if URL exceeds Excel's length limit. */ + if (lxw_utf8_strlen(url_copy) > self->max_url_length) { + LXW_WARN_FORMAT2("worksheet_write_url()/_opt(): URL exceeds " + "Excel's allowable length of %d characters: %s", + self->max_url_length, url_copy); + err = LXW_ERROR_WORKSHEET_MAX_URL_LENGTH_EXCEEDED; goto mem_error; + } + /* Use the default URL format if none is specified. */ + if (!user_format) + format = self->default_url_format; + else + format = user_format; + err = worksheet_write_string(self, row_num, col_num, string_copy, format); if (err) goto mem_error; + /* Reset default error condition. */ + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + link = _new_hyperlink_cell(row_num, col_num, link_type, url_copy, url_string, tooltip_copy); GOTO_LABEL_ON_MEM_ERROR(link, mem_error); _insert_hyperlink(self, row_num, col_num, link); @@ -4374,11 +8383,11 @@ free(string_copy); free(url_copy); free(url_external); free(url_string); free(tooltip_copy); - return LXW_ERROR_MEMORY_MALLOC_FAILED; + return err; } /* * Write a hyperlink/url to an Excel file. */ @@ -4411,11 +8420,11 @@ struct sst_element *sst_element; lxw_error err; uint8_t i; long file_size; char *rich_string = NULL; - char *string_copy = NULL; + const char *string_copy = NULL; lxw_styles *styles = NULL; lxw_format *default_format = NULL; lxw_rich_string_tuple *rich_string_tuple = NULL; FILE *tmpfile; @@ -4440,11 +8449,11 @@ if (err) return err; /* Create a tmp file for the styles object. */ - tmpfile = lxw_tmpfile(self->tmpdir); + tmpfile = lxw_get_filehandle(&rich_string, NULL, self->tmpdir); if (!tmpfile) return LXW_ERROR_CREATING_TMPFILE; /* Create a temp styles object for writing the font data. */ styles = lxw_styles_new(); @@ -4476,53 +8485,54 @@ /* Free the temp objects. */ lxw_styles_free(styles); lxw_format_free(default_format); - /* Flush the file and read the size to calculate the required memory. */ + /* Flush the file. */ fflush(tmpfile); - file_size = ftell(tmpfile); - /* Allocate a buffer for the rich string xml data. */ - rich_string = calloc(file_size + 1, 1); - GOTO_LABEL_ON_MEM_ERROR(rich_string, mem_error); + if (!rich_string) { + /* Read the size to calculate the required memory. */ + file_size = ftell(tmpfile); + /* Allocate a buffer for the rich string xml data. */ + rich_string = calloc(file_size + 1, 1); + GOTO_LABEL_ON_MEM_ERROR(rich_string, mem_error); - /* Rewind the file and read the data into the memory buffer. */ - rewind(tmpfile); - if (fread(rich_string, file_size, 1, tmpfile) < 1) { - fclose(tmpfile); - free(rich_string); - return LXW_ERROR_READING_TMPFILE; + /* Rewind the file and read the data into the memory buffer. */ + rewind(tmpfile); + if (fread((void *) rich_string, file_size, 1, tmpfile) < 1) { + fclose(tmpfile); + free((void *) rich_string); + return LXW_ERROR_READING_TMPFILE; + } } /* Close the temp file. */ fclose(tmpfile); if (lxw_utf8_strlen(rich_string) > LXW_STR_MAX) { - free(rich_string); + free((void *) rich_string); return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED; } if (!self->optimize) { /* Get the SST element and string id. */ sst_element = lxw_get_sst_index(self->sst, rich_string, LXW_TRUE); - free(rich_string); + free((void *) rich_string); if (!sst_element) return LXW_ERROR_SHARED_STRING_INDEX_NOT_FOUND; string_id = sst_element->index; cell = _new_string_cell(row_num, col_num, string_id, sst_element->string, format); } else { /* Look for and escape control chars in the string. */ - if (strpbrk(rich_string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C" - "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16" - "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) { + if (lxw_has_control_characters(rich_string)) { string_copy = lxw_escape_control_characters(rich_string); - free(rich_string); + free((void *) rich_string); } else { string_copy = rich_string; } cell = _new_inline_rich_string_cell(row_num, col_num, string_copy, @@ -4540,10 +8550,76 @@ return LXW_ERROR_MEMORY_MALLOC_FAILED; } /* + * Write a comment to a worksheet cell in Excel. + */ +lxw_error +worksheet_write_comment_opt(lxw_worksheet *self, + lxw_row_t row_num, lxw_col_t col_num, + const char *text, lxw_comment_options *options) +{ + lxw_cell *cell; + lxw_error err; + lxw_vml_obj *comment; + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + if (!text) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + if (lxw_utf8_strlen(text) > LXW_STR_MAX) + return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED; + + comment = calloc(1, sizeof(lxw_vml_obj)); + GOTO_LABEL_ON_MEM_ERROR(comment, mem_error); + + comment->text = lxw_strdup(text); + GOTO_LABEL_ON_MEM_ERROR(comment->text, mem_error); + + comment->row = row_num; + comment->col = col_num; + + cell = _new_comment_cell(row_num, col_num, comment); + GOTO_LABEL_ON_MEM_ERROR(cell, mem_error); + + _insert_comment(self, row_num, col_num, cell); + + /* Set user and default parameters for the comment. */ + _get_comment_params(comment, options); + + self->has_vml = LXW_TRUE; + self->has_comments = LXW_TRUE; + + /* Insert a placeholder in the cell RB table in the same position so + * that the worksheet row "spans" calculations are correct. */ + _insert_cell_placeholder(self, row_num, col_num); + + return LXW_NO_ERROR; + +mem_error: + if (comment) + _free_vml_object(comment); + + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + +/* + * Write a comment to a worksheet cell in Excel. + */ +lxw_error +worksheet_write_comment(lxw_worksheet *self, + lxw_row_t row_num, lxw_col_t col_num, + const char *string) +{ + return worksheet_write_comment_opt(self, row_num, col_num, string, NULL); +} + +/* * Set the properties of a single column or a range of columns with options. */ lxw_error worksheet_set_column_opt(lxw_worksheet *self, lxw_col_t firstcol, @@ -4588,20 +8664,20 @@ if (err) return err; /* Resize the col_options array if required. */ if (firstcol >= self->col_options_max) { - lxw_col_t col; + lxw_col_t col_tmp; lxw_col_t old_size = self->col_options_max; lxw_col_t new_size = _next_power_of_two(firstcol + 1); lxw_col_options **new_ptr = realloc(self->col_options, new_size * sizeof(lxw_col_options *)); if (new_ptr) { - for (col = old_size; col < new_size; col++) - new_ptr[col] = NULL; + for (col_tmp = old_size; col_tmp < new_size; col_tmp++) + new_ptr[col_tmp] = NULL; self->col_options = new_ptr; self->col_options_max = new_size; } else { @@ -4647,10 +8723,11 @@ copied_options->format = format; copied_options->hidden = hidden; copied_options->level = level; copied_options->collapsed = collapsed; + free(self->col_options[firstcol]); self->col_options[firstcol] = copied_options; /* Store the column formats for use when writing cell data. */ for (col = firstcol; col <= lastcol; col++) { self->col_formats[col] = format; @@ -4673,10 +8750,44 @@ return worksheet_set_column_opt(self, firstcol, lastcol, width, format, NULL); } /* + * Set the properties of a single column or a range of columns, with the + * width in pixels. + */ +lxw_error +worksheet_set_column_pixels(lxw_worksheet *self, + lxw_col_t firstcol, + lxw_col_t lastcol, + uint32_t pixels, lxw_format *format) +{ + double width = _pixels_to_width(pixels); + + return worksheet_set_column_opt(self, firstcol, lastcol, width, format, + NULL); +} + +/* + * Set the properties of a single column or a range of columns with options, + * with the width in pixels. + */ +lxw_error +worksheet_set_column_pixels_opt(lxw_worksheet *self, + lxw_col_t firstcol, + lxw_col_t lastcol, + uint32_t pixels, + lxw_format *format, + lxw_row_col_options *user_options) +{ + double width = _pixels_to_width(pixels); + + return worksheet_set_column_opt(self, firstcol, lastcol, width, format, + user_options); +} + +/* * Set the properties of a row with options. */ lxw_error worksheet_set_row_opt(lxw_worksheet *self, lxw_row_t row_num, @@ -4745,10 +8856,38 @@ { return worksheet_set_row_opt(self, row_num, height, format, NULL); } /* + * Set the properties of a row, with the height in pixels. + */ +lxw_error +worksheet_set_row_pixels(lxw_worksheet *self, + lxw_row_t row_num, uint32_t pixels, + lxw_format *format) +{ + double height = _pixels_to_height(pixels); + + return worksheet_set_row_opt(self, row_num, height, format, NULL); +} + +/* + * Set the properties of a row with options, with the height in pixels. + */ +lxw_error +worksheet_set_row_pixels_opt(lxw_worksheet *self, + lxw_row_t row_num, + uint32_t pixels, + lxw_format *format, + lxw_row_col_options *user_options) +{ + double height = _pixels_to_height(pixels); + + return worksheet_set_row_opt(self, row_num, height, format, user_options); +} + +/* * Merge a range of cells. The first cell should contain the data and the others * should be blank. All cells should contain the same format. */ lxw_error worksheet_merge_range(lxw_worksheet *self, lxw_row_t first_row, @@ -4819,15 +8958,13 @@ lxw_col_t last_col) { lxw_row_t tmp_row; lxw_col_t tmp_col; lxw_error err; + lxw_filter_rule_obj **filter_rules; + lxw_col_t num_filter_rules; - /* Excel doesn't allow a single cell to be merged */ - if (first_row == last_row && first_col == last_col) - return LXW_ERROR_PARAMETER_VALIDATION; - /* Swap last row/col with first row/col as necessary */ if (first_row > last_row) { tmp_row = last_row; last_row = first_row; first_row = tmp_row; @@ -4841,20 +8978,399 @@ /* Check that column number is valid and store the max value */ err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE); if (err) return err; + /* Create a array to hold filter rules. */ + self->autofilter.in_use = LXW_FALSE; + self->autofilter.has_rules = LXW_FALSE; + _free_filter_rules(self); + num_filter_rules = last_col - first_col + 1; + filter_rules = calloc(num_filter_rules, sizeof(lxw_filter_rule_obj *)); + RETURN_ON_MEM_ERROR(filter_rules, LXW_ERROR_MEMORY_MALLOC_FAILED); + self->autofilter.in_use = LXW_TRUE; self->autofilter.first_row = first_row; self->autofilter.first_col = first_col; self->autofilter.last_row = last_row; self->autofilter.last_col = last_col; + self->filter_rules = filter_rules; + self->num_filter_rules = num_filter_rules; + return LXW_NO_ERROR; } /* + * Set a autofilter rule for a filter column. + */ +lxw_error +worksheet_filter_column(lxw_worksheet *self, lxw_col_t col, + lxw_filter_rule *rule) +{ + lxw_filter_rule_obj *rule_obj; + uint16_t rule_index; + + if (!rule) { + LXW_WARN("worksheet_filter_column(): rule parameter cannot be NULL"); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (self->autofilter.in_use == LXW_FALSE) { + LXW_WARN("worksheet_filter_column(): " + "Worksheet autofilter range hasn't been defined. " + "Use worksheet_autofilter() first."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (col < self->autofilter.first_col || col > self->autofilter.last_col) { + LXW_WARN_FORMAT3("worksheet_filter_column(): " + "Column '%d' is outside autofilter range " + "'%d <= col <= %d'.", col, + self->autofilter.first_col, + self->autofilter.last_col); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any previous rule in the column slot. */ + rule_index = col - self->autofilter.first_col; + _free_filter_rule(self->filter_rules[rule_index]); + + /* Create a new rule and copy user input. */ + rule_obj = calloc(1, sizeof(lxw_filter_rule_obj)); + RETURN_ON_MEM_ERROR(rule_obj, LXW_ERROR_MEMORY_MALLOC_FAILED); + + rule_obj->col_num = rule_index; + rule_obj->type = LXW_FILTER_TYPE_SINGLE; + rule_obj->criteria1 = rule->criteria; + rule_obj->value1 = rule->value; + + if (rule_obj->criteria1 != LXW_FILTER_CRITERIA_NON_BLANKS) { + rule_obj->value1_string = lxw_strdup(rule->value_string); + } + else { + rule_obj->criteria1 = LXW_FILTER_CRITERIA_NOT_EQUAL_TO; + rule_obj->value1_string = lxw_strdup(" "); + } + + if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS) + rule_obj->has_blanks = LXW_TRUE; + + _set_custom_filter(rule_obj); + + self->filter_rules[rule_index] = rule_obj; + self->filter_on = LXW_TRUE; + self->autofilter.has_rules = LXW_TRUE; + + return LXW_NO_ERROR; +} + +/* + * Set two autofilter rules for a filter column. + */ +lxw_error +worksheet_filter_column2(lxw_worksheet *self, lxw_col_t col, + lxw_filter_rule *rule1, lxw_filter_rule *rule2, + uint8_t and_or) +{ + lxw_filter_rule_obj *rule_obj; + uint16_t rule_index; + + if (!rule1 || !rule2) { + LXW_WARN("worksheet_filter_column2(): rule parameter cannot be NULL"); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (self->autofilter.in_use == LXW_FALSE) { + LXW_WARN("worksheet_filter_column2(): " + "Worksheet autofilter range hasn't been defined. " + "Use worksheet_autofilter() first."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (col < self->autofilter.first_col || col > self->autofilter.last_col) { + LXW_WARN_FORMAT3("worksheet_filter_column2(): " + "Column '%d' is outside autofilter range " + "'%d <= col <= %d'.", col, + self->autofilter.first_col, + self->autofilter.last_col); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any previous rule in the column slot. */ + rule_index = col - self->autofilter.first_col; + _free_filter_rule(self->filter_rules[rule_index]); + + /* Create a new rule and copy user input. */ + rule_obj = calloc(1, sizeof(lxw_filter_rule_obj)); + RETURN_ON_MEM_ERROR(rule_obj, LXW_ERROR_MEMORY_MALLOC_FAILED); + + if (and_or == LXW_FILTER_AND) + rule_obj->type = LXW_FILTER_TYPE_AND; + else + rule_obj->type = LXW_FILTER_TYPE_OR; + + rule_obj->col_num = rule_index; + + rule_obj->criteria1 = rule1->criteria; + rule_obj->value1 = rule1->value; + + rule_obj->criteria2 = rule2->criteria; + rule_obj->value2 = rule2->value; + + if (rule_obj->criteria1 != LXW_FILTER_CRITERIA_NON_BLANKS) { + rule_obj->value1_string = lxw_strdup(rule1->value_string); + } + else { + rule_obj->criteria1 = LXW_FILTER_CRITERIA_NOT_EQUAL_TO; + rule_obj->value1_string = lxw_strdup(" "); + } + + if (rule_obj->criteria2 != LXW_FILTER_CRITERIA_NON_BLANKS) { + rule_obj->value2_string = lxw_strdup(rule2->value_string); + } + else { + rule_obj->criteria2 = LXW_FILTER_CRITERIA_NOT_EQUAL_TO; + rule_obj->value2_string = lxw_strdup(" "); + } + + if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS) + rule_obj->has_blanks = LXW_TRUE; + + if (rule_obj->criteria2 == LXW_FILTER_CRITERIA_BLANKS) + rule_obj->has_blanks = LXW_TRUE; + + _set_custom_filter(rule_obj); + + self->filter_rules[rule_index] = rule_obj; + self->filter_on = LXW_TRUE; + self->autofilter.has_rules = LXW_TRUE; + + return LXW_NO_ERROR; +} + +/* + * Set two autofilter rules for a filter column. + */ +lxw_error +worksheet_filter_list(lxw_worksheet *self, lxw_col_t col, const char **list) +{ + lxw_filter_rule_obj *rule_obj; + uint16_t rule_index; + uint8_t has_blanks = LXW_FALSE; + uint16_t num_filters = 0; + uint16_t input_list_index; + uint16_t rule_obj_list_index; + const char *str; + char **tmp_list; + + if (!list) { + LXW_WARN("worksheet_filter_list(): list parameter cannot be NULL"); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (self->autofilter.in_use == LXW_FALSE) { + LXW_WARN("worksheet_filter_list(): " + "Worksheet autofilter range hasn't been defined. " + "Use worksheet_autofilter() first."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (col < self->autofilter.first_col || col > self->autofilter.last_col) { + LXW_WARN_FORMAT3("worksheet_filter_list(): " + "Column '%d' is outside autofilter range " + "'%d <= col <= %d'.", col, + self->autofilter.first_col, + self->autofilter.last_col); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Count the number of non "Blanks" strings in the input list. */ + input_list_index = 0; + while ((str = list[input_list_index]) != NULL) { + if (strncmp(str, "Blanks", 6) == 0) + has_blanks = LXW_TRUE; + else + num_filters++; + + input_list_index++; + } + + /* There should be at least one filter string. */ + if (num_filters == 0) { + LXW_WARN("worksheet_filter_list(): " + "list must have at least 1 non-blanks item."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any previous rule in the column slot. */ + rule_index = col - self->autofilter.first_col; + _free_filter_rule(self->filter_rules[rule_index]); + + /* Create a new rule and copy user input. */ + rule_obj = calloc(1, sizeof(lxw_filter_rule_obj)); + RETURN_ON_MEM_ERROR(rule_obj, LXW_ERROR_MEMORY_MALLOC_FAILED); + + tmp_list = calloc(num_filters + 1, sizeof(char *)); + GOTO_LABEL_ON_MEM_ERROR(tmp_list, mem_error); + + /* Copy input list (without any "Blanks" command) to an internal list. */ + input_list_index = 0; + rule_obj_list_index = 0; + while ((str = list[input_list_index]) != NULL) { + if (strncmp(str, "Blanks", 6) != 0) { + tmp_list[rule_obj_list_index] = lxw_strdup(str); + rule_obj_list_index++; + } + + input_list_index++; + } + + rule_obj->list = tmp_list; + rule_obj->num_list_filters = num_filters; + rule_obj->is_custom = LXW_FALSE; + rule_obj->col_num = rule_index; + rule_obj->type = LXW_FILTER_TYPE_STRING_LIST; + rule_obj->has_blanks = has_blanks; + + self->filter_rules[rule_index] = rule_obj; + self->filter_on = LXW_TRUE; + self->autofilter.has_rules = LXW_TRUE; + + return LXW_NO_ERROR; + +mem_error: + free(rule_obj); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + +} + +/* + * Add an Excel table to the worksheet. + */ +lxw_error +worksheet_add_table(lxw_worksheet *self, lxw_row_t first_row, + lxw_col_t first_col, lxw_row_t last_row, + lxw_col_t last_col, lxw_table_options *user_options) +{ + lxw_row_t tmp_row; + lxw_col_t tmp_col; + lxw_col_t num_cols; + lxw_error err; + lxw_table_obj *table_obj; + lxw_table_column **columns; + + if (self->optimize) { + LXW_WARN_FORMAT("worksheet_add_table(): " + "worksheet tables aren't supported in " + "'constant_memory' mode"); + return LXW_ERROR_FEATURE_NOT_SUPPORTED; + } + + /* Swap last row/col with first row/col as necessary */ + if (first_row > last_row) { + tmp_row = last_row; + last_row = first_row; + first_row = tmp_row; + } + if (first_col > last_col) { + tmp_col = last_col; + last_col = first_col; + first_col = tmp_col; + } + + /* Check that column number is valid and store the max value */ + err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE); + if (err) + return err; + + num_cols = last_col - first_col + 1; + + /* Check that there are sufficient data rows. */ + err = _check_table_rows(first_row, last_row, user_options); + if (err) + return err; + + /* Check that the the table name is valid. */ + err = _check_table_name(user_options); + if (err) + return err; + + /* Create a table object to copy from the user options. */ + table_obj = calloc(1, sizeof(lxw_table_obj)); + RETURN_ON_MEM_ERROR(table_obj, LXW_ERROR_MEMORY_MALLOC_FAILED); + + columns = calloc(num_cols, sizeof(lxw_table_column *)); + GOTO_LABEL_ON_MEM_ERROR(columns, error); + + table_obj->columns = columns; + table_obj->num_cols = num_cols; + table_obj->first_row = first_row; + table_obj->first_col = first_col; + table_obj->last_row = last_row; + table_obj->last_col = last_col; + + err = _set_default_table_columns(table_obj); + if (err) + goto error; + + /* Create the table range. */ + lxw_rowcol_to_range(table_obj->sqref, + first_row, first_col, last_row, last_col); + lxw_rowcol_to_range(table_obj->filter_sqref, + first_row, first_col, last_row, last_col); + + /* Validate and copy user options to an internal object. */ + if (user_options) { + + _check_and_copy_table_style(table_obj, user_options); + + table_obj->total_row = user_options->total_row; + table_obj->last_column = user_options->last_column; + table_obj->first_column = user_options->first_column; + table_obj->no_autofilter = user_options->no_autofilter; + table_obj->no_header_row = user_options->no_header_row; + table_obj->no_banded_rows = user_options->no_banded_rows; + table_obj->banded_columns = user_options->banded_columns; + + if (user_options->no_header_row) + table_obj->no_autofilter = LXW_TRUE; + + if (user_options->columns) { + err = _set_custom_table_columns(table_obj, user_options); + if (err) + goto error; + } + + if (user_options->total_row) { + lxw_rowcol_to_range(table_obj->filter_sqref, + first_row, first_col, last_row - 1, last_col); + } + + if (user_options->name) { + table_obj->name = lxw_strdup(user_options->name); + if (!table_obj->name) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto error; + } + } + } + + _write_table_column_data(self, table_obj); + + STAILQ_INSERT_TAIL(self->table_objs, table_obj, list_pointers); + self->table_count++; + + return LXW_NO_ERROR; + +error: + _free_worksheet_table(table_obj); + return err; + +} + +/* * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab * highlighted. */ void worksheet_select(lxw_worksheet *self) @@ -4968,10 +9484,22 @@ STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } /* + * Set the first visible cell at the top left of the worksheet. + */ +void +worksheet_set_top_left_cell(lxw_worksheet *self, lxw_row_t row, lxw_col_t col) +{ + if (row == 0 && col == 0) + return; + + lxw_rowcol_to_cell(self->top_left_cell, row, col); +} + +/* * Set panes and mark them as frozen. With extra options. */ void worksheet_freeze_panes_opt(lxw_worksheet *self, lxw_row_t first_row, lxw_col_t first_col, @@ -5103,48 +9631,248 @@ */ lxw_error worksheet_set_header_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { - if (options) { - if (options->margin >= 0.0) - self->margin_header = options->margin; - } + lxw_error err; + char *tmp_header; + char *found_string; + char *offset_string; + uint8_t placeholder_count = 0; + uint8_t image_count = 0; - if (!string) + if (!string) { + LXW_WARN_FORMAT("worksheet_set_header_opt/footer_opt(): " + "header/footer string cannot be NULL."); return LXW_ERROR_NULL_PARAMETER_IGNORED; + } - if (lxw_utf8_strlen(string) >= LXW_HEADER_FOOTER_MAX) + if (lxw_utf8_strlen(string) > LXW_HEADER_FOOTER_MAX) { + LXW_WARN_FORMAT("worksheet_set_header_opt/footer_opt(): " + "header/footer string exceeds Excel's limit of " + "255 characters."); return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } - lxw_strcpy(self->header, string); - self->header_footer_changed = 1; + tmp_header = lxw_strdup(string); + RETURN_ON_MEM_ERROR(tmp_header, LXW_ERROR_MEMORY_MALLOC_FAILED); + /* Replace &[Picture] with &G which is used internally by Excel. */ + while ((found_string = strstr(tmp_header, "&[Picture]"))) { + found_string++; + *found_string = 'G'; + + do { + found_string++; + offset_string = found_string + sizeof("Picture"); + *found_string = *offset_string; + } while (*offset_string); + } + + /* Count &G placeholders and ensure there are sufficient images. */ + found_string = tmp_header; + while (*found_string) { + if (*found_string == '&' && *(found_string + 1) == 'G') + placeholder_count++; + found_string++; + } + + if (placeholder_count > 0 && !options) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + free(tmp_header); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any previous header string so we can overwrite it. */ + free(self->header); + self->header = NULL; + + if (options) { + /* Ensure there are enough images to match the placeholders. There is + * a potential bug where there are sufficient images but in the wrong + * positions but we don't currently try to deal with that.*/ + if (options->image_left) + image_count++; + if (options->image_center) + image_count++; + if (options->image_right) + image_count++; + + if (placeholder_count != image_count) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + free(tmp_header); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any existing header image objects. */ + _free_object_properties(self->header_left_object_props); + _free_object_properties(self->header_center_object_props); + _free_object_properties(self->header_right_object_props); + + if (options->margin > 0.0) + self->margin_header = options->margin; + + err = _worksheet_set_header_footer_image(self, + options->image_left, + HEADER_LEFT); + if (err) { + free(tmp_header); + return err; + } + + err = _worksheet_set_header_footer_image(self, + options->image_center, + HEADER_CENTER); + if (err) { + free(tmp_header); + return err; + } + + err = _worksheet_set_header_footer_image(self, + options->image_right, + HEADER_RIGHT); + if (err) { + free(tmp_header); + return err; + } + } + + self->header = tmp_header; + self->header_footer_changed = LXW_TRUE; + return LXW_NO_ERROR; } /* * Set the page footer caption and options. */ lxw_error worksheet_set_footer_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { - if (options) { - if (options->margin >= 0.0) - self->margin_footer = options->margin; - } + lxw_error err; + char *tmp_footer; + char *found_string; + char *offset_string; + uint8_t placeholder_count = 0; + uint8_t image_count = 0; - if (!string) + if (!string) { + LXW_WARN_FORMAT("worksheet_set_header_opt/footer_opt(): " + "header/footer string cannot be NULL."); return LXW_ERROR_NULL_PARAMETER_IGNORED; + } - if (lxw_utf8_strlen(string) >= LXW_HEADER_FOOTER_MAX) + if (lxw_utf8_strlen(string) > LXW_HEADER_FOOTER_MAX) { + LXW_WARN_FORMAT("worksheet_set_header_opt/footer_opt(): " + "header/footer string exceeds Excel's limit of " + "255 characters."); return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } - lxw_strcpy(self->footer, string); - self->header_footer_changed = 1; + tmp_footer = lxw_strdup(string); + RETURN_ON_MEM_ERROR(tmp_footer, LXW_ERROR_MEMORY_MALLOC_FAILED); + /* Replace &[Picture] with &G which is used internally by Excel. */ + while ((found_string = strstr(tmp_footer, "&[Picture]"))) { + found_string++; + *found_string = 'G'; + + do { + found_string++; + offset_string = found_string + sizeof("Picture"); + *found_string = *offset_string; + } while (*offset_string); + } + + /* Count &G placeholders and ensure there are sufficient images. */ + found_string = tmp_footer; + while (*found_string) { + if (*found_string == '&' && *(found_string + 1) == 'G') + placeholder_count++; + found_string++; + } + + if (placeholder_count > 0 && !options) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + free(tmp_footer); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any previous footer string so we can overwrite it. */ + free(self->footer); + self->footer = NULL; + + if (options) { + /* Ensure there are enough images to match the placeholders. There is + * a potential bug where there are sufficient images but in the wrong + * positions but we don't currently try to deal with that.*/ + if (options->image_left) + image_count++; + if (options->image_center) + image_count++; + if (options->image_right) + image_count++; + + if (placeholder_count != image_count) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + free(tmp_footer); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any existing footer image objects. */ + _free_object_properties(self->footer_left_object_props); + _free_object_properties(self->footer_center_object_props); + _free_object_properties(self->footer_right_object_props); + + if (options->margin > 0.0) + self->margin_footer = options->margin; + + err = _worksheet_set_header_footer_image(self, + options->image_left, + FOOTER_LEFT); + if (err) { + free(tmp_footer); + return err; + } + + err = _worksheet_set_header_footer_image(self, + options->image_center, + FOOTER_CENTER); + if (err) { + free(tmp_footer); + return err; + } + + err = _worksheet_set_header_footer_image(self, + options->image_right, + FOOTER_RIGHT); + if (err) { + free(tmp_footer); + return err; + } + } + + self->footer = tmp_footer; + self->header_footer_changed = LXW_TRUE; + return LXW_NO_ERROR; } /* * Set the page header caption. @@ -5349,10 +10077,20 @@ self->print_scale = scale; self->page_setup_changed = LXW_TRUE; } /* + * Set the print in black and white option. + */ +void +worksheet_print_black_and_white(lxw_worksheet *self) +{ + self->black_white = LXW_TRUE; + self->page_setup_changed = LXW_TRUE; +} + +/* * Store the horizontal page breaks on a worksheet. */ lxw_error worksheet_set_h_pagebreaks(lxw_worksheet *self, lxw_row_t hbreaks[]) { @@ -5453,11 +10191,11 @@ */ void worksheet_protect(lxw_worksheet *self, const char *password, lxw_protection *options) { - struct lxw_protection *protect = &self->protection; + struct lxw_protection_obj *protect = &self->protection; /* Copy any user parameters to the internal structure. */ if (options) { protect->no_select_locked_cells = options->no_select_locked_cells; protect->no_select_unlocked_cells = options->no_select_unlocked_cells; @@ -5479,10 +10217,11 @@ if (password) { uint16_t hash = lxw_hash_password(password); lxw_snprintf(protect->hash, 5, "%X", hash); } + protect->no_sheet = LXW_FALSE; protect->no_content = LXW_TRUE; protect->is_configured = LXW_TRUE; } /* @@ -5522,30 +10261,30 @@ self->default_row_set = LXW_TRUE; } /* - * Insert an image into the worksheet. + * Insert an image with options into the worksheet. */ lxw_error worksheet_insert_image_opt(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *filename, lxw_image_options *user_options) { FILE *image_stream; - char *description; - lxw_image_options *options; + const char *description; + lxw_object_properties *object_props; if (!filename) { LXW_WARN("worksheet_insert_image()/_opt(): " "filename must be specified."); return LXW_ERROR_NULL_PARAMETER_IGNORED; } /* Check that the image file exists and can be opened. */ - image_stream = fopen(filename, "rb"); + image_stream = lxw_fopen(filename, "rb"); if (!image_stream) { LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " "file doesn't exist or can't be opened: %s.", filename); return LXW_ERROR_PARAMETER_VALIDATION; @@ -5558,44 +10297,51 @@ "couldn't get basename for file: %s.", filename); fclose(image_stream); return LXW_ERROR_PARAMETER_VALIDATION; } - /* Create a new object to hold the image options. */ - options = calloc(1, sizeof(lxw_image_options)); - if (!options) { + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { fclose(image_stream); return 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; + object_props->x_offset = user_options->x_offset; + object_props->y_offset = user_options->y_offset; + object_props->x_scale = user_options->x_scale; + object_props->y_scale = user_options->y_scale; + object_props->object_position = user_options->object_position; + object_props->url = lxw_strdup(user_options->url); + object_props->tip = lxw_strdup(user_options->tip); + object_props->decorative = user_options->decorative; + + if (user_options->description) + description = user_options->description; } /* Copy other options or set defaults. */ - options->filename = lxw_strdup(filename); - options->description = lxw_strdup(description); - options->stream = image_stream; - options->row = row_num; - options->col = col_num; + object_props->filename = lxw_strdup(filename); + object_props->description = lxw_strdup(description); + object_props->stream = image_stream; + object_props->row = row_num; + object_props->col = col_num; - if (!options->x_scale) - options->x_scale = 1; + if (object_props->x_scale == 0.0) + object_props->x_scale = 1; - if (!options->y_scale) - options->y_scale = 1; + if (object_props->y_scale == 0.0) + object_props->y_scale = 1; - if (_get_image_properties(options) == LXW_NO_ERROR) { - STAILQ_INSERT_TAIL(self->image_data, options, list_pointers); + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers); fclose(image_stream); return LXW_NO_ERROR; } else { - _free_image_options(options); + _free_object_properties(object_props); fclose(image_stream); return LXW_ERROR_IMAGE_DIMENSIONS; } } @@ -5608,88 +10354,110 @@ const char *filename) { return worksheet_insert_image_opt(self, row_num, col_num, filename, NULL); } +/* + * Insert an image buffer, with options, into the worksheet. + */ lxw_error worksheet_insert_image_buffer_opt(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const unsigned char *image_buffer, size_t image_size, lxw_image_options *user_options) { FILE *image_stream; - lxw_image_options *options; + lxw_object_properties *object_props; if (!image_size) { LXW_WARN("worksheet_insert_image_buffer()/_opt(): " "size must be non-zero."); return LXW_ERROR_NULL_PARAMETER_IGNORED; } - /* Write the image buffer to a temporary file so we can read the - * dimensions like an ordinary file. */ + /* Write the image buffer to a file (preferably in memory) so we can read + * the dimensions like an ordinary file. */ +#ifdef USE_FMEMOPEN + image_stream = fmemopen((void *) image_buffer, image_size, "rb"); + + if (!image_stream) + return LXW_ERROR_CREATING_TMPFILE; +#else image_stream = lxw_tmpfile(self->tmpdir); + if (!image_stream) return LXW_ERROR_CREATING_TMPFILE; - fwrite(image_buffer, 1, image_size, image_stream); + if (fwrite(image_buffer, 1, image_size, image_stream) != image_size) { + fclose(image_stream); + return LXW_ERROR_CREATING_TMPFILE; + } + rewind(image_stream); +#endif - /* Create a new object to hold the image options. */ - options = calloc(1, sizeof(lxw_image_options)); - if (!options) { + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { fclose(image_stream); return LXW_ERROR_MEMORY_MALLOC_FAILED; } - /* Store the image data in the options structure. */ - options->image_buffer = calloc(1, image_size); - if (!options->image_buffer) { - _free_image_options(options); + /* Store the image data in the properties structure. */ + object_props->image_buffer = calloc(1, image_size); + if (!object_props->image_buffer) { + _free_object_properties(object_props); fclose(image_stream); return LXW_ERROR_MEMORY_MALLOC_FAILED; } else { - memcpy(options->image_buffer, image_buffer, image_size); - options->image_buffer_size = image_size; - options->is_image_buffer = LXW_TRUE; + memcpy(object_props->image_buffer, image_buffer, image_size); + object_props->image_buffer_size = image_size; + object_props->is_image_buffer = LXW_TRUE; } 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; - options->description = lxw_strdup(user_options->description); + object_props->x_offset = user_options->x_offset; + object_props->y_offset = user_options->y_offset; + object_props->x_scale = user_options->x_scale; + object_props->y_scale = user_options->y_scale; + object_props->url = lxw_strdup(user_options->url); + object_props->tip = lxw_strdup(user_options->tip); + object_props->object_position = user_options->object_position; + object_props->description = lxw_strdup(user_options->description); + object_props->decorative = user_options->decorative; } /* Copy other options or set defaults. */ - options->filename = lxw_strdup("image_buffer"); - options->stream = image_stream; - options->row = row_num; - options->col = col_num; + object_props->filename = lxw_strdup("image_buffer"); + object_props->stream = image_stream; + object_props->row = row_num; + object_props->col = col_num; - if (!options->x_scale) - options->x_scale = 1; + if (object_props->x_scale == 0.0) + object_props->x_scale = 1; - if (!options->y_scale) - options->y_scale = 1; + if (object_props->y_scale == 0.0) + object_props->y_scale = 1; - if (_get_image_properties(options) == LXW_NO_ERROR) { - STAILQ_INSERT_TAIL(self->image_data, options, list_pointers); + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers); fclose(image_stream); return LXW_NO_ERROR; } else { - _free_image_options(options); + _free_object_properties(object_props); fclose(image_stream); return LXW_ERROR_IMAGE_DIMENSIONS; } } +/* + * Insert an image buffer into the worksheet. + */ lxw_error worksheet_insert_image_buffer(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const unsigned char *image_buffer, @@ -5698,18 +10466,144 @@ return worksheet_insert_image_buffer_opt(self, row_num, col_num, image_buffer, image_size, NULL); } /* + * Set an image as a worksheet background. + */ +lxw_error +worksheet_set_background(lxw_worksheet *self, const char *filename) +{ + FILE *image_stream; + lxw_object_properties *object_props; + + if (!filename) { + LXW_WARN("worksheet_set_background(): " + "filename must be specified."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Check that the image file exists and can be opened. */ + image_stream = lxw_fopen(filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("worksheet_set_background(): " + "file doesn't exist or can't be opened: %s.", + filename); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + /* Copy other options or set defaults. */ + object_props->filename = lxw_strdup(filename); + object_props->stream = image_stream; + object_props->is_background = LXW_TRUE; + + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + _free_object_properties(self->background_image); + self->background_image = object_props; + self->has_background_image = LXW_TRUE; + fclose(image_stream); + return LXW_NO_ERROR; + } + else { + _free_object_properties(object_props); + fclose(image_stream); + return LXW_ERROR_IMAGE_DIMENSIONS; + } +} + +/* + * Set an image buffer as a worksheet background. + */ +lxw_error +worksheet_set_background_buffer(lxw_worksheet *self, + const unsigned char *image_buffer, + size_t image_size) +{ + FILE *image_stream; + lxw_object_properties *object_props; + + if (!image_size) { + LXW_WARN("worksheet_set_background(): " "size must be non-zero."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Write the image buffer to a file (preferably in memory) so we can read + * the dimensions like an ordinary file. */ +#ifdef USE_FMEMOPEN + image_stream = fmemopen((void *) image_buffer, image_size, "rb"); + + if (!image_stream) + return LXW_ERROR_CREATING_TMPFILE; +#else + image_stream = lxw_tmpfile(self->tmpdir); + + if (!image_stream) + return LXW_ERROR_CREATING_TMPFILE; + + if (fwrite(image_buffer, 1, image_size, image_stream) != image_size) { + fclose(image_stream); + return LXW_ERROR_CREATING_TMPFILE; + } + + rewind(image_stream); +#endif + + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + /* Store the image data in the properties structure. */ + object_props->image_buffer = calloc(1, image_size); + if (!object_props->image_buffer) { + _free_object_properties(object_props); + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + else { + memcpy(object_props->image_buffer, image_buffer, image_size); + object_props->image_buffer_size = image_size; + object_props->is_image_buffer = LXW_TRUE; + } + + /* Copy other options or set defaults. */ + object_props->filename = lxw_strdup("image_buffer"); + object_props->stream = image_stream; + object_props->is_background = LXW_TRUE; + + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + _free_object_properties(self->background_image); + self->background_image = object_props; + self->has_background_image = LXW_TRUE; + fclose(image_stream); + return LXW_NO_ERROR; + } + else { + _free_object_properties(object_props); + fclose(image_stream); + return LXW_ERROR_IMAGE_DIMENSIONS; + } +} + +/* * Insert an chart into the worksheet. */ lxw_error worksheet_insert_chart_opt(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, - lxw_chart *chart, lxw_image_options *user_options) + lxw_chart *chart, lxw_chart_options *user_options) { - lxw_image_options *options; + lxw_object_properties *object_props; lxw_chart_series *series; if (!chart) { LXW_WARN("worksheet_insert_chart()/_opt(): chart must be non-NULL."); return LXW_ERROR_NULL_PARAMETER_IGNORED; @@ -5739,39 +10633,41 @@ 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); + /* Create a new object to hold the chart image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + RETURN_ON_MEM_ERROR(object_props, 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; + object_props->x_offset = user_options->x_offset; + object_props->y_offset = user_options->y_offset; + object_props->x_scale = user_options->x_scale; + object_props->y_scale = user_options->y_scale; + object_props->object_position = user_options->object_position; + object_props->description = lxw_strdup(user_options->description); + object_props->decorative = user_options->decorative; } /* Copy other options or set defaults. */ - options->row = row_num; - options->col = col_num; + object_props->row = row_num; + object_props->col = col_num; - /* TODO. Read defaults from chart. */ - options->width = 480; - options->height = 288; + object_props->width = 480; + object_props->height = 288; - if (!options->x_scale) - options->x_scale = 1; + if (object_props->x_scale == 0.0) + object_props->x_scale = 1; - if (!options->y_scale) - options->y_scale = 1; + if (object_props->y_scale == 0.0) + object_props->y_scale = 1; /* Store chart references so they can be ordered in the workbook. */ - options->chart = chart; + object_props->chart = chart; - STAILQ_INSERT_TAIL(self->chart_data, options, list_pointers); + STAILQ_INSERT_TAIL(self->chart_data, object_props, list_pointers); chart->in_use = LXW_TRUE; return LXW_NO_ERROR; } @@ -5795,11 +10691,11 @@ lxw_col_t first_col, lxw_row_t last_row, lxw_col_t last_col, lxw_data_validation *validation) { - lxw_data_validation *copy; + lxw_data_val_obj *copy; uint8_t is_between = LXW_FALSE; uint8_t is_formula = LXW_FALSE; uint8_t has_criteria = LXW_TRUE; lxw_error err; lxw_row_t tmp_row; @@ -5951,26 +10847,25 @@ err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE); if (err) return err; /* Create a copy of the parameters from the user data validation. */ - copy = calloc(1, sizeof(lxw_data_validation)); + copy = calloc(1, sizeof(lxw_data_val_obj)); GOTO_LABEL_ON_MEM_ERROR(copy, mem_error); /* Create the data validation range. */ if (first_row == last_row && first_col == last_col) - lxw_rowcol_to_cell(copy->sqref, first_row, last_col); + lxw_rowcol_to_cell(copy->sqref, first_row, first_col); else lxw_rowcol_to_range(copy->sqref, first_row, first_col, last_row, last_col); /* Copy the parameters from the user data validation. */ copy->validate = validation->validate; copy->value_number = validation->value_number; copy->error_type = validation->error_type; copy->dropdown = validation->dropdown; - copy->is_between = is_between; if (has_criteria) copy->criteria = validation->criteria; if (is_between) { @@ -6020,29 +10915,24 @@ if (validation->validate == LXW_VALIDATION_TYPE_LIST) { copy->value_formula = _validation_list_to_csv(validation->value_list); GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); } - if (validation->validate == LXW_VALIDATION_TYPE_LIST_FORMULA) { - copy->value_formula = lxw_strdup_formula(validation->value_formula); - GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); - } - if (validation->validate == LXW_VALIDATION_TYPE_DATE || validation->validate == LXW_VALIDATION_TYPE_TIME) { if (is_between) { copy->value_number = - lxw_datetime_to_excel_date(&validation->minimum_datetime, - LXW_EPOCH_1900); + lxw_datetime_to_excel_date_epoch + (&validation->minimum_datetime, LXW_EPOCH_1900); copy->maximum_number = - lxw_datetime_to_excel_date(&validation->maximum_datetime, - LXW_EPOCH_1900); + lxw_datetime_to_excel_date_epoch + (&validation->maximum_datetime, LXW_EPOCH_1900); } else { copy->value_number = - lxw_datetime_to_excel_date(&validation->value_datetime, - LXW_EPOCH_1900); + lxw_datetime_to_excel_date_epoch(&validation->value_datetime, + LXW_EPOCH_1900); } } /* These options are on by default so we can't take plain booleans. */ copy->ignore_blank = validation->ignore_blank ^ 1; @@ -6070,10 +10960,249 @@ return worksheet_data_validation_range(self, row, col, row, col, validation); } /* + * Add a conditional format to a worksheet, for a range. + */ +lxw_error +worksheet_conditional_format_range(lxw_worksheet *self, lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + lxw_conditional_format *user_options) +{ + lxw_cond_format_obj *cond_format; + lxw_row_t tmp_row; + lxw_col_t tmp_col; + lxw_error err = LXW_NO_ERROR; + char *type_strings[] = { + "none", + "cellIs", + "containsText", + "timePeriod", + "aboveAverage", + "duplicateValues", + "uniqueValues", + "top10", + "top10", + "containsBlanks", + "notContainsBlanks", + "containsErrors", + "notContainsErrors", + "expression", + "colorScale", + "colorScale", + "dataBar", + "iconSet", + }; + + /* Swap last row/col with first row/col as necessary */ + if (first_row > last_row) { + tmp_row = last_row; + last_row = first_row; + first_row = tmp_row; + } + if (first_col > last_col) { + tmp_col = last_col; + last_col = first_col; + first_col = tmp_col; + } + + /* Check that dimensions are valid but don't store them. */ + err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE); + if (err) + return err; + + /* Check the validation type is in correct enum range. */ + if (user_options->type <= LXW_CONDITIONAL_TYPE_NONE || + user_options->type >= LXW_CONDITIONAL_TYPE_LAST) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "invalid type value (%d).", user_options->type); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Create a copy of the parameters from the user data validation. */ + cond_format = calloc(1, sizeof(lxw_cond_format_obj)); + GOTO_LABEL_ON_MEM_ERROR(cond_format, error); + + /* Create the data validation range. */ + if (first_row == last_row && first_col == last_col) + lxw_rowcol_to_cell(cond_format->sqref, first_row, first_col); + else + lxw_rowcol_to_range(cond_format->sqref, first_row, first_col, + last_row, last_col); + + /* Store the first cell string for text and date rules. */ + lxw_rowcol_to_cell(cond_format->first_cell, first_row, first_col); + + /* Overwrite the sqref range with a user supplied set of ranges. */ + if (user_options->multi_range) { + + if (strlen(user_options->multi_range) >= LXW_MAX_ATTRIBUTE_LENGTH) { + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "multi_range >= limit = %d.", + LXW_MAX_ATTRIBUTE_LENGTH); + err = LXW_ERROR_PARAMETER_VALIDATION; + goto error; + } + + LXW_ATTRIBUTE_COPY(cond_format->sqref, user_options->multi_range); + } + + /* Get the conditional format dxf format index. */ + if (user_options->format) + cond_format->dxf_index = + lxw_format_get_dxf_index(user_options->format); + else + cond_format->dxf_index = LXW_PROPERTY_UNSET; + + /* Set some common option for all validation types. */ + cond_format->type = user_options->type; + cond_format->criteria = user_options->criteria; + cond_format->stop_if_true = user_options->stop_if_true; + cond_format->type_string = lxw_strdup(type_strings[cond_format->type]); + + /* Validate the user input for various types of rules. */ + if (user_options->type == LXW_CONDITIONAL_TYPE_CELL + || cond_format->type == LXW_CONDITIONAL_TYPE_DUPLICATE + || cond_format->type == LXW_CONDITIONAL_TYPE_UNIQUE) { + + err = _validate_conditional_cell(cond_format, user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_TEXT) { + + err = _validate_conditional_text(cond_format, user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_TIME_PERIOD) { + + err = _validate_conditional_time_period(user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_AVERAGE) { + + err = _validate_conditional_average(user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TOP + || cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) { + + err = _validate_conditional_top(cond_format, user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_FORMULA) { + + err = _validate_conditional_formula(cond_format, user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_2_COLOR_SCALE + || cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + + err = _validate_conditional_scale(cond_format, user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_DATA_BAR) { + + err = _validate_conditional_data_bar(self, cond_format, user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_ICON_SETS) { + + err = _validate_conditional_icons(user_options); + if (err) + goto error; + + cond_format->icon_style = user_options->icon_style; + cond_format->reverse_icons = user_options->reverse_icons; + cond_format->icons_only = user_options->icons_only; + } + + /* Set the priority based on the order of adding. */ + cond_format->dxf_priority = ++self->dxf_priority; + + /* Store the conditional format object. */ + err = _store_conditional_format_object(self, cond_format); + + if (err) + goto error; + else + return LXW_NO_ERROR; + +error: + _free_cond_format(cond_format); + return err; +} + +/* + * Add a conditional format to a worksheet, for a cell. + */ +lxw_error +worksheet_conditional_format_cell(lxw_worksheet *self, + lxw_row_t row, + lxw_col_t col, + lxw_conditional_format *options) +{ + return worksheet_conditional_format_range(self, row, col, + row, col, options); +} + +/* + * Insert a button object into the worksheet. + */ +lxw_error +worksheet_insert_button(lxw_worksheet *self, lxw_row_t row_num, + lxw_col_t col_num, lxw_button_options *options) +{ + lxw_error err; + lxw_vml_obj *button; + + err = _check_dimensions(self, row_num, col_num, LXW_TRUE, LXW_TRUE); + if (err) + return err; + + button = calloc(1, sizeof(lxw_vml_obj)); + GOTO_LABEL_ON_MEM_ERROR(button, mem_error); + + button->row = row_num; + button->col = col_num; + + /* Set user and default parameters for the button. */ + err = _get_button_params(button, 1 + self->num_buttons, options); + if (err) + goto mem_error; + + /* Calculate the worksheet position of the button. */ + _worksheet_position_vml_object(self, button); + + self->has_vml = LXW_TRUE; + self->has_buttons = LXW_TRUE; + self->num_buttons++; + + STAILQ_INSERT_TAIL(self->button_objs, button, list_pointers); + + return LXW_NO_ERROR; + +mem_error: + if (button) + _free_vml_object(button); + + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + +/* * Set the VBA name for the worksheet. */ lxw_error worksheet_set_vba_name(lxw_worksheet *self, const char *name) { @@ -6081,8 +11210,85 @@ LXW_WARN("worksheet_set_vba_name(): " "name must be specified."); return LXW_ERROR_NULL_PARAMETER_IGNORED; } self->vba_codename = lxw_strdup(name); + + return LXW_NO_ERROR; +} + +/* + * Set the default author of the cell comments. + */ +void +worksheet_set_comments_author(lxw_worksheet *self, const char *author) +{ + self->comment_author = lxw_strdup(author); +} + +/* + * Make any comments in the worksheet visible, unless explicitly hidden. + */ +void +worksheet_show_comments(lxw_worksheet *self) +{ + self->comment_display_default = LXW_COMMENT_DISPLAY_VISIBLE; +} + +/* + * Ignore various Excel errors/warnings in a worksheet for user defined ranges. + */ +lxw_error +worksheet_ignore_errors(lxw_worksheet *self, uint8_t type, const char *range) +{ + if (!range) { + LXW_WARN("worksheet_ignore_errors(): " "'range' must be specified."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + if (type <= 0 || type >= LXW_IGNORE_LAST_OPTION) { + LXW_WARN("worksheet_ignore_errors(): " "unknown option in 'type'."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Set the ranges to be ignored. */ + if (type == LXW_IGNORE_NUMBER_STORED_AS_TEXT) { + free(self->ignore_number_stored_as_text); + self->ignore_number_stored_as_text = lxw_strdup(range); + } + else if (type == LXW_IGNORE_EVAL_ERROR) { + free(self->ignore_eval_error); + self->ignore_eval_error = lxw_strdup(range); + } + else if (type == LXW_IGNORE_FORMULA_DIFFERS) { + free(self->ignore_formula_differs); + self->ignore_formula_differs = lxw_strdup(range); + } + else if (type == LXW_IGNORE_FORMULA_RANGE) { + free(self->ignore_formula_range); + self->ignore_formula_range = lxw_strdup(range); + } + else if (type == LXW_IGNORE_FORMULA_UNLOCKED) { + free(self->ignore_formula_unlocked); + self->ignore_formula_unlocked = lxw_strdup(range); + } + else if (type == LXW_IGNORE_EMPTY_CELL_REFERENCE) { + free(self->ignore_empty_cell_reference); + self->ignore_empty_cell_reference = lxw_strdup(range); + } + else if (type == LXW_IGNORE_LIST_DATA_VALIDATION) { + free(self->ignore_list_data_validation); + self->ignore_list_data_validation = lxw_strdup(range); + } + else if (type == LXW_IGNORE_CALCULATED_COLUMN) { + free(self->ignore_calculated_column); + self->ignore_calculated_column = lxw_strdup(range); + } + else if (type == LXW_IGNORE_TWO_DIGIT_TEXT_YEAR) { + free(self->ignore_two_digit_text_year); + self->ignore_two_digit_text_year = lxw_strdup(range); + } + + self->has_ignore_errors = LXW_TRUE; return LXW_NO_ERROR; }