/***************************************************************************** * worksheet - A library for creating Excel XLSX worksheet files. * * Used in conjunction with the libxlsxwriter library. * * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. * */ #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/third_party/md5.h" #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 /* * 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. * ****************************************************************************/ /* * 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 tmp_row; tmp_row.row_num = row_num; 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_in_row(lxw_row *row, lxw_col_t col_num) { lxw_cell tmp_cell; if (!row) return NULL; tmp_cell.col_num = col_num; return RB_FIND(lxw_table_cells, row->cells, &tmp_cell); } /* * Create a new worksheet object. */ lxw_worksheet * lxw_worksheet_new(lxw_worksheet_init_data *init_data) { lxw_worksheet *worksheet = calloc(1, sizeof(lxw_worksheet)); GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error); worksheet->table = calloc(1, sizeof(struct lxw_table_rows)); GOTO_LABEL_ON_MEM_ERROR(worksheet->table, mem_error); RB_INIT(worksheet->table); 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); } worksheet->col_options = calloc(LXW_COL_META_MAX, sizeof(lxw_col_options *)); worksheet->col_options_max = LXW_COL_META_MAX; GOTO_LABEL_ON_MEM_ERROR(worksheet->col_options, mem_error); worksheet->col_formats = calloc(LXW_COL_META_MAX, sizeof(lxw_format *)); worksheet->col_formats_max = LXW_COL_META_MAX; GOTO_LABEL_ON_MEM_ERROR(worksheet->col_formats, mem_error); worksheet->optimize_row = calloc(1, sizeof(struct lxw_row)); GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_row, mem_error); worksheet->optimize_row->height = LXW_DEF_ROW_HEIGHT; 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_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_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->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->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 = calloc(1, sizeof(struct lxw_rel_tuples)); GOTO_LABEL_ON_MEM_ERROR(worksheet->external_drawing_links, mem_error); STAILQ_INIT(worksheet->external_drawing_links); 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); if (init_data && init_data->optimize) { FILE *tmpfile; tmpfile = lxw_tmpfile(init_data->tmpdir); if (!tmpfile) { LXW_ERROR("Error creating tmpfile() for worksheet in " "'constant_memory' mode."); goto mem_error; } 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_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; worksheet->default_row_height = LXW_DEF_ROW_HEIGHT; worksheet->default_row_pixels = 20; worksheet->default_col_pixels = 64; /* Initialize the page setup properties. */ worksheet->fit_height = 0; worksheet->fit_width = 0; worksheet->page_start = 0; worksheet->print_scale = 100; worksheet->fit_page = 0; worksheet->orientation = LXW_TRUE; worksheet->page_order = 0; worksheet->page_setup_changed = LXW_FALSE; worksheet->page_view = LXW_FALSE; worksheet->paper_size = 0; worksheet->vertical_dpi = 0; worksheet->horizontal_dpi = 0; worksheet->margin_left = 0.7; worksheet->margin_right = 0.7; worksheet->margin_top = 0.75; worksheet->margin_bottom = 0.75; worksheet->margin_header = 0.3; worksheet->margin_footer = 0.3; worksheet->print_gridlines = 0; worksheet->screen_gridlines = 1; worksheet->print_options_changed = 0; worksheet->zoom = 100; worksheet->zoom_scale_normal = LXW_TRUE; worksheet->show_zeros = LXW_TRUE; 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); } /* * Free a worksheet cell. */ STATIC void _free_cell(lxw_cell *cell) { if (!cell) return; if (cell->type != NUMBER_CELL && cell->type != STRING_CELL && cell->type != BLANK_CELL && cell->type != BOOLEAN_CELL) { free(cell->u.string); } free(cell->user_data1); free(cell->user_data2); _free_vml_object(cell->comment); free(cell); } /* * Free a worksheet row. */ STATIC void _free_row(lxw_row *row) { lxw_cell *cell; lxw_cell *next_cell; if (!row) return; for (cell = RB_MIN(lxw_table_cells, row->cells); cell; cell = next_cell) { next_cell = RB_NEXT(lxw_table_cells, row->cells, cell); RB_REMOVE(lxw_table_cells, row->cells, cell); _free_cell(cell); } free(row->cells); free(row); } /* * Free a worksheet image_options. */ STATIC void _free_object_properties(lxw_object_properties *object_property) { if (!object_property) return; 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); } /* * Free a worksheet data_validation. */ STATIC void _free_data_validation(lxw_data_val_obj *data_validation) { if (!data_validation) return; free(data_validation->value_formula); free(data_validation->maximum_formula); free(data_validation->input_title); free(data_validation->input_message); free(data_validation->error_title); free(data_validation->error_message); free(data_validation->minimum_formula); 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 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_object_properties *object_props; lxw_vml_obj *header_image_vml; lxw_selection *selection; lxw_data_val_obj *data_validation; lxw_rel_tuple *relationship; lxw_cond_format_obj *cond_format; 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) { for (col = 0; col < worksheet->col_options_max; col++) { if (worksheet->col_options[col]) free(worksheet->col_options[col]); } } free(worksheet->col_options); free(worksheet->col_sizes); free(worksheet->col_formats); if (worksheet->table) { for (row = RB_MIN(lxw_table_rows, worksheet->table); row; row = next_row) { next_row = RB_NEXT(lxw_table_rows, worksheet->table, row); RB_REMOVE(lxw_table_rows, worksheet->table, row); _free_row(row); } free(worksheet->table); } if (worksheet->hyperlinks) { for (row = RB_MIN(lxw_table_rows, worksheet->hyperlinks); row; row = next_row) { next_row = RB_NEXT(lxw_table_rows, worksheet->hyperlinks, row); RB_REMOVE(lxw_table_rows, worksheet->hyperlinks, row); _free_row(row); } 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_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_props); } if (worksheet->chart_data) { while (!STAILQ_EMPTY(worksheet->chart_data)) { object_props = STAILQ_FIRST(worksheet->chart_data); STAILQ_REMOVE_HEAD(worksheet->chart_data, list_pointers); _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)) { header_image_vml = STAILQ_FIRST(worksheet->header_image_objs); STAILQ_REMOVE_HEAD(worksheet->header_image_objs, list_pointers); _free_vml_object(header_image_vml); } free(worksheet->header_image_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->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); } while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) { relationship = STAILQ_FIRST(worksheet->external_hyperlinks); STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers); _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(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(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); 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); if (worksheet->array) { for (col = 0; col < LXW_COL_MAX; col++) { _free_cell(worksheet->array[col]); } free(worksheet->array); } if (worksheet->optimize_row) free(worksheet->optimize_row); if (worksheet->drawing) lxw_drawing_free(worksheet->drawing); free(worksheet->hbreaks); free(worksheet->vbreaks); free(worksheet->name); free(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); worksheet = NULL; } /* * Create a new worksheet row object. */ STATIC lxw_row * _new_row(lxw_row_t row_num) { lxw_row *row = calloc(1, sizeof(lxw_row)); if (row) { row->row_num = row_num; row->cells = calloc(1, sizeof(struct lxw_table_cells)); row->height = LXW_DEF_ROW_HEIGHT; if (row->cells) RB_INIT(row->cells); else LXW_MEM_ERROR(); } else { LXW_MEM_ERROR(); } return row; } /* * Create a new worksheet number cell object. */ STATIC lxw_cell * _new_number_cell(lxw_row_t row_num, lxw_col_t col_num, double value, lxw_format *format) { 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 = NUMBER_CELL; cell->format = format; cell->u.number = value; return cell; } /* * Create a new worksheet string cell object. */ STATIC lxw_cell * _new_string_cell(lxw_row_t row_num, lxw_col_t col_num, int32_t string_id, char *sst_string, lxw_format *format) { 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 = STRING_CELL; cell->format = format; cell->u.string_id = string_id; cell->sst_string = sst_string; return cell; } /* * Create a new worksheet inline_string cell object. */ STATIC lxw_cell * _new_inline_string_cell(lxw_row_t row_num, lxw_col_t col_num, char *string, lxw_format *format) { 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 = INLINE_STRING_CELL; cell->format = format; cell->u.string = string; return cell; } /* * 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_format *format) { 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 = INLINE_RICH_STRING_CELL; cell->format = format; cell->u.string = string; return cell; } /* * Create a new worksheet formula cell object. */ STATIC lxw_cell * _new_formula_cell(lxw_row_t row_num, lxw_col_t col_num, char *formula, lxw_format *format) { 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 = FORMULA_CELL; cell->format = format; cell->u.string = formula; return cell; } /* * 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) { 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; return cell; } /* * Create a new worksheet blank cell object. */ STATIC lxw_cell * _new_blank_cell(lxw_row_t row_num, lxw_col_t col_num, lxw_format *format) { 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 = BLANK_CELL; cell->format = format; return cell; } /* * Create a new worksheet boolean cell object. */ STATIC lxw_cell * _new_boolean_cell(lxw_row_t row_num, lxw_col_t col_num, int value, lxw_format *format) { 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 = BOOLEAN_CELL; cell->format = format; cell->u.number = value; 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, char *tooltip) { 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 = link_type; cell->u.string = url; cell->user_data1 = string; cell->user_data2 = tooltip; return cell; } /* * Get or create the row object for a given row number. */ STATIC lxw_row * _get_row_list(struct lxw_table_rows *table, lxw_row_t row_num) { lxw_row *row; lxw_row *existing_row; if (table->cached_row_num == row_num) return table->cached_row; /* Create a new row and try and insert it. */ row = _new_row(row_num); existing_row = RB_INSERT(lxw_table_rows, table, row); /* If existing_row is not NULL, then it already existed. Free new row */ /* and return existing_row. */ if (existing_row) { _free_row(row); row = existing_row; } table->cached_row = row; table->cached_row_num = row_num; return row; } /* * Get or create the row object for a given row number. */ STATIC lxw_row * _get_row(lxw_worksheet *self, lxw_row_t row_num) { lxw_row *row; if (!self->optimize) { row = _get_row_list(self->table, row_num); return row; } else { if (row_num < self->optimize_row->row_num) { return NULL; } else if (row_num == self->optimize_row->row_num) { return self->optimize_row; } else { /* Flush row. */ lxw_worksheet_write_single_row(self); row = self->optimize_row; row->row_num = row_num; return row; } } } /* * Insert a cell object in the cell list of a row object. */ STATIC void _insert_cell_list(struct lxw_table_cells *cell_list, lxw_cell *cell, lxw_col_t col_num) { lxw_cell *existing_cell; cell->col_num = col_num; existing_cell = RB_INSERT(lxw_table_cells, cell_list, cell); /* If existing_cell is not NULL, then that cell already existed. */ /* Remove existing_cell and add new one in again. */ if (existing_cell) { RB_REMOVE(lxw_table_cells, cell_list, existing_cell); /* Add it in again. */ RB_INSERT(lxw_table_cells, cell_list, cell); _free_cell(existing_cell); } return; } /* * Insert a cell object into the cell list or array. */ STATIC void _insert_cell(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, lxw_cell *cell) { lxw_row *row = _get_row(self, row_num); if (!self->optimize) { row->data_changed = LXW_TRUE; _insert_cell_list(row->cells, cell, col_num); } else { if (row) { row->data_changed = LXW_TRUE; /* Overwrite an existing cell if necessary. */ if (self->array[col_num]) _free_cell(self->array[col_num]); self->array[col_num] = cell; } } } /* * 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) { col--; col |= col >> 1; col |= col >> 2; col |= col >> 4; col |= col >> 8; col++; return col; } /* * Check that row and col are within the allowed Excel range and store max * and min values for use in other methods/elements. * * The ignore_row/ignore_col flags are used to indicate that we wish to * perform the dimension check without storing the value. */ STATIC lxw_error _check_dimensions(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, int8_t ignore_row, int8_t ignore_col) { if (row_num >= LXW_ROW_MAX) return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE; if (col_num >= LXW_COL_MAX) return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE; /* In optimization mode we don't change dimensions for rows that are */ /* already written. */ if (!ignore_row && !ignore_col && self->optimize) { if (row_num < self->optimize_row->row_num) return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE; } if (!ignore_row) { if (row_num < self->dim_rowmin) self->dim_rowmin = row_num; if (row_num > self->dim_rowmax) self->dim_rowmax = row_num; } if (!ignore_col) { if (col_num < self->dim_colmin) self->dim_colmin = col_num; if (col_num > self->dim_colmax) self->dim_colmax = col_num; } return LXW_NO_ERROR; } /* * Comparator for the row structure red/black tree. */ STATIC int _row_cmp(lxw_row *row1, lxw_row *row2) { if (row1->row_num > row2->row_num) return 1; if (row1->row_num < row2->row_num) return -1; return 0; } /* * Comparator for the cell structure red/black tree. */ STATIC int _cell_cmp(lxw_cell *cell1, lxw_cell *cell2) { if (cell1->col_num > cell2->col_num) return 1; if (cell1->col_num < cell2->col_num) 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 * lxw_basename(const char *path) { char *forward_slash; char *back_slash; if (!path) return NULL; forward_slash = strrchr(path, '/'); back_slash = strrchr(path, '\\'); if (!forward_slash && !back_slash) return (char *) 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) { uint8_t i = 0; size_t length = 0; if (!list || !list[0]) return 0; while (list[i] && length <= LXW_VALIDATION_MAX_STRING_LENGTH) { /* Include commas in the length. */ length += 1 + lxw_utf8_strlen(list[i]); i++; } /* Adjust the count for extraneous comma at end. */ length--; return length; } /* Function to convert an array of strings into a CSV string for data * validation lists. */ char * _validation_list_to_csv(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); if (!str) return NULL; /* Add the start quote and first element. */ strcat(str, "\""); strcat(str, list[0]); /* Add the other elements preceded by a comma. */ i = 1; while (list[i]) { strcat(str, ","); strcat(str, list[i]); i++; } /* Add the end quote. */ strcat(str, "\""); return str; } /***************************************************************************** * * XML functions. * ****************************************************************************/ /* * Write the XML declaration. */ STATIC void _worksheet_xml_declaration(lxw_worksheet *self) { lxw_xml_declaration(self->file); } /* * Write the element. */ STATIC void _worksheet_write_worksheet(lxw_worksheet *self) { struct xml_attribute_list attributes; 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(); } /* * Write the element. */ STATIC void _worksheet_write_dimension(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char ref[LXW_MAX_CELL_RANGE_LENGTH]; lxw_row_t dim_rowmin = self->dim_rowmin; lxw_row_t dim_rowmax = self->dim_rowmax; lxw_col_t dim_colmin = self->dim_colmin; lxw_col_t dim_colmax = self->dim_colmax; if (dim_rowmin == LXW_ROW_MAX && dim_colmin == LXW_COL_MAX) { /* If the rows and cols are still the defaults then no dimensions have * been set and we use the default range "A1". */ lxw_rowcol_to_range(ref, 0, 0, 0, 0); } else if (dim_rowmin == LXW_ROW_MAX && dim_colmin != LXW_COL_MAX) { /* If the rows aren't set but the columns are then the dimensions have * been changed via set_column(). */ lxw_rowcol_to_range(ref, 0, dim_colmin, 0, dim_colmax); } else { lxw_rowcol_to_range(ref, dim_rowmin, dim_colmin, dim_rowmax, dim_colmax); } LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("ref", ref); lxw_xml_empty_tag(self->file, "dimension", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element for freeze panes. */ STATIC void _worksheet_write_freeze_panes(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; lxw_selection *selection; lxw_selection *user_selection; lxw_row_t row = self->panes.first_row; lxw_col_t col = self->panes.first_col; lxw_row_t top_row = self->panes.top_row; lxw_col_t left_col = self->panes.left_col; char row_cell[LXW_MAX_CELL_NAME_LENGTH]; char col_cell[LXW_MAX_CELL_NAME_LENGTH]; char top_left_cell[LXW_MAX_CELL_NAME_LENGTH]; char active_pane[LXW_PANE_NAME_LENGTH]; /* If there is a user selection we remove it from the list and use it. */ if (!STAILQ_EMPTY(self->selections)) { user_selection = STAILQ_FIRST(self->selections); STAILQ_REMOVE_HEAD(self->selections, list_pointers); } else { /* or else create a new blank selection. */ user_selection = calloc(1, sizeof(lxw_selection)); RETURN_VOID_ON_MEM_ERROR(user_selection); } LXW_INIT_ATTRIBUTES(); lxw_rowcol_to_cell(top_left_cell, top_row, left_col); /* Set the active pane. */ if (row && col) { lxw_strcpy(active_pane, "bottomRight"); lxw_rowcol_to_cell(row_cell, row, 0); lxw_rowcol_to_cell(col_cell, 0, col); selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "topRight"); lxw_strcpy(selection->active_cell, col_cell); lxw_strcpy(selection->sqref, col_cell); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "bottomLeft"); lxw_strcpy(selection->active_cell, row_cell); lxw_strcpy(selection->sqref, row_cell); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "bottomRight"); lxw_strcpy(selection->active_cell, user_selection->active_cell); lxw_strcpy(selection->sqref, user_selection->sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } } else if (col) { lxw_strcpy(active_pane, "topRight"); selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "topRight"); lxw_strcpy(selection->active_cell, user_selection->active_cell); lxw_strcpy(selection->sqref, user_selection->sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } } else { lxw_strcpy(active_pane, "bottomLeft"); selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "bottomLeft"); lxw_strcpy(selection->active_cell, user_selection->active_cell); lxw_strcpy(selection->sqref, user_selection->sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } } if (col) LXW_PUSH_ATTRIBUTES_INT("xSplit", col); if (row) LXW_PUSH_ATTRIBUTES_INT("ySplit", row); LXW_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell); LXW_PUSH_ATTRIBUTES_STR("activePane", active_pane); if (self->panes.type == FREEZE_PANES) LXW_PUSH_ATTRIBUTES_STR("state", "frozen"); else if (self->panes.type == FREEZE_SPLIT_PANES) LXW_PUSH_ATTRIBUTES_STR("state", "frozenSplit"); lxw_xml_empty_tag(self->file, "pane", &attributes); free(user_selection); LXW_FREE_ATTRIBUTES(); } /* * Convert column width from user units to pane split width. */ STATIC uint32_t _worksheet_calculate_x_split_width(double x_split) { uint32_t width; uint32_t pixels; uint32_t points; uint32_t twips; double max_digit_width = 7.0; /* For Calabri 11. */ double padding = 5.0; /* Convert to pixels. */ if (x_split < 1.0) { pixels = (uint32_t) (x_split * (max_digit_width + padding) + 0.5); } else { pixels = (uint32_t) (x_split * max_digit_width + 0.5) + 5; } /* Convert to points. */ points = (pixels * 3) / 4; /* Convert to twips (twentieths of a point). */ twips = points * 20; /* Add offset/padding. */ width = twips + 390; return width; } /* * Write the element for split panes. */ STATIC void _worksheet_write_split_panes(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; lxw_selection *selection; lxw_selection *user_selection; lxw_row_t row = self->panes.first_row; lxw_col_t col = self->panes.first_col; lxw_row_t top_row = self->panes.top_row; lxw_col_t left_col = self->panes.left_col; double x_split = self->panes.x_split; double y_split = self->panes.y_split; uint8_t has_selection = LXW_FALSE; char row_cell[LXW_MAX_CELL_NAME_LENGTH]; char col_cell[LXW_MAX_CELL_NAME_LENGTH]; char top_left_cell[LXW_MAX_CELL_NAME_LENGTH]; char active_pane[LXW_PANE_NAME_LENGTH]; /* If there is a user selection we remove it from the list and use it. */ if (!STAILQ_EMPTY(self->selections)) { user_selection = STAILQ_FIRST(self->selections); STAILQ_REMOVE_HEAD(self->selections, list_pointers); has_selection = LXW_TRUE; } else { /* or else create a new blank selection. */ user_selection = calloc(1, sizeof(lxw_selection)); RETURN_VOID_ON_MEM_ERROR(user_selection); } LXW_INIT_ATTRIBUTES(); /* Convert the row and col to 1/20 twip units with padding. */ if (y_split > 0.0) y_split = (uint32_t) (20 * y_split + 300); if (x_split > 0.0) x_split = _worksheet_calculate_x_split_width(x_split); /* For non-explicit topLeft definitions, estimate the cell offset based on * the pixels dimensions. This is only a workaround and doesn't take * adjusted cell dimensions into account. */ if (top_row == row && left_col == col) { top_row = (lxw_row_t) (0.5 + (y_split - 300.0) / 20.0 / 15.0); left_col = (lxw_col_t) (0.5 + (x_split - 390.0) / 20.0 / 3.0 / 16.0); } lxw_rowcol_to_cell(top_left_cell, top_row, left_col); /* If there is no selection set the active cell to the top left cell. */ if (!has_selection) { lxw_strcpy(user_selection->active_cell, top_left_cell); lxw_strcpy(user_selection->sqref, top_left_cell); } /* Set the active pane. */ if (y_split > 0.0 && x_split > 0.0) { lxw_strcpy(active_pane, "bottomRight"); lxw_rowcol_to_cell(row_cell, top_row, 0); lxw_rowcol_to_cell(col_cell, 0, left_col); selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "topRight"); lxw_strcpy(selection->active_cell, col_cell); lxw_strcpy(selection->sqref, col_cell); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "bottomLeft"); lxw_strcpy(selection->active_cell, row_cell); lxw_strcpy(selection->sqref, row_cell); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "bottomRight"); lxw_strcpy(selection->active_cell, user_selection->active_cell); lxw_strcpy(selection->sqref, user_selection->sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } } else if (x_split > 0.0) { lxw_strcpy(active_pane, "topRight"); selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "topRight"); lxw_strcpy(selection->active_cell, user_selection->active_cell); lxw_strcpy(selection->sqref, user_selection->sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } } else { lxw_strcpy(active_pane, "bottomLeft"); selection = calloc(1, sizeof(lxw_selection)); if (selection) { lxw_strcpy(selection->pane, "bottomLeft"); lxw_strcpy(selection->active_cell, user_selection->active_cell); lxw_strcpy(selection->sqref, user_selection->sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } } if (x_split > 0.0) LXW_PUSH_ATTRIBUTES_DBL("xSplit", x_split); if (y_split > 0.0) LXW_PUSH_ATTRIBUTES_DBL("ySplit", y_split); LXW_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell); if (has_selection) LXW_PUSH_ATTRIBUTES_STR("activePane", active_pane); lxw_xml_empty_tag(self->file, "pane", &attributes); free(user_selection); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_selection(lxw_worksheet *self, lxw_selection *selection) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); if (*selection->pane) LXW_PUSH_ATTRIBUTES_STR("pane", selection->pane); if (*selection->active_cell) LXW_PUSH_ATTRIBUTES_STR("activeCell", selection->active_cell); if (*selection->sqref) LXW_PUSH_ATTRIBUTES_STR("sqref", selection->sqref); lxw_xml_empty_tag(self->file, "selection", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the elements. */ STATIC void _worksheet_write_selections(lxw_worksheet *self) { lxw_selection *selection; STAILQ_FOREACH(selection, self->selections, list_pointers) { _worksheet_write_selection(self, selection); } } /* * Write the frozen or split elements. */ STATIC void _worksheet_write_panes(lxw_worksheet *self) { if (self->panes.type == NO_PANES) return; else if (self->panes.type == FREEZE_PANES) _worksheet_write_freeze_panes(self); else if (self->panes.type == FREEZE_SPLIT_PANES) _worksheet_write_freeze_panes(self); else if (self->panes.type == SPLIT_PANES) _worksheet_write_split_panes(self); } /* * Write the element. */ STATIC void _worksheet_write_sheet_view(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); /* Hide screen gridlines if required */ if (!self->screen_gridlines) LXW_PUSH_ATTRIBUTES_STR("showGridLines", "0"); /* Hide zeroes in cells. */ 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) { 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) { 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 zoom level. */ if (self->zoom != 100) { if (!self->page_view) { LXW_PUSH_ATTRIBUTES_INT("zoomScale", 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)) { lxw_xml_start_tag(self->file, "sheetView", &attributes); _worksheet_write_panes(self); _worksheet_write_selections(self); lxw_xml_end_tag(self->file, "sheetView"); } else { lxw_xml_empty_tag(self->file, "sheetView", &attributes); } LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_sheet_views(lxw_worksheet *self) { lxw_xml_start_tag(self->file, "sheetViews", NULL); /* Write the sheetView element. */ _worksheet_write_sheet_view(self); lxw_xml_end_tag(self->file, "sheetViews"); } /* * Write the element. */ STATIC void _worksheet_write_sheet_format_pr(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_DBL("defaultRowHeight", self->default_row_height); if (self->default_row_height != LXW_DEF_ROW_HEIGHT) LXW_PUSH_ATTRIBUTES_STR("customHeight", "1"); if (self->default_row_zeroed) LXW_PUSH_ATTRIBUTES_STR("zeroHeight", "1"); if (self->outline_row_level) 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(); } /* * Write the element. */ STATIC void _worksheet_write_sheet_data(lxw_worksheet *self) { if (RB_EMPTY(self->table)) { lxw_xml_empty_tag(self->file, "sheetData", NULL); } else { lxw_xml_start_tag(self->file, "sheetData", NULL); _worksheet_write_rows(self); lxw_xml_end_tag(self->file, "sheetData"); } } /* * Write the element when the memory optimization is on. In which * case we read the data stored in the temp file and rewrite it to the XML * sheet file. */ STATIC void _worksheet_write_optimized_sheet_data(lxw_worksheet *self) { size_t read_size = 1; char buffer[LXW_BUFFER_SIZE]; if (self->dim_rowmin == LXW_ROW_MAX) { /* If the dimensions aren't defined then there is no data to write. */ lxw_xml_empty_tag(self->file, "sheetData", NULL); } else { lxw_xml_start_tag(self->file, "sheetData", NULL); /* Flush and rewind 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); /* Ignore return value. There is no easy way to raise error. */ (void) fwrite(buffer, 1, read_size, self->file); } fclose(self->optimize_tmpfile); lxw_xml_end_tag(self->file, "sheetData"); } } /* * Write the element. */ STATIC void _worksheet_write_page_margins(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; double left = self->margin_left; double right = self->margin_right; double top = self->margin_top; double bottom = self->margin_bottom; double header = self->margin_header; double footer = self->margin_footer; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_DBL("left", left); LXW_PUSH_ATTRIBUTES_DBL("right", right); LXW_PUSH_ATTRIBUTES_DBL("top", top); LXW_PUSH_ATTRIBUTES_DBL("bottom", bottom); LXW_PUSH_ATTRIBUTES_DBL("header", header); LXW_PUSH_ATTRIBUTES_DBL("footer", footer); lxw_xml_empty_tag(self->file, "pageMargins", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. * The following is an example taken from Excel. * */ STATIC void _worksheet_write_page_setup(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); if (!self->page_setup_changed) return; /* Set paper size. */ if (self->paper_size) LXW_PUSH_ATTRIBUTES_INT("paperSize", self->paper_size); /* Set the print_scale. */ if (self->print_scale != 100) LXW_PUSH_ATTRIBUTES_INT("scale", self->print_scale); /* Set the "Fit to page" properties. */ if (self->fit_page && self->fit_width != 1) LXW_PUSH_ATTRIBUTES_INT("fitToWidth", self->fit_width); if (self->fit_page && self->fit_height != 1) LXW_PUSH_ATTRIBUTES_INT("fitToHeight", self->fit_height); /* Set the page print direction. */ if (self->page_order) LXW_PUSH_ATTRIBUTES_STR("pageOrder", "overThenDown"); /* Set start page. */ if (self->page_start > 1) LXW_PUSH_ATTRIBUTES_INT("firstPageNumber", self->page_start); /* Set page orientation. */ if (self->orientation) LXW_PUSH_ATTRIBUTES_STR("orientation", "portrait"); else LXW_PUSH_ATTRIBUTES_STR("orientation", "landscape"); /* Set start page active flag. */ if (self->page_start) LXW_PUSH_ATTRIBUTES_INT("useFirstPageNumber", 1); /* Set the DPI. Mainly only for testing. */ if (self->horizontal_dpi) LXW_PUSH_ATTRIBUTES_INT("horizontalDpi", self->horizontal_dpi); if (self->vertical_dpi) LXW_PUSH_ATTRIBUTES_INT("verticalDpi", self->vertical_dpi); lxw_xml_empty_tag(self->file, "pageSetup", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_print_options(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; if (!self->print_options_changed) return; LXW_INIT_ATTRIBUTES(); /* Set horizontal centering. */ if (self->hcenter) { LXW_PUSH_ATTRIBUTES_STR("horizontalCentered", "1"); } /* Set vertical centering. */ if (self->vcenter) { LXW_PUSH_ATTRIBUTES_STR("verticalCentered", "1"); } /* Enable row and column headers. */ if (self->print_headers) { LXW_PUSH_ATTRIBUTES_STR("headings", "1"); } /* Set printed gridlines. */ if (self->print_gridlines) { LXW_PUSH_ATTRIBUTES_STR("gridLines", "1"); } lxw_xml_empty_tag(self->file, "printOptions", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _write_row(lxw_worksheet *self, lxw_row *row, char *spans) { struct xml_attribute_list attributes; struct xml_attribute *attribute; int32_t xf_index = 0; double height; if (row->format) { xf_index = lxw_format_get_xf_index(row->format); } if (row->height_changed) height = row->height; else height = self->default_row_height; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("r", row->row_num + 1); if (spans) LXW_PUSH_ATTRIBUTES_STR("spans", spans); if (xf_index) LXW_PUSH_ATTRIBUTES_INT("s", xf_index); if (row->format) LXW_PUSH_ATTRIBUTES_STR("customFormat", "1"); if (height != LXW_DEF_ROW_HEIGHT) LXW_PUSH_ATTRIBUTES_DBL("ht", height); if (row->hidden) LXW_PUSH_ATTRIBUTES_STR("hidden", "1"); if (height != LXW_DEF_ROW_HEIGHT) LXW_PUSH_ATTRIBUTES_STR("customHeight", "1"); if (row->level) 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); LXW_FREE_ATTRIBUTES(); } /* * 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, uint8_t anchor) { lxw_col_options *col_opt = NULL; uint32_t pixels; double width; double max_digit_width = 7.0; /* For Calabri 11. */ double padding = 5.0; lxw_col_t col_index; /* Search for the col number in the array of col_options. Each col_option * entry contains the start and end column for a range. */ for (col_index = 0; col_index < self->col_options_max; col_index++) { col_opt = self->col_options[col_index]; if (col_opt) { if (col_num >= col_opt->firstcol && col_num <= col_opt->lastcol) break; else col_opt = NULL; } } if (col_opt) { width = col_opt->width; /* Convert to pixels. */ 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); } else { pixels = (uint32_t) (width * max_digit_width + 0.5) + 5; } } else { pixels = self->default_col_pixels; } return pixels; } /* * 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, uint8_t anchor) { lxw_row *row; uint32_t pixels; 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 && anchor != LXW_OBJECT_MOVE_AND_SIZE_AFTER) pixels = 0; else pixels = (uint32_t) (row->height / 0.75); } else { pixels = (uint32_t) (self->default_row_height / 0.75); } return pixels; } /* * Calculate the vertices that define the position of a graphical object * within the worksheet in pixels. * +------------+------------+ * | A | B | * +-----+------------+------------+ * | |(x1,y1) | | * | 1 |(A1)._______|______ | * | | | | | * | | | | | * +-----+----| BITMAP |-----+ * | | | | | * | 2 | |______________. | * | | | (B2)| * | | | (x2,y2)| * +---- +------------+------------+ * * Example of an object that covers some of the area from cell A1 to cell B2. * Based on the width and height of the object we need to calculate 8 vars: * * col_start, row_start, col_end, row_end, x1, y1, x2, y2. * * We also calculate the absolute x and y position of the top left vertex of * the object. This is required for images: * * x_abs, y_abs * * The width and height of the cells that the object occupies can be variable * and have to be taken into account. * * The values of col_start and row_start are passed in from the calling * function. The values of col_end and row_end are calculated by subtracting * 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_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. */ lxw_row_t row_start; /* Row containing top left corner. */ int32_t y1; /* Distance to top of object. */ lxw_col_t col_end; /* Column containing lower right corner. */ double x2; /* Distance to right side of object. */ lxw_row_t row_end; /* Row containing bottom right corner. */ double y2; /* Distance to bottom of object. */ double width; /* Width of object frame. */ double height; /* Height of object frame. */ 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 = 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, ignore_anchor); col_start--; } /* Adjust start row for negative offsets. */ while (y1 < 0 && row_start > 0) { 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) x1 = 0; if (y1 < 0) 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, ignore_anchor); } else { /* Optimization for when the column widths haven't changed. */ x_abs += self->default_col_pixels * col_start; } x_abs += x1; /* 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, 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. */ 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. */ 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 is hidden. */ if (_worksheet_size_col(self, col_start, anchor) > 0) width = width + x1; 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, 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, 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; y2 = height; /* Add the dimensions to the drawing object. */ drawing_object->from.col = col_start; drawing_object->from.row = row_start; drawing_object->from.col_offset = x1; drawing_object->from.row_offset = y1; drawing_object->to.col = col_end; 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_object_properties *image, lxw_drawing_object *drawing_object) { _worksheet_position_object_pixels(self, image, drawing_object); /* Convert the pixel values to EMUs. See above. */ drawing_object->from.col_offset *= 9525; drawing_object->from.row_offset *= 9525; drawing_object->to.col_offset *= 9525; drawing_object->to.row_offset *= 9525; drawing_object->to.col_offset += 0.5; drawing_object->to.row_offset += 0.5; 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; } /* * Calculate the comment object position and vertices. */ void _worksheet_position_vml_object(lxw_worksheet *self, lxw_vml_obj *comment) { lxw_object_properties object_props; lxw_drawing_object drawing_object; object_props.col = comment->start_col; object_props.row = comment->start_row; object_props.x_offset = comment->x_offset; object_props.y_offset = comment->y_offset; object_props.width = comment->width; object_props.height = comment->height; drawing_object.anchor = LXW_OBJECT_DONT_MOVE_DONT_SIZE; _worksheet_position_object_pixels(self, &object_props, &drawing_object); comment->from.col = drawing_object.from.col; comment->from.row = drawing_object.from.row; comment->from.col_offset = drawing_object.from.col_offset; comment->from.row_offset = drawing_object.from.row_offset; comment->to.col = drawing_object.to.col; comment->to.row = drawing_object.to.row; comment->to.col_offset = drawing_object.to.col_offset; comment->to.row_offset = drawing_object.to.row_offset; comment->col_absolute = drawing_object.col_absolute; comment->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_object_properties *object_props) { lxw_drawing_object *drawing_object; lxw_rel_tuple *relationship; double width; double height; char *url; 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); relationship = calloc(1, sizeof(lxw_rel_tuple)); GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); relationship->type = lxw_strdup("/drawing"); GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); lxw_snprintf(filename, LXW_FILENAME_LENGTH, "../drawings/drawing%d.xml", drawing_id); relationship->target = lxw_strdup(filename); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); STAILQ_INSERT_TAIL(self->external_drawing_links, relationship, list_pointers); } drawing_object = calloc(1, sizeof(lxw_drawing_object)); RETURN_VOID_ON_MEM_ERROR(drawing_object); 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; /* Scale to user 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 / object_props->x_dpi; height *= 96.0 / object_props->y_dpi; object_props->width = width; object_props->height = height; _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); relationship->target = lxw_escape_url_characters(url + 1, LXW_TRUE); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); memcpy(relationship->target, "file:///", sizeof("file:///") - 1); } 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 = object_props->width; header_image_vml->height = 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 chart/drawings. */ void lxw_worksheet_prepare_chart(lxw_worksheet *self, uint32_t chart_ref_id, uint32_t drawing_id, lxw_object_properties *object_props, uint8_t is_chartsheet) { lxw_drawing_object *drawing_object; lxw_rel_tuple *relationship; double width; double height; char filename[LXW_FILENAME_LENGTH]; if (!self->drawing) { self->drawing = lxw_drawing_new(); RETURN_VOID_ON_MEM_ERROR(self->drawing); if (is_chartsheet) { self->drawing->embedded = LXW_FALSE; self->drawing->orientation = self->orientation; } else { self->drawing->embedded = LXW_TRUE; } relationship = calloc(1, sizeof(lxw_rel_tuple)); GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); relationship->type = lxw_strdup("/drawing"); GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); lxw_snprintf(filename, LXW_FILENAME_LENGTH, "../drawings/drawing%d.xml", drawing_id); relationship->target = lxw_strdup(filename); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); STAILQ_INSERT_TAIL(self->external_drawing_links, relationship, list_pointers); } drawing_object = calloc(1, sizeof(lxw_drawing_object)); RETURN_VOID_ON_MEM_ERROR(drawing_object); 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("TODO_DESC"); drawing_object->tip = NULL; drawing_object->rel_index = _get_drawing_rel_index(self, NULL); drawing_object->url_rel_index = 0; /* Scale to user scale. */ width = object_props->width * object_props->x_scale; height = object_props->height * object_props->y_scale; /* Convert to the nearest pixel. */ object_props->width = width; object_props->height = height; _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); relationship = calloc(1, sizeof(lxw_rel_tuple)); GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); relationship->type = lxw_strdup("/chart"); GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); lxw_snprintf(filename, 32, "../charts/chart%d.xml", chart_ref_id); relationship->target = lxw_strdup(filename); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers); return; mem_error: if (relationship) { free(relationship->type); free(relationship->target); free(relationship->target_mode); 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 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; } /* * Extract width and height information from a PNG file. */ STATIC lxw_error _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 = 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; while (!feof(stream)) { /* Read the PNG length and type fields for the sub-section. */ if (fread(&length, sizeof(length), 1, stream) < 1) break; if (fread(&type, 1, 4, stream) < 4) break; /* Convert the length to network order. */ length = LXW_UINT32_NETWORK(length); /* The offset for next fseek() is the field length + type length. */ offset = length + 4; if (memcmp(type, "IHDR", 4) == 0) { if (fread(&width, sizeof(width), 1, stream) < 1) break; if (fread(&height, sizeof(height), 1, stream) < 1) break; width = LXW_UINT32_NETWORK(width); height = LXW_UINT32_NETWORK(height); /* Reduce the offset by the length of previous freads(). */ offset -= 8; } if (memcmp(type, "pHYs", 4) == 0) { uint32_t x_ppu = 0; uint32_t y_ppu = 0; uint8_t units = 1; if (fread(&x_ppu, sizeof(x_ppu), 1, stream) < 1) break; if (fread(&y_ppu, sizeof(y_ppu), 1, stream) < 1) break; if (fread(&units, sizeof(units), 1, stream) < 1) break; if (units == 1) { x_ppu = LXW_UINT32_NETWORK(x_ppu); y_ppu = LXW_UINT32_NETWORK(y_ppu); x_dpi = (double) x_ppu *0.0254; y_dpi = (double) y_ppu *0.0254; } /* Reduce the offset by the length of previous freads(). */ offset -= 9; } if (memcmp(type, "IEND", 4) == 0) break; if (!feof(stream)) { fseek_err = fseek(stream, offset, SEEK_CUR); if (fseek_err) goto file_error; } } /* Ensure that we read some valid data from the file. */ if (width == 0) goto file_error; /* Set the image metadata. */ 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 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_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_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; /* Search through the image data and read the JPEG markers. */ while (!feof(stream)) { /* Read the JPEG marker and length fields for the sub-section. */ if (fread(&marker, sizeof(marker), 1, stream) < 1) break; if (fread(&length, sizeof(length), 1, stream) < 1) break; /* Convert the marker and length to network order. */ marker = LXW_UINT16_NETWORK(marker); length = LXW_UINT16_NETWORK(length); /* The offset for next fseek() is the field length + type length. */ offset = length - 2; /* Read the height and width in the 0xFFCn elements (except C4, C8 */ /* and CC which aren't SOF markers). */ if ((marker & 0xFFF0) == 0xFFC0 && marker != 0xFFC4 && marker != 0xFFC8 && marker != 0xFFCC) { /* Skip 1 byte to height and width. */ fseek_err = fseek(stream, 1, SEEK_CUR); if (fseek_err) goto file_error; if (fread(&height, sizeof(height), 1, stream) < 1) break; if (fread(&width, sizeof(width), 1, stream) < 1) break; height = LXW_UINT16_NETWORK(height); width = LXW_UINT16_NETWORK(width); offset -= 9; } /* Read the DPI in the 0xFFE0 element. */ if (marker == 0xFFE0) { uint16_t x_density = 0; uint16_t y_density = 0; uint8_t units = 1; fseek_err = fseek(stream, 7, SEEK_CUR); if (fseek_err) goto file_error; if (fread(&units, sizeof(units), 1, stream) < 1) break; if (fread(&x_density, sizeof(x_density), 1, stream) < 1) break; if (fread(&y_density, sizeof(y_density), 1, stream) < 1) break; x_density = LXW_UINT16_NETWORK(x_density); y_density = LXW_UINT16_NETWORK(y_density); if (units == 1) { x_dpi = x_density; y_dpi = y_density; } if (units == 2) { x_dpi = x_density * 2.54; y_dpi = y_density * 2.54; } offset -= 12; } if (marker == 0xFFDA) break; if (!feof(stream)) { fseek_err = fseek(stream, offset, SEEK_CUR); if (fseek_err) goto file_error; } } /* Ensure that we read some valid data from the file. */ if (width == 0) goto file_error; /* Set the image metadata. */ 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 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_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_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; 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_UINT32_HOST(height); width = LXW_UINT32_HOST(width); /* Set the image metadata. */ 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 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_object_properties *image_props) { unsigned char signature[4]; #ifndef USE_NO_MD5 uint8_t i; lxw_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_props->stream) < 4) { LXW_WARN_FORMAT1("worksheet image insertion: " "couldn't read image type for: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; } if (memcmp(&signature[1], "PNG", 3) == 0) { 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_props) != LXW_NO_ERROR) return LXW_ERROR_IMAGE_DIMENSIONS; } else if (memcmp(signature, "BM", 2) == 0) { if (_process_bmp(image_props) != LXW_NO_ERROR) return LXW_ERROR_IMAGE_DIMENSIONS; } else { LXW_WARN_FORMAT1("worksheet image insertion: " "unsupported image format for: %s.", 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); lxw_md5_init(&md5_context); size_read = fread(buffer, 1, LXW_IMAGE_BUFFER_SIZE, image_props->stream); while (size_read) { lxw_md5_update(&md5_context, buffer, size_read); size_read = fread(buffer, 1, LXW_IMAGE_BUFFER_SIZE, image_props->stream); } lxw_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. * ****************************************************************************/ /* * Write out a number worksheet cell. Doesn't use the xml functions as an * optimization in the inner cell writing loop. */ STATIC void _write_number_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { #ifdef USE_DOUBLE_FUNCTION char data[LXW_ATTR_32]; lxw_sprintf_dbl(data, cell->u.number); if (style_index) fprintf(self->file, "%s", range, style_index, data); else fprintf(self->file, "%s", range, data); #else if (style_index) fprintf(self->file, "%.16g", range, style_index, cell->u.number); else fprintf(self->file, "%.16g", range, cell->u.number); #endif } /* * Write out a string worksheet cell. Doesn't use the xml functions as an * optimization in the inner cell writing loop. */ STATIC void _write_string_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { if (style_index) fprintf(self->file, "%d", range, style_index, cell->u.string_id); else fprintf(self->file, "%d", range, cell->u.string_id); } /* * Write out an inline string. Doesn't use the xml functions as an * optimization in the inner cell writing loop. */ STATIC void _write_inline_string_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { char *string = lxw_escape_data(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, "" "%s", range, style_index, string); else fprintf(self->file, "" "%s", range, string); } else { if (style_index) fprintf(self->file, "" "%s", range, style_index, string); else fprintf(self->file, "" "%s", range, string); } free(string); } /* * Write out an inline rich string. Doesn't use the xml functions as an * optimization in the inner cell writing loop. */ STATIC void _write_inline_rich_string_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { 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, "%s", range, style_index, string); else fprintf(self->file, "%s", range, string); } else { if (style_index) fprintf(self->file, "" "%s", range, style_index, string); else fprintf(self->file, "" "%s", range, string); } } /* * Write out a formula worksheet cell with a numeric result. */ STATIC void _write_formula_num_cell(lxw_worksheet *self, lxw_cell *cell) { char data[LXW_ATTR_32]; lxw_sprintf_dbl(data, cell->formula_result); 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) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char data[LXW_ATTR_32]; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("t", "array"); LXW_PUSH_ATTRIBUTES_STR("ref", cell->user_data1); lxw_sprintf_dbl(data, cell->formula_result); lxw_xml_data_element(self->file, "f", cell->u.string, &attributes); lxw_xml_data_element(self->file, "v", data, NULL); LXW_FREE_ATTRIBUTES(); } /* * Write out a boolean worksheet cell. */ STATIC void _write_boolean_cell(lxw_worksheet *self, lxw_cell *cell) { char data[LXW_ATTR_32]; if (cell->u.number) data[0] = '1'; else data[0] = '0'; data[1] = '\0'; lxw_xml_data_element(self->file, "v", data, NULL); } /* * Calculate the "spans" attribute of the tag. This is an XLSX * optimization and isn't strictly required. However, it makes comparing * files easier. * * The span is the same for each block of 16 rows. */ STATIC void _calculate_spans(struct lxw_row *row, char *span, int32_t *block_num) { lxw_cell *cell_min = RB_MIN(lxw_table_cells, row->cells); lxw_cell *cell_max = RB_MAX(lxw_table_cells, row->cells); lxw_col_t span_col_min = cell_min->col_num; lxw_col_t span_col_max = cell_max->col_num; lxw_col_t col_min; lxw_col_t col_max; *block_num = row->row_num / 16; row = RB_NEXT(lxw_table_rows, root, row); while (row && (int32_t) (row->row_num / 16) == *block_num) { if (!RB_EMPTY(row->cells)) { cell_min = RB_MIN(lxw_table_cells, row->cells); cell_max = RB_MAX(lxw_table_cells, row->cells); col_min = cell_min->col_num; col_max = cell_max->col_num; if (col_min < span_col_min) span_col_min = col_min; if (col_max > span_col_max) span_col_max = col_max; } row = RB_NEXT(lxw_table_rows, root, row); } lxw_snprintf(span, LXW_MAX_CELL_RANGE_LENGTH, "%d:%d", span_col_min + 1, span_col_max + 1); } /* * Write out a generic worksheet cell. */ STATIC void _write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char range[LXW_MAX_CELL_NAME_LENGTH] = { 0 }; lxw_row_t row_num = cell->row_num; lxw_col_t col_num = cell->col_num; int32_t style_index = 0; lxw_rowcol_to_cell(range, row_num, col_num); if (cell->format) { style_index = lxw_format_get_xf_index(cell->format); } else if (row_format) { style_index = lxw_format_get_xf_index(row_format); } else if (col_num < self->col_formats_max && self->col_formats[col_num]) { style_index = lxw_format_get_xf_index(self->col_formats[col_num]); } /* Unrolled optimization for most commonly written cell types. */ if (cell->type == NUMBER_CELL) { _write_number_cell(self, range, style_index, cell); return; } if (cell->type == STRING_CELL) { _write_string_cell(self, range, style_index, cell); return; } if (cell->type == INLINE_STRING_CELL) { _write_inline_string_cell(self, range, style_index, cell); return; } if (cell->type == INLINE_RICH_STRING_CELL) { _write_inline_rich_string_cell(self, range, style_index, cell); return; } /* For other cell types use the general functions. */ LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("r", range); 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); 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) { 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); lxw_xml_end_tag(self->file, "c"); } 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"); } LXW_FREE_ATTRIBUTES(); } /* * Write out the worksheet data as a series of rows and cells. */ STATIC void _worksheet_write_rows(lxw_worksheet *self) { lxw_row *row; lxw_cell *cell; int32_t block_num = -1; char spans[LXW_MAX_CELL_RANGE_LENGTH] = { 0 }; RB_FOREACH(row, lxw_table_rows, self->table) { if (RB_EMPTY(row->cells)) { /* Row contains no cells but has height, format or other data. */ /* Write a default span for default rows. */ if (self->default_row_set) _write_row(self, row, "1:1"); else _write_row(self, row, NULL); } else { /* Row and cell data. */ if ((int32_t) row->row_num / 16 > block_num) _calculate_spans(row, spans, &block_num); _write_row(self, row, spans); 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"); } } } } /* * Write out the worksheet data as a single row with cells. This method is * used when memory optimization is on. A single row is written and the data * array is reset. That way only one row of data is kept in memory at any one * time. We don't write span data in the optimized case since it is optional. */ void lxw_worksheet_write_single_row(lxw_worksheet *self) { lxw_row *row = self->optimize_row; lxw_col_t col; /* skip row if it doesn't contain row formatting, cell data or a comment. */ if (!(row->row_changed || row->data_changed)) return; /* Write the cells if the row contains data. */ if (!row->data_changed) { /* Row data only. No cells. */ _write_row(self, row, NULL); } else { /* Row and cell data. */ _write_row(self, row, NULL); for (col = self->dim_colmin; col <= self->dim_colmax; col++) { if (self->array[col]) { _write_cell(self, self->array[col], row->format); _free_cell(self->array[col]); self->array[col] = NULL; } } lxw_xml_end_tag(self->file, "row"); } /* Reset the row. */ row->height = LXW_DEF_ROW_HEIGHT; row->format = NULL; row->hidden = LXW_FALSE; row->level = 0; 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, char *filename, uint8_t image_position) { FILE *image_stream; 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; } return LXW_NO_ERROR; } /* * Write the element. */ STATIC void _worksheet_write_col_info(lxw_worksheet *self, lxw_col_options *options) { struct xml_attribute_list attributes; struct xml_attribute *attribute; double width = options->width; uint8_t has_custom_width = LXW_TRUE; int32_t xf_index = 0; double max_digit_width = 7.0; /* For Calabri 11. */ double padding = 5.0; /* Get the format index. */ if (options->format) { xf_index = lxw_format_get_xf_index(options->format); } /* Check if width is the Excel default. */ if (width == LXW_DEF_COL_WIDTH) { /* The default col width changes to 0 for hidden columns. */ if (options->hidden) width = 0; else has_custom_width = LXW_FALSE; } /* Convert column width from user units to character width. */ if (width > 0) { if (width < 1) { width = (uint16_t) (((uint16_t) (width * (max_digit_width + padding) + 0.5)) / max_digit_width * 256.0) / 256.0; } else { width = (uint16_t) (((uint16_t) (width * max_digit_width + 0.5) + padding) / max_digit_width * 256.0) / 256.0; } } LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("min", 1 + options->firstcol); LXW_PUSH_ATTRIBUTES_INT("max", 1 + options->lastcol); LXW_PUSH_ATTRIBUTES_DBL("width", width); if (xf_index) LXW_PUSH_ATTRIBUTES_INT("style", xf_index); if (options->hidden) LXW_PUSH_ATTRIBUTES_STR("hidden", "1"); if (has_custom_width) LXW_PUSH_ATTRIBUTES_STR("customWidth", "1"); if (options->level) LXW_PUSH_ATTRIBUTES_INT("outlineLevel", options->level); if (options->collapsed) LXW_PUSH_ATTRIBUTES_STR("collapsed", "1"); lxw_xml_empty_tag(self->file, "col", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element and sub elements. */ STATIC void _worksheet_write_cols(lxw_worksheet *self) { lxw_col_t col; if (!self->col_size_changed) return; lxw_xml_start_tag(self->file, "cols", NULL); for (col = 0; col < self->col_options_max; col++) { if (self->col_options[col]) _worksheet_write_col_info(self, self->col_options[col]); } lxw_xml_end_tag(self->file, "cols"); } /* * Write the element. */ STATIC void _worksheet_write_merge_cell(lxw_worksheet *self, lxw_merged_range *merged_range) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char ref[LXW_MAX_CELL_RANGE_LENGTH]; LXW_INIT_ATTRIBUTES(); /* Convert the merge dimensions to a cell range. */ lxw_rowcol_to_range(ref, merged_range->first_row, merged_range->first_col, merged_range->last_row, merged_range->last_col); LXW_PUSH_ATTRIBUTES_STR("ref", ref); lxw_xml_empty_tag(self->file, "mergeCell", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_merge_cells(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; lxw_merged_range *merged_range; if (self->merged_range_count) { LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("count", self->merged_range_count); lxw_xml_start_tag(self->file, "mergeCells", &attributes); STAILQ_FOREACH(merged_range, self->merged_ranges, list_pointers) { _worksheet_write_merge_cell(self, merged_range); } lxw_xml_end_tag(self->file, "mergeCells"); LXW_FREE_ATTRIBUTES(); } } /* * Write the element. */ STATIC void _worksheet_write_odd_header(lxw_worksheet *self) { lxw_xml_data_element(self->file, "oddHeader", self->header, NULL); } /* * Write the element. */ STATIC void _worksheet_write_odd_footer(lxw_worksheet *self) { lxw_xml_data_element(self->file, "oddFooter", self->footer, NULL); } /* * Write the element. */ STATIC void _worksheet_write_header_footer(lxw_worksheet *self) { if (!self->header_footer_changed) return; lxw_xml_start_tag(self->file, "headerFooter", NULL); if (*self->header) _worksheet_write_odd_header(self); if (*self->footer) _worksheet_write_odd_footer(self); lxw_xml_end_tag(self->file, "headerFooter"); } /* * Write the element. */ STATIC void _worksheet_write_page_set_up_pr(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; if (!self->fit_page) return; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("fitToPage", "1"); lxw_xml_empty_tag(self->file, "pageSetUpPr", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_tab_color(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char rgb_str[LXW_ATTR_32]; if (self->tab_color == LXW_COLOR_UNSET) return; lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X", self->tab_color & LXW_COLOR_MASK); LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str); lxw_xml_empty_tag(self->file, "tabColor", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_outline_pr(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; if (!self->outline_changed) return; LXW_INIT_ATTRIBUTES(); if (self->outline_style) LXW_PUSH_ATTRIBUTES_STR("applyStyles", "1"); if (!self->outline_below) LXW_PUSH_ATTRIBUTES_STR("summaryBelow", "0"); if (!self->outline_right) LXW_PUSH_ATTRIBUTES_STR("summaryRight", "0"); if (!self->outline_on) LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0"); lxw_xml_empty_tag(self->file, "outlinePr", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element for Sheet level properties. */ STATIC void _worksheet_write_sheet_pr(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; if (!self->fit_page && !self->filter_on && self->tab_color == LXW_COLOR_UNSET && !self->outline_changed && !self->vba_codename && !self->is_chartsheet) { return; } LXW_INIT_ATTRIBUTES(); if (self->vba_codename) LXW_PUSH_ATTRIBUTES_STR("codeName", self->vba_codename); if (self->filter_on) LXW_PUSH_ATTRIBUTES_STR("filterMode", "1"); if (self->fit_page || self->tab_color != LXW_COLOR_UNSET || self->outline_changed) { lxw_xml_start_tag(self->file, "sheetPr", &attributes); _worksheet_write_tab_color(self); _worksheet_write_outline_pr(self); _worksheet_write_page_set_up_pr(self); lxw_xml_end_tag(self->file, "sheetPr"); } else { lxw_xml_empty_tag(self->file, "sheetPr", &attributes); } LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_brk(lxw_worksheet *self, uint32_t id, uint32_t max) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("id", id); LXW_PUSH_ATTRIBUTES_INT("max", max); LXW_PUSH_ATTRIBUTES_STR("man", "1"); lxw_xml_empty_tag(self->file, "brk", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_row_breaks(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; uint16_t count = self->hbreaks_count; uint16_t i; if (!count) return; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("count", count); LXW_PUSH_ATTRIBUTES_INT("manualBreakCount", count); lxw_xml_start_tag(self->file, "rowBreaks", &attributes); for (i = 0; i < count; i++) _worksheet_write_brk(self, self->hbreaks[i], LXW_COL_MAX - 1); lxw_xml_end_tag(self->file, "rowBreaks"); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_col_breaks(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; uint16_t count = self->vbreaks_count; uint16_t i; if (!count) return; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("count", count); LXW_PUSH_ATTRIBUTES_INT("manualBreakCount", count); lxw_xml_start_tag(self->file, "colBreaks", &attributes); for (i = 0; i < count; i++) _worksheet_write_brk(self, self->vbreaks[i], LXW_ROW_MAX - 1); lxw_xml_end_tag(self->file, "colBreaks"); LXW_FREE_ATTRIBUTES(); } /* * Write the 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]; if (!self->autofilter.in_use) return; lxw_rowcol_to_range(range, self->autofilter.first_row, self->autofilter.first_col, 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); LXW_FREE_ATTRIBUTES(); } /* * Write the element for external links. */ STATIC void _worksheet_write_hyperlink_external(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *location, const char *tooltip, uint16_t id) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char ref[LXW_MAX_CELL_NAME_LENGTH]; char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; lxw_rowcol_to_cell(ref, row_num, col_num); lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id); LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("ref", ref); LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); if (location) LXW_PUSH_ATTRIBUTES_STR("location", location); if (tooltip) LXW_PUSH_ATTRIBUTES_STR("tooltip", tooltip); lxw_xml_empty_tag(self->file, "hyperlink", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the element for internal links. */ STATIC void _worksheet_write_hyperlink_internal(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *location, const char *display, const char *tooltip) { struct xml_attribute_list attributes; struct xml_attribute *attribute; char ref[LXW_MAX_CELL_NAME_LENGTH]; lxw_rowcol_to_cell(ref, row_num, col_num); LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("ref", ref); if (location) LXW_PUSH_ATTRIBUTES_STR("location", location); if (tooltip) LXW_PUSH_ATTRIBUTES_STR("tooltip", tooltip); if (display) LXW_PUSH_ATTRIBUTES_STR("display", display); lxw_xml_empty_tag(self->file, "hyperlink", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Process any stored hyperlinks in row/col order and write the * element. The attributes are different for internal and external links. */ STATIC void _worksheet_write_hyperlinks(lxw_worksheet *self) { lxw_row *row; lxw_cell *link; lxw_rel_tuple *relationship; if (RB_EMPTY(self->hyperlinks)) return; /* Write the hyperlink elements. */ lxw_xml_start_tag(self->file, "hyperlinks", NULL); RB_FOREACH(row, lxw_table_rows, self->hyperlinks) { RB_FOREACH(link, lxw_table_cells, row->cells) { if (link->type == HYPERLINK_URL || link->type == HYPERLINK_EXTERNAL) { self->rel_count++; 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); relationship->target = lxw_strdup(link->u.string); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); relationship->target_mode = lxw_strdup("External"); GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error); STAILQ_INSERT_TAIL(self->external_hyperlinks, relationship, list_pointers); _worksheet_write_hyperlink_external(self, link->row_num, link->col_num, link->user_data1, link->user_data2, self->rel_count); } if (link->type == HYPERLINK_INTERNAL) { _worksheet_write_hyperlink_internal(self, link->row_num, link->col_num, link->u.string, link->user_data1, link->user_data2); } } } lxw_xml_end_tag(self->file, "hyperlinks"); return; mem_error: if (relationship) { free(relationship->type); free(relationship->target); free(relationship->target_mode); free(relationship); } lxw_xml_end_tag(self->file, "hyperlinks"); } /* * Write the element. */ STATIC void _worksheet_write_sheet_protection(lxw_worksheet *self, lxw_protection_obj *protect) { struct xml_attribute_list attributes; struct xml_attribute *attribute; if (!protect->is_configured) return; LXW_INIT_ATTRIBUTES(); if (*protect->hash) LXW_PUSH_ATTRIBUTES_STR("password", protect->hash); if (!protect->no_sheet) LXW_PUSH_ATTRIBUTES_INT("sheet", 1); if (!protect->no_content) LXW_PUSH_ATTRIBUTES_INT("content", 1); if (!protect->objects) LXW_PUSH_ATTRIBUTES_INT("objects", 1); if (!protect->scenarios) LXW_PUSH_ATTRIBUTES_INT("scenarios", 1); if (protect->format_cells) LXW_PUSH_ATTRIBUTES_INT("formatCells", 0); if (protect->format_columns) LXW_PUSH_ATTRIBUTES_INT("formatColumns", 0); if (protect->format_rows) LXW_PUSH_ATTRIBUTES_INT("formatRows", 0); if (protect->insert_columns) LXW_PUSH_ATTRIBUTES_INT("insertColumns", 0); if (protect->insert_rows) LXW_PUSH_ATTRIBUTES_INT("insertRows", 0); if (protect->insert_hyperlinks) LXW_PUSH_ATTRIBUTES_INT("insertHyperlinks", 0); if (protect->delete_columns) LXW_PUSH_ATTRIBUTES_INT("deleteColumns", 0); if (protect->delete_rows) LXW_PUSH_ATTRIBUTES_INT("deleteRows", 0); if (protect->no_select_locked_cells) LXW_PUSH_ATTRIBUTES_INT("selectLockedCells", 1); if (protect->sort) LXW_PUSH_ATTRIBUTES_INT("sort", 0); if (protect->autofilter) LXW_PUSH_ATTRIBUTES_INT("autoFilter", 0); if (protect->pivot_tables) LXW_PUSH_ATTRIBUTES_INT("pivotTables", 0); if (protect->no_select_unlocked_cells) LXW_PUSH_ATTRIBUTES_INT("selectUnlockedCells", 1); lxw_xml_empty_tag(self->file, "sheetProtection", &attributes); LXW_FREE_ATTRIBUTES(); } /* * Write the 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 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 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(); } /* * Write the elements. */ STATIC void _worksheet_write_drawings(lxw_worksheet *self) { if (!self->drawing) return; self->rel_count++; _worksheet_write_drawing(self, self->rel_count); } /* * Write the element for numbers. */ STATIC void _worksheet_write_formula1_num(lxw_worksheet *self, double number) { char data[LXW_ATTR_32]; lxw_sprintf_dbl(data, number); lxw_xml_data_element(self->file, "formula1", data, NULL); } /* * Write the element for strings/formulas. */ STATIC void _worksheet_write_formula1_str(lxw_worksheet *self, char *str) { lxw_xml_data_element(self->file, "formula1", str, NULL); } /* * Write the element for numbers. */ STATIC void _worksheet_write_formula2_num(lxw_worksheet *self, double number) { char data[LXW_ATTR_32]; lxw_sprintf_dbl(data, number); lxw_xml_data_element(self->file, "formula2", data, NULL); } /* * Write the element for strings/formulas. */ STATIC void _worksheet_write_formula2_str(lxw_worksheet *self, char *str) { lxw_xml_data_element(self->file, "formula2", str, NULL); } /* * Write the element. */ STATIC void _worksheet_write_data_validation(lxw_worksheet *self, lxw_data_val_obj *validation) { struct xml_attribute_list attributes; struct xml_attribute *attribute; uint8_t is_between = 0; LXW_INIT_ATTRIBUTES(); switch (validation->validate) { case LXW_VALIDATION_TYPE_INTEGER: case LXW_VALIDATION_TYPE_INTEGER_FORMULA: LXW_PUSH_ATTRIBUTES_STR("type", "whole"); break; case LXW_VALIDATION_TYPE_DECIMAL: case LXW_VALIDATION_TYPE_DECIMAL_FORMULA: LXW_PUSH_ATTRIBUTES_STR("type", "decimal"); break; case LXW_VALIDATION_TYPE_LIST: case LXW_VALIDATION_TYPE_LIST_FORMULA: LXW_PUSH_ATTRIBUTES_STR("type", "list"); break; case LXW_VALIDATION_TYPE_DATE: case LXW_VALIDATION_TYPE_DATE_FORMULA: case LXW_VALIDATION_TYPE_DATE_NUMBER: LXW_PUSH_ATTRIBUTES_STR("type", "date"); break; case LXW_VALIDATION_TYPE_TIME: case LXW_VALIDATION_TYPE_TIME_FORMULA: case LXW_VALIDATION_TYPE_TIME_NUMBER: LXW_PUSH_ATTRIBUTES_STR("type", "time"); break; case LXW_VALIDATION_TYPE_LENGTH: case LXW_VALIDATION_TYPE_LENGTH_FORMULA: LXW_PUSH_ATTRIBUTES_STR("type", "textLength"); break; case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: LXW_PUSH_ATTRIBUTES_STR("type", "custom"); break; } switch (validation->criteria) { case LXW_VALIDATION_CRITERIA_EQUAL_TO: LXW_PUSH_ATTRIBUTES_STR("operator", "equal"); break; case LXW_VALIDATION_CRITERIA_NOT_EQUAL_TO: LXW_PUSH_ATTRIBUTES_STR("operator", "notEqual"); break; case LXW_VALIDATION_CRITERIA_LESS_THAN: LXW_PUSH_ATTRIBUTES_STR("operator", "lessThan"); break; case LXW_VALIDATION_CRITERIA_LESS_THAN_OR_EQUAL_TO: LXW_PUSH_ATTRIBUTES_STR("operator", "lessThanOrEqual"); break; case LXW_VALIDATION_CRITERIA_GREATER_THAN: LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThan"); break; case LXW_VALIDATION_CRITERIA_GREATER_THAN_OR_EQUAL_TO: LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThanOrEqual"); break; case LXW_VALIDATION_CRITERIA_BETWEEN: /* Between is the default for 2 formulas and isn't added. */ is_between = 1; break; case LXW_VALIDATION_CRITERIA_NOT_BETWEEN: is_between = 1; LXW_PUSH_ATTRIBUTES_STR("operator", "notBetween"); break; } if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_WARNING) LXW_PUSH_ATTRIBUTES_STR("errorStyle", "warning"); if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_INFORMATION) LXW_PUSH_ATTRIBUTES_STR("errorStyle", "information"); if (validation->ignore_blank) LXW_PUSH_ATTRIBUTES_INT("allowBlank", 1); if (validation->dropdown == LXW_VALIDATION_OFF) LXW_PUSH_ATTRIBUTES_INT("showDropDown", 1); if (validation->show_input) LXW_PUSH_ATTRIBUTES_INT("showInputMessage", 1); if (validation->show_error) LXW_PUSH_ATTRIBUTES_INT("showErrorMessage", 1); if (validation->error_title) LXW_PUSH_ATTRIBUTES_STR("errorTitle", validation->error_title); if (validation->error_message) LXW_PUSH_ATTRIBUTES_STR("error", validation->error_message); if (validation->input_title) LXW_PUSH_ATTRIBUTES_STR("promptTitle", validation->input_title); if (validation->input_message) LXW_PUSH_ATTRIBUTES_STR("prompt", validation->input_message); LXW_PUSH_ATTRIBUTES_STR("sqref", validation->sqref); if (validation->validate == LXW_VALIDATION_TYPE_ANY) lxw_xml_empty_tag(self->file, "dataValidation", &attributes); else lxw_xml_start_tag(self->file, "dataValidation", &attributes); /* Write the formula1 and formula2 elements. */ switch (validation->validate) { case LXW_VALIDATION_TYPE_INTEGER: case LXW_VALIDATION_TYPE_DECIMAL: case LXW_VALIDATION_TYPE_LENGTH: case LXW_VALIDATION_TYPE_DATE: case LXW_VALIDATION_TYPE_TIME: case LXW_VALIDATION_TYPE_DATE_NUMBER: case LXW_VALIDATION_TYPE_TIME_NUMBER: _worksheet_write_formula1_num(self, validation->value_number); if (is_between) _worksheet_write_formula2_num(self, validation->maximum_number); break; case LXW_VALIDATION_TYPE_INTEGER_FORMULA: case LXW_VALIDATION_TYPE_DECIMAL_FORMULA: case LXW_VALIDATION_TYPE_LENGTH_FORMULA: case LXW_VALIDATION_TYPE_DATE_FORMULA: case LXW_VALIDATION_TYPE_TIME_FORMULA: case LXW_VALIDATION_TYPE_LIST: case LXW_VALIDATION_TYPE_LIST_FORMULA: case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: _worksheet_write_formula1_str(self, validation->value_formula); if (is_between) _worksheet_write_formula2_str(self, validation->maximum_formula); break; } if (validation->validate != LXW_VALIDATION_TYPE_ANY) lxw_xml_end_tag(self->file, "dataValidation"); LXW_FREE_ATTRIBUTES(); } /* * Write the element. */ STATIC void _worksheet_write_data_validations(lxw_worksheet *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; lxw_data_val_obj *data_validation; if (self->num_validations == 0) return; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("count", self->num_validations); lxw_xml_start_tag(self->file, "dataValidations", &attributes); STAILQ_FOREACH(data_validation, self->data_validations, list_pointers) { /* Write the dataValidation element. */ _worksheet_write_data_validation(self, data_validation); } lxw_xml_end_tag(self->file, "dataValidations"); LXW_FREE_ATTRIBUTES(); } /* * Write the 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 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 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 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 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 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 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 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 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 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 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 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 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 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 > 1.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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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"); } /* * External functions to call intern XML methods shared with chartsheet. */ void lxw_worksheet_write_sheet_views(lxw_worksheet *self) { _worksheet_write_sheet_views(self); } void lxw_worksheet_write_page_margins(lxw_worksheet *self) { _worksheet_write_page_margins(self); } void lxw_worksheet_write_drawings(lxw_worksheet *self) { _worksheet_write_drawings(self); } void lxw_worksheet_write_sheet_protection(lxw_worksheet *self, lxw_protection_obj *protect) { _worksheet_write_sheet_protection(self, protect); } void lxw_worksheet_write_sheet_pr(lxw_worksheet *self) { _worksheet_write_sheet_pr(self); } void lxw_worksheet_write_page_setup(lxw_worksheet *self) { _worksheet_write_page_setup(self); } void lxw_worksheet_write_header_footer(lxw_worksheet *self) { _worksheet_write_header_footer(self); } /* * Assemble and write the XML file. */ void lxw_worksheet_assemble_xml_file(lxw_worksheet *self) { /* Write the XML declaration. */ _worksheet_xml_declaration(self); /* Write the worksheet element. */ _worksheet_write_worksheet(self); /* Write the worksheet properties. */ _worksheet_write_sheet_pr(self); /* Write the worksheet dimensions. */ _worksheet_write_dimension(self); /* Write the sheet view properties. */ _worksheet_write_sheet_views(self); /* Write the sheet format properties. */ _worksheet_write_sheet_format_pr(self); /* Write the sheet column info. */ _worksheet_write_cols(self); /* Write the sheetData element. */ if (!self->optimize) _worksheet_write_sheet_data(self); else _worksheet_write_optimized_sheet_data(self); /* Write the sheetProtection element. */ _worksheet_write_sheet_protection(self, &self->protection); /* Write the autoFilter element. */ _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); /* Write the printOptions element. */ _worksheet_write_print_options(self); /* Write the worksheet page_margins. */ _worksheet_write_page_margins(self); /* Write the worksheet page setup. */ _worksheet_write_page_setup(self); /* Write the headerFooter element. */ _worksheet_write_header_footer(self); /* Write the rowBreaks element. */ _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 extLst element. */ _worksheet_write_ext_list(self); /* Close the worksheet tag. */ lxw_xml_end_tag(self->file, "worksheet"); } /***************************************************************************** * * Public functions. * ****************************************************************************/ /* * Write a number to a cell in Excel. */ lxw_error worksheet_write_number(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, double value, lxw_format *format) { lxw_cell *cell; lxw_error err; err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; cell = _new_number_cell(row_num, col_num, value, format); _insert_cell(self, row_num, col_num, cell); return LXW_NO_ERROR; } /* * Write a string to an Excel file. */ lxw_error worksheet_write_string(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *string, lxw_format *format) { lxw_cell *cell; int32_t string_id; char *string_copy; struct sst_element *sst_element; lxw_error err; if (!string || !*string) { /* Treat a NULL or empty string with formatting as a blank cell. */ /* Null strings without formats should be ignored. */ if (format) return worksheet_write_blank(self, row_num, col_num, format); else return LXW_NO_ERROR; } err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; if (lxw_utf8_strlen(string) > LXW_STR_MAX) 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, string, LXW_FALSE); 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 (lxw_has_control_characters(string)) { string_copy = lxw_escape_control_characters(string); } else { string_copy = lxw_strdup(string); } cell = _new_inline_string_cell(row_num, col_num, string_copy, format); } _insert_cell(self, row_num, col_num, cell); return LXW_NO_ERROR; } /* * Write a formula with a numerical result to a cell in Excel. */ lxw_error worksheet_write_formula_num(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *formula, lxw_format *format, double 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->formula_result = result; _insert_cell(self, row_num, col_num, cell); 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, lxw_col_t col_num, const char *formula, lxw_format *format) { 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. */ 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) { lxw_cell *cell; lxw_row_t tmp_row; lxw_col_t tmp_col; char *formula_copy; char *range; lxw_error err; /* 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; } if (!formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; /* 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); 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); /* 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); cell->formula_result = result; _insert_cell(self, first_row, first_col, cell); /* 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) continue; worksheet_write_number(self, tmp_row, tmp_col, 0, format); } } } return LXW_NO_ERROR; } /* * 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); } /* * 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, lxw_format *format) { lxw_cell *cell; lxw_error err; /* Blank cells without formatting are ignored by Excel. */ if (!format) return LXW_NO_ERROR; err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; cell = _new_blank_cell(row_num, col_num, format); _insert_cell(self, row_num, col_num, cell); return LXW_NO_ERROR; } /* * Write a boolean cell with a format to a cell in Excel. */ lxw_error worksheet_write_boolean(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, int value, lxw_format *format) { 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); _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_datetime(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, lxw_datetime *datetime, 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_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 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 *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; 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) { 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; /* Set the URI scheme from external links. */ found_string = strstr(url, "external:"); if (found_string) link_type = HYPERLINK_EXTERNAL; if (string) { string_copy = lxw_strdup(string); GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error); } else { if (link_type == HYPERLINK_URL) { /* Strip the mailto header. */ found_string = strstr(url, "mailto:"); if (found_string) string_copy = lxw_strdup(url + sizeof("mailto")); else string_copy = lxw_strdup(url); } else { string_copy = lxw_strdup(url + sizeof("__ternal")); } GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error); } if (url) { if (link_type == HYPERLINK_URL) url_copy = lxw_strdup(url); else url_copy = lxw_strdup(url + sizeof("__ternal")); GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error); } if (tooltip) { tooltip_copy = lxw_strdup(tooltip); GOTO_LABEL_ON_MEM_ERROR(tooltip_copy, mem_error); } if (link_type == HYPERLINK_INTERNAL) { url_string = lxw_strdup(string_copy); GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error); } /* Split url into the link and optional anchor/location. */ found_string = strchr(url_copy, '#'); if (found_string) { free(url_string); url_string = lxw_strdup(found_string + 1); GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error); *found_string = '\0'; } /* 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); 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". */ /* 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] = '\\'; /* Look for Windows style "C:/" link or Windows share "\\" link. */ found_string = strchr(url_copy, ':'); if (!found_string) found_string = strstr(url_copy, "\\\\"); if (found_string) { /* Add the file:/// URI to the url if non-local. */ string_size = sizeof("file:///") + strlen(url_copy); url_external = calloc(1, string_size); GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error); lxw_snprintf(url_external, string_size, "file:///%s", url_copy); } /* Convert a ./dir/file.xlsx link to dir/file.xlsx. */ found_string = strstr(url_copy, ".\\"); if (found_string == url_copy) memmove(url_copy, url_copy + 2, strlen(url_copy) - 1); if (url_external) { free(url_copy); url_copy = lxw_strdup(url_external); GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error); free(url_external); url_external = NULL; } } /* 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); free(string_copy); self->hlink_count++; return LXW_NO_ERROR; mem_error: free(string_copy); free(url_copy); free(url_external); free(url_string); free(tooltip_copy); return err; } /* * Write a hyperlink/url to an Excel file. */ lxw_error worksheet_write_url(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *url, lxw_format *format) { return worksheet_write_url_opt(self, row_num, col_num, url, format, NULL, NULL); } /* * Write a rich string to an Excel file. * * Rather than duplicate several of the styles.c font xml methods of styles.c * and write the data to a memory buffer this function creates a temporary * styles object and uses it to write the data to a file. It then reads that * data back into memory and closes the file. */ lxw_error worksheet_write_rich_string(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, lxw_rich_string_tuple *rich_strings[], lxw_format *format) { lxw_cell *cell; int32_t string_id; struct sst_element *sst_element; lxw_error err; uint8_t i; long file_size; char *rich_string = NULL; char *string_copy = NULL; lxw_styles *styles = NULL; lxw_format *default_format = NULL; lxw_rich_string_tuple *rich_string_tuple = NULL; FILE *tmpfile; err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; /* Iterate through rich string fragments to check for input errors. */ i = 0; err = LXW_NO_ERROR; while ((rich_string_tuple = rich_strings[i++]) != NULL) { /* Check for NULL or empty strings. */ if (!rich_string_tuple->string || !*rich_string_tuple->string) { err = LXW_ERROR_PARAMETER_VALIDATION; } } /* If there are less than 2 fragments it isn't a rich string. */ if (i <= 2) err = LXW_ERROR_PARAMETER_VALIDATION; if (err) return err; /* Create a tmp file for the styles object. */ tmpfile = lxw_tmpfile(self->tmpdir); if (!tmpfile) return LXW_ERROR_CREATING_TMPFILE; /* Create a temp styles object for writing the font data. */ styles = lxw_styles_new(); GOTO_LABEL_ON_MEM_ERROR(styles, mem_error); styles->file = tmpfile; /* Create a default format for non-formatted text. */ default_format = lxw_format_new(); GOTO_LABEL_ON_MEM_ERROR(default_format, mem_error); /* Iterate through the rich string fragments and write each one out. */ i = 0; while ((rich_string_tuple = rich_strings[i++]) != NULL) { lxw_xml_start_tag(tmpfile, "r", NULL); if (rich_string_tuple->format) { /* Write the user defined font format. */ lxw_styles_write_rich_font(styles, rich_string_tuple->format); } else { /* Write a default font format. Except for the first fragment. */ if (i > 1) lxw_styles_write_rich_font(styles, default_format); } lxw_styles_write_string_fragment(styles, rich_string_tuple->string); lxw_xml_end_tag(tmpfile, "r"); } /* 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. */ 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); /* 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; } /* Close the temp file. */ fclose(tmpfile); if (lxw_utf8_strlen(rich_string) > LXW_STR_MAX) { free(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); 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 (lxw_has_control_characters(rich_string)) { string_copy = lxw_escape_control_characters(rich_string); free(rich_string); } else { string_copy = rich_string; } cell = _new_inline_rich_string_cell(row_num, col_num, string_copy, format); } _insert_cell(self, row_num, col_num, cell); return LXW_NO_ERROR; mem_error: lxw_styles_free(styles); lxw_format_free(default_format); fclose(tmpfile); 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, lxw_col_t lastcol, double width, lxw_format *format, lxw_row_col_options *user_options) { lxw_col_options *copied_options; uint8_t ignore_row = LXW_TRUE; uint8_t ignore_col = LXW_TRUE; uint8_t hidden = LXW_FALSE; uint8_t level = 0; uint8_t collapsed = LXW_FALSE; lxw_col_t col; lxw_error err; if (user_options) { hidden = user_options->hidden; level = user_options->level; collapsed = user_options->collapsed; } /* Ensure second col is larger than first. */ if (firstcol > lastcol) { lxw_col_t tmp = firstcol; firstcol = lastcol; lastcol = tmp; } /* Ensure that the cols are valid and store max and min values. * NOTE: The check shouldn't modify the row dimensions and should only * modify the column dimensions in certain cases. */ if (format != NULL || (width != LXW_DEF_COL_WIDTH && hidden)) ignore_col = LXW_FALSE; err = _check_dimensions(self, 0, firstcol, ignore_row, ignore_col); if (!err) err = _check_dimensions(self, 0, lastcol, ignore_row, ignore_col); if (err) return err; /* Resize the col_options array if required. */ if (firstcol >= self->col_options_max) { lxw_col_t col; 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; self->col_options = new_ptr; self->col_options_max = new_size; } else { return LXW_ERROR_MEMORY_MALLOC_FAILED; } } /* Resize the col_formats array if required. */ if (lastcol >= self->col_formats_max) { lxw_col_t col; lxw_col_t old_size = self->col_formats_max; lxw_col_t new_size = _next_power_of_two(lastcol + 1); lxw_format **new_ptr = realloc(self->col_formats, new_size * sizeof(lxw_format *)); if (new_ptr) { for (col = old_size; col < new_size; col++) new_ptr[col] = NULL; self->col_formats = new_ptr; self->col_formats_max = new_size; } else { return LXW_ERROR_MEMORY_MALLOC_FAILED; } } /* Store the column options. */ copied_options = calloc(1, sizeof(lxw_col_options)); RETURN_ON_MEM_ERROR(copied_options, LXW_ERROR_MEMORY_MALLOC_FAILED); /* Ensure the level is <= 7). */ if (level > 7) level = 7; if (level > self->outline_col_level) self->outline_col_level = level; /* Set the column properties. */ copied_options->firstcol = firstcol; copied_options->lastcol = lastcol; copied_options->width = width; copied_options->format = format; copied_options->hidden = hidden; copied_options->level = level; copied_options->collapsed = collapsed; 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; } /* Store the column change to allow optimizations. */ self->col_size_changed = LXW_TRUE; return LXW_NO_ERROR; } /* * Set the properties of a single column or a range of columns. */ lxw_error worksheet_set_column(lxw_worksheet *self, lxw_col_t firstcol, lxw_col_t lastcol, double width, lxw_format *format) { return worksheet_set_column_opt(self, firstcol, lastcol, width, format, NULL); } /* * Set the properties of a row with options. */ lxw_error worksheet_set_row_opt(lxw_worksheet *self, lxw_row_t row_num, double height, lxw_format *format, lxw_row_col_options *user_options) { lxw_col_t min_col; uint8_t hidden = LXW_FALSE; uint8_t level = 0; uint8_t collapsed = LXW_FALSE; lxw_row *row; lxw_error err; if (user_options) { hidden = user_options->hidden; level = user_options->level; collapsed = user_options->collapsed; } /* Use minimum col in _check_dimensions(). */ if (self->dim_colmin != LXW_COL_MAX) min_col = self->dim_colmin; else min_col = 0; err = _check_dimensions(self, row_num, min_col, LXW_FALSE, LXW_FALSE); if (err) return err; /* If the height is 0 the row is hidden and the height is the default. */ if (height == 0) { hidden = LXW_TRUE; height = self->default_row_height; } /* Ensure the level is <= 7). */ if (level > 7) level = 7; if (level > self->outline_row_level) self->outline_row_level = level; /* Store the row properties. */ row = _get_row(self, row_num); row->height = height; row->format = format; row->hidden = hidden; row->level = level; row->collapsed = collapsed; row->row_changed = LXW_TRUE; if (height != self->default_row_height) row->height_changed = LXW_TRUE; return LXW_NO_ERROR; } /* * Set the properties of a row. */ lxw_error worksheet_set_row(lxw_worksheet *self, lxw_row_t row_num, double height, lxw_format *format) { return worksheet_set_row_opt(self, row_num, height, format, NULL); } /* * 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, lxw_col_t first_col, lxw_row_t last_row, lxw_col_t last_col, const char *string, lxw_format *format) { lxw_merged_range *merged_range; lxw_row_t tmp_row; lxw_col_t tmp_col; lxw_error err; /* 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; } 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_FALSE, LXW_FALSE); if (err) return err; /* Store the merge range. */ merged_range = calloc(1, sizeof(lxw_merged_range)); RETURN_ON_MEM_ERROR(merged_range, LXW_ERROR_MEMORY_MALLOC_FAILED); merged_range->first_row = first_row; merged_range->first_col = first_col; merged_range->last_row = last_row; merged_range->last_col = last_col; STAILQ_INSERT_TAIL(self->merged_ranges, merged_range, list_pointers); self->merged_range_count++; /* Write the first cell */ worksheet_write_string(self, first_row, first_col, string, format); /* Pad out the rest of the area with formatted blank cells. */ 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) continue; worksheet_write_blank(self, tmp_row, tmp_col, format); } } return LXW_NO_ERROR; } /* * Set the autofilter area in the worksheet. */ lxw_error worksheet_autofilter(lxw_worksheet *self, lxw_row_t first_row, lxw_col_t first_col, lxw_row_t last_row, lxw_col_t last_col) { lxw_row_t tmp_row; lxw_col_t tmp_col; lxw_error err; /* 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; } 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_FALSE, LXW_FALSE); if (err) return err; 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; return LXW_NO_ERROR; } /* * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab * highlighted. */ void worksheet_select(lxw_worksheet *self) { self->selected = LXW_TRUE; /* Selected worksheet can't be hidden. */ self->hidden = LXW_FALSE; } /* * Set this worksheet as the active worksheet, i.e. the worksheet that is * displayed when the workbook is opened. Also set it as selected. */ void worksheet_activate(lxw_worksheet *self) { self->selected = LXW_TRUE; self->active = LXW_TRUE; /* Active worksheet can't be hidden. */ self->hidden = LXW_FALSE; *self->active_sheet = self->index; } /* * Set this worksheet as the first visible sheet. This is necessary * when there are a large number of worksheets and the activated * worksheet is not visible on the screen. */ void worksheet_set_first_sheet(lxw_worksheet *self) { /* Active worksheet can't be hidden. */ self->hidden = LXW_FALSE; *self->first_sheet = self->index; } /* * Hide this worksheet. */ void worksheet_hide(lxw_worksheet *self) { self->hidden = LXW_TRUE; /* A hidden worksheet shouldn't be active or selected. */ self->selected = LXW_FALSE; /* If this is active_sheet or first_sheet reset the workbook value. */ if (*self->first_sheet == self->index) *self->first_sheet = 0; if (*self->active_sheet == self->index) *self->active_sheet = 0; } /* * Set which cell or cells are selected in a worksheet. */ void worksheet_set_selection(lxw_worksheet *self, lxw_row_t first_row, lxw_col_t first_col, lxw_row_t last_row, lxw_col_t last_col) { lxw_selection *selection; lxw_row_t tmp_row; lxw_col_t tmp_col; char active_cell[LXW_MAX_CELL_RANGE_LENGTH]; char sqref[LXW_MAX_CELL_RANGE_LENGTH]; /* Only allow selection to be set once to avoid freeing/re-creating it. */ if (!STAILQ_EMPTY(self->selections)) return; /* Excel doesn't set a selection for cell A1 since it is the default. */ if (first_row == 0 && first_col == 0 && last_row == 0 && last_col == 0) return; selection = calloc(1, sizeof(lxw_selection)); RETURN_VOID_ON_MEM_ERROR(selection); /* Set the cell range selection. Do this before swapping max/min to */ /* allow the selection direction to be reversed. */ lxw_rowcol_to_cell(active_cell, first_row, first_col); /* Swap last row/col for first row/col if necessary. */ if (first_row > last_row) { tmp_row = first_row; first_row = last_row; last_row = tmp_row; } if (first_col > last_col) { tmp_col = first_col; first_col = last_col; last_col = tmp_col; } /* If the first and last cell are the same write a single cell. */ if ((first_row == last_row) && (first_col == last_col)) lxw_rowcol_to_cell(sqref, first_row, first_col); else lxw_rowcol_to_range(sqref, first_row, first_col, last_row, last_col); lxw_strcpy(selection->pane, ""); lxw_strcpy(selection->active_cell, active_cell); lxw_strcpy(selection->sqref, sqref); STAILQ_INSERT_TAIL(self->selections, selection, list_pointers); } /* * 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, lxw_row_t top_row, lxw_col_t left_col, uint8_t type) { self->panes.first_row = first_row; self->panes.first_col = first_col; self->panes.top_row = top_row; self->panes.left_col = left_col; self->panes.x_split = 0.0; self->panes.y_split = 0.0; if (type) self->panes.type = FREEZE_SPLIT_PANES; else self->panes.type = FREEZE_PANES; } /* * Set panes and mark them as frozen. */ void worksheet_freeze_panes(lxw_worksheet *self, lxw_row_t first_row, lxw_col_t first_col) { worksheet_freeze_panes_opt(self, first_row, first_col, first_row, first_col, 0); } /* * Set panes and mark them as split.With extra options. */ void worksheet_split_panes_opt(lxw_worksheet *self, double y_split, double x_split, lxw_row_t top_row, lxw_col_t left_col) { self->panes.first_row = 0; self->panes.first_col = 0; self->panes.top_row = top_row; self->panes.left_col = left_col; self->panes.x_split = x_split; self->panes.y_split = y_split; self->panes.type = SPLIT_PANES; } /* * Set panes and mark them as split. */ void worksheet_split_panes(lxw_worksheet *self, double y_split, double x_split) { worksheet_split_panes_opt(self, y_split, x_split, 0, 0); } /* * Set the page orientation as portrait. */ void worksheet_set_portrait(lxw_worksheet *self) { self->orientation = LXW_PORTRAIT; self->page_setup_changed = LXW_TRUE; } /* * Set the page orientation as landscape. */ void worksheet_set_landscape(lxw_worksheet *self) { self->orientation = LXW_LANDSCAPE; self->page_setup_changed = LXW_TRUE; } /* * Set the page view mode for Mac Excel. */ void worksheet_set_page_view(lxw_worksheet *self) { self->page_view = LXW_TRUE; } /* * Set the paper type. Example. 1 = US Letter, 9 = A4 */ void worksheet_set_paper(lxw_worksheet *self, uint8_t paper_size) { self->paper_size = paper_size; self->page_setup_changed = LXW_TRUE; } /* * Set the order in which pages are printed. */ void worksheet_print_across(lxw_worksheet *self) { self->page_order = LXW_PRINT_ACROSS; self->page_setup_changed = LXW_TRUE; } /* * Set all the page margins in inches. */ void worksheet_set_margins(lxw_worksheet *self, double left, double right, double top, double bottom) { if (left >= 0) self->margin_left = left; if (right >= 0) self->margin_right = right; if (top >= 0) self->margin_top = top; if (bottom >= 0) self->margin_bottom = bottom; } /* * Set the page header caption and options. */ lxw_error worksheet_set_header_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { lxw_error err; char *found_string; char *offset_string; uint8_t placeholder_count = 0; uint8_t image_count = 0; if (!string) return LXW_ERROR_NULL_PARAMETER_IGNORED; if (lxw_utf8_strlen(string) >= LXW_HEADER_FOOTER_MAX) return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; lxw_strcpy(self->header, string); /* Replace &[Picture] with &G which is used internally by Excel. */ while ((found_string = strstr(self->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 = self->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); /* Reset the header string. */ self->header[0] = '\0'; return LXW_ERROR_PARAMETER_VALIDATION; } 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); /* Reset the header string. */ self->header[0] = '\0'; 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) return err; err = _worksheet_set_header_footer_image(self, options->image_center, HEADER_CENTER); if (err) return err; err = _worksheet_set_header_footer_image(self, options->image_right, HEADER_RIGHT); if (err) return err; } self->header_footer_changed = 1; 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) { lxw_error err; char *found_string; char *offset_string; uint8_t placeholder_count = 0; uint8_t image_count = 0; if (!string) return LXW_ERROR_NULL_PARAMETER_IGNORED; if (lxw_utf8_strlen(string) >= LXW_HEADER_FOOTER_MAX) return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; lxw_strcpy(self->footer, string); /* Replace &[Picture] with &G which is used internally by Excel. */ while ((found_string = strstr(self->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 = self->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); /* Reset the footer string. */ self->footer[0] = '\0'; return LXW_ERROR_PARAMETER_VALIDATION; } 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); /* Reset the header string. */ self->footer[0] = '\0'; 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) return err; err = _worksheet_set_header_footer_image(self, options->image_center, FOOTER_CENTER); if (err) return err; if (options->image_right) { err = _worksheet_set_header_footer_image(self, options->image_right, FOOTER_RIGHT); if (err) return err; } } self->header_footer_changed = 1; return LXW_NO_ERROR; } /* * Set the page header caption. */ lxw_error worksheet_set_header(lxw_worksheet *self, const char *string) { return worksheet_set_header_opt(self, string, NULL); } /* * Set the page footer caption. */ lxw_error worksheet_set_footer(lxw_worksheet *self, const char *string) { return worksheet_set_footer_opt(self, string, NULL); } /* * Set the option to show/hide gridlines on the screen and the printed page. */ void worksheet_gridlines(lxw_worksheet *self, uint8_t option) { if (option == LXW_HIDE_ALL_GRIDLINES) { self->print_gridlines = 0; self->screen_gridlines = 0; } if (option & LXW_SHOW_SCREEN_GRIDLINES) { self->screen_gridlines = 1; } if (option & LXW_SHOW_PRINT_GRIDLINES) { self->print_gridlines = 1; self->print_options_changed = 1; } } /* * Center the page horizontally. */ void worksheet_center_horizontally(lxw_worksheet *self) { self->print_options_changed = 1; self->hcenter = 1; } /* * Center the page horizontally. */ void worksheet_center_vertically(lxw_worksheet *self) { self->print_options_changed = 1; self->vcenter = 1; } /* * Set the option to print the row and column headers on the printed page. */ void worksheet_print_row_col_headers(lxw_worksheet *self) { self->print_headers = 1; self->print_options_changed = 1; } /* * Set the rows to repeat at the top of each printed page. */ lxw_error worksheet_repeat_rows(lxw_worksheet *self, lxw_row_t first_row, lxw_row_t last_row) { lxw_row_t tmp_row; lxw_error err; if (first_row > last_row) { tmp_row = last_row; last_row = first_row; first_row = tmp_row; } err = _check_dimensions(self, last_row, 0, LXW_IGNORE, LXW_IGNORE); if (err) return err; self->repeat_rows.in_use = LXW_TRUE; self->repeat_rows.first_row = first_row; self->repeat_rows.last_row = last_row; return LXW_NO_ERROR; } /* * Set the columns to repeat at the left hand side of each printed page. */ lxw_error worksheet_repeat_columns(lxw_worksheet *self, lxw_col_t first_col, lxw_col_t last_col) { lxw_col_t tmp_col; lxw_error err; if (first_col > last_col) { tmp_col = last_col; last_col = first_col; first_col = tmp_col; } err = _check_dimensions(self, last_col, 0, LXW_IGNORE, LXW_IGNORE); if (err) return err; self->repeat_cols.in_use = LXW_TRUE; self->repeat_cols.first_col = first_col; self->repeat_cols.last_col = last_col; return LXW_NO_ERROR; } /* * Set the print area in the current worksheet. */ lxw_error worksheet_print_area(lxw_worksheet *self, lxw_row_t first_row, lxw_col_t first_col, lxw_row_t last_row, lxw_col_t last_col) { lxw_row_t tmp_row; lxw_col_t tmp_col; lxw_error err; 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; } err = _check_dimensions(self, last_row, last_col, LXW_IGNORE, LXW_IGNORE); if (err) return err; /* Ignore max area since it is the same as no print area in Excel. */ if (first_row == 0 && first_col == 0 && last_row == LXW_ROW_MAX - 1 && last_col == LXW_COL_MAX - 1) { return LXW_NO_ERROR; } self->print_area.in_use = LXW_TRUE; self->print_area.first_row = first_row; self->print_area.last_row = last_row; self->print_area.first_col = first_col; self->print_area.last_col = last_col; return LXW_NO_ERROR; } /* Store the vertical and horizontal number of pages that will define the * maximum area printed. */ void worksheet_fit_to_pages(lxw_worksheet *self, uint16_t width, uint16_t height) { self->fit_page = 1; self->fit_width = width; self->fit_height = height; self->page_setup_changed = 1; } /* * Set the start page number. */ void worksheet_set_start_page(lxw_worksheet *self, uint16_t start_page) { self->page_start = start_page; } /* * Set the scale factor for the printed page. */ void worksheet_set_print_scale(lxw_worksheet *self, uint16_t scale) { /* Confine the scale to Excel"s range */ if (scale < 10 || scale > 400) return; /* Turn off "fit to page" option. */ self->fit_page = LXW_FALSE; self->print_scale = scale; 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[]) { uint16_t count = 0; if (hbreaks == NULL) return LXW_ERROR_NULL_PARAMETER_IGNORED; while (hbreaks[count]) count++; /* The Excel 2007 specification says that the maximum number of page * breaks is 1026. However, in practice it is actually 1023. */ if (count > LXW_BREAKS_MAX) count = LXW_BREAKS_MAX; self->hbreaks = calloc(count, sizeof(lxw_row_t)); RETURN_ON_MEM_ERROR(self->hbreaks, LXW_ERROR_MEMORY_MALLOC_FAILED); memcpy(self->hbreaks, hbreaks, count * sizeof(lxw_row_t)); self->hbreaks_count = count; return LXW_NO_ERROR; } /* * Store the vertical page breaks on a worksheet. */ lxw_error worksheet_set_v_pagebreaks(lxw_worksheet *self, lxw_col_t vbreaks[]) { uint16_t count = 0; if (vbreaks == NULL) return LXW_ERROR_NULL_PARAMETER_IGNORED; while (vbreaks[count]) count++; /* The Excel 2007 specification says that the maximum number of page * breaks is 1026. However, in practice it is actually 1023. */ if (count > LXW_BREAKS_MAX) count = LXW_BREAKS_MAX; self->vbreaks = calloc(count, sizeof(lxw_col_t)); RETURN_ON_MEM_ERROR(self->vbreaks, LXW_ERROR_MEMORY_MALLOC_FAILED); memcpy(self->vbreaks, vbreaks, count * sizeof(lxw_col_t)); self->vbreaks_count = count; return LXW_NO_ERROR; } /* * Set the worksheet zoom factor. */ void worksheet_set_zoom(lxw_worksheet *self, uint16_t scale) { /* Confine the scale to Excel"s range */ if (scale < 10 || scale > 400) { LXW_WARN("worksheet_set_zoom(): " "Zoom factor scale outside range: 10 <= zoom <= 400."); return; } self->zoom = scale; } /* * Hide cell zero values. */ void worksheet_hide_zero(lxw_worksheet *self) { self->show_zeros = LXW_FALSE; } /* * Display the worksheet right to left for some eastern versions of Excel. */ void worksheet_right_to_left(lxw_worksheet *self) { self->right_to_left = LXW_TRUE; } /* * Set the color of the worksheet tab. */ void worksheet_set_tab_color(lxw_worksheet *self, lxw_color_t color) { self->tab_color = color; } /* * Set the worksheet protection flags to prevent modification of worksheet * objects. */ void worksheet_protect(lxw_worksheet *self, const char *password, lxw_protection *options) { 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; protect->format_cells = options->format_cells; protect->format_columns = options->format_columns; protect->format_rows = options->format_rows; protect->insert_columns = options->insert_columns; protect->insert_rows = options->insert_rows; protect->insert_hyperlinks = options->insert_hyperlinks; protect->delete_columns = options->delete_columns; protect->delete_rows = options->delete_rows; protect->sort = options->sort; protect->autofilter = options->autofilter; protect->pivot_tables = options->pivot_tables; protect->scenarios = options->scenarios; protect->objects = options->objects; } 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; } /* * Set the worksheet properties for outlines and grouping. */ void worksheet_outline_settings(lxw_worksheet *self, uint8_t visible, uint8_t symbols_below, uint8_t symbols_right, uint8_t auto_style) { self->outline_on = visible; self->outline_below = symbols_below; self->outline_right = symbols_right; self->outline_style = auto_style; self->outline_changed = LXW_TRUE; } /* * Set the default row properties */ void worksheet_set_default_row(lxw_worksheet *self, double height, uint8_t hide_unused_rows) { if (height < 0) height = self->default_row_height; if (height != self->default_row_height) { self->default_row_height = height; self->row_size_changed = LXW_TRUE; } if (hide_unused_rows) self->default_row_zeroed = LXW_TRUE; self->default_row_set = LXW_TRUE; } /* * Insert an image 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_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 = 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; } /* Use the filename as the default description, like Excel. */ description = lxw_basename(filename); if (!description) { LXW_WARN_FORMAT1("worksheet_insert_image()/_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; } if (user_options) { 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); if (user_options->description) description = user_options->description; } /* Copy other options or set defaults. */ 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 (!object_props->x_scale) object_props->x_scale = 1; if (!object_props->y_scale) object_props->y_scale = 1; 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_object_properties(object_props); fclose(image_stream); return LXW_ERROR_IMAGE_DIMENSIONS; } } /* * Insert an image into the worksheet. */ lxw_error worksheet_insert_image(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const char *filename) { return worksheet_insert_image_opt(self, row_num, col_num, filename, NULL); } 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_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 file (preferably in memory) so we can read * the dimensions like an ordinary file. */ #ifdef USE_FMEMOPEN image_stream = fmemopen(NULL, image_size, "w+b"); #else image_stream = lxw_tmpfile(self->tmpdir); #endif 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); /* 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; } if (user_options) { 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); } /* Copy other options or set defaults. */ object_props->filename = lxw_strdup("image_buffer"); object_props->stream = image_stream; object_props->row = row_num; object_props->col = col_num; if (!object_props->x_scale) object_props->x_scale = 1; if (!object_props->y_scale) object_props->y_scale = 1; 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_object_properties(object_props); fclose(image_stream); return LXW_ERROR_IMAGE_DIMENSIONS; } } lxw_error worksheet_insert_image_buffer(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, const unsigned char *image_buffer, size_t image_size) { return worksheet_insert_image_buffer_opt(self, row_num, col_num, image_buffer, image_size, NULL); } /* * 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_chart_options *user_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; } /* Check that the chart isn't being used more than once. */ if (chart->in_use) { LXW_WARN("worksheet_insert_chart()/_opt(): the same chart object " "cannot be inserted in a worksheet more than once."); return LXW_ERROR_PARAMETER_VALIDATION; } /* Check that the chart has a data series. */ if (STAILQ_EMPTY(chart->series_list)) { LXW_WARN ("worksheet_insert_chart()/_opt(): chart must have a series."); return LXW_ERROR_PARAMETER_VALIDATION; } /* Check that the chart has a 'values' series. */ STAILQ_FOREACH(series, chart->series_list, list_pointers) { if (!series->values->formula && !series->values->sheetname) { LXW_WARN("worksheet_insert_chart()/_opt(): chart must have a " "'values' series."); return LXW_ERROR_PARAMETER_VALIDATION; } } /* 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) { 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; } /* Copy other options or set defaults. */ object_props->row = row_num; object_props->col = col_num; object_props->width = 480; object_props->height = 288; if (!object_props->x_scale) object_props->x_scale = 1; if (!object_props->y_scale) object_props->y_scale = 1; /* Store chart references so they can be ordered in the workbook. */ object_props->chart = chart; STAILQ_INSERT_TAIL(self->chart_data, object_props, list_pointers); chart->in_use = LXW_TRUE; return LXW_NO_ERROR; } /* * Insert an image into the worksheet. */ lxw_error worksheet_insert_chart(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, lxw_chart *chart) { return worksheet_insert_chart_opt(self, row_num, col_num, chart, NULL); } /* * Add a data validation to a worksheet, for a range. Ironically this requires * a lot of validation of the user input. */ lxw_error worksheet_data_validation_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_data_validation *validation) { 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; lxw_col_t tmp_col; size_t length; /* No action is required for validation type 'any' unless there are * input messages to display.*/ if (validation->validate == LXW_VALIDATION_TYPE_ANY && !(validation->input_title || validation->input_message)) { return LXW_NO_ERROR; } /* Check for formula types. */ switch (validation->validate) { case LXW_VALIDATION_TYPE_INTEGER_FORMULA: case LXW_VALIDATION_TYPE_DECIMAL_FORMULA: case LXW_VALIDATION_TYPE_LIST_FORMULA: case LXW_VALIDATION_TYPE_LENGTH_FORMULA: case LXW_VALIDATION_TYPE_DATE_FORMULA: case LXW_VALIDATION_TYPE_TIME_FORMULA: case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: is_formula = LXW_TRUE; break; } /* Check for types without a criteria. */ switch (validation->validate) { case LXW_VALIDATION_TYPE_LIST: case LXW_VALIDATION_TYPE_LIST_FORMULA: case LXW_VALIDATION_TYPE_ANY: case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: has_criteria = LXW_FALSE; break; } /* Check that a validation parameter has been specified * except for 'list', 'any' and 'custom'. */ if (has_criteria && validation->criteria == LXW_VALIDATION_CRITERIA_NONE) { LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " "criteria parameter must be specified."); return LXW_ERROR_PARAMETER_VALIDATION; } /* Check for "between" criteria so we can do additional checks. */ if (has_criteria && (validation->criteria == LXW_VALIDATION_CRITERIA_BETWEEN || validation->criteria == LXW_VALIDATION_CRITERIA_NOT_BETWEEN)) { is_between = LXW_TRUE; } /* Check that formula values are non NULL. */ if (is_formula) { if (is_between) { if (!validation->minimum_formula) { LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " "minimum_formula parameter cannot be NULL."); return LXW_ERROR_PARAMETER_VALIDATION; } if (!validation->maximum_formula) { LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " "maximum_formula parameter cannot be NULL."); return LXW_ERROR_PARAMETER_VALIDATION; } } else { if (!validation->value_formula) { LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " "formula parameter cannot be NULL."); return LXW_ERROR_PARAMETER_VALIDATION; } } } /* Check Excel limitations on input strings. */ if (validation->input_title) { length = lxw_utf8_strlen(validation->input_title); if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) { LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " "input_title length > Excel limit of %d.", LXW_VALIDATION_MAX_TITLE_LENGTH); return LXW_ERROR_32_STRING_LENGTH_EXCEEDED; } } if (validation->error_title) { length = lxw_utf8_strlen(validation->error_title); if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) { LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " "error_title length > Excel limit of %d.", LXW_VALIDATION_MAX_TITLE_LENGTH); return LXW_ERROR_32_STRING_LENGTH_EXCEEDED; } } if (validation->input_message) { length = lxw_utf8_strlen(validation->input_message); if (length > LXW_VALIDATION_MAX_STRING_LENGTH) { LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " "input_message length > Excel limit of %d.", LXW_VALIDATION_MAX_STRING_LENGTH); return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; } } if (validation->error_message) { length = lxw_utf8_strlen(validation->error_message); if (length > LXW_VALIDATION_MAX_STRING_LENGTH) { LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " "error_message length > Excel limit of %d.", LXW_VALIDATION_MAX_STRING_LENGTH); return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; } } if (validation->validate == LXW_VALIDATION_TYPE_LIST) { length = _validation_list_length(validation->value_list); if (length == 0) { LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " "list parameters cannot be zero."); return LXW_ERROR_PARAMETER_VALIDATION; } if (length > LXW_VALIDATION_MAX_STRING_LENGTH) { LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " "list length with commas > Excel limit of %d.", LXW_VALIDATION_MAX_STRING_LENGTH); return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; } } /* 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; /* Create a copy of the parameters from the user 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); 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; if (has_criteria) copy->criteria = validation->criteria; if (is_between) { copy->value_number = validation->minimum_number; copy->maximum_number = validation->maximum_number; } /* Copy the input/error titles and messages. */ if (validation->input_title) { copy->input_title = lxw_strdup_formula(validation->input_title); GOTO_LABEL_ON_MEM_ERROR(copy->input_title, mem_error); } if (validation->input_message) { copy->input_message = lxw_strdup_formula(validation->input_message); GOTO_LABEL_ON_MEM_ERROR(copy->input_message, mem_error); } if (validation->error_title) { copy->error_title = lxw_strdup_formula(validation->error_title); GOTO_LABEL_ON_MEM_ERROR(copy->error_title, mem_error); } if (validation->error_message) { copy->error_message = lxw_strdup_formula(validation->error_message); GOTO_LABEL_ON_MEM_ERROR(copy->error_message, mem_error); } /* Copy the formula strings. */ if (is_formula) { if (is_between) { copy->value_formula = lxw_strdup_formula(validation->minimum_formula); GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); copy->maximum_formula = lxw_strdup_formula(validation->maximum_formula); GOTO_LABEL_ON_MEM_ERROR(copy->maximum_formula, mem_error); } else { copy->value_formula = lxw_strdup_formula(validation->value_formula); GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); } } /* Copy the validation list as a csv string. */ 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_epoch(&validation-> minimum_datetime, LXW_EPOCH_1900); copy->maximum_number = lxw_datetime_to_excel_date_epoch(&validation-> maximum_datetime, LXW_EPOCH_1900); } else { copy->value_number = 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; copy->show_input = validation->show_input ^ 1; copy->show_error = validation->show_error ^ 1; STAILQ_INSERT_TAIL(self->data_validations, copy, list_pointers); self->num_validations++; return LXW_NO_ERROR; mem_error: _free_data_validation(copy); return LXW_ERROR_MEMORY_MALLOC_FAILED; } /* * Add a data validation to a worksheet, for a cell. */ lxw_error worksheet_data_validation_cell(lxw_worksheet *self, lxw_row_t row, lxw_col_t col, lxw_data_validation *validation) { 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, last_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, last_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); } /* * Set the VBA name for the worksheet. */ lxw_error worksheet_set_vba_name(lxw_worksheet *self, const char *name) { if (!name) { 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; }