1 /*****************************************************************************
2  * worksheet - A library for creating Excel XLSX worksheet files.
3  *
4  * Used in conjunction with the libxlsxwriter library.
5  *
6  * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
7  *
8  */
9 
10 #ifdef USE_FMEMOPEN
11 #define _POSIX_C_SOURCE 200809L
12 #endif
13 
14 #include "xlsxwriter/xmlwriter.h"
15 #include "xlsxwriter/worksheet.h"
16 #include "xlsxwriter/format.h"
17 #include "xlsxwriter/utility.h"
18 
19 #ifdef USE_OPENSSL_MD5
20 #include <openssl/md5.h>
21 #else
22 #ifndef USE_NO_MD5
23 #include "xlsxwriter/third_party/md5.h"
24 #endif
25 #endif
26 
27 #define LXW_STR_MAX                      32767
28 #define LXW_BUFFER_SIZE                  4096
29 #define LXW_PRINT_ACROSS                 1
30 #define LXW_VALIDATION_MAX_TITLE_LENGTH  32
31 #define LXW_VALIDATION_MAX_STRING_LENGTH 255
32 #define LXW_THIS_ROW "[#This Row],"
33 /*
34  * Forward declarations.
35  */
36 STATIC void _worksheet_write_rows(lxw_worksheet *self);
37 STATIC int _row_cmp(lxw_row *row1, lxw_row *row2);
38 STATIC int _cell_cmp(lxw_cell *cell1, lxw_cell *cell2);
39 STATIC int _drawing_rel_id_cmp(lxw_drawing_rel_id *tuple1,
40                                lxw_drawing_rel_id *tuple2);
41 STATIC int _cond_format_hash_cmp(lxw_cond_format_hash_element *elem_1,
42                                  lxw_cond_format_hash_element *elem_2);
43 
44 #ifndef __clang_analyzer__
45 LXW_RB_GENERATE_ROW(lxw_table_rows, lxw_row, tree_pointers, _row_cmp);
46 LXW_RB_GENERATE_CELL(lxw_table_cells, lxw_cell, tree_pointers, _cell_cmp);
47 LXW_RB_GENERATE_DRAWING_REL_IDS(lxw_drawing_rel_ids, lxw_drawing_rel_id,
48                                 tree_pointers, _drawing_rel_id_cmp);
49 LXW_RB_GENERATE_VML_DRAWING_REL_IDS(lxw_vml_drawing_rel_ids,
50                                     lxw_drawing_rel_id, tree_pointers,
51                                     _drawing_rel_id_cmp);
52 LXW_RB_GENERATE_COND_FORMAT_HASH(lxw_cond_format_hash,
53                                  lxw_cond_format_hash_element, tree_pointers,
54                                  _cond_format_hash_cmp);
55 #endif
56 
57 /*****************************************************************************
58  *
59  * Private functions.
60  *
61  ****************************************************************************/
62 
63 /*
64  * Find but don't create a row object for a given row number.
65  */
66 lxw_row *
lxw_worksheet_find_row(lxw_worksheet * self,lxw_row_t row_num)67 lxw_worksheet_find_row(lxw_worksheet *self, lxw_row_t row_num)
68 {
69     lxw_row tmp_row;
70 
71     tmp_row.row_num = row_num;
72 
73     return RB_FIND(lxw_table_rows, self->table, &tmp_row);
74 }
75 
76 /*
77  * Find but don't create a cell object for a given row object and col number.
78  */
79 lxw_cell *
lxw_worksheet_find_cell_in_row(lxw_row * row,lxw_col_t col_num)80 lxw_worksheet_find_cell_in_row(lxw_row *row, lxw_col_t col_num)
81 {
82     lxw_cell tmp_cell;
83 
84     if (!row)
85         return NULL;
86 
87     tmp_cell.col_num = col_num;
88 
89     return RB_FIND(lxw_table_cells, row->cells, &tmp_cell);
90 }
91 
92 /*
93  * Create a new worksheet object.
94  */
95 lxw_worksheet *
lxw_worksheet_new(lxw_worksheet_init_data * init_data)96 lxw_worksheet_new(lxw_worksheet_init_data *init_data)
97 {
98     lxw_worksheet *worksheet = calloc(1, sizeof(lxw_worksheet));
99     GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error);
100 
101     worksheet->table = calloc(1, sizeof(struct lxw_table_rows));
102     GOTO_LABEL_ON_MEM_ERROR(worksheet->table, mem_error);
103     RB_INIT(worksheet->table);
104 
105     worksheet->hyperlinks = calloc(1, sizeof(struct lxw_table_rows));
106     GOTO_LABEL_ON_MEM_ERROR(worksheet->hyperlinks, mem_error);
107     RB_INIT(worksheet->hyperlinks);
108 
109     worksheet->comments = calloc(1, sizeof(struct lxw_table_rows));
110     GOTO_LABEL_ON_MEM_ERROR(worksheet->comments, mem_error);
111     RB_INIT(worksheet->comments);
112 
113     /* Initialize the cached rows. */
114     worksheet->table->cached_row_num = LXW_ROW_MAX + 1;
115     worksheet->hyperlinks->cached_row_num = LXW_ROW_MAX + 1;
116     worksheet->comments->cached_row_num = LXW_ROW_MAX + 1;
117 
118     if (init_data && init_data->optimize) {
119         worksheet->array = calloc(LXW_COL_MAX, sizeof(struct lxw_cell *));
120         GOTO_LABEL_ON_MEM_ERROR(worksheet->array, mem_error);
121     }
122 
123     worksheet->col_options =
124         calloc(LXW_COL_META_MAX, sizeof(lxw_col_options *));
125     worksheet->col_options_max = LXW_COL_META_MAX;
126     GOTO_LABEL_ON_MEM_ERROR(worksheet->col_options, mem_error);
127 
128     worksheet->col_formats = calloc(LXW_COL_META_MAX, sizeof(lxw_format *));
129     worksheet->col_formats_max = LXW_COL_META_MAX;
130     GOTO_LABEL_ON_MEM_ERROR(worksheet->col_formats, mem_error);
131 
132     worksheet->optimize_row = calloc(1, sizeof(struct lxw_row));
133     GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_row, mem_error);
134     worksheet->optimize_row->height = LXW_DEF_ROW_HEIGHT;
135 
136     worksheet->merged_ranges = calloc(1, sizeof(struct lxw_merged_ranges));
137     GOTO_LABEL_ON_MEM_ERROR(worksheet->merged_ranges, mem_error);
138     STAILQ_INIT(worksheet->merged_ranges);
139 
140     worksheet->image_props = calloc(1, sizeof(struct lxw_image_props));
141     GOTO_LABEL_ON_MEM_ERROR(worksheet->image_props, mem_error);
142     STAILQ_INIT(worksheet->image_props);
143 
144     worksheet->chart_data = calloc(1, sizeof(struct lxw_chart_props));
145     GOTO_LABEL_ON_MEM_ERROR(worksheet->chart_data, mem_error);
146     STAILQ_INIT(worksheet->chart_data);
147 
148     worksheet->comment_objs = calloc(1, sizeof(struct lxw_comment_objs));
149     GOTO_LABEL_ON_MEM_ERROR(worksheet->comment_objs, mem_error);
150     STAILQ_INIT(worksheet->comment_objs);
151 
152     worksheet->header_image_objs = calloc(1, sizeof(struct lxw_comment_objs));
153     GOTO_LABEL_ON_MEM_ERROR(worksheet->header_image_objs, mem_error);
154     STAILQ_INIT(worksheet->header_image_objs);
155 
156     worksheet->button_objs = calloc(1, sizeof(struct lxw_comment_objs));
157     GOTO_LABEL_ON_MEM_ERROR(worksheet->button_objs, mem_error);
158     STAILQ_INIT(worksheet->button_objs);
159 
160     worksheet->selections = calloc(1, sizeof(struct lxw_selections));
161     GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error);
162     STAILQ_INIT(worksheet->selections);
163 
164     worksheet->data_validations =
165         calloc(1, sizeof(struct lxw_data_validations));
166     GOTO_LABEL_ON_MEM_ERROR(worksheet->data_validations, mem_error);
167     STAILQ_INIT(worksheet->data_validations);
168 
169     worksheet->table_objs = calloc(1, sizeof(struct lxw_table_objs));
170     GOTO_LABEL_ON_MEM_ERROR(worksheet->table_objs, mem_error);
171     STAILQ_INIT(worksheet->table_objs);
172 
173     worksheet->external_hyperlinks = calloc(1, sizeof(struct lxw_rel_tuples));
174     GOTO_LABEL_ON_MEM_ERROR(worksheet->external_hyperlinks, mem_error);
175     STAILQ_INIT(worksheet->external_hyperlinks);
176 
177     worksheet->external_drawing_links =
178         calloc(1, sizeof(struct lxw_rel_tuples));
179     GOTO_LABEL_ON_MEM_ERROR(worksheet->external_drawing_links, mem_error);
180     STAILQ_INIT(worksheet->external_drawing_links);
181 
182     worksheet->drawing_links = calloc(1, sizeof(struct lxw_rel_tuples));
183     GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_links, mem_error);
184     STAILQ_INIT(worksheet->drawing_links);
185 
186     worksheet->vml_drawing_links = calloc(1, sizeof(struct lxw_rel_tuples));
187     GOTO_LABEL_ON_MEM_ERROR(worksheet->vml_drawing_links, mem_error);
188     STAILQ_INIT(worksheet->vml_drawing_links);
189 
190     worksheet->external_table_links =
191         calloc(1, sizeof(struct lxw_rel_tuples));
192     GOTO_LABEL_ON_MEM_ERROR(worksheet->external_table_links, mem_error);
193     STAILQ_INIT(worksheet->external_table_links);
194 
195     if (init_data && init_data->optimize) {
196         FILE *tmpfile;
197 
198         tmpfile = lxw_tmpfile(init_data->tmpdir);
199         if (!tmpfile) {
200             LXW_ERROR("Error creating tmpfile() for worksheet in "
201                       "'constant_memory' mode.");
202             goto mem_error;
203         }
204 
205         worksheet->optimize_tmpfile = tmpfile;
206         GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_tmpfile, mem_error);
207         worksheet->file = worksheet->optimize_tmpfile;
208     }
209 
210     worksheet->drawing_rel_ids =
211         calloc(1, sizeof(struct lxw_drawing_rel_ids));
212     GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_rel_ids, mem_error);
213     RB_INIT(worksheet->drawing_rel_ids);
214 
215     worksheet->vml_drawing_rel_ids =
216         calloc(1, sizeof(struct lxw_vml_drawing_rel_ids));
217     GOTO_LABEL_ON_MEM_ERROR(worksheet->vml_drawing_rel_ids, mem_error);
218     RB_INIT(worksheet->vml_drawing_rel_ids);
219 
220     worksheet->conditional_formats =
221         calloc(1, sizeof(struct lxw_cond_format_hash));
222     GOTO_LABEL_ON_MEM_ERROR(worksheet->conditional_formats, mem_error);
223     RB_INIT(worksheet->conditional_formats);
224 
225     /* Initialize the worksheet dimensions. */
226     worksheet->dim_rowmax = 0;
227     worksheet->dim_colmax = 0;
228     worksheet->dim_rowmin = LXW_ROW_MAX;
229     worksheet->dim_colmin = LXW_COL_MAX;
230 
231     worksheet->default_row_height = LXW_DEF_ROW_HEIGHT;
232     worksheet->default_row_pixels = 20;
233     worksheet->default_col_pixels = 64;
234 
235     /* Initialize the page setup properties. */
236     worksheet->fit_height = 0;
237     worksheet->fit_width = 0;
238     worksheet->page_start = 0;
239     worksheet->print_scale = 100;
240     worksheet->fit_page = 0;
241     worksheet->orientation = LXW_TRUE;
242     worksheet->page_order = 0;
243     worksheet->page_setup_changed = LXW_FALSE;
244     worksheet->page_view = LXW_FALSE;
245     worksheet->paper_size = 0;
246     worksheet->vertical_dpi = 0;
247     worksheet->horizontal_dpi = 0;
248     worksheet->margin_left = 0.7;
249     worksheet->margin_right = 0.7;
250     worksheet->margin_top = 0.75;
251     worksheet->margin_bottom = 0.75;
252     worksheet->margin_header = 0.3;
253     worksheet->margin_footer = 0.3;
254     worksheet->print_gridlines = 0;
255     worksheet->screen_gridlines = 1;
256     worksheet->print_options_changed = 0;
257     worksheet->zoom = 100;
258     worksheet->zoom_scale_normal = LXW_TRUE;
259     worksheet->show_zeros = LXW_TRUE;
260     worksheet->outline_on = LXW_TRUE;
261     worksheet->outline_style = LXW_TRUE;
262     worksheet->outline_below = LXW_TRUE;
263     worksheet->outline_right = LXW_FALSE;
264     worksheet->tab_color = LXW_COLOR_UNSET;
265     worksheet->max_url_length = 2079;
266     worksheet->comment_display_default = LXW_COMMENT_DISPLAY_HIDDEN;
267 
268     worksheet->header_footer_objs[0] = &worksheet->header_left_object_props;
269     worksheet->header_footer_objs[1] = &worksheet->header_center_object_props;
270     worksheet->header_footer_objs[2] = &worksheet->header_right_object_props;
271     worksheet->header_footer_objs[3] = &worksheet->footer_left_object_props;
272     worksheet->header_footer_objs[4] = &worksheet->footer_center_object_props;
273     worksheet->header_footer_objs[5] = &worksheet->footer_right_object_props;
274 
275     if (init_data) {
276         worksheet->name = init_data->name;
277         worksheet->quoted_name = init_data->quoted_name;
278         worksheet->tmpdir = init_data->tmpdir;
279         worksheet->index = init_data->index;
280         worksheet->hidden = init_data->hidden;
281         worksheet->sst = init_data->sst;
282         worksheet->optimize = init_data->optimize;
283         worksheet->active_sheet = init_data->active_sheet;
284         worksheet->first_sheet = init_data->first_sheet;
285         worksheet->default_url_format = init_data->default_url_format;
286         worksheet->max_url_length = init_data->max_url_length;
287     }
288 
289     return worksheet;
290 
291 mem_error:
292     lxw_worksheet_free(worksheet);
293     return NULL;
294 }
295 
296 /*
297  * Free vml object.
298  */
299 STATIC void
_free_vml_object(lxw_vml_obj * vml_obj)300 _free_vml_object(lxw_vml_obj *vml_obj)
301 {
302     if (!vml_obj)
303         return;
304 
305     free(vml_obj->author);
306     free(vml_obj->font_name);
307     free(vml_obj->text);
308     free(vml_obj->image_position);
309     free(vml_obj->name);
310     free(vml_obj->macro);
311 
312     free(vml_obj);
313 }
314 
315 /*
316  * Free autofilter rule object.
317  */
318 STATIC void
_free_filter_rule(lxw_filter_rule_obj * rule_obj)319 _free_filter_rule(lxw_filter_rule_obj *rule_obj)
320 {
321     uint16_t i;
322 
323     if (!rule_obj)
324         return;
325 
326     free(rule_obj->value1_string);
327     free(rule_obj->value2_string);
328 
329     if (rule_obj->list) {
330         for (i = 0; i < rule_obj->num_list_filters; i++)
331             free(rule_obj->list[i]);
332 
333         free(rule_obj->list);
334     }
335 
336     free(rule_obj);
337 }
338 
339 /*
340  * Free autofilter rules.
341  */
342 STATIC void
_free_filter_rules(lxw_worksheet * worksheet)343 _free_filter_rules(lxw_worksheet *worksheet)
344 {
345     uint16_t i;
346 
347     if (!worksheet->filter_rules)
348         return;
349 
350     for (i = 0; i < worksheet->num_filter_rules; i++)
351         _free_filter_rule(worksheet->filter_rules[i]);
352 
353     free(worksheet->filter_rules);
354 }
355 
356 /*
357  * Free a worksheet cell.
358  */
359 STATIC void
_free_cell(lxw_cell * cell)360 _free_cell(lxw_cell *cell)
361 {
362     if (!cell)
363         return;
364 
365     if (cell->type != NUMBER_CELL && cell->type != STRING_CELL
366         && cell->type != BLANK_CELL && cell->type != BOOLEAN_CELL) {
367 
368         free(cell->u.string);
369     }
370 
371     free(cell->user_data1);
372     free(cell->user_data2);
373 
374     _free_vml_object(cell->comment);
375 
376     free(cell);
377 }
378 
379 /*
380  * Free a worksheet row.
381  */
382 STATIC void
_free_row(lxw_row * row)383 _free_row(lxw_row *row)
384 {
385     lxw_cell *cell;
386     lxw_cell *next_cell;
387 
388     if (!row)
389         return;
390 
391     for (cell = RB_MIN(lxw_table_cells, row->cells); cell; cell = next_cell) {
392         next_cell = RB_NEXT(lxw_table_cells, row->cells, cell);
393         RB_REMOVE(lxw_table_cells, row->cells, cell);
394         _free_cell(cell);
395     }
396 
397     free(row->cells);
398     free(row);
399 }
400 
401 /*
402  * Free a worksheet image_options.
403  */
404 STATIC void
_free_object_properties(lxw_object_properties * object_property)405 _free_object_properties(lxw_object_properties *object_property)
406 {
407     if (!object_property)
408         return;
409 
410     free(object_property->filename);
411     free(object_property->description);
412     free(object_property->extension);
413     free(object_property->url);
414     free(object_property->tip);
415     free(object_property->image_buffer);
416     free(object_property->md5);
417     free(object_property->image_position);
418     free(object_property);
419 }
420 
421 /*
422  * Free a worksheet data_validation.
423  */
424 STATIC void
_free_data_validation(lxw_data_val_obj * data_validation)425 _free_data_validation(lxw_data_val_obj *data_validation)
426 {
427     if (!data_validation)
428         return;
429 
430     free(data_validation->value_formula);
431     free(data_validation->maximum_formula);
432     free(data_validation->input_title);
433     free(data_validation->input_message);
434     free(data_validation->error_title);
435     free(data_validation->error_message);
436     free(data_validation->minimum_formula);
437 
438     free(data_validation);
439 }
440 
441 /*
442  * Free a worksheet conditional format obj.
443  */
444 STATIC void
_free_cond_format(lxw_cond_format_obj * cond_format)445 _free_cond_format(lxw_cond_format_obj *cond_format)
446 {
447     if (!cond_format)
448         return;
449 
450     free(cond_format->min_value_string);
451     free(cond_format->mid_value_string);
452     free(cond_format->max_value_string);
453     free(cond_format->type_string);
454     free(cond_format->guid);
455 
456     free(cond_format);
457 }
458 
459 /*
460  * Free a relationship structure.
461  */
462 STATIC void
_free_relationship(lxw_rel_tuple * relationship)463 _free_relationship(lxw_rel_tuple *relationship)
464 {
465     if (!relationship)
466         return;
467 
468     free(relationship->type);
469     free(relationship->target);
470     free(relationship->target_mode);
471 
472     free(relationship);
473 }
474 
475 /*
476  * Free a worksheet table column object.
477  */
478 STATIC void
_free_worksheet_table_column(lxw_table_column * column)479 _free_worksheet_table_column(lxw_table_column *column)
480 {
481     if (!column)
482         return;
483 
484     free(column->header);
485     free(column->formula);
486     free(column->total_string);
487 
488     free(column);
489 }
490 
491 /*
492  * Free a worksheet table  object.
493  */
494 STATIC void
_free_worksheet_table(lxw_table_obj * table)495 _free_worksheet_table(lxw_table_obj *table)
496 {
497     uint16_t i;
498 
499     if (!table)
500         return;
501 
502     for (i = 0; i < table->num_cols; i++)
503         _free_worksheet_table_column(table->columns[i]);
504 
505     free(table->name);
506     free(table->total_string);
507     free(table->columns);
508 
509     free(table);
510 }
511 
512 /*
513  * Free a worksheet object.
514  */
515 void
lxw_worksheet_free(lxw_worksheet * worksheet)516 lxw_worksheet_free(lxw_worksheet *worksheet)
517 {
518     lxw_row *row;
519     lxw_row *next_row;
520     lxw_col_t col;
521     lxw_merged_range *merged_range;
522     lxw_object_properties *object_props;
523     lxw_vml_obj *vml_obj;
524     lxw_selection *selection;
525     lxw_data_val_obj *data_validation;
526     lxw_rel_tuple *relationship;
527     lxw_cond_format_obj *cond_format;
528     lxw_table_obj *table_obj;
529     struct lxw_drawing_rel_id *drawing_rel_id;
530     struct lxw_drawing_rel_id *next_drawing_rel_id;
531     struct lxw_cond_format_hash_element *cond_format_elem;
532     struct lxw_cond_format_hash_element *next_cond_format_elem;
533 
534     if (!worksheet)
535         return;
536 
537     if (worksheet->col_options) {
538         for (col = 0; col < worksheet->col_options_max; col++) {
539             if (worksheet->col_options[col])
540                 free(worksheet->col_options[col]);
541         }
542     }
543 
544     free(worksheet->col_options);
545     free(worksheet->col_sizes);
546     free(worksheet->col_formats);
547 
548     if (worksheet->table) {
549         for (row = RB_MIN(lxw_table_rows, worksheet->table); row;
550              row = next_row) {
551 
552             next_row = RB_NEXT(lxw_table_rows, worksheet->table, row);
553             RB_REMOVE(lxw_table_rows, worksheet->table, row);
554             _free_row(row);
555         }
556 
557         free(worksheet->table);
558     }
559 
560     if (worksheet->hyperlinks) {
561         for (row = RB_MIN(lxw_table_rows, worksheet->hyperlinks); row;
562              row = next_row) {
563 
564             next_row = RB_NEXT(lxw_table_rows, worksheet->hyperlinks, row);
565             RB_REMOVE(lxw_table_rows, worksheet->hyperlinks, row);
566             _free_row(row);
567         }
568 
569         free(worksheet->hyperlinks);
570     }
571 
572     if (worksheet->comments) {
573         for (row = RB_MIN(lxw_table_rows, worksheet->comments); row;
574              row = next_row) {
575 
576             next_row = RB_NEXT(lxw_table_rows, worksheet->comments, row);
577             RB_REMOVE(lxw_table_rows, worksheet->comments, row);
578             _free_row(row);
579         }
580 
581         free(worksheet->comments);
582     }
583 
584     if (worksheet->merged_ranges) {
585         while (!STAILQ_EMPTY(worksheet->merged_ranges)) {
586             merged_range = STAILQ_FIRST(worksheet->merged_ranges);
587             STAILQ_REMOVE_HEAD(worksheet->merged_ranges, list_pointers);
588             free(merged_range);
589         }
590 
591         free(worksheet->merged_ranges);
592     }
593 
594     if (worksheet->image_props) {
595         while (!STAILQ_EMPTY(worksheet->image_props)) {
596             object_props = STAILQ_FIRST(worksheet->image_props);
597             STAILQ_REMOVE_HEAD(worksheet->image_props, list_pointers);
598             _free_object_properties(object_props);
599         }
600 
601         free(worksheet->image_props);
602     }
603 
604     if (worksheet->chart_data) {
605         while (!STAILQ_EMPTY(worksheet->chart_data)) {
606             object_props = STAILQ_FIRST(worksheet->chart_data);
607             STAILQ_REMOVE_HEAD(worksheet->chart_data, list_pointers);
608             _free_object_properties(object_props);
609         }
610 
611         free(worksheet->chart_data);
612     }
613 
614     /* Just free the list. The list objects are freed from the RB tree. */
615     free(worksheet->comment_objs);
616 
617     if (worksheet->header_image_objs) {
618         while (!STAILQ_EMPTY(worksheet->header_image_objs)) {
619             vml_obj = STAILQ_FIRST(worksheet->header_image_objs);
620             STAILQ_REMOVE_HEAD(worksheet->header_image_objs, list_pointers);
621             _free_vml_object(vml_obj);
622         }
623 
624         free(worksheet->header_image_objs);
625     }
626 
627     if (worksheet->button_objs) {
628         while (!STAILQ_EMPTY(worksheet->button_objs)) {
629             vml_obj = STAILQ_FIRST(worksheet->button_objs);
630             STAILQ_REMOVE_HEAD(worksheet->button_objs, list_pointers);
631             _free_vml_object(vml_obj);
632         }
633 
634         free(worksheet->button_objs);
635     }
636 
637     if (worksheet->selections) {
638         while (!STAILQ_EMPTY(worksheet->selections)) {
639             selection = STAILQ_FIRST(worksheet->selections);
640             STAILQ_REMOVE_HEAD(worksheet->selections, list_pointers);
641             free(selection);
642         }
643 
644         free(worksheet->selections);
645     }
646 
647     if (worksheet->table_objs) {
648         while (!STAILQ_EMPTY(worksheet->table_objs)) {
649             table_obj = STAILQ_FIRST(worksheet->table_objs);
650             STAILQ_REMOVE_HEAD(worksheet->table_objs, list_pointers);
651             _free_worksheet_table(table_obj);
652         }
653 
654         free(worksheet->table_objs);
655     }
656 
657     if (worksheet->data_validations) {
658         while (!STAILQ_EMPTY(worksheet->data_validations)) {
659             data_validation = STAILQ_FIRST(worksheet->data_validations);
660             STAILQ_REMOVE_HEAD(worksheet->data_validations, list_pointers);
661             _free_data_validation(data_validation);
662         }
663 
664         free(worksheet->data_validations);
665     }
666 
667     while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) {
668         relationship = STAILQ_FIRST(worksheet->external_hyperlinks);
669         STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers);
670         _free_relationship(relationship);
671     }
672     free(worksheet->external_hyperlinks);
673 
674     while (!STAILQ_EMPTY(worksheet->external_drawing_links)) {
675         relationship = STAILQ_FIRST(worksheet->external_drawing_links);
676         STAILQ_REMOVE_HEAD(worksheet->external_drawing_links, list_pointers);
677         _free_relationship(relationship);
678     }
679     free(worksheet->external_drawing_links);
680 
681     while (!STAILQ_EMPTY(worksheet->drawing_links)) {
682         relationship = STAILQ_FIRST(worksheet->drawing_links);
683         STAILQ_REMOVE_HEAD(worksheet->drawing_links, list_pointers);
684         _free_relationship(relationship);
685     }
686     free(worksheet->drawing_links);
687 
688     while (!STAILQ_EMPTY(worksheet->vml_drawing_links)) {
689         relationship = STAILQ_FIRST(worksheet->vml_drawing_links);
690         STAILQ_REMOVE_HEAD(worksheet->vml_drawing_links, list_pointers);
691         _free_relationship(relationship);
692     }
693     free(worksheet->vml_drawing_links);
694 
695     while (!STAILQ_EMPTY(worksheet->external_table_links)) {
696         relationship = STAILQ_FIRST(worksheet->external_table_links);
697         STAILQ_REMOVE_HEAD(worksheet->external_table_links, list_pointers);
698         _free_relationship(relationship);
699     }
700     free(worksheet->external_table_links);
701 
702     if (worksheet->drawing_rel_ids) {
703         for (drawing_rel_id =
704              RB_MIN(lxw_drawing_rel_ids, worksheet->drawing_rel_ids);
705              drawing_rel_id; drawing_rel_id = next_drawing_rel_id) {
706 
707             next_drawing_rel_id =
708                 RB_NEXT(lxw_drawing_rel_ids, worksheet->drawing_rel_id,
709                         drawing_rel_id);
710             RB_REMOVE(lxw_drawing_rel_ids, worksheet->drawing_rel_ids,
711                       drawing_rel_id);
712             free(drawing_rel_id->target);
713             free(drawing_rel_id);
714         }
715 
716         free(worksheet->drawing_rel_ids);
717     }
718 
719     if (worksheet->vml_drawing_rel_ids) {
720         for (drawing_rel_id =
721              RB_MIN(lxw_vml_drawing_rel_ids, worksheet->vml_drawing_rel_ids);
722              drawing_rel_id; drawing_rel_id = next_drawing_rel_id) {
723 
724             next_drawing_rel_id =
725                 RB_NEXT(lxw_vml_drawing_rel_ids, worksheet->drawing_rel_id,
726                         drawing_rel_id);
727             RB_REMOVE(lxw_vml_drawing_rel_ids, worksheet->vml_drawing_rel_ids,
728                       drawing_rel_id);
729             free(drawing_rel_id->target);
730             free(drawing_rel_id);
731         }
732 
733         free(worksheet->vml_drawing_rel_ids);
734     }
735 
736     if (worksheet->conditional_formats) {
737         for (cond_format_elem =
738              RB_MIN(lxw_cond_format_hash, worksheet->conditional_formats);
739              cond_format_elem; cond_format_elem = next_cond_format_elem) {
740 
741             next_cond_format_elem = RB_NEXT(lxw_cond_format_hash,
742                                             worksheet->conditional_formats,
743                                             cond_format_elem);
744             RB_REMOVE(lxw_cond_format_hash,
745                       worksheet->conditional_formats, cond_format_elem);
746 
747             while (!STAILQ_EMPTY(cond_format_elem->cond_formats)) {
748                 cond_format = STAILQ_FIRST(cond_format_elem->cond_formats);
749                 STAILQ_REMOVE_HEAD(cond_format_elem->cond_formats,
750                                    list_pointers);
751                 _free_cond_format(cond_format);
752             }
753 
754             free(cond_format_elem->cond_formats);
755             free(cond_format_elem);
756         }
757 
758         free(worksheet->conditional_formats);
759     }
760 
761     _free_relationship(worksheet->external_vml_comment_link);
762     _free_relationship(worksheet->external_comment_link);
763     _free_relationship(worksheet->external_vml_header_link);
764     _free_relationship(worksheet->external_background_link);
765 
766     _free_filter_rules(worksheet);
767 
768     if (worksheet->array) {
769         for (col = 0; col < LXW_COL_MAX; col++) {
770             _free_cell(worksheet->array[col]);
771         }
772         free(worksheet->array);
773     }
774 
775     if (worksheet->optimize_row)
776         free(worksheet->optimize_row);
777 
778     if (worksheet->drawing)
779         lxw_drawing_free(worksheet->drawing);
780 
781     free(worksheet->hbreaks);
782     free(worksheet->vbreaks);
783     free(worksheet->name);
784     free(worksheet->quoted_name);
785     free(worksheet->vba_codename);
786     free(worksheet->vml_data_id_str);
787     free(worksheet->vml_header_id_str);
788     free(worksheet->comment_author);
789     free(worksheet->ignore_number_stored_as_text);
790     free(worksheet->ignore_eval_error);
791     free(worksheet->ignore_formula_differs);
792     free(worksheet->ignore_formula_range);
793     free(worksheet->ignore_formula_unlocked);
794     free(worksheet->ignore_empty_cell_reference);
795     free(worksheet->ignore_list_data_validation);
796     free(worksheet->ignore_calculated_column);
797     free(worksheet->ignore_two_digit_text_year);
798     free(worksheet->header);
799     free(worksheet->footer);
800 
801     free(worksheet);
802     worksheet = NULL;
803 }
804 
805 /*
806  * Create a new worksheet row object.
807  */
808 STATIC lxw_row *
_new_row(lxw_row_t row_num)809 _new_row(lxw_row_t row_num)
810 {
811     lxw_row *row = calloc(1, sizeof(lxw_row));
812 
813     if (row) {
814         row->row_num = row_num;
815         row->cells = calloc(1, sizeof(struct lxw_table_cells));
816         row->height = LXW_DEF_ROW_HEIGHT;
817 
818         if (row->cells)
819             RB_INIT(row->cells);
820         else
821             LXW_MEM_ERROR();
822     }
823     else {
824         LXW_MEM_ERROR();
825     }
826 
827     return row;
828 }
829 
830 /*
831  * Create a new worksheet number cell object.
832  */
833 STATIC lxw_cell *
_new_number_cell(lxw_row_t row_num,lxw_col_t col_num,double value,lxw_format * format)834 _new_number_cell(lxw_row_t row_num,
835                  lxw_col_t col_num, double value, lxw_format *format)
836 {
837     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
838     RETURN_ON_MEM_ERROR(cell, cell);
839 
840     cell->row_num = row_num;
841     cell->col_num = col_num;
842     cell->type = NUMBER_CELL;
843     cell->format = format;
844     cell->u.number = value;
845 
846     return cell;
847 }
848 
849 /*
850  * Create a new worksheet string cell object.
851  */
852 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)853 _new_string_cell(lxw_row_t row_num,
854                  lxw_col_t col_num, int32_t string_id, char *sst_string,
855                  lxw_format *format)
856 {
857     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
858     RETURN_ON_MEM_ERROR(cell, cell);
859 
860     cell->row_num = row_num;
861     cell->col_num = col_num;
862     cell->type = STRING_CELL;
863     cell->format = format;
864     cell->u.string_id = string_id;
865     cell->sst_string = sst_string;
866 
867     return cell;
868 }
869 
870 /*
871  * Create a new worksheet inline_string cell object.
872  */
873 STATIC lxw_cell *
_new_inline_string_cell(lxw_row_t row_num,lxw_col_t col_num,char * string,lxw_format * format)874 _new_inline_string_cell(lxw_row_t row_num,
875                         lxw_col_t col_num, char *string, lxw_format *format)
876 {
877     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
878     RETURN_ON_MEM_ERROR(cell, cell);
879 
880     cell->row_num = row_num;
881     cell->col_num = col_num;
882     cell->type = INLINE_STRING_CELL;
883     cell->format = format;
884     cell->u.string = string;
885 
886     return cell;
887 }
888 
889 /*
890  * Create a new worksheet inline_string cell object for rich strings.
891  */
892 STATIC lxw_cell *
_new_inline_rich_string_cell(lxw_row_t row_num,lxw_col_t col_num,char * string,lxw_format * format)893 _new_inline_rich_string_cell(lxw_row_t row_num,
894                              lxw_col_t col_num, char *string,
895                              lxw_format *format)
896 {
897     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
898     RETURN_ON_MEM_ERROR(cell, cell);
899 
900     cell->row_num = row_num;
901     cell->col_num = col_num;
902     cell->type = INLINE_RICH_STRING_CELL;
903     cell->format = format;
904     cell->u.string = string;
905 
906     return cell;
907 }
908 
909 /*
910  * Create a new worksheet formula cell object.
911  */
912 STATIC lxw_cell *
_new_formula_cell(lxw_row_t row_num,lxw_col_t col_num,char * formula,lxw_format * format)913 _new_formula_cell(lxw_row_t row_num,
914                   lxw_col_t col_num, char *formula, lxw_format *format)
915 {
916     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
917     RETURN_ON_MEM_ERROR(cell, cell);
918 
919     cell->row_num = row_num;
920     cell->col_num = col_num;
921     cell->type = FORMULA_CELL;
922     cell->format = format;
923     cell->u.string = formula;
924 
925     return cell;
926 }
927 
928 /*
929  * Create a new worksheet array formula cell object.
930  */
931 STATIC lxw_cell *
_new_array_formula_cell(lxw_row_t row_num,lxw_col_t col_num,char * formula,char * range,lxw_format * format,uint8_t is_dynamic)932 _new_array_formula_cell(lxw_row_t row_num, lxw_col_t col_num, char *formula,
933                         char *range, lxw_format *format, uint8_t is_dynamic)
934 {
935     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
936     RETURN_ON_MEM_ERROR(cell, cell);
937 
938     cell->row_num = row_num;
939     cell->col_num = col_num;
940     cell->format = format;
941     cell->u.string = formula;
942     cell->user_data1 = range;
943 
944     if (is_dynamic)
945         cell->type = DYNAMIC_ARRAY_FORMULA_CELL;
946     else
947         cell->type = ARRAY_FORMULA_CELL;
948 
949     return cell;
950 }
951 
952 /*
953  * Create a new worksheet blank cell object.
954  */
955 STATIC lxw_cell *
_new_blank_cell(lxw_row_t row_num,lxw_col_t col_num,lxw_format * format)956 _new_blank_cell(lxw_row_t row_num, lxw_col_t col_num, lxw_format *format)
957 {
958     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
959     RETURN_ON_MEM_ERROR(cell, cell);
960 
961     cell->row_num = row_num;
962     cell->col_num = col_num;
963     cell->type = BLANK_CELL;
964     cell->format = format;
965 
966     return cell;
967 }
968 
969 /*
970  * Create a new worksheet boolean cell object.
971  */
972 STATIC lxw_cell *
_new_boolean_cell(lxw_row_t row_num,lxw_col_t col_num,int value,lxw_format * format)973 _new_boolean_cell(lxw_row_t row_num, lxw_col_t col_num, int value,
974                   lxw_format *format)
975 {
976     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
977     RETURN_ON_MEM_ERROR(cell, cell);
978 
979     cell->row_num = row_num;
980     cell->col_num = col_num;
981     cell->type = BOOLEAN_CELL;
982     cell->format = format;
983     cell->u.number = value;
984 
985     return cell;
986 }
987 
988 /*
989  * Create a new comment cell object.
990  */
991 STATIC lxw_cell *
_new_comment_cell(lxw_row_t row_num,lxw_col_t col_num,lxw_vml_obj * comment_obj)992 _new_comment_cell(lxw_row_t row_num, lxw_col_t col_num,
993                   lxw_vml_obj *comment_obj)
994 {
995     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
996     RETURN_ON_MEM_ERROR(cell, cell);
997 
998     cell->row_num = row_num;
999     cell->col_num = col_num;
1000     cell->type = COMMENT;
1001     cell->comment = comment_obj;
1002 
1003     return cell;
1004 }
1005 
1006 /*
1007  * Create a new worksheet hyperlink cell object.
1008  */
1009 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)1010 _new_hyperlink_cell(lxw_row_t row_num, lxw_col_t col_num,
1011                     enum cell_types link_type, char *url, char *string,
1012                     char *tooltip)
1013 {
1014     lxw_cell *cell = calloc(1, sizeof(lxw_cell));
1015     RETURN_ON_MEM_ERROR(cell, cell);
1016 
1017     cell->row_num = row_num;
1018     cell->col_num = col_num;
1019     cell->type = link_type;
1020     cell->u.string = url;
1021     cell->user_data1 = string;
1022     cell->user_data2 = tooltip;
1023 
1024     return cell;
1025 }
1026 
1027 /*
1028  * Get or create the row object for a given row number.
1029  */
1030 STATIC lxw_row *
_get_row_list(struct lxw_table_rows * table,lxw_row_t row_num)1031 _get_row_list(struct lxw_table_rows *table, lxw_row_t row_num)
1032 {
1033     lxw_row *row;
1034     lxw_row *existing_row;
1035 
1036     if (table->cached_row_num == row_num)
1037         return table->cached_row;
1038 
1039     /* Create a new row and try and insert it. */
1040     row = _new_row(row_num);
1041     existing_row = RB_INSERT(lxw_table_rows, table, row);
1042 
1043     /* If existing_row is not NULL, then it already existed. Free new row */
1044     /* and return existing_row. */
1045     if (existing_row) {
1046         _free_row(row);
1047         row = existing_row;
1048     }
1049 
1050     table->cached_row = row;
1051     table->cached_row_num = row_num;
1052 
1053     return row;
1054 }
1055 
1056 /*
1057  * Get or create the row object for a given row number.
1058  */
1059 STATIC lxw_row *
_get_row(lxw_worksheet * self,lxw_row_t row_num)1060 _get_row(lxw_worksheet *self, lxw_row_t row_num)
1061 {
1062     lxw_row *row;
1063 
1064     if (!self->optimize) {
1065         row = _get_row_list(self->table, row_num);
1066         return row;
1067     }
1068     else {
1069         if (row_num < self->optimize_row->row_num) {
1070             return NULL;
1071         }
1072         else if (row_num == self->optimize_row->row_num) {
1073             return self->optimize_row;
1074         }
1075         else {
1076             /* Flush row. */
1077             lxw_worksheet_write_single_row(self);
1078             row = self->optimize_row;
1079             row->row_num = row_num;
1080             return row;
1081         }
1082     }
1083 }
1084 
1085 /*
1086  * Insert a cell object in the cell list of a row object.
1087  */
1088 STATIC void
_insert_cell_list(struct lxw_table_cells * cell_list,lxw_cell * cell,lxw_col_t col_num)1089 _insert_cell_list(struct lxw_table_cells *cell_list,
1090                   lxw_cell *cell, lxw_col_t col_num)
1091 {
1092     lxw_cell *existing_cell;
1093 
1094     cell->col_num = col_num;
1095 
1096     existing_cell = RB_INSERT(lxw_table_cells, cell_list, cell);
1097 
1098     /* If existing_cell is not NULL, then that cell already existed. */
1099     /* Remove existing_cell and add new one in again. */
1100     if (existing_cell) {
1101         RB_REMOVE(lxw_table_cells, cell_list, existing_cell);
1102 
1103         /* Add it in again. */
1104         RB_INSERT(lxw_table_cells, cell_list, cell);
1105         _free_cell(existing_cell);
1106     }
1107 
1108     return;
1109 }
1110 
1111 /*
1112  * Insert a cell object into the cell list or array.
1113  */
1114 STATIC void
_insert_cell(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_cell * cell)1115 _insert_cell(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
1116              lxw_cell *cell)
1117 {
1118     lxw_row *row = _get_row(self, row_num);
1119 
1120     if (!self->optimize) {
1121         row->data_changed = LXW_TRUE;
1122         _insert_cell_list(row->cells, cell, col_num);
1123     }
1124     else {
1125         if (row) {
1126             row->data_changed = LXW_TRUE;
1127 
1128             /* Overwrite an existing cell if necessary. */
1129             if (self->array[col_num])
1130                 _free_cell(self->array[col_num]);
1131 
1132             self->array[col_num] = cell;
1133         }
1134     }
1135 }
1136 
1137 /*
1138  * Insert a blank placeholder cell in the cells RB tree in the same position
1139  * as a comment so that the rows "spans" calculation is correct. Since the
1140  * blank cell doesn't have a format it is ignored when writing. If there is
1141  * already a cell in the required position we don't have add a new cell.
1142  */
1143 STATIC void
_insert_cell_placeholder(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num)1144 _insert_cell_placeholder(lxw_worksheet *self, lxw_row_t row_num,
1145                          lxw_col_t col_num)
1146 {
1147     lxw_row *row;
1148     lxw_cell *cell;
1149 
1150     /* The spans calculation isn't required in constant_memory mode. */
1151     if (self->optimize)
1152         return;
1153 
1154     cell = _new_blank_cell(row_num, col_num, NULL);
1155     if (!cell)
1156         return;
1157 
1158     /* Only add a cell if one doesn't already exist. */
1159     row = _get_row(self, row_num);
1160     if (!RB_FIND(lxw_table_cells, row->cells, cell)) {
1161         _insert_cell_list(row->cells, cell, col_num);
1162     }
1163     else {
1164         _free_cell(cell);
1165     }
1166 }
1167 
1168 /*
1169  * Insert a hyperlink object into the hyperlink RB tree.
1170  */
1171 STATIC void
_insert_hyperlink(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_cell * link)1172 _insert_hyperlink(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
1173                   lxw_cell *link)
1174 {
1175     lxw_row *row = _get_row_list(self->hyperlinks, row_num);
1176 
1177     _insert_cell_list(row->cells, link, col_num);
1178 }
1179 
1180 /*
1181  * Insert a comment into the comment RB tree.
1182  */
1183 STATIC void
_insert_comment(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_cell * link)1184 _insert_comment(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
1185                 lxw_cell *link)
1186 {
1187     lxw_row *row = _get_row_list(self->comments, row_num);
1188 
1189     _insert_cell_list(row->cells, link, col_num);
1190 }
1191 
1192 /*
1193  * Next power of two for column reallocs. Taken from bithacks in the public
1194  * domain.
1195  */
1196 STATIC lxw_col_t
_next_power_of_two(uint16_t col)1197 _next_power_of_two(uint16_t col)
1198 {
1199     col--;
1200     col |= col >> 1;
1201     col |= col >> 2;
1202     col |= col >> 4;
1203     col |= col >> 8;
1204     col++;
1205 
1206     return col;
1207 }
1208 
1209 /*
1210  * Check that row and col are within the allowed Excel range and store max
1211  * and min values for use in other methods/elements.
1212  *
1213  * The ignore_row/ignore_col flags are used to indicate that we wish to
1214  * perform the dimension check without storing the value.
1215  */
1216 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)1217 _check_dimensions(lxw_worksheet *self,
1218                   lxw_row_t row_num,
1219                   lxw_col_t col_num, int8_t ignore_row, int8_t ignore_col)
1220 {
1221     if (row_num >= LXW_ROW_MAX)
1222         return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
1223 
1224     if (col_num >= LXW_COL_MAX)
1225         return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
1226 
1227     /* In optimization mode we don't change dimensions for rows that are */
1228     /* already written. */
1229     if (!ignore_row && !ignore_col && self->optimize) {
1230         if (row_num < self->optimize_row->row_num)
1231             return LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
1232     }
1233 
1234     if (!ignore_row) {
1235         if (row_num < self->dim_rowmin)
1236             self->dim_rowmin = row_num;
1237         if (row_num > self->dim_rowmax)
1238             self->dim_rowmax = row_num;
1239     }
1240 
1241     if (!ignore_col) {
1242         if (col_num < self->dim_colmin)
1243             self->dim_colmin = col_num;
1244         if (col_num > self->dim_colmax)
1245             self->dim_colmax = col_num;
1246     }
1247 
1248     return LXW_NO_ERROR;
1249 }
1250 
1251 /*
1252  * Comparator for the row structure red/black tree.
1253  */
1254 STATIC int
_row_cmp(lxw_row * row1,lxw_row * row2)1255 _row_cmp(lxw_row *row1, lxw_row *row2)
1256 {
1257     if (row1->row_num > row2->row_num)
1258         return 1;
1259     if (row1->row_num < row2->row_num)
1260         return -1;
1261     return 0;
1262 }
1263 
1264 /*
1265  * Comparator for the cell structure red/black tree.
1266  */
1267 STATIC int
_cell_cmp(lxw_cell * cell1,lxw_cell * cell2)1268 _cell_cmp(lxw_cell *cell1, lxw_cell *cell2)
1269 {
1270     if (cell1->col_num > cell2->col_num)
1271         return 1;
1272     if (cell1->col_num < cell2->col_num)
1273         return -1;
1274     return 0;
1275 }
1276 
1277 /*
1278  * Comparator for the image/hyperlink relationship ids.
1279  */
1280 STATIC int
_drawing_rel_id_cmp(lxw_drawing_rel_id * rel_id1,lxw_drawing_rel_id * rel_id2)1281 _drawing_rel_id_cmp(lxw_drawing_rel_id *rel_id1, lxw_drawing_rel_id *rel_id2)
1282 {
1283     return strcmp(rel_id1->target, rel_id2->target);
1284 }
1285 
1286 /*
1287  * Comparator for the conditional format RB hash elements.
1288  */
1289 STATIC int
_cond_format_hash_cmp(lxw_cond_format_hash_element * elem_1,lxw_cond_format_hash_element * elem_2)1290 _cond_format_hash_cmp(lxw_cond_format_hash_element *elem_1,
1291                       lxw_cond_format_hash_element *elem_2)
1292 {
1293     return strcmp(elem_1->sqref, elem_2->sqref);
1294 }
1295 
1296 /*
1297  * Get the index used to address a drawing rel link.
1298  */
1299 STATIC uint32_t
_get_drawing_rel_index(lxw_worksheet * self,char * target)1300 _get_drawing_rel_index(lxw_worksheet *self, char *target)
1301 {
1302     lxw_drawing_rel_id tmp_drawing_rel_id;
1303     lxw_drawing_rel_id *found_duplicate_target = NULL;
1304     lxw_drawing_rel_id *new_drawing_rel_id = NULL;
1305 
1306     if (target) {
1307         tmp_drawing_rel_id.target = target;
1308         found_duplicate_target = RB_FIND(lxw_drawing_rel_ids,
1309                                          self->drawing_rel_ids,
1310                                          &tmp_drawing_rel_id);
1311     }
1312 
1313     if (found_duplicate_target) {
1314         return found_duplicate_target->id;
1315     }
1316     else {
1317         self->drawing_rel_id++;
1318 
1319         if (target) {
1320             new_drawing_rel_id = calloc(1, sizeof(lxw_drawing_rel_id));
1321 
1322             if (new_drawing_rel_id) {
1323                 new_drawing_rel_id->id = self->drawing_rel_id;
1324                 new_drawing_rel_id->target = lxw_strdup(target);
1325 
1326                 RB_INSERT(lxw_drawing_rel_ids, self->drawing_rel_ids,
1327                           new_drawing_rel_id);
1328             }
1329         }
1330 
1331         return self->drawing_rel_id;
1332     }
1333 }
1334 
1335 /*
1336  * find the index used to address a drawing rel link.
1337  */
1338 STATIC uint32_t
_find_drawing_rel_index(lxw_worksheet * self,char * target)1339 _find_drawing_rel_index(lxw_worksheet *self, char *target)
1340 {
1341     lxw_drawing_rel_id tmp_drawing_rel_id;
1342     lxw_drawing_rel_id *found_duplicate_target = NULL;
1343 
1344     if (!target)
1345         return 0;
1346 
1347     tmp_drawing_rel_id.target = target;
1348     found_duplicate_target = RB_FIND(lxw_drawing_rel_ids,
1349                                      self->drawing_rel_ids,
1350                                      &tmp_drawing_rel_id);
1351 
1352     if (found_duplicate_target)
1353         return found_duplicate_target->id;
1354     else
1355         return 0;
1356 }
1357 
1358 /*
1359  * Get the index used to address a VMLdrawing rel link.
1360  */
1361 STATIC uint32_t
_get_vml_drawing_rel_index(lxw_worksheet * self,char * target)1362 _get_vml_drawing_rel_index(lxw_worksheet *self, char *target)
1363 {
1364     lxw_drawing_rel_id tmp_drawing_rel_id;
1365     lxw_drawing_rel_id *found_duplicate_target = NULL;
1366     lxw_drawing_rel_id *new_drawing_rel_id = NULL;
1367 
1368     if (target) {
1369         tmp_drawing_rel_id.target = target;
1370         found_duplicate_target = RB_FIND(lxw_vml_drawing_rel_ids,
1371                                          self->vml_drawing_rel_ids,
1372                                          &tmp_drawing_rel_id);
1373     }
1374 
1375     if (found_duplicate_target) {
1376         return found_duplicate_target->id;
1377     }
1378     else {
1379         self->vml_drawing_rel_id++;
1380 
1381         if (target) {
1382             new_drawing_rel_id = calloc(1, sizeof(lxw_drawing_rel_id));
1383 
1384             if (new_drawing_rel_id) {
1385                 new_drawing_rel_id->id = self->vml_drawing_rel_id;
1386                 new_drawing_rel_id->target = lxw_strdup(target);
1387 
1388                 RB_INSERT(lxw_vml_drawing_rel_ids, self->vml_drawing_rel_ids,
1389                           new_drawing_rel_id);
1390             }
1391         }
1392 
1393         return self->vml_drawing_rel_id;
1394     }
1395 }
1396 
1397 /*
1398  * find the index used to address a VML drawing rel link.
1399  */
1400 STATIC uint32_t
_find_vml_drawing_rel_index(lxw_worksheet * self,char * target)1401 _find_vml_drawing_rel_index(lxw_worksheet *self, char *target)
1402 {
1403     lxw_drawing_rel_id tmp_drawing_rel_id;
1404     lxw_drawing_rel_id *found_duplicate_target = NULL;
1405 
1406     if (!target)
1407         return 0;
1408 
1409     tmp_drawing_rel_id.target = target;
1410     found_duplicate_target = RB_FIND(lxw_vml_drawing_rel_ids,
1411                                      self->vml_drawing_rel_ids,
1412                                      &tmp_drawing_rel_id);
1413 
1414     if (found_duplicate_target)
1415         return found_duplicate_target->id;
1416     else
1417         return 0;
1418 }
1419 
1420 /*
1421  * Simple replacement for libgen.h basename() for compatibility with MSVC. It
1422  * handles forward and back slashes. It doesn't copy exactly the return
1423  * format of basename().
1424  */
1425 const char *
lxw_basename(const char * path)1426 lxw_basename(const char *path)
1427 {
1428 
1429     const char *forward_slash;
1430     const char *back_slash;
1431 
1432     if (!path)
1433         return NULL;
1434 
1435     forward_slash = strrchr(path, '/');
1436     back_slash = strrchr(path, '\\');
1437 
1438     if (!forward_slash && !back_slash)
1439         return path;
1440 
1441     if (forward_slash > back_slash)
1442         return forward_slash + 1;
1443     else
1444         return back_slash + 1;
1445 }
1446 
1447 /* Function to count the total concatenated length of the strings in a
1448  * validation list array, including commas. */
1449 size_t
_validation_list_length(char ** list)1450 _validation_list_length(char **list)
1451 {
1452     uint8_t i = 0;
1453     size_t length = 0;
1454 
1455     if (!list || !list[0])
1456         return 0;
1457 
1458     while (list[i] && length <= LXW_VALIDATION_MAX_STRING_LENGTH) {
1459         /* Include commas in the length. */
1460         length += 1 + lxw_utf8_strlen(list[i]);
1461         i++;
1462     }
1463 
1464     /* Adjust the count for extraneous comma at end. */
1465     length--;
1466 
1467     return length;
1468 }
1469 
1470 /* Function to convert an array of strings into a CSV string for data
1471  * validation lists. */
1472 char *
_validation_list_to_csv(char ** list)1473 _validation_list_to_csv(char **list)
1474 {
1475     uint8_t i = 0;
1476     char *str;
1477 
1478     /* Create a buffer for the concatenated, and quoted, string. */
1479     /* Add +3 for quotes and EOL. */
1480     str = calloc(1, LXW_VALIDATION_MAX_STRING_LENGTH + 3);
1481     if (!str)
1482         return NULL;
1483 
1484     /* Add the start quote and first element. */
1485     strcat(str, "\"");
1486     strcat(str, list[0]);
1487 
1488     /* Add the other elements preceded by a comma. */
1489     i = 1;
1490     while (list[i]) {
1491         strcat(str, ",");
1492         strcat(str, list[i]);
1493         i++;
1494     }
1495 
1496     /* Add the end quote. */
1497     strcat(str, "\"");
1498 
1499     return str;
1500 }
1501 
1502 STATIC double
_pixels_to_width(double pixels)1503 _pixels_to_width(double pixels)
1504 {
1505     double max_digit_width = 7.0;
1506     double padding = 5.0;
1507     double width;
1508 
1509     if (pixels == LXW_DEF_COL_WIDTH_PIXELS)
1510         width = LXW_DEF_COL_WIDTH;
1511     else if (pixels <= 12.0)
1512         width = pixels / (max_digit_width + padding);
1513     else
1514         width = (pixels - padding) / max_digit_width;
1515 
1516     return width;
1517 }
1518 
1519 STATIC double
_pixels_to_height(double pixels)1520 _pixels_to_height(double pixels)
1521 {
1522     if (pixels == LXW_DEF_ROW_HEIGHT_PIXELS)
1523         return LXW_DEF_ROW_HEIGHT;
1524     else
1525         return pixels * 0.75;
1526 }
1527 
1528 /* Check and set if an autofilter is a standard or custom filter. */
1529 void
_set_custom_filter(lxw_filter_rule_obj * rule_obj)1530 _set_custom_filter(lxw_filter_rule_obj *rule_obj)
1531 {
1532     rule_obj->is_custom = LXW_TRUE;
1533 
1534     if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_EQUAL_TO)
1535         rule_obj->is_custom = LXW_FALSE;
1536 
1537     if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS)
1538         rule_obj->is_custom = LXW_FALSE;
1539 
1540     if (rule_obj->criteria2 != LXW_FILTER_CRITERIA_NONE) {
1541         if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_EQUAL_TO)
1542             rule_obj->is_custom = LXW_FALSE;
1543 
1544         if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS)
1545             rule_obj->is_custom = LXW_FALSE;
1546 
1547         if (rule_obj->type == LXW_FILTER_TYPE_AND)
1548             rule_obj->is_custom = LXW_TRUE;
1549     }
1550 
1551     if (rule_obj->value1_string && strpbrk(rule_obj->value1_string, "*?"))
1552         rule_obj->is_custom = LXW_TRUE;
1553 
1554     if (rule_obj->value2_string && strpbrk(rule_obj->value2_string, "*?"))
1555         rule_obj->is_custom = LXW_TRUE;
1556 }
1557 
1558 /* Check and copy user input for table styles in worksheet_add_table(). */
1559 void
_check_and_copy_table_style(lxw_table_obj * table_obj,lxw_table_options * user_options)1560 _check_and_copy_table_style(lxw_table_obj *table_obj,
1561                             lxw_table_options *user_options)
1562 {
1563     if (!user_options)
1564         return;
1565 
1566     /* Set the defaults. */
1567     table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM;
1568     table_obj->style_type_number = 9;
1569 
1570     if (user_options->style_type > LXW_TABLE_STYLE_TYPE_DARK) {
1571         LXW_WARN_FORMAT1
1572             ("worksheet_add_table(): invalid style_type = %d. "
1573              "Using default TableStyleMedium9", user_options->style_type);
1574 
1575         table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM;
1576         table_obj->style_type_number = 9;
1577     }
1578     else {
1579         table_obj->style_type = user_options->style_type;
1580     }
1581 
1582     /* Each type (light, medium and dark) has a different number of styles. */
1583     if (user_options->style_type == LXW_TABLE_STYLE_TYPE_LIGHT) {
1584         if (user_options->style_type_number > 21) {
1585             LXW_WARN_FORMAT1("worksheet_add_table(): "
1586                              "invalid style_type_number = %d for style type "
1587                              "LXW_TABLE_STYLE_TYPE_LIGHT. "
1588                              "Using default TableStyleMedium9",
1589                              user_options->style_type);
1590 
1591             table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM;
1592             table_obj->style_type_number = 9;
1593         }
1594         else {
1595             table_obj->style_type_number = user_options->style_type_number;
1596         }
1597     }
1598 
1599     if (user_options->style_type == LXW_TABLE_STYLE_TYPE_MEDIUM) {
1600         if (user_options->style_type_number < 1
1601             || user_options->style_type_number > 28) {
1602             LXW_WARN_FORMAT1("worksheet_add_table(): "
1603                              "invalid style_type_number = %d for style type "
1604                              "LXW_TABLE_STYLE_TYPE_MEDIUM. "
1605                              "Using default TableStyleMedium9",
1606                              user_options->style_type_number);
1607 
1608             table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM;
1609             table_obj->style_type_number = 9;
1610         }
1611         else {
1612             table_obj->style_type_number = user_options->style_type_number;
1613         }
1614     }
1615 
1616     if (user_options->style_type == LXW_TABLE_STYLE_TYPE_DARK) {
1617         if (user_options->style_type_number < 1
1618             || user_options->style_type_number > 11) {
1619             LXW_WARN_FORMAT1("worksheet_add_table(): "
1620                              "invalid style_type_number = %d for style type "
1621                              "LXW_TABLE_STYLE_TYPE_DARK. "
1622                              "Using default TableStyleMedium9",
1623                              user_options->style_type_number);
1624 
1625             table_obj->style_type = LXW_TABLE_STYLE_TYPE_MEDIUM;
1626             table_obj->style_type_number = 9;
1627         }
1628         else {
1629             table_obj->style_type_number = user_options->style_type_number;
1630         }
1631     }
1632 }
1633 
1634 /* Set the defaults for table columns in worksheet_add_table(). */
1635 lxw_error
_set_default_table_columns(lxw_table_obj * table_obj)1636 _set_default_table_columns(lxw_table_obj *table_obj)
1637 {
1638 
1639     char col_name[LXW_ATTR_32];
1640     char *header;
1641     uint16_t i;
1642     lxw_table_column *column;
1643     uint16_t num_cols = table_obj->num_cols;
1644     lxw_table_column **columns = table_obj->columns;
1645 
1646     for (i = 0; i < num_cols; i++) {
1647         lxw_snprintf(col_name, LXW_ATTR_32, "Column%d", i + 1);
1648 
1649         column = calloc(num_cols, sizeof(lxw_table_column));
1650         RETURN_ON_MEM_ERROR(column, LXW_ERROR_MEMORY_MALLOC_FAILED);
1651 
1652         header = lxw_strdup(col_name);
1653         if (!header) {
1654             free(column);
1655             RETURN_ON_MEM_ERROR(header, LXW_ERROR_MEMORY_MALLOC_FAILED);
1656         }
1657         columns[i] = column;
1658         columns[i]->header = header;
1659     }
1660 
1661     return LXW_NO_ERROR;
1662 }
1663 
1664 /*  Convert Excel 2010 style "@" structural references to the Excel 2007 style
1665  *  "[#This Row]" in table formulas. This is the format that Excel uses to
1666  *  store the references. */
1667 char *
_expand_table_formula(char * formula)1668 _expand_table_formula(char *formula)
1669 {
1670     char *expanded;
1671     char *ptr;
1672     size_t i;
1673     size_t ref_count = 0;
1674     size_t expanded_len;
1675 
1676     ptr = formula;
1677 
1678     while (*ptr++) {
1679         if (*ptr == '@')
1680             ref_count++;
1681     }
1682 
1683     if (ref_count == 0) {
1684         /* String doesn't need to be expanded. Just copy it. */
1685         expanded = lxw_strdup_formula(formula);
1686     }
1687     else {
1688         /* Convert "@" in the formula string to "[#This Row],".  */
1689         expanded_len = strlen(formula) + (sizeof(LXW_THIS_ROW) * ref_count);
1690         expanded = calloc(1, expanded_len);
1691 
1692         if (!expanded)
1693             return NULL;
1694 
1695         i = 0;
1696         ptr = formula;
1697         /* Ignore the = in the formula. */
1698         if (*ptr == '=')
1699             ptr++;
1700 
1701         /* Do the "@" expansion. */
1702         while (*ptr) {
1703             if (*ptr == '@') {
1704                 strcat(&expanded[i], LXW_THIS_ROW);
1705                 i += sizeof(LXW_THIS_ROW) - 1;
1706             }
1707             else {
1708                 expanded[i] = *ptr;
1709                 i++;
1710             }
1711 
1712             ptr++;
1713         }
1714     }
1715 
1716     return expanded;
1717 }
1718 
1719 /* Set user values for table columns in worksheet_add_table(). */
1720 lxw_error
_set_custom_table_columns(lxw_table_obj * table_obj,lxw_table_options * user_options)1721 _set_custom_table_columns(lxw_table_obj *table_obj,
1722                           lxw_table_options *user_options)
1723 {
1724     char *str;
1725     uint16_t i;
1726     lxw_table_column *table_column;
1727     lxw_table_column *user_column;
1728     uint16_t num_cols = table_obj->num_cols;
1729     lxw_table_column **user_columns = user_options->columns;
1730 
1731     for (i = 0; i < num_cols; i++) {
1732 
1733         user_column = user_columns[i];
1734         table_column = table_obj->columns[i];
1735 
1736         /* NULL indicates end of user input array. */
1737         if (user_column == NULL)
1738             return LXW_NO_ERROR;
1739 
1740         if (user_column->header) {
1741             if (lxw_utf8_strlen(user_column->header) > 255) {
1742                 LXW_WARN_FORMAT("worksheet_add_table(): column parameter "
1743                                 "'header' exceeds Excel length limit of 255.");
1744                 return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
1745             }
1746 
1747             str = lxw_strdup(user_column->header);
1748             RETURN_ON_MEM_ERROR(str, LXW_ERROR_MEMORY_MALLOC_FAILED);
1749 
1750             /* Free the default column header. */
1751             free(table_column->header);
1752             table_column->header = str;
1753         }
1754 
1755         if (user_column->total_string) {
1756             str = lxw_strdup(user_column->total_string);
1757             RETURN_ON_MEM_ERROR(str, LXW_ERROR_MEMORY_MALLOC_FAILED);
1758 
1759             table_column->total_string = str;
1760         }
1761 
1762         if (user_column->formula) {
1763             str = _expand_table_formula(user_column->formula);
1764             RETURN_ON_MEM_ERROR(str, LXW_ERROR_MEMORY_MALLOC_FAILED);
1765 
1766             table_column->formula = str;
1767         }
1768 
1769         table_column->format = user_column->format;
1770         table_column->total_value = user_column->total_value;
1771         table_column->header_format = user_column->header_format;
1772         table_column->total_function = user_column->total_function;
1773     }
1774 
1775     return LXW_NO_ERROR;
1776 }
1777 
1778 /* Write a worksheet table column formula like SUBTOTAL(109,[Column1]). */
1779 void
_write_column_function(lxw_worksheet * self,lxw_row_t row,lxw_col_t col,lxw_table_column * column)1780 _write_column_function(lxw_worksheet *self, lxw_row_t row, lxw_col_t col,
1781                        lxw_table_column *column)
1782 {
1783     size_t offset;
1784     char formula[LXW_MAX_ATTRIBUTE_LENGTH];
1785     lxw_format *format = column->format;
1786     uint8_t total_function = column->total_function;
1787     double value = column->total_value;
1788     const char *header = column->header;
1789 
1790     /* Write the subtotal formula number. */
1791     lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, "SUBTOTAL(%d,[",
1792                  total_function);
1793 
1794     /* Copy the header string but escape any special characters. Note, this is
1795      * guaranteed to fit in the 2k buffer since the header is max 255
1796      * characters, checked in _set_custom_table_columns(). */
1797     offset = strlen(formula);
1798     while (*header) {
1799         switch (*header) {
1800             case '\'':
1801             case '#':
1802             case '[':
1803             case ']':
1804                 formula[offset++] = '\'';
1805                 formula[offset] = *header;
1806                 break;
1807             default:
1808                 formula[offset] = *header;
1809                 break;
1810         }
1811         offset++;
1812         header++;
1813     }
1814 
1815     /* Write the end of the string. */
1816     memcpy(&formula[offset], "])\0", sizeof("])\0"));
1817 
1818     worksheet_write_formula_num(self, row, col, formula, format, value);
1819 }
1820 
1821 /* Write a worksheet table column formula. */
1822 void
_write_column_formula(lxw_worksheet * self,lxw_row_t first_row,lxw_row_t last_row,lxw_col_t col,lxw_table_column * column)1823 _write_column_formula(lxw_worksheet *self, lxw_row_t first_row,
1824                       lxw_row_t last_row, lxw_col_t col,
1825                       lxw_table_column *column)
1826 {
1827     lxw_row_t row;
1828     const char *formula = column->formula;
1829     lxw_format *format = column->format;
1830 
1831     for (row = first_row; row <= last_row; row++)
1832         worksheet_write_formula(self, row, col, formula, format);
1833 }
1834 
1835 /* Set the defaults for table columns in worksheet_add_table(). */
1836 void
_write_table_column_data(lxw_worksheet * self,lxw_table_obj * table_obj)1837 _write_table_column_data(lxw_worksheet *self, lxw_table_obj *table_obj)
1838 {
1839     uint16_t i;
1840     lxw_table_column *column;
1841     lxw_table_column **columns = table_obj->columns;
1842 
1843     lxw_col_t col;
1844     lxw_row_t first_row = table_obj->first_row;
1845     lxw_col_t first_col = table_obj->first_col;
1846     lxw_row_t last_row = table_obj->last_row;
1847     lxw_row_t first_data_row = first_row;
1848     lxw_row_t last_data_row = last_row;
1849 
1850     if (!table_obj->no_header_row)
1851         first_data_row++;
1852 
1853     if (table_obj->total_row)
1854         last_data_row--;
1855 
1856     for (i = 0; i < table_obj->num_cols; i++) {
1857         col = first_col + i;
1858         column = columns[i];
1859 
1860         if (table_obj->no_header_row == LXW_FALSE)
1861             worksheet_write_string(self, first_row, col, column->header,
1862                                    column->header_format);
1863 
1864         if (column->total_string)
1865             worksheet_write_string(self, last_row, col, column->total_string,
1866                                    NULL);
1867 
1868         if (column->total_function)
1869             _write_column_function(self, last_row, col, column);
1870 
1871         if (column->formula)
1872             _write_column_formula(self, first_data_row, last_data_row, col,
1873                                   column);
1874     }
1875 }
1876 
1877 /*
1878  * Check that there are sufficient data rows in a worksheet table.
1879  */
1880 lxw_error
_check_table_rows(lxw_row_t first_row,lxw_row_t last_row,lxw_table_options * user_options)1881 _check_table_rows(lxw_row_t first_row, lxw_row_t last_row,
1882                   lxw_table_options *user_options)
1883 {
1884     lxw_row_t num_non_header_rows = last_row - first_row;
1885 
1886     if (user_options && user_options->no_header_row == LXW_TRUE)
1887         num_non_header_rows++;
1888 
1889     if (num_non_header_rows == 0) {
1890         LXW_WARN_FORMAT("worksheet_add_table(): "
1891                         "table must have at least 1 non-header row.");
1892         return LXW_ERROR_PARAMETER_VALIDATION;
1893     }
1894 
1895     return LXW_NO_ERROR;
1896 }
1897 
1898 /*
1899  * Check that the the table name is valid.
1900  */
1901 lxw_error
_check_table_name(lxw_table_options * user_options)1902 _check_table_name(lxw_table_options *user_options)
1903 {
1904     const char *name;
1905     char *ptr;
1906     char first[2] = { 0, 0 };
1907 
1908     if (!user_options)
1909         return LXW_NO_ERROR;
1910 
1911     if (!user_options->name)
1912         return LXW_NO_ERROR;
1913 
1914     name = user_options->name;
1915 
1916     /* Check table name length. */
1917     if (lxw_utf8_strlen(name) > 255) {
1918         LXW_WARN_FORMAT("worksheet_add_table(): "
1919                         "Table name exceeds Excel's limit of 255.");
1920         return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
1921     }
1922 
1923     /* Check some short invalid names. */
1924     if (strlen(name) == 1
1925         && (name[0] == 'C' || name[0] == 'c' || name[0] == 'R'
1926             || name[0] == 'r')) {
1927         LXW_WARN_FORMAT1("worksheet_add_table(): "
1928                          "invalid table name \"%s\".", name);
1929         return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
1930     }
1931 
1932     /* Check for invalid characters in Table name, while trying to allow
1933      * for utf8 strings. */
1934     ptr = strpbrk(name, " !\"#$%&'()*+,-/:;<=>?@[\\]^`{|}~");
1935     if (ptr) {
1936         LXW_WARN_FORMAT2("worksheet_add_table(): "
1937                          "invalid character '%c' in table name \"%s\".",
1938                          *ptr, name);
1939         return LXW_ERROR_PARAMETER_VALIDATION;
1940     }
1941 
1942     /* Check for invalid initial character in Table name, while trying to allow
1943      * for utf8 strings. */
1944     first[0] = name[0];
1945     ptr = strpbrk(first, " !\"#$%&'()*+,-./0123456789:;<=>?@[\\]^`{|}~");
1946     if (ptr) {
1947         LXW_WARN_FORMAT2("worksheet_add_table(): "
1948                          "invalid first character '%c' in table name \"%s\".",
1949                          *ptr, name);
1950         return LXW_ERROR_PARAMETER_VALIDATION;
1951     }
1952 
1953     return LXW_NO_ERROR;
1954 }
1955 
1956 /*****************************************************************************
1957  *
1958  * XML functions.
1959  *
1960  ****************************************************************************/
1961 /*
1962  * Write the XML declaration.
1963  */
1964 STATIC void
_worksheet_xml_declaration(lxw_worksheet * self)1965 _worksheet_xml_declaration(lxw_worksheet *self)
1966 {
1967     lxw_xml_declaration(self->file);
1968 }
1969 
1970 /*
1971  * Write the <worksheet> element.
1972  */
1973 STATIC void
_worksheet_write_worksheet(lxw_worksheet * self)1974 _worksheet_write_worksheet(lxw_worksheet *self)
1975 {
1976     struct xml_attribute_list attributes;
1977     struct xml_attribute *attribute;
1978     char xmlns[] = "http://schemas.openxmlformats.org/"
1979         "spreadsheetml/2006/main";
1980     char xmlns_r[] = "http://schemas.openxmlformats.org/"
1981         "officeDocument/2006/relationships";
1982     char xmlns_mc[] = "http://schemas.openxmlformats.org/"
1983         "markup-compatibility/2006";
1984     char xmlns_x14ac[] = "http://schemas.microsoft.com/"
1985         "office/spreadsheetml/2009/9/ac";
1986 
1987     LXW_INIT_ATTRIBUTES();
1988     LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
1989     LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
1990 
1991     if (self->excel_version == 2010) {
1992         LXW_PUSH_ATTRIBUTES_STR("xmlns:mc", xmlns_mc);
1993         LXW_PUSH_ATTRIBUTES_STR("xmlns:x14ac", xmlns_x14ac);
1994         LXW_PUSH_ATTRIBUTES_STR("mc:Ignorable", "x14ac");
1995     }
1996 
1997     lxw_xml_start_tag(self->file, "worksheet", &attributes);
1998     LXW_FREE_ATTRIBUTES();
1999 }
2000 
2001 /*
2002  * Write the <dimension> element.
2003  */
2004 STATIC void
_worksheet_write_dimension(lxw_worksheet * self)2005 _worksheet_write_dimension(lxw_worksheet *self)
2006 {
2007     struct xml_attribute_list attributes;
2008     struct xml_attribute *attribute;
2009     char ref[LXW_MAX_CELL_RANGE_LENGTH];
2010     lxw_row_t dim_rowmin = self->dim_rowmin;
2011     lxw_row_t dim_rowmax = self->dim_rowmax;
2012     lxw_col_t dim_colmin = self->dim_colmin;
2013     lxw_col_t dim_colmax = self->dim_colmax;
2014 
2015     if (dim_rowmin == LXW_ROW_MAX && dim_colmin == LXW_COL_MAX) {
2016         /* If the rows and cols are still the defaults then no dimensions have
2017          * been set and we use the default range "A1". */
2018         lxw_rowcol_to_range(ref, 0, 0, 0, 0);
2019     }
2020     else if (dim_rowmin == LXW_ROW_MAX && dim_colmin != LXW_COL_MAX) {
2021         /* If the rows aren't set but the columns are then the dimensions have
2022          * been changed via set_column(). */
2023         lxw_rowcol_to_range(ref, 0, dim_colmin, 0, dim_colmax);
2024     }
2025     else {
2026         lxw_rowcol_to_range(ref, dim_rowmin, dim_colmin, dim_rowmax,
2027                             dim_colmax);
2028     }
2029 
2030     LXW_INIT_ATTRIBUTES();
2031     LXW_PUSH_ATTRIBUTES_STR("ref", ref);
2032 
2033     lxw_xml_empty_tag(self->file, "dimension", &attributes);
2034 
2035     LXW_FREE_ATTRIBUTES();
2036 }
2037 
2038 /*
2039  * Write the <pane> element for freeze panes.
2040  */
2041 STATIC void
_worksheet_write_freeze_panes(lxw_worksheet * self)2042 _worksheet_write_freeze_panes(lxw_worksheet *self)
2043 {
2044     struct xml_attribute_list attributes;
2045     struct xml_attribute *attribute;
2046 
2047     lxw_selection *selection;
2048     lxw_selection *user_selection;
2049     lxw_row_t row = self->panes.first_row;
2050     lxw_col_t col = self->panes.first_col;
2051     lxw_row_t top_row = self->panes.top_row;
2052     lxw_col_t left_col = self->panes.left_col;
2053 
2054     char row_cell[LXW_MAX_CELL_NAME_LENGTH];
2055     char col_cell[LXW_MAX_CELL_NAME_LENGTH];
2056     char top_left_cell[LXW_MAX_CELL_NAME_LENGTH];
2057     char active_pane[LXW_PANE_NAME_LENGTH];
2058 
2059     /* If there is a user selection we remove it from the list and use it. */
2060     if (!STAILQ_EMPTY(self->selections)) {
2061         user_selection = STAILQ_FIRST(self->selections);
2062         STAILQ_REMOVE_HEAD(self->selections, list_pointers);
2063     }
2064     else {
2065         /* or else create a new blank selection. */
2066         user_selection = calloc(1, sizeof(lxw_selection));
2067         RETURN_VOID_ON_MEM_ERROR(user_selection);
2068     }
2069 
2070     LXW_INIT_ATTRIBUTES();
2071 
2072     lxw_rowcol_to_cell(top_left_cell, top_row, left_col);
2073 
2074     /* Set the active pane. */
2075     if (row && col) {
2076         lxw_strcpy(active_pane, "bottomRight");
2077 
2078         lxw_rowcol_to_cell(row_cell, row, 0);
2079         lxw_rowcol_to_cell(col_cell, 0, col);
2080 
2081         selection = calloc(1, sizeof(lxw_selection));
2082         if (selection) {
2083             lxw_strcpy(selection->pane, "topRight");
2084             lxw_strcpy(selection->active_cell, col_cell);
2085             lxw_strcpy(selection->sqref, col_cell);
2086 
2087             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2088         }
2089 
2090         selection = calloc(1, sizeof(lxw_selection));
2091         if (selection) {
2092             lxw_strcpy(selection->pane, "bottomLeft");
2093             lxw_strcpy(selection->active_cell, row_cell);
2094             lxw_strcpy(selection->sqref, row_cell);
2095 
2096             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2097         }
2098 
2099         selection = calloc(1, sizeof(lxw_selection));
2100         if (selection) {
2101             lxw_strcpy(selection->pane, "bottomRight");
2102             lxw_strcpy(selection->active_cell, user_selection->active_cell);
2103             lxw_strcpy(selection->sqref, user_selection->sqref);
2104 
2105             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2106         }
2107     }
2108     else if (col) {
2109         lxw_strcpy(active_pane, "topRight");
2110 
2111         selection = calloc(1, sizeof(lxw_selection));
2112         if (selection) {
2113             lxw_strcpy(selection->pane, "topRight");
2114             lxw_strcpy(selection->active_cell, user_selection->active_cell);
2115             lxw_strcpy(selection->sqref, user_selection->sqref);
2116 
2117             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2118         }
2119     }
2120     else {
2121         lxw_strcpy(active_pane, "bottomLeft");
2122 
2123         selection = calloc(1, sizeof(lxw_selection));
2124         if (selection) {
2125             lxw_strcpy(selection->pane, "bottomLeft");
2126             lxw_strcpy(selection->active_cell, user_selection->active_cell);
2127             lxw_strcpy(selection->sqref, user_selection->sqref);
2128 
2129             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2130         }
2131     }
2132 
2133     if (col)
2134         LXW_PUSH_ATTRIBUTES_INT("xSplit", col);
2135 
2136     if (row)
2137         LXW_PUSH_ATTRIBUTES_INT("ySplit", row);
2138 
2139     LXW_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell);
2140     LXW_PUSH_ATTRIBUTES_STR("activePane", active_pane);
2141 
2142     if (self->panes.type == FREEZE_PANES)
2143         LXW_PUSH_ATTRIBUTES_STR("state", "frozen");
2144     else if (self->panes.type == FREEZE_SPLIT_PANES)
2145         LXW_PUSH_ATTRIBUTES_STR("state", "frozenSplit");
2146 
2147     lxw_xml_empty_tag(self->file, "pane", &attributes);
2148 
2149     free(user_selection);
2150 
2151     LXW_FREE_ATTRIBUTES();
2152 }
2153 
2154 /*
2155  * Convert column width from user units to pane split width.
2156  */
2157 STATIC uint32_t
_worksheet_calculate_x_split_width(double x_split)2158 _worksheet_calculate_x_split_width(double x_split)
2159 {
2160     uint32_t width;
2161     uint32_t pixels;
2162     uint32_t points;
2163     uint32_t twips;
2164     double max_digit_width = 7.0;       /* For Calabri 11. */
2165     double padding = 5.0;
2166 
2167     /* Convert to pixels. */
2168     if (x_split < 1.0) {
2169         pixels = (uint32_t) (x_split * (max_digit_width + padding) + 0.5);
2170     }
2171     else {
2172         pixels = (uint32_t) (x_split * max_digit_width + 0.5) + 5;
2173     }
2174 
2175     /* Convert to points. */
2176     points = (pixels * 3) / 4;
2177 
2178     /* Convert to twips (twentieths of a point). */
2179     twips = points * 20;
2180 
2181     /* Add offset/padding. */
2182     width = twips + 390;
2183 
2184     return width;
2185 }
2186 
2187 /*
2188  * Write the <pane> element for split panes.
2189  */
2190 STATIC void
_worksheet_write_split_panes(lxw_worksheet * self)2191 _worksheet_write_split_panes(lxw_worksheet *self)
2192 {
2193     struct xml_attribute_list attributes;
2194     struct xml_attribute *attribute;
2195 
2196     lxw_selection *selection;
2197     lxw_selection *user_selection;
2198     lxw_row_t row = self->panes.first_row;
2199     lxw_col_t col = self->panes.first_col;
2200     lxw_row_t top_row = self->panes.top_row;
2201     lxw_col_t left_col = self->panes.left_col;
2202     double x_split = self->panes.x_split;
2203     double y_split = self->panes.y_split;
2204     uint8_t has_selection = LXW_FALSE;
2205 
2206     char row_cell[LXW_MAX_CELL_NAME_LENGTH];
2207     char col_cell[LXW_MAX_CELL_NAME_LENGTH];
2208     char top_left_cell[LXW_MAX_CELL_NAME_LENGTH];
2209     char active_pane[LXW_PANE_NAME_LENGTH];
2210 
2211     /* If there is a user selection we remove it from the list and use it. */
2212     if (!STAILQ_EMPTY(self->selections)) {
2213         user_selection = STAILQ_FIRST(self->selections);
2214         STAILQ_REMOVE_HEAD(self->selections, list_pointers);
2215         has_selection = LXW_TRUE;
2216     }
2217     else {
2218         /* or else create a new blank selection. */
2219         user_selection = calloc(1, sizeof(lxw_selection));
2220         RETURN_VOID_ON_MEM_ERROR(user_selection);
2221     }
2222 
2223     LXW_INIT_ATTRIBUTES();
2224 
2225     /* Convert the row and col to 1/20 twip units with padding. */
2226     if (y_split > 0.0)
2227         y_split = (uint32_t) (20 * y_split + 300);
2228 
2229     if (x_split > 0.0)
2230         x_split = _worksheet_calculate_x_split_width(x_split);
2231 
2232     /* For non-explicit topLeft definitions, estimate the cell offset based on
2233      * the pixels dimensions. This is only a workaround and doesn't take
2234      * adjusted cell dimensions into account.
2235      */
2236     if (top_row == row && left_col == col) {
2237         top_row = (lxw_row_t) (0.5 + (y_split - 300.0) / 20.0 / 15.0);
2238         left_col = (lxw_col_t) (0.5 + (x_split - 390.0) / 20.0 / 3.0 / 16.0);
2239     }
2240 
2241     lxw_rowcol_to_cell(top_left_cell, top_row, left_col);
2242 
2243     /* If there is no selection set the active cell to the top left cell. */
2244     if (!has_selection) {
2245         lxw_strcpy(user_selection->active_cell, top_left_cell);
2246         lxw_strcpy(user_selection->sqref, top_left_cell);
2247     }
2248 
2249     /* Set the active pane. */
2250     if (y_split > 0.0 && x_split > 0.0) {
2251         lxw_strcpy(active_pane, "bottomRight");
2252 
2253         lxw_rowcol_to_cell(row_cell, top_row, 0);
2254         lxw_rowcol_to_cell(col_cell, 0, left_col);
2255 
2256         selection = calloc(1, sizeof(lxw_selection));
2257         if (selection) {
2258             lxw_strcpy(selection->pane, "topRight");
2259             lxw_strcpy(selection->active_cell, col_cell);
2260             lxw_strcpy(selection->sqref, col_cell);
2261 
2262             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2263         }
2264 
2265         selection = calloc(1, sizeof(lxw_selection));
2266         if (selection) {
2267             lxw_strcpy(selection->pane, "bottomLeft");
2268             lxw_strcpy(selection->active_cell, row_cell);
2269             lxw_strcpy(selection->sqref, row_cell);
2270 
2271             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2272         }
2273 
2274         selection = calloc(1, sizeof(lxw_selection));
2275         if (selection) {
2276             lxw_strcpy(selection->pane, "bottomRight");
2277             lxw_strcpy(selection->active_cell, user_selection->active_cell);
2278             lxw_strcpy(selection->sqref, user_selection->sqref);
2279 
2280             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2281         }
2282     }
2283     else if (x_split > 0.0) {
2284         lxw_strcpy(active_pane, "topRight");
2285 
2286         selection = calloc(1, sizeof(lxw_selection));
2287         if (selection) {
2288             lxw_strcpy(selection->pane, "topRight");
2289             lxw_strcpy(selection->active_cell, user_selection->active_cell);
2290             lxw_strcpy(selection->sqref, user_selection->sqref);
2291 
2292             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2293         }
2294     }
2295     else {
2296         lxw_strcpy(active_pane, "bottomLeft");
2297 
2298         selection = calloc(1, sizeof(lxw_selection));
2299         if (selection) {
2300             lxw_strcpy(selection->pane, "bottomLeft");
2301             lxw_strcpy(selection->active_cell, user_selection->active_cell);
2302             lxw_strcpy(selection->sqref, user_selection->sqref);
2303 
2304             STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
2305         }
2306     }
2307 
2308     if (x_split > 0.0)
2309         LXW_PUSH_ATTRIBUTES_DBL("xSplit", x_split);
2310 
2311     if (y_split > 0.0)
2312         LXW_PUSH_ATTRIBUTES_DBL("ySplit", y_split);
2313 
2314     LXW_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell);
2315 
2316     if (has_selection)
2317         LXW_PUSH_ATTRIBUTES_STR("activePane", active_pane);
2318 
2319     lxw_xml_empty_tag(self->file, "pane", &attributes);
2320 
2321     free(user_selection);
2322 
2323     LXW_FREE_ATTRIBUTES();
2324 }
2325 
2326 /*
2327  * Write the <selection> element.
2328  */
2329 STATIC void
_worksheet_write_selection(lxw_worksheet * self,lxw_selection * selection)2330 _worksheet_write_selection(lxw_worksheet *self, lxw_selection *selection)
2331 {
2332     struct xml_attribute_list attributes;
2333     struct xml_attribute *attribute;
2334 
2335     LXW_INIT_ATTRIBUTES();
2336 
2337     if (*selection->pane)
2338         LXW_PUSH_ATTRIBUTES_STR("pane", selection->pane);
2339 
2340     if (*selection->active_cell)
2341         LXW_PUSH_ATTRIBUTES_STR("activeCell", selection->active_cell);
2342 
2343     if (*selection->sqref)
2344         LXW_PUSH_ATTRIBUTES_STR("sqref", selection->sqref);
2345 
2346     lxw_xml_empty_tag(self->file, "selection", &attributes);
2347 
2348     LXW_FREE_ATTRIBUTES();
2349 }
2350 
2351 /*
2352  * Write the <selection> elements.
2353  */
2354 STATIC void
_worksheet_write_selections(lxw_worksheet * self)2355 _worksheet_write_selections(lxw_worksheet *self)
2356 {
2357     lxw_selection *selection;
2358 
2359     STAILQ_FOREACH(selection, self->selections, list_pointers) {
2360         _worksheet_write_selection(self, selection);
2361     }
2362 }
2363 
2364 /*
2365  * Write the frozen or split <pane> elements.
2366  */
2367 STATIC void
_worksheet_write_panes(lxw_worksheet * self)2368 _worksheet_write_panes(lxw_worksheet *self)
2369 {
2370     if (self->panes.type == NO_PANES)
2371         return;
2372 
2373     else if (self->panes.type == FREEZE_PANES)
2374         _worksheet_write_freeze_panes(self);
2375 
2376     else if (self->panes.type == FREEZE_SPLIT_PANES)
2377         _worksheet_write_freeze_panes(self);
2378 
2379     else if (self->panes.type == SPLIT_PANES)
2380         _worksheet_write_split_panes(self);
2381 }
2382 
2383 /*
2384  * Write the <sheetView> element.
2385  */
2386 STATIC void
_worksheet_write_sheet_view(lxw_worksheet * self)2387 _worksheet_write_sheet_view(lxw_worksheet *self)
2388 {
2389     struct xml_attribute_list attributes;
2390     struct xml_attribute *attribute;
2391 
2392     LXW_INIT_ATTRIBUTES();
2393 
2394     /* Hide screen gridlines if required */
2395     if (!self->screen_gridlines)
2396         LXW_PUSH_ATTRIBUTES_STR("showGridLines", "0");
2397 
2398     /* Hide zeroes in cells. */
2399     if (!self->show_zeros) {
2400         LXW_PUSH_ATTRIBUTES_STR("showZeros", "0");
2401     }
2402 
2403     /* Display worksheet right to left for Hebrew, Arabic and others. */
2404     if (self->right_to_left) {
2405         LXW_PUSH_ATTRIBUTES_STR("rightToLeft", "1");
2406     }
2407 
2408     /* Show that the sheet tab is selected. */
2409     if (self->selected)
2410         LXW_PUSH_ATTRIBUTES_STR("tabSelected", "1");
2411 
2412     /* Turn outlines off. Also required in the outlinePr element. */
2413     if (!self->outline_on) {
2414         LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0");
2415     }
2416 
2417     /* Set the page view/layout mode if required. */
2418     if (self->page_view)
2419         LXW_PUSH_ATTRIBUTES_STR("view", "pageLayout");
2420 
2421     /* Set the zoom level. */
2422     if (self->zoom != 100 && !self->page_view) {
2423         LXW_PUSH_ATTRIBUTES_INT("zoomScale", self->zoom);
2424 
2425         if (self->zoom_scale_normal)
2426             LXW_PUSH_ATTRIBUTES_INT("zoomScaleNormal", self->zoom);
2427     }
2428 
2429     LXW_PUSH_ATTRIBUTES_STR("workbookViewId", "0");
2430 
2431     if (self->panes.type != NO_PANES || !STAILQ_EMPTY(self->selections)) {
2432         lxw_xml_start_tag(self->file, "sheetView", &attributes);
2433         _worksheet_write_panes(self);
2434         _worksheet_write_selections(self);
2435         lxw_xml_end_tag(self->file, "sheetView");
2436     }
2437     else {
2438         lxw_xml_empty_tag(self->file, "sheetView", &attributes);
2439     }
2440 
2441     LXW_FREE_ATTRIBUTES();
2442 }
2443 
2444 /*
2445  * Write the <sheetViews> element.
2446  */
2447 STATIC void
_worksheet_write_sheet_views(lxw_worksheet * self)2448 _worksheet_write_sheet_views(lxw_worksheet *self)
2449 {
2450     lxw_xml_start_tag(self->file, "sheetViews", NULL);
2451 
2452     /* Write the sheetView element. */
2453     _worksheet_write_sheet_view(self);
2454 
2455     lxw_xml_end_tag(self->file, "sheetViews");
2456 }
2457 
2458 /*
2459  * Write the <sheetFormatPr> element.
2460  */
2461 STATIC void
_worksheet_write_sheet_format_pr(lxw_worksheet * self)2462 _worksheet_write_sheet_format_pr(lxw_worksheet *self)
2463 {
2464     struct xml_attribute_list attributes;
2465     struct xml_attribute *attribute;
2466 
2467     LXW_INIT_ATTRIBUTES();
2468     LXW_PUSH_ATTRIBUTES_DBL("defaultRowHeight", self->default_row_height);
2469 
2470     if (self->default_row_height != LXW_DEF_ROW_HEIGHT)
2471         LXW_PUSH_ATTRIBUTES_STR("customHeight", "1");
2472 
2473     if (self->default_row_zeroed)
2474         LXW_PUSH_ATTRIBUTES_STR("zeroHeight", "1");
2475 
2476     if (self->outline_row_level)
2477         LXW_PUSH_ATTRIBUTES_INT("outlineLevelRow", self->outline_row_level);
2478 
2479     if (self->outline_col_level)
2480         LXW_PUSH_ATTRIBUTES_INT("outlineLevelCol", self->outline_col_level);
2481 
2482     if (self->excel_version == 2010)
2483         LXW_PUSH_ATTRIBUTES_STR("x14ac:dyDescent", "0.25");
2484 
2485     lxw_xml_empty_tag(self->file, "sheetFormatPr", &attributes);
2486 
2487     LXW_FREE_ATTRIBUTES();
2488 }
2489 
2490 /*
2491  * Write the <sheetData> element.
2492  */
2493 STATIC void
_worksheet_write_sheet_data(lxw_worksheet * self)2494 _worksheet_write_sheet_data(lxw_worksheet *self)
2495 {
2496     if (RB_EMPTY(self->table)) {
2497         lxw_xml_empty_tag(self->file, "sheetData", NULL);
2498     }
2499     else {
2500         lxw_xml_start_tag(self->file, "sheetData", NULL);
2501         _worksheet_write_rows(self);
2502         lxw_xml_end_tag(self->file, "sheetData");
2503     }
2504 }
2505 
2506 /*
2507  * Write the <sheetData> element when the memory optimization is on. In which
2508  * case we read the data stored in the temp file and rewrite it to the XML
2509  * sheet file.
2510  */
2511 STATIC void
_worksheet_write_optimized_sheet_data(lxw_worksheet * self)2512 _worksheet_write_optimized_sheet_data(lxw_worksheet *self)
2513 {
2514     size_t read_size = 1;
2515     char buffer[LXW_BUFFER_SIZE];
2516 
2517     if (self->dim_rowmin == LXW_ROW_MAX) {
2518         /* If the dimensions aren't defined then there is no data to write. */
2519         lxw_xml_empty_tag(self->file, "sheetData", NULL);
2520     }
2521     else {
2522 
2523         lxw_xml_start_tag(self->file, "sheetData", NULL);
2524 
2525         /* Flush and rewind the temp file. */
2526         fflush(self->optimize_tmpfile);
2527         rewind(self->optimize_tmpfile);
2528 
2529         while (read_size) {
2530             read_size =
2531                 fread(buffer, 1, LXW_BUFFER_SIZE, self->optimize_tmpfile);
2532             /* Ignore return value. There is no easy way to raise error. */
2533             (void) fwrite(buffer, 1, read_size, self->file);
2534         }
2535 
2536         fclose(self->optimize_tmpfile);
2537 
2538         lxw_xml_end_tag(self->file, "sheetData");
2539     }
2540 }
2541 
2542 /*
2543  * Write the <pageMargins> element.
2544  */
2545 STATIC void
_worksheet_write_page_margins(lxw_worksheet * self)2546 _worksheet_write_page_margins(lxw_worksheet *self)
2547 {
2548     struct xml_attribute_list attributes;
2549     struct xml_attribute *attribute;
2550     double left = self->margin_left;
2551     double right = self->margin_right;
2552     double top = self->margin_top;
2553     double bottom = self->margin_bottom;
2554     double header = self->margin_header;
2555     double footer = self->margin_footer;
2556 
2557     LXW_INIT_ATTRIBUTES();
2558     LXW_PUSH_ATTRIBUTES_DBL("left", left);
2559     LXW_PUSH_ATTRIBUTES_DBL("right", right);
2560     LXW_PUSH_ATTRIBUTES_DBL("top", top);
2561     LXW_PUSH_ATTRIBUTES_DBL("bottom", bottom);
2562     LXW_PUSH_ATTRIBUTES_DBL("header", header);
2563     LXW_PUSH_ATTRIBUTES_DBL("footer", footer);
2564 
2565     lxw_xml_empty_tag(self->file, "pageMargins", &attributes);
2566 
2567     LXW_FREE_ATTRIBUTES();
2568 }
2569 
2570 /*
2571  * Write the <pageSetup> element.
2572  * The following is an example taken from Excel.
2573  * <pageSetup
2574  *     paperSize="9"
2575  *     scale="110"
2576  *     fitToWidth="2"
2577  *     fitToHeight="2"
2578  *     pageOrder="overThenDown"
2579  *     orientation="portrait"
2580  *     blackAndWhite="1"
2581  *     draft="1"
2582  *     horizontalDpi="200"
2583  *     verticalDpi="200"
2584  *     r:id="rId1"
2585  * />
2586  */
2587 STATIC void
_worksheet_write_page_setup(lxw_worksheet * self)2588 _worksheet_write_page_setup(lxw_worksheet *self)
2589 {
2590     struct xml_attribute_list attributes;
2591     struct xml_attribute *attribute;
2592 
2593     LXW_INIT_ATTRIBUTES();
2594 
2595     if (!self->page_setup_changed)
2596         return;
2597 
2598     /* Set paper size. */
2599     if (self->paper_size)
2600         LXW_PUSH_ATTRIBUTES_INT("paperSize", self->paper_size);
2601 
2602     /* Set the print_scale. */
2603     if (self->print_scale != 100)
2604         LXW_PUSH_ATTRIBUTES_INT("scale", self->print_scale);
2605 
2606     /* Set the "Fit to page" properties. */
2607     if (self->fit_page && self->fit_width != 1)
2608         LXW_PUSH_ATTRIBUTES_INT("fitToWidth", self->fit_width);
2609 
2610     if (self->fit_page && self->fit_height != 1)
2611         LXW_PUSH_ATTRIBUTES_INT("fitToHeight", self->fit_height);
2612 
2613     /* Set the page print direction. */
2614     if (self->page_order)
2615         LXW_PUSH_ATTRIBUTES_STR("pageOrder", "overThenDown");
2616 
2617     /* Set start page. */
2618     if (self->page_start > 1)
2619         LXW_PUSH_ATTRIBUTES_INT("firstPageNumber", self->page_start);
2620 
2621     /* Set page orientation. */
2622     if (self->orientation)
2623         LXW_PUSH_ATTRIBUTES_STR("orientation", "portrait");
2624     else
2625         LXW_PUSH_ATTRIBUTES_STR("orientation", "landscape");
2626 
2627     /* Set start page active flag. */
2628     if (self->page_start)
2629         LXW_PUSH_ATTRIBUTES_INT("useFirstPageNumber", 1);
2630 
2631     /* Set the DPI. Mainly only for testing. */
2632     if (self->horizontal_dpi)
2633         LXW_PUSH_ATTRIBUTES_INT("horizontalDpi", self->horizontal_dpi);
2634 
2635     if (self->vertical_dpi)
2636         LXW_PUSH_ATTRIBUTES_INT("verticalDpi", self->vertical_dpi);
2637 
2638     lxw_xml_empty_tag(self->file, "pageSetup", &attributes);
2639 
2640     LXW_FREE_ATTRIBUTES();
2641 }
2642 
2643 /*
2644  * Write the <printOptions> element.
2645  */
2646 STATIC void
_worksheet_write_print_options(lxw_worksheet * self)2647 _worksheet_write_print_options(lxw_worksheet *self)
2648 {
2649     struct xml_attribute_list attributes;
2650     struct xml_attribute *attribute;
2651     if (!self->print_options_changed)
2652         return;
2653 
2654     LXW_INIT_ATTRIBUTES();
2655 
2656     /* Set horizontal centering. */
2657     if (self->hcenter) {
2658         LXW_PUSH_ATTRIBUTES_STR("horizontalCentered", "1");
2659     }
2660 
2661     /* Set vertical centering. */
2662     if (self->vcenter) {
2663         LXW_PUSH_ATTRIBUTES_STR("verticalCentered", "1");
2664     }
2665 
2666     /* Enable row and column headers. */
2667     if (self->print_headers) {
2668         LXW_PUSH_ATTRIBUTES_STR("headings", "1");
2669     }
2670 
2671     /* Set printed gridlines. */
2672     if (self->print_gridlines) {
2673         LXW_PUSH_ATTRIBUTES_STR("gridLines", "1");
2674     }
2675 
2676     lxw_xml_empty_tag(self->file, "printOptions", &attributes);
2677 
2678     LXW_FREE_ATTRIBUTES();
2679 }
2680 
2681 /*
2682  * Write the <row> element.
2683  */
2684 STATIC void
_write_row(lxw_worksheet * self,lxw_row * row,char * spans)2685 _write_row(lxw_worksheet *self, lxw_row *row, char *spans)
2686 {
2687     struct xml_attribute_list attributes;
2688     struct xml_attribute *attribute;
2689     int32_t xf_index = 0;
2690     double height;
2691 
2692     if (row->format) {
2693         xf_index = lxw_format_get_xf_index(row->format);
2694     }
2695 
2696     if (row->height_changed)
2697         height = row->height;
2698     else
2699         height = self->default_row_height;
2700 
2701     LXW_INIT_ATTRIBUTES();
2702     LXW_PUSH_ATTRIBUTES_INT("r", row->row_num + 1);
2703 
2704     if (spans)
2705         LXW_PUSH_ATTRIBUTES_STR("spans", spans);
2706 
2707     if (xf_index)
2708         LXW_PUSH_ATTRIBUTES_INT("s", xf_index);
2709 
2710     if (row->format)
2711         LXW_PUSH_ATTRIBUTES_STR("customFormat", "1");
2712 
2713     if (height != LXW_DEF_ROW_HEIGHT)
2714         LXW_PUSH_ATTRIBUTES_DBL("ht", height);
2715 
2716     if (row->hidden)
2717         LXW_PUSH_ATTRIBUTES_STR("hidden", "1");
2718 
2719     if (height != LXW_DEF_ROW_HEIGHT)
2720         LXW_PUSH_ATTRIBUTES_STR("customHeight", "1");
2721 
2722     if (row->level)
2723         LXW_PUSH_ATTRIBUTES_INT("outlineLevel", row->level);
2724 
2725     if (row->collapsed)
2726         LXW_PUSH_ATTRIBUTES_STR("collapsed", "1");
2727 
2728     if (self->excel_version == 2010)
2729         LXW_PUSH_ATTRIBUTES_STR("x14ac:dyDescent", "0.25");
2730 
2731     if (!row->data_changed)
2732         lxw_xml_empty_tag(self->file, "row", &attributes);
2733     else
2734         lxw_xml_start_tag(self->file, "row", &attributes);
2735 
2736     LXW_FREE_ATTRIBUTES();
2737 }
2738 
2739 /*
2740  * Convert the width of a cell from user's units to pixels. Excel rounds the
2741  * column width to the nearest pixel. If the width hasn't been set by the user
2742  * we use the default value. If the column is hidden it has a value of zero.
2743  */
2744 STATIC int32_t
_worksheet_size_col(lxw_worksheet * self,lxw_col_t col_num,uint8_t anchor)2745 _worksheet_size_col(lxw_worksheet *self, lxw_col_t col_num, uint8_t anchor)
2746 {
2747     lxw_col_options *col_opt = NULL;
2748     uint32_t pixels;
2749     double width;
2750     double max_digit_width = 7.0;       /* For Calabri 11. */
2751     double padding = 5.0;
2752     lxw_col_t col_index;
2753 
2754     /* Search for the col number in the array of col_options. Each col_option
2755      * entry contains the start and end column for a range.
2756      */
2757     for (col_index = 0; col_index < self->col_options_max; col_index++) {
2758         col_opt = self->col_options[col_index];
2759 
2760         if (col_opt) {
2761             if (col_num >= col_opt->firstcol && col_num <= col_opt->lastcol)
2762                 break;
2763             else
2764                 col_opt = NULL;
2765         }
2766     }
2767 
2768     if (col_opt) {
2769         width = col_opt->width;
2770 
2771         /* Convert to pixels. */
2772         if (col_opt->hidden && anchor != LXW_OBJECT_MOVE_AND_SIZE_AFTER) {
2773             pixels = 0;
2774         }
2775         else if (width < 1.0) {
2776             pixels = (uint32_t) (width * (max_digit_width + padding) + 0.5);
2777         }
2778         else {
2779             pixels = (uint32_t) (width * max_digit_width + 0.5) + 5;
2780         }
2781     }
2782     else {
2783         pixels = self->default_col_pixels;
2784     }
2785 
2786     return pixels;
2787 }
2788 
2789 /*
2790  * Convert the height of a cell from user's units to pixels. If the height
2791  * hasn't been set by the user we use the default value. If the row is hidden
2792  * it has a value of zero.
2793  */
2794 STATIC int32_t
_worksheet_size_row(lxw_worksheet * self,lxw_row_t row_num,uint8_t anchor)2795 _worksheet_size_row(lxw_worksheet *self, lxw_row_t row_num, uint8_t anchor)
2796 {
2797     lxw_row *row;
2798     uint32_t pixels;
2799 
2800     row = lxw_worksheet_find_row(self, row_num);
2801 
2802     /* Note, the 0.75 below is due to the difference between 72/96 DPI. */
2803     if (row) {
2804         if (row->hidden && anchor != LXW_OBJECT_MOVE_AND_SIZE_AFTER)
2805             pixels = 0;
2806         else
2807             pixels = (uint32_t) (row->height / 0.75);
2808     }
2809     else {
2810         pixels = (uint32_t) (self->default_row_height / 0.75);
2811     }
2812 
2813     return pixels;
2814 }
2815 
2816 /*
2817  * Calculate the vertices that define the position of a graphical object
2818  * within the worksheet in pixels.
2819  *         +------------+------------+
2820  *         |     A      |      B     |
2821  *   +-----+------------+------------+
2822  *   |     |(x1,y1)     |            |
2823  *   |  1  |(A1)._______|______      |
2824  *   |     |    |              |     |
2825  *   |     |    |              |     |
2826  *   +-----+----|    BITMAP    |-----+
2827  *   |     |    |              |     |
2828  *   |  2  |    |______________.     |
2829  *   |     |            |        (B2)|
2830  *   |     |            |     (x2,y2)|
2831  *   +---- +------------+------------+
2832  *
2833  * Example of an object that covers some of the area from cell A1 to cell B2.
2834  * Based on the width and height of the object we need to calculate 8 vars:
2835  *
2836  *     col_start, row_start, col_end, row_end, x1, y1, x2, y2.
2837  *
2838  * We also calculate the absolute x and y position of the top left vertex of
2839  * the object. This is required for images:
2840  *
2841  *    x_abs, y_abs
2842  *
2843  * The width and height of the cells that the object occupies can be variable
2844  * and have to be taken into account.
2845  *
2846  * The values of col_start and row_start are passed in from the calling
2847  * function. The values of col_end and row_end are calculated by subtracting
2848  * the width and height of the object from the width and height of the
2849  * underlying cells.
2850  */
2851 STATIC void
_worksheet_position_object_pixels(lxw_worksheet * self,lxw_object_properties * object_props,lxw_drawing_object * drawing_object)2852 _worksheet_position_object_pixels(lxw_worksheet *self,
2853                                   lxw_object_properties *object_props,
2854                                   lxw_drawing_object *drawing_object)
2855 {
2856     lxw_col_t col_start;        /* Column containing upper left corner.  */
2857     int32_t x1;                 /* Distance to left side of object.      */
2858 
2859     lxw_row_t row_start;        /* Row containing top left corner.       */
2860     int32_t y1;                 /* Distance to top of object.            */
2861 
2862     lxw_col_t col_end;          /* Column containing lower right corner. */
2863     double x2;                  /* Distance to right side of object.     */
2864 
2865     lxw_row_t row_end;          /* Row containing bottom right corner.   */
2866     double y2;                  /* Distance to bottom of object.         */
2867 
2868     double width;               /* Width of object frame.                */
2869     double height;              /* Height of object frame.               */
2870 
2871     uint32_t x_abs = 0;         /* Abs. distance to left side of object. */
2872     uint32_t y_abs = 0;         /* Abs. distance to top  side of object. */
2873 
2874     uint32_t i;
2875     uint8_t anchor = drawing_object->anchor;
2876     uint8_t ignore_anchor = LXW_OBJECT_POSITION_DEFAULT;
2877 
2878     col_start = object_props->col;
2879     row_start = object_props->row;
2880     x1 = object_props->x_offset;
2881     y1 = object_props->y_offset;
2882     width = object_props->width;
2883     height = object_props->height;
2884 
2885     /* Adjust start column for negative offsets. */
2886     while (x1 < 0 && col_start > 0) {
2887         x1 += _worksheet_size_col(self, col_start - 1, ignore_anchor);
2888         col_start--;
2889     }
2890 
2891     /* Adjust start row for negative offsets. */
2892     while (y1 < 0 && row_start > 0) {
2893         y1 += _worksheet_size_row(self, row_start - 1, ignore_anchor);
2894         row_start--;
2895     }
2896 
2897     /* Ensure that the image isn't shifted off the page at top left. */
2898     if (x1 < 0)
2899         x1 = 0;
2900 
2901     if (y1 < 0)
2902         y1 = 0;
2903 
2904     /* Calculate the absolute x offset of the top-left vertex. */
2905     if (self->col_size_changed) {
2906         for (i = 0; i < col_start; i++)
2907             x_abs += _worksheet_size_col(self, i, ignore_anchor);
2908     }
2909     else {
2910         /* Optimization for when the column widths haven't changed. */
2911         x_abs += self->default_col_pixels * col_start;
2912     }
2913 
2914     x_abs += x1;
2915 
2916     /* Calculate the absolute y offset of the top-left vertex. */
2917     /* Store the column change to allow optimizations. */
2918     if (self->row_size_changed) {
2919         for (i = 0; i < row_start; i++)
2920             y_abs += _worksheet_size_row(self, i, ignore_anchor);
2921     }
2922     else {
2923         /* Optimization for when the row heights haven"t changed. */
2924         y_abs += self->default_row_pixels * row_start;
2925     }
2926 
2927     y_abs += y1;
2928 
2929     /* Adjust start col for offsets that are greater than the col width. */
2930     while (x1 >= _worksheet_size_col(self, col_start, anchor)) {
2931         x1 -= _worksheet_size_col(self, col_start, ignore_anchor);
2932         col_start++;
2933     }
2934 
2935     /* Adjust start row for offsets that are greater than the row height. */
2936     while (y1 >= _worksheet_size_row(self, row_start, anchor)) {
2937         y1 -= _worksheet_size_row(self, row_start, ignore_anchor);
2938         row_start++;
2939     }
2940 
2941     /* Initialize end cell to the same as the start cell. */
2942     col_end = col_start;
2943     row_end = row_start;
2944 
2945     /* Only offset the image in the cell if the row/col is hidden. */
2946     if (_worksheet_size_col(self, col_start, anchor) > 0)
2947         width = width + x1;
2948     if (_worksheet_size_row(self, row_start, anchor) > 0)
2949         height = height + y1;
2950 
2951     /* Subtract the underlying cell widths to find the end cell. */
2952     while (width >= _worksheet_size_col(self, col_end, anchor)) {
2953         width -= _worksheet_size_col(self, col_end, anchor);
2954         col_end++;
2955     }
2956 
2957     /* Subtract the underlying cell heights to find the end cell. */
2958     while (height >= _worksheet_size_row(self, row_end, anchor)) {
2959         height -= _worksheet_size_row(self, row_end, anchor);
2960         row_end++;
2961     }
2962 
2963     /* The end vertices are whatever is left from the width and height. */
2964     x2 = width;
2965     y2 = height;
2966 
2967     /* Add the dimensions to the drawing object. */
2968     drawing_object->from.col = col_start;
2969     drawing_object->from.row = row_start;
2970     drawing_object->from.col_offset = x1;
2971     drawing_object->from.row_offset = y1;
2972     drawing_object->to.col = col_end;
2973     drawing_object->to.row = row_end;
2974     drawing_object->to.col_offset = x2;
2975     drawing_object->to.row_offset = y2;
2976     drawing_object->col_absolute = x_abs;
2977     drawing_object->row_absolute = y_abs;
2978 
2979 }
2980 
2981 /*
2982  * Calculate the vertices that define the position of a graphical object
2983  * within the worksheet in EMUs. The vertices are expressed as English
2984  * Metric Units (EMUs). There are 12,700 EMUs per point.
2985  * Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel.
2986  */
2987 STATIC void
_worksheet_position_object_emus(lxw_worksheet * self,lxw_object_properties * image,lxw_drawing_object * drawing_object)2988 _worksheet_position_object_emus(lxw_worksheet *self,
2989                                 lxw_object_properties *image,
2990                                 lxw_drawing_object *drawing_object)
2991 {
2992 
2993     _worksheet_position_object_pixels(self, image, drawing_object);
2994 
2995     /* Convert the pixel values to EMUs. See above. */
2996     drawing_object->from.col_offset *= 9525;
2997     drawing_object->from.row_offset *= 9525;
2998     drawing_object->to.col_offset *= 9525;
2999     drawing_object->to.row_offset *= 9525;
3000     drawing_object->to.col_offset += 0.5;
3001     drawing_object->to.row_offset += 0.5;
3002     drawing_object->col_absolute *= 9525;
3003     drawing_object->row_absolute *= 9525;
3004 }
3005 
3006 /*
3007  * This function handles the additional optional parameters to
3008  * worksheet_write_comment_opt() as well as calculating the comment object
3009  * position and vertices.
3010  */
3011 void
_get_comment_params(lxw_vml_obj * comment,lxw_comment_options * options)3012 _get_comment_params(lxw_vml_obj *comment, lxw_comment_options *options)
3013 {
3014 
3015     lxw_row_t start_row;
3016     lxw_col_t start_col;
3017     int32_t x_offset;
3018     int32_t y_offset;
3019     uint32_t height = 74;
3020     uint32_t width = 128;
3021     double x_scale = 1.0;
3022     double y_scale = 1.0;
3023     lxw_row_t row = comment->row;
3024     lxw_col_t col = comment->col;;
3025 
3026     /* Set the default start cell and offsets for the comment. These are
3027      * generally fixed in relation to the parent cell. However there are some
3028      * edge cases for cells at the, well yes, edges. */
3029     if (row == 0)
3030         y_offset = 2;
3031     else if (row == LXW_ROW_MAX - 3)
3032         y_offset = 16;
3033     else if (row == LXW_ROW_MAX - 2)
3034         y_offset = 16;
3035     else if (row == LXW_ROW_MAX - 1)
3036         y_offset = 14;
3037     else
3038         y_offset = 10;
3039 
3040     if (col == LXW_COL_MAX - 3)
3041         x_offset = 49;
3042     else if (col == LXW_COL_MAX - 2)
3043         x_offset = 49;
3044     else if (col == LXW_COL_MAX - 1)
3045         x_offset = 49;
3046     else
3047         x_offset = 15;
3048 
3049     if (row == 0)
3050         start_row = 0;
3051     else if (row == LXW_ROW_MAX - 3)
3052         start_row = LXW_ROW_MAX - 7;
3053     else if (row == LXW_ROW_MAX - 2)
3054         start_row = LXW_ROW_MAX - 6;
3055     else if (row == LXW_ROW_MAX - 1)
3056         start_row = LXW_ROW_MAX - 5;
3057     else
3058         start_row = row - 1;
3059 
3060     if (col == LXW_COL_MAX - 3)
3061         start_col = LXW_COL_MAX - 6;
3062     else if (col == LXW_COL_MAX - 2)
3063         start_col = LXW_COL_MAX - 5;
3064     else if (col == LXW_COL_MAX - 1)
3065         start_col = LXW_COL_MAX - 4;
3066     else
3067         start_col = col + 1;
3068 
3069     /* Set the default font properties. */
3070     comment->font_size = 8;
3071     comment->font_family = 2;
3072 
3073     /* Set any user defined options. */
3074     if (options) {
3075 
3076         if (options->width > 0.0)
3077             width = options->width;
3078 
3079         if (options->height > 0.0)
3080             height = options->height;
3081 
3082         if (options->x_scale > 0.0)
3083             x_scale = options->x_scale;
3084 
3085         if (options->y_scale > 0.0)
3086             y_scale = options->y_scale;
3087 
3088         if (options->x_offset != 0)
3089             x_offset = options->x_offset;
3090 
3091         if (options->y_offset != 0)
3092             y_offset = options->y_offset;
3093 
3094         if (options->start_row > 0 || options->start_col > 0) {
3095             start_row = options->start_row;
3096             start_col = options->start_col;
3097         }
3098 
3099         if (options->font_size > 0.0)
3100             comment->font_size = options->font_size;
3101 
3102         if (options->font_family > 0)
3103             comment->font_family = options->font_family;
3104 
3105         comment->visible = options->visible;
3106         comment->color = options->color;
3107         comment->author = lxw_strdup(options->author);
3108         comment->font_name = lxw_strdup(options->font_name);
3109     }
3110 
3111     /* Scale the width/height to the default/user scale and round to the
3112      * nearest pixel. */
3113     width = (uint32_t) (0.5 + x_scale * width);
3114     height = (uint32_t) (0.5 + y_scale * height);
3115 
3116     comment->width = width;
3117     comment->height = height;
3118     comment->start_col = start_col;
3119     comment->start_row = start_row;
3120     comment->x_offset = x_offset;
3121     comment->y_offset = y_offset;
3122 }
3123 
3124 /*
3125  * This function handles the additional optional parameters to
3126  * worksheet_insert_button() as well as calculating the button object
3127  * position and vertices.
3128  */
3129 lxw_error
_get_button_params(lxw_vml_obj * button,uint16_t button_number,lxw_button_options * options)3130 _get_button_params(lxw_vml_obj *button, uint16_t button_number,
3131                    lxw_button_options *options)
3132 {
3133 
3134     int32_t x_offset = 0;
3135     int32_t y_offset = 0;
3136     uint32_t height = LXW_DEF_ROW_HEIGHT_PIXELS;
3137     uint32_t width = LXW_DEF_COL_WIDTH_PIXELS;
3138     double x_scale = 1.0;
3139     double y_scale = 1.0;
3140     lxw_row_t row = button->row;
3141     lxw_col_t col = button->col;
3142     char buffer[LXW_ATTR_32];
3143     uint8_t has_caption = LXW_FALSE;
3144     uint8_t has_macro = LXW_FALSE;
3145     size_t len;
3146 
3147     /* Set any user defined options. */
3148     if (options) {
3149 
3150         if (options->width > 0.0)
3151             width = options->width;
3152 
3153         if (options->height > 0.0)
3154             height = options->height;
3155 
3156         if (options->x_scale > 0.0)
3157             x_scale = options->x_scale;
3158 
3159         if (options->y_scale > 0.0)
3160             y_scale = options->y_scale;
3161 
3162         if (options->x_offset != 0)
3163             x_offset = options->x_offset;
3164 
3165         if (options->y_offset != 0)
3166             y_offset = options->y_offset;
3167 
3168         if (options->caption) {
3169             button->name = lxw_strdup(options->caption);
3170             RETURN_ON_MEM_ERROR(button->name, LXW_ERROR_MEMORY_MALLOC_FAILED);
3171             has_caption = LXW_TRUE;
3172         }
3173 
3174         if (options->macro) {
3175             len = sizeof("[0]!") + strlen(options->macro);
3176             button->macro = calloc(1, len);
3177             RETURN_ON_MEM_ERROR(button->macro,
3178                                 LXW_ERROR_MEMORY_MALLOC_FAILED);
3179 
3180             if (button->macro)
3181                 lxw_snprintf(button->macro, len, "[0]!%s", options->macro);
3182 
3183             has_macro = LXW_TRUE;
3184         }
3185 
3186         if (options->description) {
3187             button->text = lxw_strdup(options->description);
3188             RETURN_ON_MEM_ERROR(button->text, LXW_ERROR_MEMORY_MALLOC_FAILED);
3189         }
3190     }
3191 
3192     if (!has_caption) {
3193         lxw_snprintf(buffer, LXW_ATTR_32, "Button %d", button_number);
3194         button->name = lxw_strdup(buffer);
3195         RETURN_ON_MEM_ERROR(button->name, LXW_ERROR_MEMORY_MALLOC_FAILED);
3196     }
3197 
3198     if (!has_macro) {
3199         lxw_snprintf(buffer, LXW_ATTR_32, "[0]!Button%d_Click",
3200                      button_number);
3201         button->macro = lxw_strdup(buffer);
3202         RETURN_ON_MEM_ERROR(button->macro, LXW_ERROR_MEMORY_MALLOC_FAILED);
3203     }
3204 
3205     /* Scale the width/height to the default/user scale and round to the
3206      * nearest pixel. */
3207     width = (uint32_t) (0.5 + x_scale * width);
3208     height = (uint32_t) (0.5 + y_scale * height);
3209 
3210     button->width = width;
3211     button->height = height;
3212     button->start_col = col;
3213     button->start_row = row;
3214     button->x_offset = x_offset;
3215     button->y_offset = y_offset;
3216 
3217     return LXW_NO_ERROR;
3218 }
3219 
3220 /*
3221  * Calculate the vml_obj object position and vertices.
3222  */
3223 void
_worksheet_position_vml_object(lxw_worksheet * self,lxw_vml_obj * vml_obj)3224 _worksheet_position_vml_object(lxw_worksheet *self, lxw_vml_obj *vml_obj)
3225 {
3226     lxw_object_properties object_props;
3227     lxw_drawing_object drawing_object;
3228 
3229     object_props.col = vml_obj->start_col;
3230     object_props.row = vml_obj->start_row;
3231     object_props.x_offset = vml_obj->x_offset;
3232     object_props.y_offset = vml_obj->y_offset;
3233     object_props.width = vml_obj->width;
3234     object_props.height = vml_obj->height;
3235 
3236     drawing_object.anchor = LXW_OBJECT_DONT_MOVE_DONT_SIZE;
3237 
3238     _worksheet_position_object_pixels(self, &object_props, &drawing_object);
3239 
3240     vml_obj->from.col = drawing_object.from.col;
3241     vml_obj->from.row = drawing_object.from.row;
3242     vml_obj->from.col_offset = drawing_object.from.col_offset;
3243     vml_obj->from.row_offset = drawing_object.from.row_offset;
3244     vml_obj->to.col = drawing_object.to.col;
3245     vml_obj->to.row = drawing_object.to.row;
3246     vml_obj->to.col_offset = drawing_object.to.col_offset;
3247     vml_obj->to.row_offset = drawing_object.to.row_offset;
3248     vml_obj->col_absolute = drawing_object.col_absolute;
3249     vml_obj->row_absolute = drawing_object.row_absolute;
3250 }
3251 
3252 /*
3253  * Set up image/drawings.
3254  */
3255 void
lxw_worksheet_prepare_image(lxw_worksheet * self,uint32_t image_ref_id,uint32_t drawing_id,lxw_object_properties * object_props)3256 lxw_worksheet_prepare_image(lxw_worksheet *self,
3257                             uint32_t image_ref_id, uint32_t drawing_id,
3258                             lxw_object_properties *object_props)
3259 {
3260     lxw_drawing_object *drawing_object;
3261     lxw_rel_tuple *relationship;
3262     double width;
3263     double height;
3264     char *url;
3265     char *found_string;
3266     size_t i;
3267     char filename[LXW_FILENAME_LENGTH];
3268     enum cell_types link_type = HYPERLINK_URL;
3269 
3270     if (!self->drawing) {
3271         self->drawing = lxw_drawing_new();
3272         self->drawing->embedded = LXW_TRUE;
3273         RETURN_VOID_ON_MEM_ERROR(self->drawing);
3274 
3275         relationship = calloc(1, sizeof(lxw_rel_tuple));
3276         GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3277 
3278         relationship->type = lxw_strdup("/drawing");
3279         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3280 
3281         lxw_snprintf(filename, LXW_FILENAME_LENGTH,
3282                      "../drawings/drawing%d.xml", drawing_id);
3283 
3284         relationship->target = lxw_strdup(filename);
3285         GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3286 
3287         STAILQ_INSERT_TAIL(self->external_drawing_links, relationship,
3288                            list_pointers);
3289     }
3290 
3291     drawing_object = calloc(1, sizeof(lxw_drawing_object));
3292     RETURN_VOID_ON_MEM_ERROR(drawing_object);
3293 
3294     drawing_object->anchor = LXW_OBJECT_MOVE_DONT_SIZE;
3295     if (object_props->object_position)
3296         drawing_object->anchor = object_props->object_position;
3297 
3298     drawing_object->type = LXW_DRAWING_IMAGE;
3299     drawing_object->description = lxw_strdup(object_props->description);
3300     drawing_object->tip = lxw_strdup(object_props->tip);
3301     drawing_object->rel_index = 0;
3302     drawing_object->url_rel_index = 0;
3303     drawing_object->decorative = object_props->decorative;
3304 
3305     /* Scale to user scale. */
3306     width = object_props->width * object_props->x_scale;
3307     height = object_props->height * object_props->y_scale;
3308 
3309     /* Scale by non 96dpi resolutions. */
3310     width *= 96.0 / object_props->x_dpi;
3311     height *= 96.0 / object_props->y_dpi;
3312 
3313     object_props->width = width;
3314     object_props->height = height;
3315 
3316     _worksheet_position_object_emus(self, object_props, drawing_object);
3317 
3318     /* Convert from pixels to emus. */
3319     drawing_object->width = (uint32_t) (0.5 + width * 9525);
3320     drawing_object->height = (uint32_t) (0.5 + height * 9525);
3321 
3322     lxw_add_drawing_object(self->drawing, drawing_object);
3323 
3324     if (object_props->url) {
3325         url = object_props->url;
3326 
3327         relationship = calloc(1, sizeof(lxw_rel_tuple));
3328         GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3329 
3330         relationship->type = lxw_strdup("/hyperlink");
3331         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3332 
3333         /* Check the link type. Default to external hyperlinks. */
3334         if (strstr(url, "internal:"))
3335             link_type = HYPERLINK_INTERNAL;
3336         else if (strstr(url, "external:"))
3337             link_type = HYPERLINK_EXTERNAL;
3338         else
3339             link_type = HYPERLINK_URL;
3340 
3341         /* Set the relationship object for each type of link. */
3342         if (link_type == HYPERLINK_INTERNAL) {
3343             relationship->target_mode = NULL;
3344             relationship->target = lxw_strdup(url + sizeof("internal") - 1);
3345             GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3346 
3347             /* We need to prefix the internal link/range with #. */
3348             relationship->target[0] = '#';
3349         }
3350         else if (link_type == HYPERLINK_EXTERNAL) {
3351             relationship->target_mode = lxw_strdup("External");
3352             GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error);
3353 
3354             /* Look for Windows style "C:/" link or Windows share "\\" link. */
3355             found_string = strchr(url + sizeof("external:") - 1, ':');
3356             if (!found_string)
3357                 found_string = strstr(url, "\\\\");
3358 
3359             if (found_string) {
3360                 /* Copy the url with some space at the start to overwrite
3361                  * "external:" with "file:///". */
3362                 relationship->target = lxw_escape_url_characters(url + 1,
3363                                                                  LXW_TRUE);
3364                 GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3365 
3366                 /* Add the file:/// URI to the url if absolute path. */
3367                 memcpy(relationship->target, "file:///",
3368                        sizeof("file:///") - 1);
3369             }
3370             else {
3371                 /* Copy the relative url without "external:". */
3372                 relationship->target =
3373                     lxw_escape_url_characters(url + sizeof("external:") - 1,
3374                                               LXW_TRUE);
3375                 GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3376 
3377                 /* Switch backslash to forward slash. */
3378                 for (i = 0; i <= strlen(relationship->target); i++)
3379                     if (relationship->target[i] == '\\')
3380                         relationship->target[i] = '/';
3381             }
3382 
3383         }
3384         else {
3385             relationship->target_mode = lxw_strdup("External");
3386             GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error);
3387 
3388             relationship->target =
3389                 lxw_escape_url_characters(object_props->url, LXW_FALSE);
3390             GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3391         }
3392 
3393         /* Check if URL exceeds Excel's length limit. */
3394         if (lxw_utf8_strlen(url) > self->max_url_length) {
3395             LXW_WARN_FORMAT2("worksheet_insert_image()/_opt(): URL exceeds "
3396                              "Excel's allowable length of %d characters: %s",
3397                              self->max_url_length, url);
3398             goto mem_error;
3399         }
3400 
3401         if (!_find_drawing_rel_index(self, url)) {
3402             STAILQ_INSERT_TAIL(self->drawing_links, relationship,
3403                                list_pointers);
3404         }
3405         else {
3406             free(relationship->type);
3407             free(relationship->target);
3408             free(relationship->target_mode);
3409             free(relationship);
3410         }
3411 
3412         drawing_object->url_rel_index = _get_drawing_rel_index(self, url);
3413 
3414     }
3415 
3416     if (!_find_drawing_rel_index(self, object_props->md5)) {
3417         relationship = calloc(1, sizeof(lxw_rel_tuple));
3418         GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3419 
3420         relationship->type = lxw_strdup("/image");
3421         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3422 
3423         lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id,
3424                      object_props->extension);
3425 
3426         relationship->target = lxw_strdup(filename);
3427         GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3428 
3429         STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers);
3430     }
3431 
3432     drawing_object->rel_index =
3433         _get_drawing_rel_index(self, object_props->md5);
3434 
3435     return;
3436 
3437 mem_error:
3438     if (relationship) {
3439         free(relationship->type);
3440         free(relationship->target);
3441         free(relationship->target_mode);
3442         free(relationship);
3443     }
3444 }
3445 
3446 /*
3447  * Set up image/drawings for header/footer images.
3448  */
3449 void
lxw_worksheet_prepare_header_image(lxw_worksheet * self,uint32_t image_ref_id,lxw_object_properties * object_props)3450 lxw_worksheet_prepare_header_image(lxw_worksheet *self,
3451                                    uint32_t image_ref_id,
3452                                    lxw_object_properties *object_props)
3453 {
3454     lxw_rel_tuple *relationship = NULL;
3455     char filename[LXW_FILENAME_LENGTH];
3456     lxw_vml_obj *header_image_vml;
3457     char *extension;
3458 
3459     STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers);
3460 
3461     if (!_find_vml_drawing_rel_index(self, object_props->md5)) {
3462         relationship = calloc(1, sizeof(lxw_rel_tuple));
3463         RETURN_VOID_ON_MEM_ERROR(relationship);
3464 
3465         relationship->type = lxw_strdup("/image");
3466         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3467 
3468         lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id,
3469                      object_props->extension);
3470 
3471         relationship->target = lxw_strdup(filename);
3472         GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3473 
3474         STAILQ_INSERT_TAIL(self->vml_drawing_links, relationship,
3475                            list_pointers);
3476     }
3477 
3478     header_image_vml = calloc(1, sizeof(lxw_vml_obj));
3479     GOTO_LABEL_ON_MEM_ERROR(header_image_vml, mem_error);
3480 
3481     header_image_vml->width = object_props->width;
3482     header_image_vml->height = object_props->height;
3483     header_image_vml->x_dpi = object_props->x_dpi;
3484     header_image_vml->y_dpi = object_props->y_dpi;
3485     header_image_vml->rel_index = 1;
3486 
3487     header_image_vml->image_position =
3488         lxw_strdup(object_props->image_position);
3489     header_image_vml->name = lxw_strdup(object_props->description);
3490 
3491     /* Strip the extension from the filename. */
3492     extension = strchr(header_image_vml->name, '.');
3493     if (extension)
3494         *extension = '\0';
3495 
3496     header_image_vml->rel_index =
3497         _get_vml_drawing_rel_index(self, object_props->md5);
3498 
3499     STAILQ_INSERT_TAIL(self->header_image_objs, header_image_vml,
3500                        list_pointers);
3501 
3502     return;
3503 
3504 mem_error:
3505     if (relationship) {
3506         free(relationship->type);
3507         free(relationship->target);
3508         free(relationship->target_mode);
3509         free(relationship);
3510     }
3511 }
3512 
3513 /*
3514  * Set up background image.
3515  */
3516 void
lxw_worksheet_prepare_background(lxw_worksheet * self,uint32_t image_ref_id,lxw_object_properties * object_props)3517 lxw_worksheet_prepare_background(lxw_worksheet *self,
3518                                  uint32_t image_ref_id,
3519                                  lxw_object_properties *object_props)
3520 {
3521     lxw_rel_tuple *relationship = NULL;
3522     char filename[LXW_FILENAME_LENGTH];
3523 
3524     STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers);
3525 
3526     relationship = calloc(1, sizeof(lxw_rel_tuple));
3527     RETURN_VOID_ON_MEM_ERROR(relationship);
3528 
3529     relationship->type = lxw_strdup("/image");
3530     GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3531 
3532     lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id,
3533                  object_props->extension);
3534 
3535     relationship->target = lxw_strdup(filename);
3536     GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3537 
3538     self->external_background_link = relationship;
3539 
3540     return;
3541 
3542 mem_error:
3543     if (relationship) {
3544         free(relationship->type);
3545         free(relationship->target);
3546         free(relationship->target_mode);
3547         free(relationship);
3548     }
3549 }
3550 
3551 /*
3552  * Set up chart/drawings.
3553  */
3554 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)3555 lxw_worksheet_prepare_chart(lxw_worksheet *self,
3556                             uint32_t chart_ref_id,
3557                             uint32_t drawing_id,
3558                             lxw_object_properties *object_props,
3559                             uint8_t is_chartsheet)
3560 {
3561     lxw_drawing_object *drawing_object;
3562     lxw_rel_tuple *relationship;
3563     double width;
3564     double height;
3565     char filename[LXW_FILENAME_LENGTH];
3566 
3567     if (!self->drawing) {
3568         self->drawing = lxw_drawing_new();
3569         RETURN_VOID_ON_MEM_ERROR(self->drawing);
3570 
3571         if (is_chartsheet) {
3572             self->drawing->embedded = LXW_FALSE;
3573             self->drawing->orientation = self->orientation;
3574         }
3575         else {
3576             self->drawing->embedded = LXW_TRUE;
3577         }
3578 
3579         relationship = calloc(1, sizeof(lxw_rel_tuple));
3580         GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3581 
3582         relationship->type = lxw_strdup("/drawing");
3583         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3584 
3585         lxw_snprintf(filename, LXW_FILENAME_LENGTH,
3586                      "../drawings/drawing%d.xml", drawing_id);
3587 
3588         relationship->target = lxw_strdup(filename);
3589         GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3590 
3591         STAILQ_INSERT_TAIL(self->external_drawing_links, relationship,
3592                            list_pointers);
3593     }
3594 
3595     drawing_object = calloc(1, sizeof(lxw_drawing_object));
3596     RETURN_VOID_ON_MEM_ERROR(drawing_object);
3597 
3598     drawing_object->anchor = LXW_OBJECT_MOVE_AND_SIZE;
3599     if (object_props->object_position)
3600         drawing_object->anchor = object_props->object_position;
3601 
3602     drawing_object->type = LXW_DRAWING_CHART;
3603     drawing_object->description = lxw_strdup(object_props->description);
3604     drawing_object->tip = NULL;
3605     drawing_object->rel_index = _get_drawing_rel_index(self, NULL);
3606     drawing_object->url_rel_index = 0;
3607     drawing_object->decorative = object_props->decorative;
3608 
3609     /* Scale to user scale. */
3610     width = object_props->width * object_props->x_scale;
3611     height = object_props->height * object_props->y_scale;
3612 
3613     /* Convert to the nearest pixel. */
3614     object_props->width = width;
3615     object_props->height = height;
3616 
3617     _worksheet_position_object_emus(self, object_props, drawing_object);
3618 
3619     /* Convert from pixels to emus. */
3620     drawing_object->width = (uint32_t) (0.5 + width * 9525);
3621     drawing_object->height = (uint32_t) (0.5 + height * 9525);
3622 
3623     lxw_add_drawing_object(self->drawing, drawing_object);
3624 
3625     relationship = calloc(1, sizeof(lxw_rel_tuple));
3626     GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3627 
3628     relationship->type = lxw_strdup("/chart");
3629     GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3630 
3631     lxw_snprintf(filename, 32, "../charts/chart%d.xml", chart_ref_id);
3632 
3633     relationship->target = lxw_strdup(filename);
3634     GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3635 
3636     STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers);
3637 
3638     return;
3639 
3640 mem_error:
3641     if (relationship) {
3642         free(relationship->type);
3643         free(relationship->target);
3644         free(relationship->target_mode);
3645         free(relationship);
3646     }
3647 }
3648 
3649 /*
3650  * Set up VML objects, such as comments, in the worksheet.
3651  */
3652 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)3653 lxw_worksheet_prepare_vml_objects(lxw_worksheet *self,
3654                                   uint32_t vml_data_id,
3655                                   uint32_t vml_shape_id,
3656                                   uint32_t vml_drawing_id,
3657                                   uint32_t comment_id)
3658 {
3659     lxw_row *row;
3660     lxw_cell *cell;
3661     lxw_rel_tuple *relationship;
3662     char filename[LXW_FILENAME_LENGTH];
3663     uint32_t comment_count = 0;
3664     uint32_t i;
3665     uint32_t tmp_data_id;
3666     size_t data_str_len = 0;
3667     size_t used = 0;
3668     char *vml_data_id_str;
3669 
3670     RB_FOREACH(row, lxw_table_rows, self->comments) {
3671 
3672         RB_FOREACH(cell, lxw_table_cells, row->cells) {
3673             /* Calculate the worksheet position of the comment. */
3674             _worksheet_position_vml_object(self, cell->comment);
3675 
3676             /* Store comment in a simple list for use by packager. */
3677             STAILQ_INSERT_TAIL(self->comment_objs, cell->comment,
3678                                list_pointers);
3679             comment_count++;
3680         }
3681     }
3682 
3683     /* Set up the VML relationship for comments/buttons/header images. */
3684     relationship = calloc(1, sizeof(lxw_rel_tuple));
3685     GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3686 
3687     relationship->type = lxw_strdup("/vmlDrawing");
3688     GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3689 
3690     lxw_snprintf(filename, 32, "../drawings/vmlDrawing%d.vml",
3691                  vml_drawing_id);
3692 
3693     relationship->target = lxw_strdup(filename);
3694     GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3695 
3696     self->external_vml_comment_link = relationship;
3697 
3698     if (self->has_comments) {
3699         /* Only need this relationship object for comment VMLs. */
3700 
3701         relationship = calloc(1, sizeof(lxw_rel_tuple));
3702         GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3703 
3704         relationship->type = lxw_strdup("/comments");
3705         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3706 
3707         lxw_snprintf(filename, 32, "../comments%d.xml", comment_id);
3708 
3709         relationship->target = lxw_strdup(filename);
3710         GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3711 
3712         self->external_comment_link = relationship;
3713     }
3714 
3715     /* The vml.c <o:idmap> element data id contains a comma separated range
3716      * when there is more than one 1024 block of comments, like this:
3717      * data="1,2,3". Since this could potentially (but unlikely) exceed
3718      * LXW_MAX_ATTRIBUTE_LENGTH we need to allocate space dynamically. */
3719 
3720     /* Calculate the total space required for the ID for each 1024 block. */
3721     for (i = 0; i <= comment_count / 1024; i++) {
3722         tmp_data_id = vml_data_id + i;
3723 
3724         /* Calculate the space required for the digits in the id. */
3725         while (tmp_data_id) {
3726             data_str_len++;
3727             tmp_data_id /= 10;
3728         }
3729 
3730         /* Add an extra char for comma separator or '\O'. */
3731         data_str_len++;
3732     };
3733 
3734     /* If this allocation fails it will be dealt with in packager.c. */
3735     vml_data_id_str = calloc(1, data_str_len + 2);
3736     GOTO_LABEL_ON_MEM_ERROR(vml_data_id_str, mem_error);
3737 
3738     /* Create the CSV list in the allocated space. */
3739     for (i = 0; i <= comment_count / 1024; i++) {
3740         tmp_data_id = vml_data_id + i;
3741         lxw_snprintf(vml_data_id_str + used, data_str_len - used, "%d,",
3742                      tmp_data_id);
3743 
3744         used = strlen(vml_data_id_str);
3745     };
3746 
3747     self->vml_shape_id = vml_shape_id;
3748     self->vml_data_id_str = vml_data_id_str;
3749 
3750     return comment_count;
3751 
3752 mem_error:
3753     if (relationship) {
3754         free(relationship->type);
3755         free(relationship->target);
3756         free(relationship->target_mode);
3757         free(relationship);
3758     }
3759 
3760     return 0;
3761 }
3762 
3763 /*
3764  * Set up external linkage for VML header/footer images.
3765  */
3766 void
lxw_worksheet_prepare_header_vml_objects(lxw_worksheet * self,uint32_t vml_header_id,uint32_t vml_drawing_id)3767 lxw_worksheet_prepare_header_vml_objects(lxw_worksheet *self,
3768                                          uint32_t vml_header_id,
3769                                          uint32_t vml_drawing_id)
3770 {
3771 
3772     lxw_rel_tuple *relationship;
3773     char filename[LXW_FILENAME_LENGTH];
3774     char *vml_data_id_str;
3775 
3776     self->vml_header_id = vml_header_id;
3777 
3778     /* Set up the VML relationship for header images. */
3779     relationship = calloc(1, sizeof(lxw_rel_tuple));
3780     GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3781 
3782     relationship->type = lxw_strdup("/vmlDrawing");
3783     GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3784 
3785     lxw_snprintf(filename, 32, "../drawings/vmlDrawing%d.vml",
3786                  vml_drawing_id);
3787 
3788     relationship->target = lxw_strdup(filename);
3789     GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3790 
3791     self->external_vml_header_link = relationship;
3792 
3793     /* If this allocation fails it will be dealt with in packager.c. */
3794     vml_data_id_str = calloc(1, sizeof("4294967295"));
3795     GOTO_LABEL_ON_MEM_ERROR(vml_data_id_str, mem_error);
3796 
3797     lxw_snprintf(vml_data_id_str, sizeof("4294967295"), "%d", vml_header_id);
3798 
3799     self->vml_header_id_str = vml_data_id_str;
3800 
3801     return;
3802 
3803 mem_error:
3804     if (relationship) {
3805         free(relationship->type);
3806         free(relationship->target);
3807         free(relationship->target_mode);
3808         free(relationship);
3809     }
3810 
3811     return;
3812 }
3813 
3814 /*
3815  * Set up external linkage for VML header/footer images.
3816  */
3817 void
lxw_worksheet_prepare_tables(lxw_worksheet * self,uint32_t table_id)3818 lxw_worksheet_prepare_tables(lxw_worksheet *self, uint32_t table_id)
3819 {
3820     lxw_table_obj *table_obj;
3821     lxw_rel_tuple *relationship;
3822     char name[LXW_ATTR_32];
3823     char filename[LXW_FILENAME_LENGTH];
3824 
3825     STAILQ_FOREACH(table_obj, self->table_objs, list_pointers) {
3826 
3827         relationship = calloc(1, sizeof(lxw_rel_tuple));
3828         GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
3829 
3830         relationship->type = lxw_strdup("/table");
3831         GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
3832 
3833         lxw_snprintf(filename, LXW_FILENAME_LENGTH,
3834                      "../tables/table%d.xml", table_id);
3835 
3836         relationship->target = lxw_strdup(filename);
3837         GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
3838 
3839         STAILQ_INSERT_TAIL(self->external_table_links, relationship,
3840                            list_pointers);
3841 
3842         if (!table_obj->name) {
3843             lxw_snprintf(name, LXW_ATTR_32, "Table%d", table_id);
3844             table_obj->name = lxw_strdup(name);
3845             GOTO_LABEL_ON_MEM_ERROR(table_obj->name, mem_error);
3846         }
3847         table_obj->id = table_id;
3848         table_id++;
3849     }
3850 
3851     return;
3852 
3853 mem_error:
3854     if (relationship) {
3855         free(relationship->type);
3856         free(relationship->target);
3857         free(relationship->target_mode);
3858         free(relationship);
3859     }
3860 
3861     return;
3862 }
3863 
3864 /*
3865  * Extract width and height information from a PNG file.
3866  */
3867 STATIC lxw_error
_process_png(lxw_object_properties * object_props)3868 _process_png(lxw_object_properties *object_props)
3869 {
3870     uint32_t length;
3871     uint32_t offset;
3872     char type[4];
3873     uint32_t width = 0;
3874     uint32_t height = 0;
3875     double x_dpi = 96;
3876     double y_dpi = 96;
3877     int fseek_err;
3878 
3879     FILE *stream = object_props->stream;
3880 
3881     /* Skip another 4 bytes to the end of the PNG header. */
3882     fseek_err = fseek(stream, 4, SEEK_CUR);
3883     if (fseek_err)
3884         goto file_error;
3885 
3886     while (!feof(stream)) {
3887 
3888         /* Read the PNG length and type fields for the sub-section. */
3889         if (fread(&length, sizeof(length), 1, stream) < 1)
3890             break;
3891 
3892         if (fread(&type, 1, 4, stream) < 4)
3893             break;
3894 
3895         /* Convert the length to network order. */
3896         length = LXW_UINT32_NETWORK(length);
3897 
3898         /* The offset for next fseek() is the field length + type length. */
3899         offset = length + 4;
3900 
3901         if (memcmp(type, "IHDR", 4) == 0) {
3902             if (fread(&width, sizeof(width), 1, stream) < 1)
3903                 break;
3904 
3905             if (fread(&height, sizeof(height), 1, stream) < 1)
3906                 break;
3907 
3908             width = LXW_UINT32_NETWORK(width);
3909             height = LXW_UINT32_NETWORK(height);
3910 
3911             /* Reduce the offset by the length of previous freads(). */
3912             offset -= 8;
3913         }
3914 
3915         if (memcmp(type, "pHYs", 4) == 0) {
3916             uint32_t x_ppu = 0;
3917             uint32_t y_ppu = 0;
3918             uint8_t units = 1;
3919 
3920             if (fread(&x_ppu, sizeof(x_ppu), 1, stream) < 1)
3921                 break;
3922 
3923             if (fread(&y_ppu, sizeof(y_ppu), 1, stream) < 1)
3924                 break;
3925 
3926             if (fread(&units, sizeof(units), 1, stream) < 1)
3927                 break;
3928 
3929             if (units == 1) {
3930                 x_ppu = LXW_UINT32_NETWORK(x_ppu);
3931                 y_ppu = LXW_UINT32_NETWORK(y_ppu);
3932 
3933                 x_dpi = (double) x_ppu *0.0254;
3934                 y_dpi = (double) y_ppu *0.0254;
3935             }
3936 
3937             /* Reduce the offset by the length of previous freads(). */
3938             offset -= 9;
3939         }
3940 
3941         if (memcmp(type, "IEND", 4) == 0)
3942             break;
3943 
3944         if (!feof(stream)) {
3945             fseek_err = fseek(stream, offset, SEEK_CUR);
3946             if (fseek_err)
3947                 goto file_error;
3948         }
3949     }
3950 
3951     /* Ensure that we read some valid data from the file. */
3952     if (width == 0)
3953         goto file_error;
3954 
3955     /* Set the image metadata. */
3956     object_props->image_type = LXW_IMAGE_PNG;
3957     object_props->width = width;
3958     object_props->height = height;
3959     object_props->x_dpi = x_dpi ? x_dpi : 96;
3960     object_props->y_dpi = y_dpi ? y_dpi : 96;
3961     object_props->extension = lxw_strdup("png");
3962 
3963     return LXW_NO_ERROR;
3964 
3965 file_error:
3966     LXW_WARN_FORMAT1("worksheet image insertion: "
3967                      "no size data found in: %s.", object_props->filename);
3968 
3969     return LXW_ERROR_IMAGE_DIMENSIONS;
3970 }
3971 
3972 /*
3973  * Extract width and height information from a JPEG file.
3974  */
3975 STATIC lxw_error
_process_jpeg(lxw_object_properties * image_props)3976 _process_jpeg(lxw_object_properties *image_props)
3977 {
3978     uint16_t length;
3979     uint16_t marker;
3980     uint32_t offset;
3981     uint16_t width = 0;
3982     uint16_t height = 0;
3983     double x_dpi = 96;
3984     double y_dpi = 96;
3985     int fseek_err;
3986 
3987     FILE *stream = image_props->stream;
3988 
3989     /* Read back 2 bytes to the end of the initial 0xFFD8 marker. */
3990     fseek_err = fseek(stream, -2, SEEK_CUR);
3991     if (fseek_err)
3992         goto file_error;
3993 
3994     /* Search through the image data and read the JPEG markers. */
3995     while (!feof(stream)) {
3996 
3997         /* Read the JPEG marker and length fields for the sub-section. */
3998         if (fread(&marker, sizeof(marker), 1, stream) < 1)
3999             break;
4000 
4001         if (fread(&length, sizeof(length), 1, stream) < 1)
4002             break;
4003 
4004         /* Convert the marker and length to network order. */
4005         marker = LXW_UINT16_NETWORK(marker);
4006         length = LXW_UINT16_NETWORK(length);
4007 
4008         /* The offset for next fseek() is the field length + type length. */
4009         offset = length - 2;
4010 
4011         /* Read the height and width in the 0xFFCn elements (except C4, C8 */
4012         /* and CC which aren't SOF markers). */
4013         if ((marker & 0xFFF0) == 0xFFC0 && marker != 0xFFC4
4014             && marker != 0xFFC8 && marker != 0xFFCC) {
4015             /* Skip 1 byte to height and width. */
4016             fseek_err = fseek(stream, 1, SEEK_CUR);
4017             if (fseek_err)
4018                 goto file_error;
4019 
4020             if (fread(&height, sizeof(height), 1, stream) < 1)
4021                 break;
4022 
4023             if (fread(&width, sizeof(width), 1, stream) < 1)
4024                 break;
4025 
4026             height = LXW_UINT16_NETWORK(height);
4027             width = LXW_UINT16_NETWORK(width);
4028 
4029             offset -= 9;
4030         }
4031 
4032         /* Read the DPI in the 0xFFE0 element. */
4033         if (marker == 0xFFE0) {
4034             uint16_t x_density = 0;
4035             uint16_t y_density = 0;
4036             uint8_t units = 1;
4037 
4038             fseek_err = fseek(stream, 7, SEEK_CUR);
4039             if (fseek_err)
4040                 goto file_error;
4041 
4042             if (fread(&units, sizeof(units), 1, stream) < 1)
4043                 break;
4044 
4045             if (fread(&x_density, sizeof(x_density), 1, stream) < 1)
4046                 break;
4047 
4048             if (fread(&y_density, sizeof(y_density), 1, stream) < 1)
4049                 break;
4050 
4051             x_density = LXW_UINT16_NETWORK(x_density);
4052             y_density = LXW_UINT16_NETWORK(y_density);
4053 
4054             if (units == 1) {
4055                 x_dpi = x_density;
4056                 y_dpi = y_density;
4057             }
4058 
4059             if (units == 2) {
4060                 x_dpi = x_density * 2.54;
4061                 y_dpi = y_density * 2.54;
4062             }
4063 
4064             offset -= 12;
4065         }
4066 
4067         if (marker == 0xFFDA)
4068             break;
4069 
4070         if (!feof(stream)) {
4071             fseek_err = fseek(stream, offset, SEEK_CUR);
4072             if (fseek_err)
4073                 break;
4074         }
4075     }
4076 
4077     /* Ensure that we read some valid data from the file. */
4078     if (width == 0)
4079         goto file_error;
4080 
4081     /* Set the image metadata. */
4082     image_props->image_type = LXW_IMAGE_JPEG;
4083     image_props->width = width;
4084     image_props->height = height;
4085     image_props->x_dpi = x_dpi ? x_dpi : 96;
4086     image_props->y_dpi = y_dpi ? y_dpi : 96;
4087     image_props->extension = lxw_strdup("jpeg");
4088 
4089     return LXW_NO_ERROR;
4090 
4091 file_error:
4092     LXW_WARN_FORMAT1("worksheet image insertion: "
4093                      "no size data found in: %s.", image_props->filename);
4094 
4095     return LXW_ERROR_IMAGE_DIMENSIONS;
4096 }
4097 
4098 /*
4099  * Extract width and height information from a BMP file.
4100  */
4101 STATIC lxw_error
_process_bmp(lxw_object_properties * image_props)4102 _process_bmp(lxw_object_properties *image_props)
4103 {
4104     uint32_t width = 0;
4105     uint32_t height = 0;
4106     double x_dpi = 96;
4107     double y_dpi = 96;
4108     int fseek_err;
4109 
4110     FILE *stream = image_props->stream;
4111 
4112     /* Skip another 14 bytes to the start of the BMP height/width. */
4113     fseek_err = fseek(stream, 14, SEEK_CUR);
4114     if (fseek_err)
4115         goto file_error;
4116 
4117     if (fread(&width, sizeof(width), 1, stream) < 1)
4118         width = 0;
4119 
4120     if (fread(&height, sizeof(height), 1, stream) < 1)
4121         height = 0;
4122 
4123     /* Ensure that we read some valid data from the file. */
4124     if (width == 0)
4125         goto file_error;
4126 
4127     height = LXW_UINT32_HOST(height);
4128     width = LXW_UINT32_HOST(width);
4129 
4130     /* Set the image metadata. */
4131     image_props->image_type = LXW_IMAGE_BMP;
4132     image_props->width = width;
4133     image_props->height = height;
4134     image_props->x_dpi = x_dpi;
4135     image_props->y_dpi = y_dpi;
4136     image_props->extension = lxw_strdup("bmp");
4137 
4138     return LXW_NO_ERROR;
4139 
4140 file_error:
4141     LXW_WARN_FORMAT1("worksheet image insertion: "
4142                      "no size data found in: %s.", image_props->filename);
4143 
4144     return LXW_ERROR_IMAGE_DIMENSIONS;
4145 }
4146 
4147 /*
4148  * Extract width and height information from a GIF file.
4149  */
4150 STATIC lxw_error
_process_gif(lxw_object_properties * image_props)4151 _process_gif(lxw_object_properties *image_props)
4152 {
4153     uint16_t width = 0;
4154     uint16_t height = 0;
4155     double x_dpi = 96;
4156     double y_dpi = 96;
4157     int fseek_err;
4158 
4159     FILE *stream = image_props->stream;
4160 
4161     /* Skip another 2 bytes to the start of the GIF height/width. */
4162     fseek_err = fseek(stream, 2, SEEK_CUR);
4163     if (fseek_err)
4164         goto file_error;
4165 
4166     if (fread(&width, sizeof(width), 1, stream) < 1)
4167         width = 0;
4168 
4169     if (fread(&height, sizeof(height), 1, stream) < 1)
4170         height = 0;
4171 
4172     /* Ensure that we read some valid data from the file. */
4173     if (width == 0)
4174         goto file_error;
4175 
4176     height = LXW_UINT16_HOST(height);
4177     width = LXW_UINT16_HOST(width);
4178 
4179     /* Set the image metadata. */
4180     image_props->image_type = LXW_IMAGE_GIF;
4181     image_props->width = width;
4182     image_props->height = height;
4183     image_props->x_dpi = x_dpi;
4184     image_props->y_dpi = y_dpi;
4185     image_props->extension = lxw_strdup("gif");
4186 
4187     return LXW_NO_ERROR;
4188 
4189 file_error:
4190     LXW_WARN_FORMAT1("worksheet image insertion: "
4191                      "no size data found in: %s.", image_props->filename);
4192 
4193     return LXW_ERROR_IMAGE_DIMENSIONS;
4194 }
4195 
4196 /*
4197  * Extract information from the image file such as dimension, type, filename,
4198  * and extension.
4199  */
4200 STATIC lxw_error
_get_image_properties(lxw_object_properties * image_props)4201 _get_image_properties(lxw_object_properties *image_props)
4202 {
4203     unsigned char signature[4];
4204 #ifndef USE_NO_MD5
4205     uint8_t i;
4206     MD5_CTX md5_context;
4207     size_t size_read;
4208     char buffer[LXW_IMAGE_BUFFER_SIZE];
4209     unsigned char md5_checksum[LXW_MD5_SIZE];
4210 #endif
4211 
4212     /* Read 4 bytes to look for the file header/signature. */
4213     if (fread(signature, 1, 4, image_props->stream) < 4) {
4214         LXW_WARN_FORMAT1("worksheet image insertion: "
4215                          "couldn't read image type for: %s.",
4216                          image_props->filename);
4217         return LXW_ERROR_IMAGE_DIMENSIONS;
4218     }
4219 
4220     if (memcmp(&signature[1], "PNG", 3) == 0) {
4221         if (_process_png(image_props) != LXW_NO_ERROR)
4222             return LXW_ERROR_IMAGE_DIMENSIONS;
4223     }
4224     else if (signature[0] == 0xFF && signature[1] == 0xD8) {
4225         if (_process_jpeg(image_props) != LXW_NO_ERROR)
4226             return LXW_ERROR_IMAGE_DIMENSIONS;
4227     }
4228     else if (memcmp(signature, "BM", 2) == 0) {
4229         if (_process_bmp(image_props) != LXW_NO_ERROR)
4230             return LXW_ERROR_IMAGE_DIMENSIONS;
4231     }
4232     else if (memcmp(signature, "GIF8", 4) == 0) {
4233         if (_process_gif(image_props) != LXW_NO_ERROR)
4234             return LXW_ERROR_IMAGE_DIMENSIONS;
4235     }
4236     else {
4237         LXW_WARN_FORMAT1("worksheet image insertion: "
4238                          "unsupported image format for: %s.",
4239                          image_props->filename);
4240         return LXW_ERROR_IMAGE_DIMENSIONS;
4241     }
4242 
4243 #ifndef USE_NO_MD5
4244     /* Calculate an MD5 checksum for the image so that we can remove duplicate
4245      * images to reduce the xlsx file size.*/
4246     rewind(image_props->stream);
4247 
4248     MD5_Init(&md5_context);
4249 
4250     size_read = fread(buffer, 1, LXW_IMAGE_BUFFER_SIZE, image_props->stream);
4251     while (size_read) {
4252         MD5_Update(&md5_context, buffer, size_read);
4253         size_read =
4254             fread(buffer, 1, LXW_IMAGE_BUFFER_SIZE, image_props->stream);
4255     }
4256 
4257     MD5_Final(md5_checksum, &md5_context);
4258 
4259     /* Create a 32 char hex string buffer for the MD5 checksum. */
4260     image_props->md5 = calloc(1, LXW_MD5_SIZE * 2 + 1);
4261 
4262     /* If this calloc fails we just return and don't remove duplicates. */
4263     RETURN_ON_MEM_ERROR(image_props->md5, LXW_NO_ERROR);
4264 
4265     /* Convert the 16 byte MD5 buffer to a 32 char hex string. */
4266     for (i = 0; i < LXW_MD5_SIZE; i++) {
4267         lxw_snprintf(&image_props->md5[2 * i], 3, "%02x", md5_checksum[i]);
4268     }
4269 #endif
4270 
4271     return LXW_NO_ERROR;
4272 }
4273 
4274 /* Conditional formats that refer to the same cell sqref range, like A or
4275  * B1:B9, need to be written as part of one xml structure. Therefore we need
4276  * to store them in a RB hash/tree keyed by sqref. Within the RB hash element
4277  * we then store conditional formats that refer to sqref in a STAILQ list. */
4278 lxw_error
_store_conditional_format_object(lxw_worksheet * self,lxw_cond_format_obj * cond_format)4279 _store_conditional_format_object(lxw_worksheet *self,
4280                                  lxw_cond_format_obj *cond_format)
4281 {
4282     lxw_cond_format_hash_element tmp_hash_element;
4283     lxw_cond_format_hash_element *found_hash_element = NULL;
4284     lxw_cond_format_hash_element *new_hash_element = NULL;
4285 
4286     /* Create a temp hash element to do the lookup. */
4287     LXW_ATTRIBUTE_COPY(tmp_hash_element.sqref, cond_format->sqref);
4288     found_hash_element = RB_FIND(lxw_cond_format_hash,
4289                                  self->conditional_formats,
4290                                  &tmp_hash_element);
4291 
4292     if (found_hash_element) {
4293         /* If the RB element exists then add the conditional format to the
4294          * list for the sqref range.*/
4295         STAILQ_INSERT_TAIL(found_hash_element->cond_formats, cond_format,
4296                            list_pointers);
4297     }
4298     else {
4299         /* Create a new RB hash element. */
4300         new_hash_element = calloc(1, sizeof(lxw_cond_format_hash_element));
4301         GOTO_LABEL_ON_MEM_ERROR(new_hash_element, mem_error);
4302 
4303         /* Use the sqref as the key. */
4304         LXW_ATTRIBUTE_COPY(new_hash_element->sqref, cond_format->sqref);
4305 
4306         /* Also create the list where we store the cond format objects. */
4307         new_hash_element->cond_formats =
4308             calloc(1, sizeof(struct lxw_cond_format_list));
4309         GOTO_LABEL_ON_MEM_ERROR(new_hash_element->cond_formats, mem_error);
4310 
4311         /* Initialize the list and add the conditional format object. */
4312         STAILQ_INIT(new_hash_element->cond_formats);
4313         STAILQ_INSERT_TAIL(new_hash_element->cond_formats, cond_format,
4314                            list_pointers);
4315 
4316         /* Now insert the RB hash element into the tree. */
4317         RB_INSERT(lxw_cond_format_hash, self->conditional_formats,
4318                   new_hash_element);
4319 
4320     }
4321 
4322     return LXW_NO_ERROR;
4323 
4324 mem_error:
4325     free(new_hash_element);
4326     return LXW_ERROR_MEMORY_MALLOC_FAILED;
4327 }
4328 
4329 /*****************************************************************************
4330  *
4331  * XML file assembly functions.
4332  *
4333  ****************************************************************************/
4334 
4335 /*
4336  * Write out a number worksheet cell. Doesn't use the xml functions as an
4337  * optimization in the inner cell writing loop.
4338  */
4339 STATIC void
_write_number_cell(lxw_worksheet * self,char * range,int32_t style_index,lxw_cell * cell)4340 _write_number_cell(lxw_worksheet *self, char *range,
4341                    int32_t style_index, lxw_cell *cell)
4342 {
4343 #ifdef USE_DTOA_LIBRARY
4344     char data[LXW_ATTR_32];
4345 
4346     lxw_sprintf_dbl(data, cell->u.number);
4347 
4348     if (style_index)
4349         fprintf(self->file,
4350                 "<c r=\"%s\" s=\"%d\"><v>%s</v></c>",
4351                 range, style_index, data);
4352     else
4353         fprintf(self->file, "<c r=\"%s\"><v>%s</v></c>", range, data);
4354 #else
4355     if (style_index)
4356         fprintf(self->file,
4357                 "<c r=\"%s\" s=\"%d\"><v>%.16G</v></c>",
4358                 range, style_index, cell->u.number);
4359     else
4360         fprintf(self->file,
4361                 "<c r=\"%s\"><v>%.16G</v></c>", range, cell->u.number);
4362 
4363 #endif
4364 }
4365 
4366 /*
4367  * Write out a string worksheet cell. Doesn't use the xml functions as an
4368  * optimization in the inner cell writing loop.
4369  */
4370 STATIC void
_write_string_cell(lxw_worksheet * self,char * range,int32_t style_index,lxw_cell * cell)4371 _write_string_cell(lxw_worksheet *self, char *range,
4372                    int32_t style_index, lxw_cell *cell)
4373 {
4374 
4375     if (style_index)
4376         fprintf(self->file,
4377                 "<c r=\"%s\" s=\"%d\" t=\"s\"><v>%d</v></c>",
4378                 range, style_index, cell->u.string_id);
4379     else
4380         fprintf(self->file,
4381                 "<c r=\"%s\" t=\"s\"><v>%d</v></c>",
4382                 range, cell->u.string_id);
4383 }
4384 
4385 /*
4386  * Write out an inline string. Doesn't use the xml functions as an
4387  * optimization in the inner cell writing loop.
4388  */
4389 STATIC void
_write_inline_string_cell(lxw_worksheet * self,char * range,int32_t style_index,lxw_cell * cell)4390 _write_inline_string_cell(lxw_worksheet *self, char *range,
4391                           int32_t style_index, lxw_cell *cell)
4392 {
4393     char *string = lxw_escape_data(cell->u.string);
4394 
4395     /* Add attribute to preserve leading or trailing whitespace. */
4396     if (isspace((unsigned char) string[0])
4397         || isspace((unsigned char) string[strlen(string) - 1])) {
4398 
4399         if (style_index)
4400             fprintf(self->file,
4401                     "<c r=\"%s\" s=\"%d\" t=\"inlineStr\"><is>"
4402                     "<t xml:space=\"preserve\">%s</t></is></c>",
4403                     range, style_index, string);
4404         else
4405             fprintf(self->file,
4406                     "<c r=\"%s\" t=\"inlineStr\"><is>"
4407                     "<t xml:space=\"preserve\">%s</t></is></c>",
4408                     range, string);
4409     }
4410     else {
4411         if (style_index)
4412             fprintf(self->file,
4413                     "<c r=\"%s\" s=\"%d\" t=\"inlineStr\">"
4414                     "<is><t>%s</t></is></c>", range, style_index, string);
4415         else
4416             fprintf(self->file,
4417                     "<c r=\"%s\" t=\"inlineStr\">"
4418                     "<is><t>%s</t></is></c>", range, string);
4419     }
4420 
4421     free(string);
4422 }
4423 
4424 /*
4425  * Write out an inline rich string. Doesn't use the xml functions as an
4426  * optimization in the inner cell writing loop.
4427  */
4428 STATIC void
_write_inline_rich_string_cell(lxw_worksheet * self,char * range,int32_t style_index,lxw_cell * cell)4429 _write_inline_rich_string_cell(lxw_worksheet *self, char *range,
4430                                int32_t style_index, lxw_cell *cell)
4431 {
4432     char *string = cell->u.string;
4433 
4434     if (style_index)
4435         fprintf(self->file,
4436                 "<c r=\"%s\" s=\"%d\" t=\"inlineStr\">"
4437                 "<is>%s</is></c>", range, style_index, string);
4438     else
4439         fprintf(self->file,
4440                 "<c r=\"%s\" t=\"inlineStr\">"
4441                 "<is>%s</is></c>", range, string);
4442 }
4443 
4444 /*
4445  * Write out a formula worksheet cell with a numeric result.
4446  */
4447 STATIC void
_write_formula_num_cell(lxw_worksheet * self,lxw_cell * cell)4448 _write_formula_num_cell(lxw_worksheet *self, lxw_cell *cell)
4449 {
4450     char data[LXW_ATTR_32];
4451 
4452     lxw_sprintf_dbl(data, cell->formula_result);
4453     lxw_xml_data_element(self->file, "f", cell->u.string, NULL);
4454     lxw_xml_data_element(self->file, "v", data, NULL);
4455 }
4456 
4457 /*
4458  * Write out a formula worksheet cell with a numeric result.
4459  */
4460 STATIC void
_write_formula_str_cell(lxw_worksheet * self,lxw_cell * cell)4461 _write_formula_str_cell(lxw_worksheet *self, lxw_cell *cell)
4462 {
4463     lxw_xml_data_element(self->file, "f", cell->u.string, NULL);
4464     lxw_xml_data_element(self->file, "v", cell->user_data2, NULL);
4465 }
4466 
4467 /*
4468  * Write out an array formula worksheet cell with a numeric result.
4469  */
4470 STATIC void
_write_array_formula_num_cell(lxw_worksheet * self,lxw_cell * cell)4471 _write_array_formula_num_cell(lxw_worksheet *self, lxw_cell *cell)
4472 {
4473     struct xml_attribute_list attributes;
4474     struct xml_attribute *attribute;
4475     char data[LXW_ATTR_32];
4476 
4477     LXW_INIT_ATTRIBUTES();
4478     LXW_PUSH_ATTRIBUTES_STR("t", "array");
4479     LXW_PUSH_ATTRIBUTES_STR("ref", cell->user_data1);
4480 
4481     lxw_sprintf_dbl(data, cell->formula_result);
4482 
4483     lxw_xml_data_element(self->file, "f", cell->u.string, &attributes);
4484     lxw_xml_data_element(self->file, "v", data, NULL);
4485 
4486     LXW_FREE_ATTRIBUTES();
4487 }
4488 
4489 /*
4490  * Write out a boolean worksheet cell.
4491  */
4492 STATIC void
_write_boolean_cell(lxw_worksheet * self,lxw_cell * cell)4493 _write_boolean_cell(lxw_worksheet *self, lxw_cell *cell)
4494 {
4495     char data[LXW_ATTR_32];
4496 
4497     if (cell->u.number == 0.0)
4498         data[0] = '0';
4499     else
4500         data[0] = '1';
4501 
4502     data[1] = '\0';
4503 
4504     lxw_xml_data_element(self->file, "v", data, NULL);
4505 }
4506 
4507 /*
4508  * Calculate the "spans" attribute of the <row> tag. This is an XLSX
4509  * optimization and isn't strictly required. However, it makes comparing
4510  * files easier.
4511  *
4512  * The span is the same for each block of 16 rows.
4513  */
4514 STATIC void
_calculate_spans(struct lxw_row * row,char * span,int32_t * block_num)4515 _calculate_spans(struct lxw_row *row, char *span, int32_t *block_num)
4516 {
4517     lxw_cell *cell_min = RB_MIN(lxw_table_cells, row->cells);
4518     lxw_cell *cell_max = RB_MAX(lxw_table_cells, row->cells);
4519     lxw_col_t span_col_min = cell_min->col_num;
4520     lxw_col_t span_col_max = cell_max->col_num;
4521     lxw_col_t col_min;
4522     lxw_col_t col_max;
4523     *block_num = row->row_num / 16;
4524 
4525     row = RB_NEXT(lxw_table_rows, root, row);
4526 
4527     while (row && (int32_t) (row->row_num / 16) == *block_num) {
4528 
4529         if (!RB_EMPTY(row->cells)) {
4530             cell_min = RB_MIN(lxw_table_cells, row->cells);
4531             cell_max = RB_MAX(lxw_table_cells, row->cells);
4532             col_min = cell_min->col_num;
4533             col_max = cell_max->col_num;
4534 
4535             if (col_min < span_col_min)
4536                 span_col_min = col_min;
4537 
4538             if (col_max > span_col_max)
4539                 span_col_max = col_max;
4540         }
4541 
4542         row = RB_NEXT(lxw_table_rows, root, row);
4543     }
4544 
4545     lxw_snprintf(span, LXW_MAX_CELL_RANGE_LENGTH,
4546                  "%d:%d", span_col_min + 1, span_col_max + 1);
4547 }
4548 
4549 /*
4550  * Write out a generic worksheet cell.
4551  */
4552 STATIC void
_write_cell(lxw_worksheet * self,lxw_cell * cell,lxw_format * row_format)4553 _write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format)
4554 {
4555     struct xml_attribute_list attributes;
4556     struct xml_attribute *attribute;
4557     char range[LXW_MAX_CELL_NAME_LENGTH] = { 0 };
4558     lxw_row_t row_num = cell->row_num;
4559     lxw_col_t col_num = cell->col_num;
4560     int32_t style_index = 0;
4561 
4562     lxw_rowcol_to_cell(range, row_num, col_num);
4563 
4564     if (cell->format) {
4565         style_index = lxw_format_get_xf_index(cell->format);
4566     }
4567     else if (row_format) {
4568         style_index = lxw_format_get_xf_index(row_format);
4569     }
4570     else if (col_num < self->col_formats_max && self->col_formats[col_num]) {
4571         style_index = lxw_format_get_xf_index(self->col_formats[col_num]);
4572     }
4573 
4574     /* Unrolled optimization for most commonly written cell types. */
4575     if (cell->type == NUMBER_CELL) {
4576         _write_number_cell(self, range, style_index, cell);
4577         return;
4578     }
4579 
4580     if (cell->type == STRING_CELL) {
4581         _write_string_cell(self, range, style_index, cell);
4582         return;
4583     }
4584 
4585     if (cell->type == INLINE_STRING_CELL) {
4586         _write_inline_string_cell(self, range, style_index, cell);
4587         return;
4588     }
4589 
4590     if (cell->type == INLINE_RICH_STRING_CELL) {
4591         _write_inline_rich_string_cell(self, range, style_index, cell);
4592         return;
4593     }
4594 
4595     /* For other cell types use the general functions. */
4596     LXW_INIT_ATTRIBUTES();
4597     LXW_PUSH_ATTRIBUTES_STR("r", range);
4598 
4599     if (style_index)
4600         LXW_PUSH_ATTRIBUTES_INT("s", style_index);
4601 
4602     if (cell->type == FORMULA_CELL) {
4603         /* If user_data2 is set then the formula has a string result. */
4604         if (cell->user_data2)
4605             LXW_PUSH_ATTRIBUTES_STR("t", "str");
4606 
4607         lxw_xml_start_tag(self->file, "c", &attributes);
4608 
4609         if (cell->user_data2)
4610             _write_formula_str_cell(self, cell);
4611         else
4612             _write_formula_num_cell(self, cell);
4613 
4614         lxw_xml_end_tag(self->file, "c");
4615     }
4616     else if (cell->type == BLANK_CELL) {
4617         if (cell->format)
4618             lxw_xml_empty_tag(self->file, "c", &attributes);
4619     }
4620     else if (cell->type == BOOLEAN_CELL) {
4621         LXW_PUSH_ATTRIBUTES_STR("t", "b");
4622         lxw_xml_start_tag(self->file, "c", &attributes);
4623         _write_boolean_cell(self, cell);
4624         lxw_xml_end_tag(self->file, "c");
4625     }
4626     else if (cell->type == ARRAY_FORMULA_CELL) {
4627         lxw_xml_start_tag(self->file, "c", &attributes);
4628         _write_array_formula_num_cell(self, cell);
4629         lxw_xml_end_tag(self->file, "c");
4630     }
4631     else if (cell->type == DYNAMIC_ARRAY_FORMULA_CELL) {
4632         LXW_PUSH_ATTRIBUTES_STR("cm", "1");
4633         lxw_xml_start_tag(self->file, "c", &attributes);
4634         _write_array_formula_num_cell(self, cell);
4635         lxw_xml_end_tag(self->file, "c");
4636     }
4637 
4638     LXW_FREE_ATTRIBUTES();
4639 }
4640 
4641 /*
4642  * Write out the worksheet data as a series of rows and cells.
4643  */
4644 STATIC void
_worksheet_write_rows(lxw_worksheet * self)4645 _worksheet_write_rows(lxw_worksheet *self)
4646 {
4647     lxw_row *row;
4648     lxw_cell *cell;
4649     int32_t block_num = -1;
4650     char spans[LXW_MAX_CELL_RANGE_LENGTH] = { 0 };
4651 
4652     RB_FOREACH(row, lxw_table_rows, self->table) {
4653 
4654         if (RB_EMPTY(row->cells)) {
4655             /* Row contains no cells but has height, format or other data. */
4656 
4657             /* Write a default span for default rows. */
4658             if (self->default_row_set)
4659                 _write_row(self, row, "1:1");
4660             else
4661                 _write_row(self, row, NULL);
4662         }
4663         else {
4664             /* Row and cell data. */
4665             if ((int32_t) row->row_num / 16 > block_num)
4666                 _calculate_spans(row, spans, &block_num);
4667 
4668             _write_row(self, row, spans);
4669 
4670             if (row->data_changed) {
4671                 RB_FOREACH(cell, lxw_table_cells, row->cells) {
4672                     _write_cell(self, cell, row->format);
4673                 }
4674 
4675                 lxw_xml_end_tag(self->file, "row");
4676             }
4677         }
4678     }
4679 }
4680 
4681 /*
4682  * Write out the worksheet data as a single row with cells. This method is
4683  * used when memory optimization is on. A single row is written and the data
4684  * array is reset. That way only one row of data is kept in memory at any one
4685  * time. We don't write span data in the optimized case since it is optional.
4686  */
4687 void
lxw_worksheet_write_single_row(lxw_worksheet * self)4688 lxw_worksheet_write_single_row(lxw_worksheet *self)
4689 {
4690     lxw_row *row = self->optimize_row;
4691     lxw_col_t col;
4692 
4693     /* skip row if it doesn't contain row formatting, cell data or a comment. */
4694     if (!(row->row_changed || row->data_changed))
4695         return;
4696 
4697     /* Write the cells if the row contains data. */
4698     if (!row->data_changed) {
4699         /* Row data only. No cells. */
4700         _write_row(self, row, NULL);
4701     }
4702     else {
4703         /* Row and cell data. */
4704         _write_row(self, row, NULL);
4705 
4706         for (col = self->dim_colmin; col <= self->dim_colmax; col++) {
4707             if (self->array[col]) {
4708                 _write_cell(self, self->array[col], row->format);
4709                 _free_cell(self->array[col]);
4710                 self->array[col] = NULL;
4711             }
4712         }
4713 
4714         lxw_xml_end_tag(self->file, "row");
4715     }
4716 
4717     /* Reset the row. */
4718     row->height = LXW_DEF_ROW_HEIGHT;
4719     row->format = NULL;
4720     row->hidden = LXW_FALSE;
4721     row->level = 0;
4722     row->collapsed = LXW_FALSE;
4723     row->data_changed = LXW_FALSE;
4724     row->row_changed = LXW_FALSE;
4725 }
4726 
4727 /* Process a header/footer image and store it in the correct slot. */
4728 lxw_error
_worksheet_set_header_footer_image(lxw_worksheet * self,char * filename,uint8_t image_position)4729 _worksheet_set_header_footer_image(lxw_worksheet *self, char *filename,
4730                                    uint8_t image_position)
4731 {
4732     FILE *image_stream;
4733     const char *description;
4734     lxw_object_properties *object_props;
4735     char *image_strings[] = { "LH", "CH", "RH", "LF", "CF", "RF" };
4736 
4737     /* Not all slots will have image files. */
4738     if (!filename)
4739         return LXW_NO_ERROR;
4740 
4741     /* Check that the image file exists and can be opened. */
4742     image_stream = lxw_fopen(filename, "rb");
4743     if (!image_stream) {
4744         LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): "
4745                          "file doesn't exist or can't be opened: %s.",
4746                          filename);
4747         return LXW_ERROR_PARAMETER_VALIDATION;
4748     }
4749 
4750     /* Use the filename as the default description, like Excel. */
4751     description = lxw_basename(filename);
4752     if (!description) {
4753         LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): "
4754                          "couldn't get basename for file: %s.", filename);
4755         fclose(image_stream);
4756         return LXW_ERROR_PARAMETER_VALIDATION;
4757     }
4758 
4759     /* Create a new object to hold the image properties. */
4760     object_props = calloc(1, sizeof(lxw_object_properties));
4761     if (!object_props) {
4762         fclose(image_stream);
4763         return LXW_ERROR_MEMORY_MALLOC_FAILED;
4764     }
4765 
4766     /* Copy other options or set defaults. */
4767     object_props->filename = lxw_strdup(filename);
4768     object_props->description = lxw_strdup(description);
4769     object_props->stream = image_stream;
4770 
4771     /* Set VML image position string based on the header/footer/position. */
4772     object_props->image_position = lxw_strdup(image_strings[image_position]);
4773 
4774     if (_get_image_properties(object_props) == LXW_NO_ERROR) {
4775         *self->header_footer_objs[image_position] = object_props;
4776         self->has_header_vml = LXW_TRUE;
4777         fclose(image_stream);
4778         return LXW_NO_ERROR;
4779     }
4780     else {
4781         _free_object_properties(object_props);
4782         fclose(image_stream);
4783         return LXW_ERROR_IMAGE_DIMENSIONS;
4784     }
4785 }
4786 
4787 /*
4788  * Write the <col> element.
4789  */
4790 STATIC void
_worksheet_write_col_info(lxw_worksheet * self,lxw_col_options * options)4791 _worksheet_write_col_info(lxw_worksheet *self, lxw_col_options *options)
4792 {
4793     struct xml_attribute_list attributes;
4794     struct xml_attribute *attribute;
4795 
4796     double width = options->width;
4797     uint8_t has_custom_width = LXW_TRUE;
4798     int32_t xf_index = 0;
4799     double max_digit_width = 7.0;       /* For Calabri 11. */
4800     double padding = 5.0;
4801 
4802     /* Get the format index. */
4803     if (options->format) {
4804         xf_index = lxw_format_get_xf_index(options->format);
4805     }
4806 
4807     /* Check if width is the Excel default. */
4808     if (width == LXW_DEF_COL_WIDTH) {
4809 
4810         /* The default col width changes to 0 for hidden columns. */
4811         if (options->hidden)
4812             width = 0;
4813         else
4814             has_custom_width = LXW_FALSE;
4815 
4816     }
4817 
4818     /* Convert column width from user units to character width. */
4819     if (width > 0) {
4820         if (width < 1) {
4821             width = (uint16_t) (((uint16_t)
4822                                  (width * (max_digit_width + padding) + 0.5))
4823                                 / max_digit_width * 256.0) / 256.0;
4824         }
4825         else {
4826             width = (uint16_t) (((uint16_t)
4827                                  (width * max_digit_width + 0.5) + padding)
4828                                 / max_digit_width * 256.0) / 256.0;
4829         }
4830     }
4831 
4832     LXW_INIT_ATTRIBUTES();
4833     LXW_PUSH_ATTRIBUTES_INT("min", 1 + options->firstcol);
4834     LXW_PUSH_ATTRIBUTES_INT("max", 1 + options->lastcol);
4835     LXW_PUSH_ATTRIBUTES_DBL("width", width);
4836 
4837     if (xf_index)
4838         LXW_PUSH_ATTRIBUTES_INT("style", xf_index);
4839 
4840     if (options->hidden)
4841         LXW_PUSH_ATTRIBUTES_STR("hidden", "1");
4842 
4843     if (has_custom_width)
4844         LXW_PUSH_ATTRIBUTES_STR("customWidth", "1");
4845 
4846     if (options->level)
4847         LXW_PUSH_ATTRIBUTES_INT("outlineLevel", options->level);
4848 
4849     if (options->collapsed)
4850         LXW_PUSH_ATTRIBUTES_STR("collapsed", "1");
4851 
4852     lxw_xml_empty_tag(self->file, "col", &attributes);
4853 
4854     LXW_FREE_ATTRIBUTES();
4855 }
4856 
4857 /*
4858  * Write the <cols> element and <col> sub elements.
4859  */
4860 STATIC void
_worksheet_write_cols(lxw_worksheet * self)4861 _worksheet_write_cols(lxw_worksheet *self)
4862 {
4863     lxw_col_t col;
4864 
4865     if (!self->col_size_changed)
4866         return;
4867 
4868     lxw_xml_start_tag(self->file, "cols", NULL);
4869 
4870     for (col = 0; col < self->col_options_max; col++) {
4871         if (self->col_options[col])
4872             _worksheet_write_col_info(self, self->col_options[col]);
4873     }
4874 
4875     lxw_xml_end_tag(self->file, "cols");
4876 }
4877 
4878 /*
4879  * Write the <mergeCell> element.
4880  */
4881 STATIC void
_worksheet_write_merge_cell(lxw_worksheet * self,lxw_merged_range * merged_range)4882 _worksheet_write_merge_cell(lxw_worksheet *self,
4883                             lxw_merged_range *merged_range)
4884 {
4885 
4886     struct xml_attribute_list attributes;
4887     struct xml_attribute *attribute;
4888     char ref[LXW_MAX_CELL_RANGE_LENGTH];
4889 
4890     LXW_INIT_ATTRIBUTES();
4891 
4892     /* Convert the merge dimensions to a cell range. */
4893     lxw_rowcol_to_range(ref, merged_range->first_row, merged_range->first_col,
4894                         merged_range->last_row, merged_range->last_col);
4895 
4896     LXW_PUSH_ATTRIBUTES_STR("ref", ref);
4897 
4898     lxw_xml_empty_tag(self->file, "mergeCell", &attributes);
4899 
4900     LXW_FREE_ATTRIBUTES();
4901 }
4902 
4903 /*
4904  * Write the <mergeCells> element.
4905  */
4906 STATIC void
_worksheet_write_merge_cells(lxw_worksheet * self)4907 _worksheet_write_merge_cells(lxw_worksheet *self)
4908 {
4909     struct xml_attribute_list attributes;
4910     struct xml_attribute *attribute;
4911     lxw_merged_range *merged_range;
4912 
4913     if (self->merged_range_count) {
4914         LXW_INIT_ATTRIBUTES();
4915 
4916         LXW_PUSH_ATTRIBUTES_INT("count", self->merged_range_count);
4917 
4918         lxw_xml_start_tag(self->file, "mergeCells", &attributes);
4919 
4920         STAILQ_FOREACH(merged_range, self->merged_ranges, list_pointers) {
4921             _worksheet_write_merge_cell(self, merged_range);
4922         }
4923         lxw_xml_end_tag(self->file, "mergeCells");
4924 
4925         LXW_FREE_ATTRIBUTES();
4926     }
4927 }
4928 
4929 /*
4930  * Write the <oddHeader> element.
4931  */
4932 STATIC void
_worksheet_write_odd_header(lxw_worksheet * self)4933 _worksheet_write_odd_header(lxw_worksheet *self)
4934 {
4935     lxw_xml_data_element(self->file, "oddHeader", self->header, NULL);
4936 }
4937 
4938 /*
4939  * Write the <oddFooter> element.
4940  */
4941 STATIC void
_worksheet_write_odd_footer(lxw_worksheet * self)4942 _worksheet_write_odd_footer(lxw_worksheet *self)
4943 {
4944     lxw_xml_data_element(self->file, "oddFooter", self->footer, NULL);
4945 }
4946 
4947 /*
4948  * Write the <headerFooter> element.
4949  */
4950 STATIC void
_worksheet_write_header_footer(lxw_worksheet * self)4951 _worksheet_write_header_footer(lxw_worksheet *self)
4952 {
4953     if (!self->header_footer_changed)
4954         return;
4955 
4956     lxw_xml_start_tag(self->file, "headerFooter", NULL);
4957 
4958     if (self->header)
4959         _worksheet_write_odd_header(self);
4960 
4961     if (self->footer)
4962         _worksheet_write_odd_footer(self);
4963 
4964     lxw_xml_end_tag(self->file, "headerFooter");
4965 }
4966 
4967 /*
4968  * Write the <pageSetUpPr> element.
4969  */
4970 STATIC void
_worksheet_write_page_set_up_pr(lxw_worksheet * self)4971 _worksheet_write_page_set_up_pr(lxw_worksheet *self)
4972 {
4973     struct xml_attribute_list attributes;
4974     struct xml_attribute *attribute;
4975 
4976     if (!self->fit_page)
4977         return;
4978 
4979     LXW_INIT_ATTRIBUTES();
4980     LXW_PUSH_ATTRIBUTES_STR("fitToPage", "1");
4981 
4982     lxw_xml_empty_tag(self->file, "pageSetUpPr", &attributes);
4983 
4984     LXW_FREE_ATTRIBUTES();
4985 
4986 }
4987 
4988 /*
4989  * Write the <tabColor> element.
4990  */
4991 STATIC void
_worksheet_write_tab_color(lxw_worksheet * self)4992 _worksheet_write_tab_color(lxw_worksheet *self)
4993 {
4994     struct xml_attribute_list attributes;
4995     struct xml_attribute *attribute;
4996     char rgb_str[LXW_ATTR_32];
4997 
4998     if (self->tab_color == LXW_COLOR_UNSET)
4999         return;
5000 
5001     lxw_snprintf(rgb_str, LXW_ATTR_32, "FF%06X",
5002                  self->tab_color & LXW_COLOR_MASK);
5003 
5004     LXW_INIT_ATTRIBUTES();
5005     LXW_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
5006 
5007     lxw_xml_empty_tag(self->file, "tabColor", &attributes);
5008 
5009     LXW_FREE_ATTRIBUTES();
5010 }
5011 
5012 /*
5013  * Write the <outlinePr> element.
5014  */
5015 STATIC void
_worksheet_write_outline_pr(lxw_worksheet * self)5016 _worksheet_write_outline_pr(lxw_worksheet *self)
5017 {
5018     struct xml_attribute_list attributes;
5019     struct xml_attribute *attribute;
5020 
5021     if (!self->outline_changed)
5022         return;
5023 
5024     LXW_INIT_ATTRIBUTES();
5025 
5026     if (self->outline_style)
5027         LXW_PUSH_ATTRIBUTES_STR("applyStyles", "1");
5028 
5029     if (!self->outline_below)
5030         LXW_PUSH_ATTRIBUTES_STR("summaryBelow", "0");
5031 
5032     if (!self->outline_right)
5033         LXW_PUSH_ATTRIBUTES_STR("summaryRight", "0");
5034 
5035     if (!self->outline_on)
5036         LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0");
5037 
5038     lxw_xml_empty_tag(self->file, "outlinePr", &attributes);
5039 
5040     LXW_FREE_ATTRIBUTES();
5041 }
5042 
5043 /*
5044  * Write the <sheetPr> element for Sheet level properties.
5045  */
5046 STATIC void
_worksheet_write_sheet_pr(lxw_worksheet * self)5047 _worksheet_write_sheet_pr(lxw_worksheet *self)
5048 {
5049     struct xml_attribute_list attributes;
5050     struct xml_attribute *attribute;
5051 
5052     if (!self->fit_page
5053         && !self->filter_on
5054         && self->tab_color == LXW_COLOR_UNSET
5055         && !self->outline_changed
5056         && !self->vba_codename && !self->is_chartsheet) {
5057         return;
5058     }
5059 
5060     LXW_INIT_ATTRIBUTES();
5061 
5062     if (self->vba_codename)
5063         LXW_PUSH_ATTRIBUTES_STR("codeName", self->vba_codename);
5064 
5065     if (self->filter_on)
5066         LXW_PUSH_ATTRIBUTES_STR("filterMode", "1");
5067 
5068     if (self->fit_page || self->tab_color != LXW_COLOR_UNSET
5069         || self->outline_changed) {
5070         lxw_xml_start_tag(self->file, "sheetPr", &attributes);
5071         _worksheet_write_tab_color(self);
5072         _worksheet_write_outline_pr(self);
5073         _worksheet_write_page_set_up_pr(self);
5074         lxw_xml_end_tag(self->file, "sheetPr");
5075     }
5076     else {
5077         lxw_xml_empty_tag(self->file, "sheetPr", &attributes);
5078     }
5079 
5080     LXW_FREE_ATTRIBUTES();
5081 
5082 }
5083 
5084 /*
5085  * Write the <brk> element.
5086  */
5087 STATIC void
_worksheet_write_brk(lxw_worksheet * self,uint32_t id,uint32_t max)5088 _worksheet_write_brk(lxw_worksheet *self, uint32_t id, uint32_t max)
5089 {
5090     struct xml_attribute_list attributes;
5091     struct xml_attribute *attribute;
5092 
5093     LXW_INIT_ATTRIBUTES();
5094     LXW_PUSH_ATTRIBUTES_INT("id", id);
5095     LXW_PUSH_ATTRIBUTES_INT("max", max);
5096     LXW_PUSH_ATTRIBUTES_STR("man", "1");
5097 
5098     lxw_xml_empty_tag(self->file, "brk", &attributes);
5099 
5100     LXW_FREE_ATTRIBUTES();
5101 }
5102 
5103 /*
5104  * Write the <rowBreaks> element.
5105  */
5106 STATIC void
_worksheet_write_row_breaks(lxw_worksheet * self)5107 _worksheet_write_row_breaks(lxw_worksheet *self)
5108 {
5109     struct xml_attribute_list attributes;
5110     struct xml_attribute *attribute;
5111     uint16_t count = self->hbreaks_count;
5112     uint16_t i;
5113 
5114     if (!count)
5115         return;
5116 
5117     LXW_INIT_ATTRIBUTES();
5118     LXW_PUSH_ATTRIBUTES_INT("count", count);
5119     LXW_PUSH_ATTRIBUTES_INT("manualBreakCount", count);
5120 
5121     lxw_xml_start_tag(self->file, "rowBreaks", &attributes);
5122 
5123     for (i = 0; i < count; i++)
5124         _worksheet_write_brk(self, self->hbreaks[i], LXW_COL_MAX - 1);
5125 
5126     lxw_xml_end_tag(self->file, "rowBreaks");
5127 
5128     LXW_FREE_ATTRIBUTES();
5129 }
5130 
5131 /*
5132  * Write the <colBreaks> element.
5133  */
5134 STATIC void
_worksheet_write_col_breaks(lxw_worksheet * self)5135 _worksheet_write_col_breaks(lxw_worksheet *self)
5136 {
5137     struct xml_attribute_list attributes;
5138     struct xml_attribute *attribute;
5139     uint16_t count = self->vbreaks_count;
5140     uint16_t i;
5141 
5142     if (!count)
5143         return;
5144 
5145     LXW_INIT_ATTRIBUTES();
5146     LXW_PUSH_ATTRIBUTES_INT("count", count);
5147     LXW_PUSH_ATTRIBUTES_INT("manualBreakCount", count);
5148 
5149     lxw_xml_start_tag(self->file, "colBreaks", &attributes);
5150 
5151     for (i = 0; i < count; i++)
5152         _worksheet_write_brk(self, self->vbreaks[i], LXW_ROW_MAX - 1);
5153 
5154     lxw_xml_end_tag(self->file, "colBreaks");
5155 
5156     LXW_FREE_ATTRIBUTES();
5157 }
5158 
5159 /*
5160  * Write the <filter> element.
5161  */
5162 STATIC void
_worksheet_write_filter(lxw_worksheet * self,const char * str,double num,uint8_t criteria)5163 _worksheet_write_filter(lxw_worksheet *self, const char *str, double num,
5164                         uint8_t criteria)
5165 {
5166     struct xml_attribute_list attributes;
5167     struct xml_attribute *attribute;
5168 
5169     if (criteria == LXW_FILTER_CRITERIA_BLANKS)
5170         return;
5171 
5172     LXW_INIT_ATTRIBUTES();
5173 
5174     if (str)
5175         LXW_PUSH_ATTRIBUTES_STR("val", str);
5176     else
5177         LXW_PUSH_ATTRIBUTES_DBL("val", num);
5178 
5179     lxw_xml_empty_tag(self->file, "filter", &attributes);
5180 
5181     LXW_FREE_ATTRIBUTES();
5182 }
5183 
5184 /*
5185  * Write the <filterColumn> element as simple equality.
5186  */
5187 STATIC void
_worksheet_write_filter_standard(lxw_worksheet * self,lxw_filter_rule_obj * filter)5188 _worksheet_write_filter_standard(lxw_worksheet *self,
5189                                  lxw_filter_rule_obj *filter)
5190 {
5191     struct xml_attribute_list attributes;
5192     struct xml_attribute *attribute;
5193 
5194     LXW_INIT_ATTRIBUTES();
5195 
5196     if (filter->has_blanks) {
5197         LXW_PUSH_ATTRIBUTES_STR("blank", "1");
5198     }
5199 
5200     if (filter->type == LXW_FILTER_TYPE_SINGLE && filter->has_blanks) {
5201         lxw_xml_empty_tag(self->file, "filters", &attributes);
5202 
5203     }
5204     else {
5205         lxw_xml_start_tag(self->file, "filters", &attributes);
5206 
5207         /* Write the filter element. */
5208         if (filter->type == LXW_FILTER_TYPE_SINGLE) {
5209             _worksheet_write_filter(self, filter->value1_string,
5210                                     filter->value1, filter->criteria1);
5211         }
5212         else if (filter->type == LXW_FILTER_TYPE_AND
5213                  || filter->type == LXW_FILTER_TYPE_OR) {
5214             _worksheet_write_filter(self, filter->value1_string,
5215                                     filter->value1, filter->criteria1);
5216             _worksheet_write_filter(self, filter->value2_string,
5217                                     filter->value2, filter->criteria2);
5218         }
5219 
5220         lxw_xml_end_tag(self->file, "filters");
5221     }
5222 
5223     LXW_FREE_ATTRIBUTES();
5224 }
5225 
5226 /*
5227  * Write the <customFilter> element.
5228  */
5229 STATIC void
_worksheet_write_custom_filter(lxw_worksheet * self,const char * str,double num,uint8_t criteria)5230 _worksheet_write_custom_filter(lxw_worksheet *self, const char *str,
5231                                double num, uint8_t criteria)
5232 {
5233     struct xml_attribute_list attributes;
5234     struct xml_attribute *attribute;
5235 
5236     LXW_INIT_ATTRIBUTES();
5237 
5238     if (criteria == LXW_FILTER_CRITERIA_NOT_EQUAL_TO)
5239         LXW_PUSH_ATTRIBUTES_STR("operator", "notEqual");
5240     if (criteria == LXW_FILTER_CRITERIA_GREATER_THAN)
5241         LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThan");
5242     else if (criteria == LXW_FILTER_CRITERIA_GREATER_THAN_OR_EQUAL_TO)
5243         LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThanOrEqual");
5244     else if (criteria == LXW_FILTER_CRITERIA_LESS_THAN)
5245         LXW_PUSH_ATTRIBUTES_STR("operator", "lessThan");
5246     else if (criteria == LXW_FILTER_CRITERIA_LESS_THAN_OR_EQUAL_TO)
5247         LXW_PUSH_ATTRIBUTES_STR("operator", "lessThanOrEqual");
5248 
5249     if (str)
5250         LXW_PUSH_ATTRIBUTES_STR("val", str);
5251     else
5252         LXW_PUSH_ATTRIBUTES_DBL("val", num);
5253 
5254     lxw_xml_empty_tag(self->file, "customFilter", &attributes);
5255 
5256     LXW_FREE_ATTRIBUTES();
5257 }
5258 
5259 /*
5260  * Write the <filterColumn> element as a list.
5261  */
5262 STATIC void
_worksheet_write_filter_list(lxw_worksheet * self,lxw_filter_rule_obj * filter)5263 _worksheet_write_filter_list(lxw_worksheet *self, lxw_filter_rule_obj *filter)
5264 {
5265     struct xml_attribute_list attributes;
5266     struct xml_attribute *attribute;
5267     uint16_t i;
5268 
5269     LXW_INIT_ATTRIBUTES();
5270 
5271     if (filter->has_blanks) {
5272         LXW_PUSH_ATTRIBUTES_STR("blank", "1");
5273     }
5274 
5275     lxw_xml_start_tag(self->file, "filters", &attributes);
5276 
5277     for (i = 0; i < filter->num_list_filters; i++) {
5278         _worksheet_write_filter(self, filter->list[i], 0, 0);
5279     }
5280 
5281     lxw_xml_end_tag(self->file, "filters");
5282 
5283     LXW_FREE_ATTRIBUTES();
5284 }
5285 
5286 /*
5287  * Write the <filterColumn> element.
5288  */
5289 STATIC void
_worksheet_write_filter_custom(lxw_worksheet * self,lxw_filter_rule_obj * filter)5290 _worksheet_write_filter_custom(lxw_worksheet *self,
5291                                lxw_filter_rule_obj *filter)
5292 {
5293     struct xml_attribute_list attributes;
5294     struct xml_attribute *attribute;
5295 
5296     LXW_INIT_ATTRIBUTES();
5297 
5298     if (filter->type == LXW_FILTER_TYPE_AND)
5299         LXW_PUSH_ATTRIBUTES_STR("and", "1");
5300 
5301     lxw_xml_start_tag(self->file, "customFilters", &attributes);
5302 
5303     /* Write the filter element. */
5304     if (filter->type == LXW_FILTER_TYPE_SINGLE) {
5305         _worksheet_write_custom_filter(self, filter->value1_string,
5306                                        filter->value1, filter->criteria1);
5307     }
5308     else if (filter->type == LXW_FILTER_TYPE_AND
5309              || filter->type == LXW_FILTER_TYPE_OR) {
5310         _worksheet_write_custom_filter(self, filter->value1_string,
5311                                        filter->value1, filter->criteria1);
5312         _worksheet_write_custom_filter(self, filter->value2_string,
5313                                        filter->value2, filter->criteria2);
5314     }
5315 
5316     lxw_xml_end_tag(self->file, "customFilters");
5317 
5318     LXW_FREE_ATTRIBUTES();
5319 }
5320 
5321 /*
5322  * Write the <filterColumn> element.
5323  */
5324 STATIC void
_worksheet_write_filter_column(lxw_worksheet * self,lxw_filter_rule_obj * filter)5325 _worksheet_write_filter_column(lxw_worksheet *self,
5326                                lxw_filter_rule_obj *filter)
5327 {
5328     struct xml_attribute_list attributes;
5329     struct xml_attribute *attribute;
5330 
5331     if (!filter)
5332         return;
5333 
5334     LXW_INIT_ATTRIBUTES();
5335     LXW_PUSH_ATTRIBUTES_INT("colId", filter->col_num);
5336 
5337     lxw_xml_start_tag(self->file, "filterColumn", &attributes);
5338 
5339     if (filter->list)
5340         _worksheet_write_filter_list(self, filter);
5341     else if (filter->is_custom)
5342         _worksheet_write_filter_custom(self, filter);
5343     else
5344         _worksheet_write_filter_standard(self, filter);
5345 
5346     lxw_xml_end_tag(self->file, "filterColumn");
5347 
5348     LXW_FREE_ATTRIBUTES();
5349 }
5350 
5351 /*
5352  * Write the <autoFilter> element.
5353  */
5354 STATIC void
_worksheet_write_auto_filter(lxw_worksheet * self)5355 _worksheet_write_auto_filter(lxw_worksheet *self)
5356 {
5357     struct xml_attribute_list attributes;
5358     struct xml_attribute *attribute;
5359     char range[LXW_MAX_CELL_RANGE_LENGTH];
5360     uint16_t i;
5361 
5362     if (!self->autofilter.in_use)
5363         return;
5364 
5365     lxw_rowcol_to_range(range,
5366                         self->autofilter.first_row,
5367                         self->autofilter.first_col,
5368                         self->autofilter.last_row, self->autofilter.last_col);
5369 
5370     LXW_INIT_ATTRIBUTES();
5371     LXW_PUSH_ATTRIBUTES_STR("ref", range);
5372 
5373     if (self->autofilter.has_rules) {
5374         lxw_xml_start_tag(self->file, "autoFilter", &attributes);
5375 
5376         for (i = 0; i < self->num_filter_rules; i++)
5377             _worksheet_write_filter_column(self, self->filter_rules[i]);
5378 
5379         lxw_xml_end_tag(self->file, "autoFilter");
5380 
5381     }
5382     else {
5383         lxw_xml_empty_tag(self->file, "autoFilter", &attributes);
5384     }
5385 
5386     LXW_FREE_ATTRIBUTES();
5387 }
5388 
5389 /*
5390  * Write the <hyperlink> element for external links.
5391  */
5392 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)5393 _worksheet_write_hyperlink_external(lxw_worksheet *self, lxw_row_t row_num,
5394                                     lxw_col_t col_num, const char *location,
5395                                     const char *tooltip, uint16_t id)
5396 {
5397     struct xml_attribute_list attributes;
5398     struct xml_attribute *attribute;
5399     char ref[LXW_MAX_CELL_NAME_LENGTH];
5400     char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
5401 
5402     lxw_rowcol_to_cell(ref, row_num, col_num);
5403 
5404     lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id);
5405 
5406     LXW_INIT_ATTRIBUTES();
5407     LXW_PUSH_ATTRIBUTES_STR("ref", ref);
5408     LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
5409 
5410     if (location)
5411         LXW_PUSH_ATTRIBUTES_STR("location", location);
5412 
5413     if (tooltip)
5414         LXW_PUSH_ATTRIBUTES_STR("tooltip", tooltip);
5415 
5416     lxw_xml_empty_tag(self->file, "hyperlink", &attributes);
5417 
5418     LXW_FREE_ATTRIBUTES();
5419 }
5420 
5421 /*
5422  * Write the <hyperlink> element for internal links.
5423  */
5424 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)5425 _worksheet_write_hyperlink_internal(lxw_worksheet *self, lxw_row_t row_num,
5426                                     lxw_col_t col_num, const char *location,
5427                                     const char *display, const char *tooltip)
5428 {
5429     struct xml_attribute_list attributes;
5430     struct xml_attribute *attribute;
5431     char ref[LXW_MAX_CELL_NAME_LENGTH];
5432 
5433     lxw_rowcol_to_cell(ref, row_num, col_num);
5434 
5435     LXW_INIT_ATTRIBUTES();
5436     LXW_PUSH_ATTRIBUTES_STR("ref", ref);
5437 
5438     if (location)
5439         LXW_PUSH_ATTRIBUTES_STR("location", location);
5440 
5441     if (tooltip)
5442         LXW_PUSH_ATTRIBUTES_STR("tooltip", tooltip);
5443 
5444     if (display)
5445         LXW_PUSH_ATTRIBUTES_STR("display", display);
5446 
5447     lxw_xml_empty_tag(self->file, "hyperlink", &attributes);
5448 
5449     LXW_FREE_ATTRIBUTES();
5450 }
5451 
5452 /*
5453  * Process any stored hyperlinks in row/col order and write the <hyperlinks>
5454  * element. The attributes are different for internal and external links.
5455  */
5456 STATIC void
_worksheet_write_hyperlinks(lxw_worksheet * self)5457 _worksheet_write_hyperlinks(lxw_worksheet *self)
5458 {
5459 
5460     lxw_row *row;
5461     lxw_cell *link;
5462     lxw_rel_tuple *relationship;
5463 
5464     if (RB_EMPTY(self->hyperlinks))
5465         return;
5466 
5467     /* Write the hyperlink elements. */
5468     lxw_xml_start_tag(self->file, "hyperlinks", NULL);
5469 
5470     RB_FOREACH(row, lxw_table_rows, self->hyperlinks) {
5471 
5472         RB_FOREACH(link, lxw_table_cells, row->cells) {
5473 
5474             if (link->type == HYPERLINK_URL
5475                 || link->type == HYPERLINK_EXTERNAL) {
5476 
5477                 self->rel_count++;
5478 
5479                 relationship = calloc(1, sizeof(lxw_rel_tuple));
5480                 GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
5481 
5482                 relationship->type = lxw_strdup("/hyperlink");
5483                 GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
5484 
5485                 relationship->target = lxw_strdup(link->u.string);
5486                 GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
5487 
5488                 relationship->target_mode = lxw_strdup("External");
5489                 GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error);
5490 
5491                 STAILQ_INSERT_TAIL(self->external_hyperlinks, relationship,
5492                                    list_pointers);
5493 
5494                 _worksheet_write_hyperlink_external(self, link->row_num,
5495                                                     link->col_num,
5496                                                     link->user_data1,
5497                                                     link->user_data2,
5498                                                     self->rel_count);
5499             }
5500 
5501             if (link->type == HYPERLINK_INTERNAL) {
5502 
5503                 _worksheet_write_hyperlink_internal(self, link->row_num,
5504                                                     link->col_num,
5505                                                     link->u.string,
5506                                                     link->user_data1,
5507                                                     link->user_data2);
5508             }
5509 
5510         }
5511 
5512     }
5513 
5514     lxw_xml_end_tag(self->file, "hyperlinks");
5515     return;
5516 
5517 mem_error:
5518     if (relationship) {
5519         free(relationship->type);
5520         free(relationship->target);
5521         free(relationship->target_mode);
5522         free(relationship);
5523     }
5524     lxw_xml_end_tag(self->file, "hyperlinks");
5525 }
5526 
5527 /*
5528  * Write the <sheetProtection> element.
5529  */
5530 STATIC void
_worksheet_write_sheet_protection(lxw_worksheet * self,lxw_protection_obj * protect)5531 _worksheet_write_sheet_protection(lxw_worksheet *self,
5532                                   lxw_protection_obj *protect)
5533 {
5534     struct xml_attribute_list attributes;
5535     struct xml_attribute *attribute;
5536 
5537     if (!protect->is_configured)
5538         return;
5539 
5540     LXW_INIT_ATTRIBUTES();
5541 
5542     if (*protect->hash)
5543         LXW_PUSH_ATTRIBUTES_STR("password", protect->hash);
5544 
5545     if (!protect->no_sheet)
5546         LXW_PUSH_ATTRIBUTES_INT("sheet", 1);
5547 
5548     if (!protect->no_content)
5549         LXW_PUSH_ATTRIBUTES_INT("content", 1);
5550 
5551     if (!protect->objects)
5552         LXW_PUSH_ATTRIBUTES_INT("objects", 1);
5553 
5554     if (!protect->scenarios)
5555         LXW_PUSH_ATTRIBUTES_INT("scenarios", 1);
5556 
5557     if (protect->format_cells)
5558         LXW_PUSH_ATTRIBUTES_INT("formatCells", 0);
5559 
5560     if (protect->format_columns)
5561         LXW_PUSH_ATTRIBUTES_INT("formatColumns", 0);
5562 
5563     if (protect->format_rows)
5564         LXW_PUSH_ATTRIBUTES_INT("formatRows", 0);
5565 
5566     if (protect->insert_columns)
5567         LXW_PUSH_ATTRIBUTES_INT("insertColumns", 0);
5568 
5569     if (protect->insert_rows)
5570         LXW_PUSH_ATTRIBUTES_INT("insertRows", 0);
5571 
5572     if (protect->insert_hyperlinks)
5573         LXW_PUSH_ATTRIBUTES_INT("insertHyperlinks", 0);
5574 
5575     if (protect->delete_columns)
5576         LXW_PUSH_ATTRIBUTES_INT("deleteColumns", 0);
5577 
5578     if (protect->delete_rows)
5579         LXW_PUSH_ATTRIBUTES_INT("deleteRows", 0);
5580 
5581     if (protect->no_select_locked_cells)
5582         LXW_PUSH_ATTRIBUTES_INT("selectLockedCells", 1);
5583 
5584     if (protect->sort)
5585         LXW_PUSH_ATTRIBUTES_INT("sort", 0);
5586 
5587     if (protect->autofilter)
5588         LXW_PUSH_ATTRIBUTES_INT("autoFilter", 0);
5589 
5590     if (protect->pivot_tables)
5591         LXW_PUSH_ATTRIBUTES_INT("pivotTables", 0);
5592 
5593     if (protect->no_select_unlocked_cells)
5594         LXW_PUSH_ATTRIBUTES_INT("selectUnlockedCells", 1);
5595 
5596     lxw_xml_empty_tag(self->file, "sheetProtection", &attributes);
5597 
5598     LXW_FREE_ATTRIBUTES();
5599 }
5600 
5601 /*
5602  * Write the <legacyDrawing> element.
5603  */
5604 STATIC void
_worksheet_write_legacy_drawing(lxw_worksheet * self)5605 _worksheet_write_legacy_drawing(lxw_worksheet *self)
5606 {
5607     struct xml_attribute_list attributes;
5608     struct xml_attribute *attribute;
5609     char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
5610 
5611     if (!self->has_vml)
5612         return;
5613     else
5614         self->rel_count++;
5615 
5616     lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count);
5617     LXW_INIT_ATTRIBUTES();
5618     LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
5619 
5620     lxw_xml_empty_tag(self->file, "legacyDrawing", &attributes);
5621 
5622     LXW_FREE_ATTRIBUTES();
5623 
5624 }
5625 
5626 /*
5627  * Write the <legacyDrawingHF> element.
5628  */
5629 STATIC void
_worksheet_write_legacy_drawing_hf(lxw_worksheet * self)5630 _worksheet_write_legacy_drawing_hf(lxw_worksheet *self)
5631 {
5632     struct xml_attribute_list attributes;
5633     struct xml_attribute *attribute;
5634     char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
5635 
5636     if (!self->has_header_vml)
5637         return;
5638     else
5639         self->rel_count++;
5640 
5641     lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count);
5642     LXW_INIT_ATTRIBUTES();
5643     LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
5644 
5645     lxw_xml_empty_tag(self->file, "legacyDrawingHF", &attributes);
5646 
5647     LXW_FREE_ATTRIBUTES();
5648 
5649 }
5650 
5651 /*
5652  * Write the <picture> element.
5653  */
5654 STATIC void
_worksheet_write_picture(lxw_worksheet * self)5655 _worksheet_write_picture(lxw_worksheet *self)
5656 {
5657     struct xml_attribute_list attributes;
5658     struct xml_attribute *attribute;
5659     char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
5660 
5661     if (!self->has_background_image)
5662         return;
5663     else
5664         self->rel_count++;
5665 
5666     lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count);
5667     LXW_INIT_ATTRIBUTES();
5668     LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
5669 
5670     lxw_xml_empty_tag(self->file, "picture", &attributes);
5671 
5672     LXW_FREE_ATTRIBUTES();
5673 }
5674 
5675 /*
5676  * Write the <drawing> element.
5677  */
5678 STATIC void
_worksheet_write_drawing(lxw_worksheet * self,uint16_t id)5679 _worksheet_write_drawing(lxw_worksheet *self, uint16_t id)
5680 {
5681     struct xml_attribute_list attributes;
5682     struct xml_attribute *attribute;
5683     char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
5684 
5685     lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id);
5686     LXW_INIT_ATTRIBUTES();
5687     LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
5688 
5689     lxw_xml_empty_tag(self->file, "drawing", &attributes);
5690 
5691     LXW_FREE_ATTRIBUTES();
5692 
5693 }
5694 
5695 /*
5696  * Write the <drawing> elements.
5697  */
5698 STATIC void
_worksheet_write_drawings(lxw_worksheet * self)5699 _worksheet_write_drawings(lxw_worksheet *self)
5700 {
5701     if (!self->drawing)
5702         return;
5703 
5704     self->rel_count++;
5705 
5706     _worksheet_write_drawing(self, self->rel_count);
5707 }
5708 
5709 /*
5710  * Write the <formula1> element for numbers.
5711  */
5712 STATIC void
_worksheet_write_formula1_num(lxw_worksheet * self,double number)5713 _worksheet_write_formula1_num(lxw_worksheet *self, double number)
5714 {
5715     char data[LXW_ATTR_32];
5716 
5717     lxw_sprintf_dbl(data, number);
5718 
5719     lxw_xml_data_element(self->file, "formula1", data, NULL);
5720 }
5721 
5722 /*
5723  * Write the <formula1> element for strings/formulas.
5724  */
5725 STATIC void
_worksheet_write_formula1_str(lxw_worksheet * self,char * str)5726 _worksheet_write_formula1_str(lxw_worksheet *self, char *str)
5727 {
5728     lxw_xml_data_element(self->file, "formula1", str, NULL);
5729 }
5730 
5731 /*
5732  * Write the <formula2> element for numbers.
5733  */
5734 STATIC void
_worksheet_write_formula2_num(lxw_worksheet * self,double number)5735 _worksheet_write_formula2_num(lxw_worksheet *self, double number)
5736 {
5737     char data[LXW_ATTR_32];
5738 
5739     lxw_sprintf_dbl(data, number);
5740 
5741     lxw_xml_data_element(self->file, "formula2", data, NULL);
5742 }
5743 
5744 /*
5745  * Write the <formula2> element for strings/formulas.
5746  */
5747 STATIC void
_worksheet_write_formula2_str(lxw_worksheet * self,char * str)5748 _worksheet_write_formula2_str(lxw_worksheet *self, char *str)
5749 {
5750     lxw_xml_data_element(self->file, "formula2", str, NULL);
5751 }
5752 
5753 /*
5754  * Write the <dataValidation> element.
5755  */
5756 STATIC void
_worksheet_write_data_validation(lxw_worksheet * self,lxw_data_val_obj * validation)5757 _worksheet_write_data_validation(lxw_worksheet *self,
5758                                  lxw_data_val_obj *validation)
5759 {
5760     struct xml_attribute_list attributes;
5761     struct xml_attribute *attribute;
5762     uint8_t is_between = 0;
5763 
5764     LXW_INIT_ATTRIBUTES();
5765 
5766     switch (validation->validate) {
5767         case LXW_VALIDATION_TYPE_INTEGER:
5768         case LXW_VALIDATION_TYPE_INTEGER_FORMULA:
5769             LXW_PUSH_ATTRIBUTES_STR("type", "whole");
5770             break;
5771         case LXW_VALIDATION_TYPE_DECIMAL:
5772         case LXW_VALIDATION_TYPE_DECIMAL_FORMULA:
5773             LXW_PUSH_ATTRIBUTES_STR("type", "decimal");
5774             break;
5775         case LXW_VALIDATION_TYPE_LIST:
5776         case LXW_VALIDATION_TYPE_LIST_FORMULA:
5777             LXW_PUSH_ATTRIBUTES_STR("type", "list");
5778             break;
5779         case LXW_VALIDATION_TYPE_DATE:
5780         case LXW_VALIDATION_TYPE_DATE_FORMULA:
5781         case LXW_VALIDATION_TYPE_DATE_NUMBER:
5782             LXW_PUSH_ATTRIBUTES_STR("type", "date");
5783             break;
5784         case LXW_VALIDATION_TYPE_TIME:
5785         case LXW_VALIDATION_TYPE_TIME_FORMULA:
5786         case LXW_VALIDATION_TYPE_TIME_NUMBER:
5787             LXW_PUSH_ATTRIBUTES_STR("type", "time");
5788             break;
5789         case LXW_VALIDATION_TYPE_LENGTH:
5790         case LXW_VALIDATION_TYPE_LENGTH_FORMULA:
5791             LXW_PUSH_ATTRIBUTES_STR("type", "textLength");
5792             break;
5793         case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
5794             LXW_PUSH_ATTRIBUTES_STR("type", "custom");
5795             break;
5796     }
5797 
5798     switch (validation->criteria) {
5799         case LXW_VALIDATION_CRITERIA_EQUAL_TO:
5800             LXW_PUSH_ATTRIBUTES_STR("operator", "equal");
5801             break;
5802         case LXW_VALIDATION_CRITERIA_NOT_EQUAL_TO:
5803             LXW_PUSH_ATTRIBUTES_STR("operator", "notEqual");
5804             break;
5805         case LXW_VALIDATION_CRITERIA_LESS_THAN:
5806             LXW_PUSH_ATTRIBUTES_STR("operator", "lessThan");
5807             break;
5808         case LXW_VALIDATION_CRITERIA_LESS_THAN_OR_EQUAL_TO:
5809             LXW_PUSH_ATTRIBUTES_STR("operator", "lessThanOrEqual");
5810             break;
5811         case LXW_VALIDATION_CRITERIA_GREATER_THAN:
5812             LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThan");
5813             break;
5814         case LXW_VALIDATION_CRITERIA_GREATER_THAN_OR_EQUAL_TO:
5815             LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThanOrEqual");
5816             break;
5817         case LXW_VALIDATION_CRITERIA_BETWEEN:
5818             /* Between is the default for 2 formulas and isn't added. */
5819             is_between = 1;
5820             break;
5821         case LXW_VALIDATION_CRITERIA_NOT_BETWEEN:
5822             is_between = 1;
5823             LXW_PUSH_ATTRIBUTES_STR("operator", "notBetween");
5824             break;
5825     }
5826 
5827     if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_WARNING)
5828         LXW_PUSH_ATTRIBUTES_STR("errorStyle", "warning");
5829 
5830     if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_INFORMATION)
5831         LXW_PUSH_ATTRIBUTES_STR("errorStyle", "information");
5832 
5833     if (validation->ignore_blank)
5834         LXW_PUSH_ATTRIBUTES_INT("allowBlank", 1);
5835 
5836     if (validation->dropdown == LXW_VALIDATION_OFF)
5837         LXW_PUSH_ATTRIBUTES_INT("showDropDown", 1);
5838 
5839     if (validation->show_input)
5840         LXW_PUSH_ATTRIBUTES_INT("showInputMessage", 1);
5841 
5842     if (validation->show_error)
5843         LXW_PUSH_ATTRIBUTES_INT("showErrorMessage", 1);
5844 
5845     if (validation->error_title)
5846         LXW_PUSH_ATTRIBUTES_STR("errorTitle", validation->error_title);
5847 
5848     if (validation->error_message)
5849         LXW_PUSH_ATTRIBUTES_STR("error", validation->error_message);
5850 
5851     if (validation->input_title)
5852         LXW_PUSH_ATTRIBUTES_STR("promptTitle", validation->input_title);
5853 
5854     if (validation->input_message)
5855         LXW_PUSH_ATTRIBUTES_STR("prompt", validation->input_message);
5856 
5857     LXW_PUSH_ATTRIBUTES_STR("sqref", validation->sqref);
5858 
5859     if (validation->validate == LXW_VALIDATION_TYPE_ANY)
5860         lxw_xml_empty_tag(self->file, "dataValidation", &attributes);
5861     else
5862         lxw_xml_start_tag(self->file, "dataValidation", &attributes);
5863 
5864     /* Write the formula1 and formula2 elements. */
5865     switch (validation->validate) {
5866         case LXW_VALIDATION_TYPE_INTEGER:
5867         case LXW_VALIDATION_TYPE_DECIMAL:
5868         case LXW_VALIDATION_TYPE_LENGTH:
5869         case LXW_VALIDATION_TYPE_DATE:
5870         case LXW_VALIDATION_TYPE_TIME:
5871         case LXW_VALIDATION_TYPE_DATE_NUMBER:
5872         case LXW_VALIDATION_TYPE_TIME_NUMBER:
5873             _worksheet_write_formula1_num(self, validation->value_number);
5874             if (is_between)
5875                 _worksheet_write_formula2_num(self,
5876                                               validation->maximum_number);
5877             break;
5878         case LXW_VALIDATION_TYPE_INTEGER_FORMULA:
5879         case LXW_VALIDATION_TYPE_DECIMAL_FORMULA:
5880         case LXW_VALIDATION_TYPE_LENGTH_FORMULA:
5881         case LXW_VALIDATION_TYPE_DATE_FORMULA:
5882         case LXW_VALIDATION_TYPE_TIME_FORMULA:
5883         case LXW_VALIDATION_TYPE_LIST:
5884         case LXW_VALIDATION_TYPE_LIST_FORMULA:
5885         case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
5886             _worksheet_write_formula1_str(self, validation->value_formula);
5887             if (is_between)
5888                 _worksheet_write_formula2_str(self,
5889                                               validation->maximum_formula);
5890             break;
5891     }
5892 
5893     if (validation->validate != LXW_VALIDATION_TYPE_ANY)
5894         lxw_xml_end_tag(self->file, "dataValidation");
5895 
5896     LXW_FREE_ATTRIBUTES();
5897 }
5898 
5899 /*
5900  * Write the <dataValidations> element.
5901  */
5902 STATIC void
_worksheet_write_data_validations(lxw_worksheet * self)5903 _worksheet_write_data_validations(lxw_worksheet *self)
5904 {
5905     struct xml_attribute_list attributes;
5906     struct xml_attribute *attribute;
5907     lxw_data_val_obj *data_validation;
5908 
5909     if (self->num_validations == 0)
5910         return;
5911 
5912     LXW_INIT_ATTRIBUTES();
5913     LXW_PUSH_ATTRIBUTES_INT("count", self->num_validations);
5914 
5915     lxw_xml_start_tag(self->file, "dataValidations", &attributes);
5916 
5917     STAILQ_FOREACH(data_validation, self->data_validations, list_pointers) {
5918         /* Write the dataValidation element. */
5919         _worksheet_write_data_validation(self, data_validation);
5920     }
5921 
5922     lxw_xml_end_tag(self->file, "dataValidations");
5923 
5924     LXW_FREE_ATTRIBUTES();
5925 }
5926 
5927 /*
5928  * Write the <formula> element for strings.
5929  */
5930 STATIC void
_worksheet_write_formula_str(lxw_worksheet * self,char * data)5931 _worksheet_write_formula_str(lxw_worksheet *self, char *data)
5932 {
5933     lxw_xml_data_element(self->file, "formula", data, NULL);
5934 }
5935 
5936 /*
5937  * Write the <formula> element for numbers.
5938  */
5939 STATIC void
_worksheet_write_formula_num(lxw_worksheet * self,double num)5940 _worksheet_write_formula_num(lxw_worksheet *self, double num)
5941 {
5942     char data[LXW_ATTR_32];
5943 
5944     lxw_sprintf_dbl(data, num);
5945     lxw_xml_data_element(self->file, "formula", data, NULL);
5946 }
5947 
5948 /*
5949  * Write the <ext> element.
5950  */
5951 STATIC void
_worksheet_write_ext(lxw_worksheet * self,char * uri)5952 _worksheet_write_ext(lxw_worksheet *self, char *uri)
5953 {
5954     struct xml_attribute_list attributes;
5955     struct xml_attribute *attribute;
5956     char xmlns_x_14[] =
5957         "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main";
5958 
5959     LXW_INIT_ATTRIBUTES();
5960     LXW_PUSH_ATTRIBUTES_STR("xmlns:x14", xmlns_x_14);
5961     LXW_PUSH_ATTRIBUTES_STR("uri", uri);
5962 
5963     lxw_xml_start_tag(self->file, "ext", &attributes);
5964 
5965     LXW_FREE_ATTRIBUTES();
5966 }
5967 
5968 /*
5969  * Write the <extLst> dataBar extension element.
5970  */
5971 STATIC void
_worksheet_write_data_bar_ext(lxw_worksheet * self,lxw_cond_format_obj * cond_format)5972 _worksheet_write_data_bar_ext(lxw_worksheet *self,
5973                               lxw_cond_format_obj *cond_format)
5974 {
5975     /* Create a pseudo GUID for each unique Excel 2010 data bar. */
5976     cond_format->guid = calloc(1, LXW_GUID_LENGTH);
5977     lxw_snprintf(cond_format->guid, LXW_GUID_LENGTH,
5978                  "{DA7ABA51-AAAA-BBBB-%04X-%012X}",
5979                  self->index + 1, ++self->data_bar_2010_index);
5980 
5981     lxw_xml_start_tag(self->file, "extLst", NULL);
5982 
5983     _worksheet_write_ext(self, "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}");
5984 
5985     lxw_xml_data_element(self->file, "x14:id", cond_format->guid, NULL);
5986 
5987     lxw_xml_end_tag(self->file, "ext");
5988     lxw_xml_end_tag(self->file, "extLst");
5989 }
5990 
5991 /*
5992  * Write the <color> element.
5993  */
5994 STATIC void
_worksheet_write_color(lxw_worksheet * self,lxw_color_t color)5995 _worksheet_write_color(lxw_worksheet *self, lxw_color_t color)
5996 {
5997     struct xml_attribute_list attributes;
5998     struct xml_attribute *attribute;
5999     char rgb[LXW_ATTR_32];
6000 
6001     lxw_snprintf(rgb, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK);
6002 
6003     LXW_INIT_ATTRIBUTES();
6004     LXW_PUSH_ATTRIBUTES_STR("rgb", rgb);
6005 
6006     lxw_xml_empty_tag(self->file, "color", &attributes);
6007 
6008     LXW_FREE_ATTRIBUTES();
6009 }
6010 
6011 /*
6012  * Write the <cfvo> element for strings.
6013  */
6014 STATIC void
_worksheet_write_cfvo_str(lxw_worksheet * self,uint8_t rule_type,char * value,uint8_t data_bar_2010)6015 _worksheet_write_cfvo_str(lxw_worksheet *self, uint8_t rule_type,
6016                           char *value, uint8_t data_bar_2010)
6017 {
6018     struct xml_attribute_list attributes;
6019     struct xml_attribute *attribute;
6020 
6021     LXW_INIT_ATTRIBUTES();
6022 
6023     if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM)
6024         LXW_PUSH_ATTRIBUTES_STR("type", "min");
6025     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER)
6026         LXW_PUSH_ATTRIBUTES_STR("type", "num");
6027     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT)
6028         LXW_PUSH_ATTRIBUTES_STR("type", "percent");
6029     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE)
6030         LXW_PUSH_ATTRIBUTES_STR("type", "percentile");
6031     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA)
6032         LXW_PUSH_ATTRIBUTES_STR("type", "formula");
6033     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM)
6034         LXW_PUSH_ATTRIBUTES_STR("type", "max");
6035 
6036     if (!data_bar_2010 || (rule_type != LXW_CONDITIONAL_RULE_TYPE_MINIMUM
6037                            && rule_type != LXW_CONDITIONAL_RULE_TYPE_MAXIMUM))
6038         LXW_PUSH_ATTRIBUTES_STR("val", value);
6039 
6040     lxw_xml_empty_tag(self->file, "cfvo", &attributes);
6041 
6042     LXW_FREE_ATTRIBUTES();
6043 }
6044 
6045 /*
6046  * Write the <cfvo> element for numbers.
6047  */
6048 STATIC void
_worksheet_write_cfvo_num(lxw_worksheet * self,uint8_t rule_type,double value,uint8_t data_bar_2010)6049 _worksheet_write_cfvo_num(lxw_worksheet *self, uint8_t rule_type,
6050                           double value, uint8_t data_bar_2010)
6051 {
6052     struct xml_attribute_list attributes;
6053     struct xml_attribute *attribute;
6054 
6055     LXW_INIT_ATTRIBUTES();
6056 
6057     if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM)
6058         LXW_PUSH_ATTRIBUTES_STR("type", "min");
6059     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER)
6060         LXW_PUSH_ATTRIBUTES_STR("type", "num");
6061     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT)
6062         LXW_PUSH_ATTRIBUTES_STR("type", "percent");
6063     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE)
6064         LXW_PUSH_ATTRIBUTES_STR("type", "percentile");
6065     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA)
6066         LXW_PUSH_ATTRIBUTES_STR("type", "formula");
6067     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM)
6068         LXW_PUSH_ATTRIBUTES_STR("type", "max");
6069 
6070     if (!data_bar_2010 || (rule_type != LXW_CONDITIONAL_RULE_TYPE_MINIMUM
6071                            && rule_type != LXW_CONDITIONAL_RULE_TYPE_MAXIMUM))
6072         LXW_PUSH_ATTRIBUTES_DBL("val", value);
6073 
6074     lxw_xml_empty_tag(self->file, "cfvo", &attributes);
6075 
6076     LXW_FREE_ATTRIBUTES();
6077 }
6078 
6079 /*
6080  * Write the <iconSet> element.
6081  */
6082 STATIC void
_worksheet_write_icon_set(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6083 _worksheet_write_icon_set(lxw_worksheet *self,
6084                           lxw_cond_format_obj *cond_format)
6085 {
6086     struct xml_attribute_list attributes;
6087     struct xml_attribute *attribute;
6088     char *icon_set[] = {
6089         "3Arrows",
6090         "3ArrowsGray",
6091         "3Flags",
6092         "3TrafficLights",
6093         "3TrafficLights2",
6094         "3Signs",
6095         "3Symbols",
6096         "3Symbols2",
6097         "4Arrows",
6098         "4ArrowsGray",
6099         "4RedToBlack",
6100         "4Rating",
6101         "4TrafficLights",
6102         "5Arrows",
6103         "5ArrowsGray",
6104         "5Rating",
6105         "5Quarters",
6106     };
6107     uint8_t percent = LXW_CONDITIONAL_RULE_TYPE_PERCENT;
6108     uint8_t style = cond_format->icon_style;
6109 
6110     LXW_INIT_ATTRIBUTES();
6111 
6112     if (style != LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED)
6113         LXW_PUSH_ATTRIBUTES_STR("iconSet", icon_set[style]);
6114 
6115     if (cond_format->reverse_icons == LXW_TRUE)
6116         LXW_PUSH_ATTRIBUTES_STR("reverse", "1");
6117 
6118     if (cond_format->icons_only == LXW_TRUE)
6119         LXW_PUSH_ATTRIBUTES_STR("showValue", "0");
6120 
6121     lxw_xml_start_tag(self->file, "iconSet", &attributes);
6122 
6123     if (style < LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED) {
6124         _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE);
6125         _worksheet_write_cfvo_num(self, percent, 33, LXW_FALSE);
6126         _worksheet_write_cfvo_num(self, percent, 67, LXW_FALSE);
6127     }
6128 
6129     if (style >= LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED
6130         && style < LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED) {
6131         _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE);
6132         _worksheet_write_cfvo_num(self, percent, 25, LXW_FALSE);
6133         _worksheet_write_cfvo_num(self, percent, 50, LXW_FALSE);
6134         _worksheet_write_cfvo_num(self, percent, 75, LXW_FALSE);
6135     }
6136 
6137     if (style >= LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED
6138         && style <= LXW_CONDITIONAL_ICONS_5_QUARTERS) {
6139         _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE);
6140         _worksheet_write_cfvo_num(self, percent, 20, LXW_FALSE);
6141         _worksheet_write_cfvo_num(self, percent, 40, LXW_FALSE);
6142         _worksheet_write_cfvo_num(self, percent, 60, LXW_FALSE);
6143         _worksheet_write_cfvo_num(self, percent, 80, LXW_FALSE);
6144     }
6145 
6146     LXW_FREE_ATTRIBUTES();
6147 }
6148 
6149 /*
6150  * Write the <cfRule> element for data bar rules.
6151  */
6152 STATIC void
_worksheet_write_cf_rule_icons(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6153 _worksheet_write_cf_rule_icons(lxw_worksheet *self,
6154                                lxw_cond_format_obj *cond_format)
6155 {
6156     struct xml_attribute_list attributes;
6157     struct xml_attribute *attribute;
6158 
6159     LXW_INIT_ATTRIBUTES();
6160 
6161     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6162     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6163     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6164 
6165     _worksheet_write_icon_set(self, cond_format);
6166 
6167     lxw_xml_end_tag(self->file, "iconSet");
6168     lxw_xml_end_tag(self->file, "cfRule");
6169 
6170     LXW_FREE_ATTRIBUTES();
6171 }
6172 
6173 /*
6174  * Write the <dataBar> element.
6175  */
6176 STATIC void
_worksheet_write_data_bar(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6177 _worksheet_write_data_bar(lxw_worksheet *self,
6178                           lxw_cond_format_obj *cond_format)
6179 {
6180     struct xml_attribute_list attributes;
6181     struct xml_attribute *attribute;
6182 
6183     LXW_INIT_ATTRIBUTES();
6184 
6185     if (cond_format->bar_only)
6186         LXW_PUSH_ATTRIBUTES_STR("showValue", "0");
6187 
6188     lxw_xml_start_tag(self->file, "dataBar", &attributes);
6189 
6190     LXW_FREE_ATTRIBUTES();
6191 }
6192 
6193 /*
6194  * Write the <cfRule> element for data bar rules.
6195  */
6196 STATIC void
_worksheet_write_cf_rule_data_bar(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6197 _worksheet_write_cf_rule_data_bar(lxw_worksheet *self,
6198                                   lxw_cond_format_obj *cond_format)
6199 {
6200     struct xml_attribute_list attributes;
6201     struct xml_attribute *attribute;
6202 
6203     LXW_INIT_ATTRIBUTES();
6204 
6205     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6206     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6207     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6208 
6209     _worksheet_write_data_bar(self, cond_format);
6210 
6211     if (cond_format->min_value_string) {
6212         _worksheet_write_cfvo_str(self, cond_format->min_rule_type,
6213                                   cond_format->min_value_string,
6214                                   cond_format->data_bar_2010);
6215     }
6216     else {
6217         _worksheet_write_cfvo_num(self, cond_format->min_rule_type,
6218                                   cond_format->min_value,
6219                                   cond_format->data_bar_2010);
6220     }
6221 
6222     if (cond_format->max_value_string) {
6223         _worksheet_write_cfvo_str(self, cond_format->max_rule_type,
6224                                   cond_format->max_value_string,
6225                                   cond_format->data_bar_2010);
6226     }
6227     else {
6228         _worksheet_write_cfvo_num(self, cond_format->max_rule_type,
6229                                   cond_format->max_value,
6230                                   cond_format->data_bar_2010);
6231     }
6232 
6233     _worksheet_write_color(self, cond_format->bar_color);
6234 
6235     lxw_xml_end_tag(self->file, "dataBar");
6236 
6237     if (cond_format->data_bar_2010)
6238         _worksheet_write_data_bar_ext(self, cond_format);
6239 
6240     lxw_xml_end_tag(self->file, "cfRule");
6241 
6242     LXW_FREE_ATTRIBUTES();
6243 }
6244 
6245 /*
6246  * Write the <cfRule> element for 2 and 3 color scale rules.
6247  */
6248 STATIC void
_worksheet_write_cf_rule_color_scale(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6249 _worksheet_write_cf_rule_color_scale(lxw_worksheet *self,
6250                                      lxw_cond_format_obj *cond_format)
6251 {
6252     struct xml_attribute_list attributes;
6253     struct xml_attribute *attribute;
6254 
6255     LXW_INIT_ATTRIBUTES();
6256 
6257     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6258     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6259     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6260 
6261     lxw_xml_start_tag(self->file, "colorScale", NULL);
6262 
6263     if (cond_format->min_value_string) {
6264         _worksheet_write_cfvo_str(self, cond_format->min_rule_type,
6265                                   cond_format->min_value_string, LXW_FALSE);
6266     }
6267     else {
6268         _worksheet_write_cfvo_num(self, cond_format->min_rule_type,
6269                                   cond_format->min_value, LXW_FALSE);
6270     }
6271 
6272     if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) {
6273         if (cond_format->mid_value_string) {
6274             _worksheet_write_cfvo_str(self, cond_format->mid_rule_type,
6275                                       cond_format->mid_value_string,
6276                                       LXW_FALSE);
6277         }
6278         else {
6279             _worksheet_write_cfvo_num(self, cond_format->mid_rule_type,
6280                                       cond_format->mid_value, LXW_FALSE);
6281         }
6282     }
6283 
6284     if (cond_format->max_value_string) {
6285         _worksheet_write_cfvo_str(self, cond_format->max_rule_type,
6286                                   cond_format->max_value_string, LXW_FALSE);
6287     }
6288     else {
6289         _worksheet_write_cfvo_num(self, cond_format->max_rule_type,
6290                                   cond_format->max_value, LXW_FALSE);
6291     }
6292 
6293     _worksheet_write_color(self, cond_format->min_color);
6294 
6295     if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE)
6296         _worksheet_write_color(self, cond_format->mid_color);
6297 
6298     _worksheet_write_color(self, cond_format->max_color);
6299 
6300     lxw_xml_end_tag(self->file, "colorScale");
6301     lxw_xml_end_tag(self->file, "cfRule");
6302 
6303     LXW_FREE_ATTRIBUTES();
6304 }
6305 
6306 /*
6307  * Write the <cfRule> element for formula rules.
6308  */
6309 STATIC void
_worksheet_write_cf_rule_formula(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6310 _worksheet_write_cf_rule_formula(lxw_worksheet *self,
6311                                  lxw_cond_format_obj *cond_format)
6312 {
6313     struct xml_attribute_list attributes;
6314     struct xml_attribute *attribute;
6315 
6316     LXW_INIT_ATTRIBUTES();
6317 
6318     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6319 
6320     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6321         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6322 
6323     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6324 
6325     if (cond_format->stop_if_true)
6326         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6327 
6328     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6329 
6330     _worksheet_write_formula_str(self, cond_format->min_value_string);
6331 
6332     lxw_xml_end_tag(self->file, "cfRule");
6333 
6334     LXW_FREE_ATTRIBUTES();
6335 }
6336 
6337 /*
6338  * Write the <cfRule> element for top and bottom rules.
6339  */
6340 STATIC void
_worksheet_write_cf_rule_top(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6341 _worksheet_write_cf_rule_top(lxw_worksheet *self,
6342                              lxw_cond_format_obj *cond_format)
6343 {
6344     struct xml_attribute_list attributes;
6345     struct xml_attribute *attribute;
6346 
6347     LXW_INIT_ATTRIBUTES();
6348 
6349     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6350 
6351     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6352         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6353 
6354     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6355 
6356     if (cond_format->stop_if_true)
6357         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6358 
6359     if (cond_format->criteria ==
6360         LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT)
6361         LXW_PUSH_ATTRIBUTES_INT("percent", 1);
6362 
6363     if (cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM)
6364         LXW_PUSH_ATTRIBUTES_INT("bottom", 1);
6365 
6366     /* Rank must be an int in the range 1-1000 . */
6367     if (cond_format->min_value < 1.0 || cond_format->min_value > 1.0)
6368         LXW_PUSH_ATTRIBUTES_DBL("rank", 10);
6369     else
6370         LXW_PUSH_ATTRIBUTES_DBL("rank", (uint16_t) cond_format->min_value);
6371 
6372     lxw_xml_empty_tag(self->file, "cfRule", &attributes);
6373 
6374     LXW_FREE_ATTRIBUTES();
6375 }
6376 
6377 /*
6378  * Write the <cfRule> element for unique/duplicate rules.
6379  */
6380 STATIC void
_worksheet_write_cf_rule_duplicate(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6381 _worksheet_write_cf_rule_duplicate(lxw_worksheet *self,
6382                                    lxw_cond_format_obj *cond_format)
6383 {
6384     struct xml_attribute_list attributes;
6385     struct xml_attribute *attribute;
6386 
6387     LXW_INIT_ATTRIBUTES();
6388 
6389     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6390 
6391     /* Set the attributes common to all rule types. */
6392     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6393         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6394 
6395     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6396 
6397     lxw_xml_empty_tag(self->file, "cfRule", &attributes);
6398 
6399     LXW_FREE_ATTRIBUTES();
6400 }
6401 
6402 /*
6403  * Write the <cfRule> element for averages rules.
6404  */
6405 STATIC void
_worksheet_write_cf_rule_average(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6406 _worksheet_write_cf_rule_average(lxw_worksheet *self,
6407                                  lxw_cond_format_obj *cond_format)
6408 {
6409     struct xml_attribute_list attributes;
6410     struct xml_attribute *attribute;
6411     uint8_t criteria = cond_format->criteria;
6412 
6413     LXW_INIT_ATTRIBUTES();
6414 
6415     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6416 
6417     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6418         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6419 
6420     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6421 
6422     if (cond_format->stop_if_true)
6423         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6424 
6425     if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW
6426         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL
6427         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW
6428         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW
6429         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW)
6430         LXW_PUSH_ATTRIBUTES_INT("aboveAverage", 0);
6431 
6432     if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE_OR_EQUAL
6433         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL)
6434         LXW_PUSH_ATTRIBUTES_INT("equalAverage", 1);
6435 
6436     if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_ABOVE
6437         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW)
6438         LXW_PUSH_ATTRIBUTES_INT("stdDev", 1);
6439 
6440     if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_ABOVE
6441         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW)
6442         LXW_PUSH_ATTRIBUTES_INT("stdDev", 2);
6443 
6444     if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_ABOVE
6445         || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW)
6446         LXW_PUSH_ATTRIBUTES_INT("stdDev", 3);
6447 
6448     lxw_xml_empty_tag(self->file, "cfRule", &attributes);
6449 
6450     LXW_FREE_ATTRIBUTES();
6451 }
6452 
6453 /*
6454  * Write the <cfRule> element for time_period rules.
6455  */
6456 STATIC void
_worksheet_write_cf_rule_time_period(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6457 _worksheet_write_cf_rule_time_period(lxw_worksheet *self,
6458                                      lxw_cond_format_obj *cond_format)
6459 {
6460     struct xml_attribute_list attributes;
6461     struct xml_attribute *attribute;
6462     char formula[LXW_MAX_ATTRIBUTE_LENGTH];
6463     uint8_t pos;
6464     uint8_t criteria = cond_format->criteria;
6465     char *first_cell = cond_format->first_cell;
6466     char *time_periods[] = {
6467         "yesterday",
6468         "today",
6469         "tomorrow",
6470         "last7Days",
6471         "lastWeek",
6472         "thisWeek",
6473         "nextWeek",
6474         "lastMonth",
6475         "thisMonth",
6476         "nextMonth",
6477     };
6478 
6479     LXW_INIT_ATTRIBUTES();
6480 
6481     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6482 
6483     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6484         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6485 
6486     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6487 
6488     pos = criteria - LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY;
6489     LXW_PUSH_ATTRIBUTES_STR("timePeriod", time_periods[pos]);
6490 
6491     if (cond_format->stop_if_true)
6492         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6493 
6494     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6495 
6496     if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY) {
6497         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6498                      "FLOOR(%s,1)=TODAY()-1", first_cell);
6499         _worksheet_write_formula_str(self, formula);
6500     }
6501     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TODAY) {
6502         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6503                      "FLOOR(%s,1)=TODAY()", first_cell);
6504         _worksheet_write_formula_str(self, formula);
6505     }
6506     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TOMORROW) {
6507         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6508                      "FLOOR(%s,1)=TODAY()+1", first_cell);
6509         _worksheet_write_formula_str(self, formula);
6510     }
6511     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_7_DAYS) {
6512         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6513                      "AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())",
6514                      first_cell, first_cell);
6515         _worksheet_write_formula_str(self, formula);
6516     }
6517     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_WEEK) {
6518         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6519                      "AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),"
6520                      "TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))",
6521                      first_cell, first_cell);
6522         _worksheet_write_formula_str(self, formula);
6523     }
6524     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_WEEK) {
6525         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6526                      "AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,"
6527                      "ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))",
6528                      first_cell, first_cell);
6529         _worksheet_write_formula_str(self, formula);
6530     }
6531     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_WEEK) {
6532         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6533                      "AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),"
6534                      "ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))",
6535                      first_cell, first_cell);
6536         _worksheet_write_formula_str(self, formula);
6537     }
6538     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_MONTH) {
6539         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6540                      "AND(MONTH(%s)=MONTH(TODAY())-1,OR(YEAR(%s)=YEAR("
6541                      "TODAY()),AND(MONTH(%s)=1,YEAR(A1)=YEAR(TODAY())-1)))",
6542                      first_cell, first_cell, first_cell);
6543         _worksheet_write_formula_str(self, formula);
6544     }
6545     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_MONTH) {
6546         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6547                      "AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))",
6548                      first_cell, first_cell);
6549         _worksheet_write_formula_str(self, formula);
6550     }
6551     else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH) {
6552         lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH,
6553                      "AND(MONTH(%s)=MONTH(TODAY())+1,OR(YEAR(%s)=YEAR("
6554                      "TODAY()),AND(MONTH(%s)=12,YEAR(%s)=YEAR(TODAY())+1)))",
6555                      first_cell, first_cell, first_cell, first_cell);
6556         _worksheet_write_formula_str(self, formula);
6557     }
6558 
6559     lxw_xml_end_tag(self->file, "cfRule");
6560 
6561     LXW_FREE_ATTRIBUTES();
6562 }
6563 
6564 /*
6565  * Write the <cfRule> element for blanks/no_blanks, errors/no_errors rules.
6566  */
6567 STATIC void
_worksheet_write_cf_rule_blanks(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6568 _worksheet_write_cf_rule_blanks(lxw_worksheet *self,
6569                                 lxw_cond_format_obj *cond_format)
6570 {
6571     struct xml_attribute_list attributes;
6572     struct xml_attribute *attribute;
6573     char formula[LXW_ATTR_32];
6574     uint8_t type = cond_format->type;
6575 
6576     LXW_INIT_ATTRIBUTES();
6577 
6578     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6579 
6580     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6581         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6582 
6583     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6584 
6585     if (cond_format->stop_if_true)
6586         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6587 
6588     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6589 
6590     if (type == LXW_CONDITIONAL_TYPE_BLANKS) {
6591         lxw_snprintf(formula, LXW_ATTR_32, "LEN(TRIM(%s))=0",
6592                      cond_format->first_cell);
6593         _worksheet_write_formula_str(self, formula);
6594     }
6595     else if (type == LXW_CONDITIONAL_TYPE_NO_BLANKS) {
6596         lxw_snprintf(formula, LXW_ATTR_32, "LEN(TRIM(%s))>0",
6597                      cond_format->first_cell);
6598         _worksheet_write_formula_str(self, formula);
6599     }
6600     else if (type == LXW_CONDITIONAL_TYPE_ERRORS) {
6601         lxw_snprintf(formula, LXW_ATTR_32, "ISERROR(%s)",
6602                      cond_format->first_cell);
6603         _worksheet_write_formula_str(self, formula);
6604     }
6605     else if (type == LXW_CONDITIONAL_TYPE_NO_ERRORS) {
6606         lxw_snprintf(formula, LXW_ATTR_32, "NOT(ISERROR(%s))",
6607                      cond_format->first_cell);
6608         _worksheet_write_formula_str(self, formula);
6609     }
6610 
6611     lxw_xml_end_tag(self->file, "cfRule");
6612 
6613     LXW_FREE_ATTRIBUTES();
6614 }
6615 
6616 /*
6617  * Write the <cfRule> element for text rules.
6618  */
6619 STATIC void
_worksheet_write_cf_rule_text(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6620 _worksheet_write_cf_rule_text(lxw_worksheet *self,
6621                               lxw_cond_format_obj *cond_format)
6622 {
6623     struct xml_attribute_list attributes;
6624     struct xml_attribute *attribute;
6625     uint8_t pos;
6626     char formula[LXW_ATTR_32 * 2];
6627     char *operators[] = {
6628         "containsText",
6629         "notContains",
6630         "beginsWith",
6631         "endsWith",
6632     };
6633     uint8_t criteria = cond_format->criteria;
6634 
6635     LXW_INIT_ATTRIBUTES();
6636 
6637     if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING)
6638         LXW_PUSH_ATTRIBUTES_STR("type", "containsText");
6639     else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING)
6640         LXW_PUSH_ATTRIBUTES_STR("type", "notContainsText");
6641     else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH)
6642         LXW_PUSH_ATTRIBUTES_STR("type", "beginsWith");
6643     else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH)
6644         LXW_PUSH_ATTRIBUTES_STR("type", "endsWith");
6645 
6646     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6647         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6648 
6649     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6650 
6651     if (cond_format->stop_if_true)
6652         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6653 
6654     pos = criteria - LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING;
6655     LXW_PUSH_ATTRIBUTES_STR("operator", operators[pos]);
6656 
6657     LXW_PUSH_ATTRIBUTES_STR("text", cond_format->min_value_string);
6658 
6659     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6660 
6661     if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING) {
6662         lxw_snprintf(formula, LXW_ATTR_32 * 2,
6663                      "NOT(ISERROR(SEARCH(\"%s\",%s)))",
6664                      cond_format->min_value_string, cond_format->first_cell);
6665         _worksheet_write_formula_str(self, formula);
6666     }
6667     else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING) {
6668         lxw_snprintf(formula, LXW_ATTR_32 * 2,
6669                      "ISERROR(SEARCH(\"%s\",%s))",
6670                      cond_format->min_value_string, cond_format->first_cell);
6671         _worksheet_write_formula_str(self, formula);
6672     }
6673     else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH) {
6674         lxw_snprintf(formula, LXW_ATTR_32 * 2,
6675                      "LEFT(%s,%d)=\"%s\"",
6676                      cond_format->first_cell,
6677                      (uint16_t) strlen(cond_format->min_value_string),
6678                      cond_format->min_value_string);
6679         _worksheet_write_formula_str(self, formula);
6680     }
6681     else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) {
6682         lxw_snprintf(formula, LXW_ATTR_32 * 2,
6683                      "RIGHT(%s,%d)=\"%s\"",
6684                      cond_format->first_cell,
6685                      (uint16_t) strlen(cond_format->min_value_string),
6686                      cond_format->min_value_string);
6687         _worksheet_write_formula_str(self, formula);
6688     }
6689 
6690     lxw_xml_end_tag(self->file, "cfRule");
6691 
6692     LXW_FREE_ATTRIBUTES();
6693 }
6694 
6695 /*
6696  * Write the <cfRule> element for cell rules.
6697  */
6698 STATIC void
_worksheet_write_cf_rule_cell(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6699 _worksheet_write_cf_rule_cell(lxw_worksheet *self,
6700                               lxw_cond_format_obj *cond_format)
6701 {
6702     struct xml_attribute_list attributes;
6703     struct xml_attribute *attribute;
6704     char *operators[] = {
6705         "none",
6706         "equal",
6707         "notEqual",
6708         "greaterThan",
6709         "lessThan",
6710         "greaterThanOrEqual",
6711         "lessThanOrEqual",
6712         "between",
6713         "notBetween",
6714     };
6715 
6716     LXW_INIT_ATTRIBUTES();
6717 
6718     LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string);
6719 
6720     if (cond_format->dxf_index != LXW_PROPERTY_UNSET)
6721         LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index);
6722 
6723     LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority);
6724 
6725     if (cond_format->stop_if_true)
6726         LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1);
6727 
6728     LXW_PUSH_ATTRIBUTES_STR("operator", operators[cond_format->criteria]);
6729 
6730     lxw_xml_start_tag(self->file, "cfRule", &attributes);
6731 
6732     if (cond_format->min_value_string)
6733         _worksheet_write_formula_str(self, cond_format->min_value_string);
6734     else
6735         _worksheet_write_formula_num(self, cond_format->min_value);
6736 
6737     if (cond_format->has_max) {
6738         if (cond_format->max_value_string)
6739             _worksheet_write_formula_str(self, cond_format->max_value_string);
6740         else
6741             _worksheet_write_formula_num(self, cond_format->max_value);
6742     }
6743 
6744     lxw_xml_end_tag(self->file, "cfRule");
6745 
6746     LXW_FREE_ATTRIBUTES();
6747 }
6748 
6749 /*
6750  * Write the <cfRule> element.
6751  */
6752 STATIC void
_worksheet_write_cf_rule(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6753 _worksheet_write_cf_rule(lxw_worksheet *self,
6754                          lxw_cond_format_obj *cond_format)
6755 {
6756     if (cond_format->type == LXW_CONDITIONAL_TYPE_CELL) {
6757 
6758         _worksheet_write_cf_rule_cell(self, cond_format);
6759     }
6760     else if (cond_format->type == LXW_CONDITIONAL_TYPE_TEXT) {
6761 
6762         _worksheet_write_cf_rule_text(self, cond_format);
6763     }
6764     else if (cond_format->type == LXW_CONDITIONAL_TYPE_TIME_PERIOD) {
6765 
6766         _worksheet_write_cf_rule_time_period(self, cond_format);
6767     }
6768     else if (cond_format->type == LXW_CONDITIONAL_TYPE_DUPLICATE
6769              || cond_format->type == LXW_CONDITIONAL_TYPE_UNIQUE) {
6770 
6771         _worksheet_write_cf_rule_duplicate(self, cond_format);
6772     }
6773     else if (cond_format->type == LXW_CONDITIONAL_TYPE_AVERAGE) {
6774 
6775         _worksheet_write_cf_rule_average(self, cond_format);
6776     }
6777     else if (cond_format->type == LXW_CONDITIONAL_TYPE_TOP
6778              || cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) {
6779 
6780         _worksheet_write_cf_rule_top(self, cond_format);
6781     }
6782     else if (cond_format->type == LXW_CONDITIONAL_TYPE_BLANKS
6783              || cond_format->type == LXW_CONDITIONAL_TYPE_NO_BLANKS
6784              || cond_format->type == LXW_CONDITIONAL_TYPE_ERRORS
6785              || cond_format->type == LXW_CONDITIONAL_TYPE_NO_ERRORS) {
6786 
6787         _worksheet_write_cf_rule_blanks(self, cond_format);
6788     }
6789     else if (cond_format->type == LXW_CONDITIONAL_TYPE_FORMULA) {
6790 
6791         _worksheet_write_cf_rule_formula(self, cond_format);
6792     }
6793     else if (cond_format->type == LXW_CONDITIONAL_2_COLOR_SCALE
6794              || cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) {
6795 
6796         _worksheet_write_cf_rule_color_scale(self, cond_format);
6797     }
6798     else if (cond_format->type == LXW_CONDITIONAL_DATA_BAR) {
6799 
6800         _worksheet_write_cf_rule_data_bar(self, cond_format);
6801     }
6802     else if (cond_format->type == LXW_CONDITIONAL_TYPE_ICON_SETS) {
6803 
6804         _worksheet_write_cf_rule_icons(self, cond_format);
6805     }
6806 
6807 }
6808 
6809 /*
6810  * Write the <conditionalFormatting> element.
6811  */
6812 STATIC void
_worksheet_write_conditional_formatting(lxw_worksheet * self,lxw_cond_format_hash_element * element)6813 _worksheet_write_conditional_formatting(lxw_worksheet *self,
6814                                         lxw_cond_format_hash_element *element)
6815 {
6816     struct xml_attribute_list attributes;
6817     struct xml_attribute *attribute;
6818     lxw_cond_format_obj *cond_format;
6819 
6820     LXW_INIT_ATTRIBUTES();
6821     LXW_PUSH_ATTRIBUTES_STR("sqref", element->sqref);
6822 
6823     lxw_xml_start_tag(self->file, "conditionalFormatting", &attributes);
6824 
6825     STAILQ_FOREACH(cond_format, element->cond_formats, list_pointers) {
6826         /* Write the cfRule element. */
6827         _worksheet_write_cf_rule(self, cond_format);
6828     }
6829 
6830     lxw_xml_end_tag(self->file, "conditionalFormatting");
6831 
6832     LXW_FREE_ATTRIBUTES();
6833 }
6834 
6835 /*
6836  * Write the conditional formatting> elements.
6837  */
6838 STATIC void
_worksheet_write_conditional_formats(lxw_worksheet * self)6839 _worksheet_write_conditional_formats(lxw_worksheet *self)
6840 {
6841     lxw_cond_format_hash_element *element;
6842     lxw_cond_format_hash_element *next_element;
6843 
6844     for (element = RB_MIN(lxw_cond_format_hash, self->conditional_formats);
6845          element; element = next_element) {
6846 
6847         _worksheet_write_conditional_formatting(self, element);
6848 
6849         next_element =
6850             RB_NEXT(lxw_cond_format_hash, self->conditional_formats, element);
6851     }
6852 }
6853 
6854 /*
6855  * Write the <x14:xxxColor> elements for data bar conditional formats.
6856  */
6857 STATIC void
_worksheet_write_x14_color(lxw_worksheet * self,char * type,lxw_color_t color)6858 _worksheet_write_x14_color(lxw_worksheet *self, char *type, lxw_color_t color)
6859 {
6860     struct xml_attribute_list attributes;
6861     struct xml_attribute *attribute;
6862     char rgb[LXW_ATTR_32];
6863 
6864     lxw_snprintf(rgb, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK);
6865 
6866     LXW_INIT_ATTRIBUTES();
6867     LXW_PUSH_ATTRIBUTES_STR("rgb", rgb);
6868     lxw_xml_empty_tag(self->file, type, &attributes);
6869 
6870     LXW_FREE_ATTRIBUTES();
6871 }
6872 
6873 /*
6874  * Write the <x14:cfvo> element.
6875  */
6876 STATIC void
_worksheet_write_x14_cfvo(lxw_worksheet * self,uint8_t rule_type,double number,char * string)6877 _worksheet_write_x14_cfvo(lxw_worksheet *self, uint8_t rule_type,
6878                           double number, char *string)
6879 {
6880     struct xml_attribute_list attributes;
6881     struct xml_attribute *attribute;
6882     char data[LXW_ATTR_32];
6883     uint8_t has_value = LXW_FALSE;
6884 
6885     LXW_INIT_ATTRIBUTES();
6886 
6887     if (!string)
6888         lxw_sprintf_dbl(data, number);
6889 
6890     if (rule_type == LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN) {
6891         LXW_PUSH_ATTRIBUTES_STR("type", "autoMin");
6892         has_value = LXW_FALSE;
6893     }
6894     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) {
6895         LXW_PUSH_ATTRIBUTES_STR("type", "min");
6896         has_value = LXW_FALSE;
6897     }
6898     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) {
6899         LXW_PUSH_ATTRIBUTES_STR("type", "num");
6900         has_value = LXW_TRUE;
6901     }
6902     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) {
6903         LXW_PUSH_ATTRIBUTES_STR("type", "percent");
6904         has_value = LXW_TRUE;
6905     }
6906     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) {
6907         LXW_PUSH_ATTRIBUTES_STR("type", "percentile");
6908         has_value = LXW_TRUE;
6909     }
6910     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) {
6911         LXW_PUSH_ATTRIBUTES_STR("type", "formula");
6912         has_value = LXW_TRUE;
6913     }
6914     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) {
6915         LXW_PUSH_ATTRIBUTES_STR("type", "max");
6916         has_value = LXW_FALSE;
6917     }
6918     else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX) {
6919         LXW_PUSH_ATTRIBUTES_STR("type", "autoMax");
6920         has_value = LXW_FALSE;
6921     }
6922 
6923     if (has_value) {
6924         lxw_xml_start_tag(self->file, "x14:cfvo", &attributes);
6925 
6926         if (string)
6927             lxw_xml_data_element(self->file, "xm:f", string, NULL);
6928         else
6929             lxw_xml_data_element(self->file, "xm:f", data, NULL);
6930 
6931         lxw_xml_end_tag(self->file, "x14:cfvo");
6932     }
6933     else {
6934         lxw_xml_empty_tag(self->file, "x14:cfvo", &attributes);
6935     }
6936     LXW_FREE_ATTRIBUTES();
6937 }
6938 
6939 /*
6940  * Write the <x14:dataBar> element.
6941  */
6942 STATIC void
_worksheet_write_x14_data_bar(lxw_worksheet * self,lxw_cond_format_obj * cond_format)6943 _worksheet_write_x14_data_bar(lxw_worksheet *self,
6944                               lxw_cond_format_obj *cond_format)
6945 {
6946     struct xml_attribute_list attributes;
6947     struct xml_attribute *attribute;
6948     char min_length[] = "0";
6949     char max_length[] = "100";
6950     char border[] = "1";
6951 
6952     LXW_INIT_ATTRIBUTES();
6953     LXW_PUSH_ATTRIBUTES_STR("minLength", min_length);
6954     LXW_PUSH_ATTRIBUTES_STR("maxLength", max_length);
6955 
6956     if (!cond_format->bar_no_border)
6957         LXW_PUSH_ATTRIBUTES_STR("border", border);
6958 
6959     if (cond_format->bar_solid)
6960         LXW_PUSH_ATTRIBUTES_STR("gradient", "0");
6961 
6962     if (cond_format->bar_direction ==
6963         LXW_CONDITIONAL_BAR_DIRECTION_RIGHT_TO_LEFT)
6964         LXW_PUSH_ATTRIBUTES_STR("direction", "rightToLeft");
6965 
6966     if (cond_format->bar_direction ==
6967         LXW_CONDITIONAL_BAR_DIRECTION_LEFT_TO_RIGHT)
6968         LXW_PUSH_ATTRIBUTES_STR("direction", "leftToRight");
6969 
6970     if (cond_format->bar_negative_color_same)
6971         LXW_PUSH_ATTRIBUTES_STR("negativeBarColorSameAsPositive", "1");
6972 
6973     if (!cond_format->bar_no_border
6974         && !cond_format->bar_negative_border_color_same)
6975         LXW_PUSH_ATTRIBUTES_STR("negativeBarBorderColorSameAsPositive", "0");
6976 
6977     if (cond_format->bar_axis_position == LXW_CONDITIONAL_BAR_AXIS_MIDPOINT)
6978         LXW_PUSH_ATTRIBUTES_STR("axisPosition", "middle");
6979 
6980     if (cond_format->bar_axis_position == LXW_CONDITIONAL_BAR_AXIS_NONE)
6981         LXW_PUSH_ATTRIBUTES_STR("axisPosition", "none");
6982 
6983     lxw_xml_start_tag(self->file, "x14:dataBar", &attributes);
6984 
6985     if (cond_format->auto_min)
6986         cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN;
6987 
6988     _worksheet_write_x14_cfvo(self, cond_format->min_rule_type,
6989                               cond_format->min_value,
6990                               cond_format->min_value_string);
6991 
6992     if (cond_format->auto_max)
6993         cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX;
6994 
6995     _worksheet_write_x14_cfvo(self, cond_format->max_rule_type,
6996                               cond_format->max_value,
6997                               cond_format->max_value_string);
6998 
6999     if (!cond_format->bar_no_border)
7000         _worksheet_write_x14_color(self, "x14:borderColor",
7001                                    cond_format->bar_border_color);
7002 
7003     if (!cond_format->bar_negative_color_same)
7004         _worksheet_write_x14_color(self, "x14:negativeFillColor",
7005                                    cond_format->bar_negative_color);
7006 
7007     if (!cond_format->bar_no_border
7008         && !cond_format->bar_negative_border_color_same)
7009         _worksheet_write_x14_color(self, "x14:negativeBorderColor",
7010                                    cond_format->bar_negative_border_color);
7011 
7012     if (cond_format->bar_axis_position != LXW_CONDITIONAL_BAR_AXIS_NONE)
7013         _worksheet_write_x14_color(self, "x14:axisColor",
7014                                    cond_format->bar_axis_color);
7015 
7016     LXW_FREE_ATTRIBUTES();
7017 }
7018 
7019 /*
7020  * Write the <x14:cfRule> element.
7021  */
7022 STATIC void
_worksheet_write_x14_cf_rule(lxw_worksheet * self,lxw_cond_format_obj * cond_format)7023 _worksheet_write_x14_cf_rule(lxw_worksheet *self,
7024                              lxw_cond_format_obj *cond_format)
7025 {
7026     struct xml_attribute_list attributes;
7027     struct xml_attribute *attribute;
7028 
7029     LXW_INIT_ATTRIBUTES();
7030     LXW_PUSH_ATTRIBUTES_STR("type", "dataBar");
7031     LXW_PUSH_ATTRIBUTES_STR("id", cond_format->guid);
7032 
7033     lxw_xml_start_tag(self->file, "x14:cfRule", &attributes);
7034 
7035     /* Write the x14:dataBar element. */
7036     _worksheet_write_x14_data_bar(self, cond_format);
7037 
7038     LXW_FREE_ATTRIBUTES();
7039 }
7040 
7041 /*
7042  * Write the <xm:sqref> element.
7043  */
7044 STATIC void
_worksheet_write_xm_sqref(lxw_worksheet * self,lxw_cond_format_obj * cond_format)7045 _worksheet_write_xm_sqref(lxw_worksheet *self,
7046                           lxw_cond_format_obj *cond_format)
7047 {
7048     lxw_xml_data_element(self->file, "xm:sqref", cond_format->sqref, NULL);
7049 }
7050 
7051 /*
7052  * Write the <conditionalFormatting> element.
7053  */
7054 STATIC void
_worksheet_write_conditional_formatting_2010(lxw_worksheet * self,lxw_cond_format_hash_element * element)7055 _worksheet_write_conditional_formatting_2010(lxw_worksheet *self, lxw_cond_format_hash_element
7056                                              *element)
7057 {
7058     struct xml_attribute_list attributes;
7059     struct xml_attribute *attribute;
7060     lxw_cond_format_obj *cond_format;
7061 
7062     LXW_INIT_ATTRIBUTES();
7063     LXW_PUSH_ATTRIBUTES_STR("xmlns:xm",
7064                             "http://schemas.microsoft.com/office/excel/2006/main");
7065 
7066     STAILQ_FOREACH(cond_format, element->cond_formats, list_pointers) {
7067         if (!cond_format->data_bar_2010)
7068             continue;
7069 
7070         lxw_xml_start_tag(self->file, "x14:conditionalFormatting",
7071                           &attributes);
7072 
7073         _worksheet_write_x14_cf_rule(self, cond_format);
7074 
7075         lxw_xml_end_tag(self->file, "x14:dataBar");
7076         lxw_xml_end_tag(self->file, "x14:cfRule");
7077         _worksheet_write_xm_sqref(self, cond_format);
7078         lxw_xml_end_tag(self->file, "x14:conditionalFormatting");
7079     }
7080 
7081     LXW_FREE_ATTRIBUTES();
7082 }
7083 
7084 /*
7085  * Write the <extLst> element for Excel 2010 conditional formatting data bars.
7086  */
7087 STATIC void
_worksheet_write_ext_list_data_bars(lxw_worksheet * self)7088 _worksheet_write_ext_list_data_bars(lxw_worksheet *self)
7089 {
7090     lxw_cond_format_hash_element *element;
7091     lxw_cond_format_hash_element *next_element;
7092 
7093     _worksheet_write_ext(self, "{78C0D931-6437-407d-A8EE-F0AAD7539E65}");
7094     lxw_xml_start_tag(self->file, "x14:conditionalFormattings", NULL);
7095 
7096     for (element = RB_MIN(lxw_cond_format_hash, self->conditional_formats);
7097          element; element = next_element) {
7098 
7099         _worksheet_write_conditional_formatting_2010(self, element);
7100 
7101         next_element =
7102             RB_NEXT(lxw_cond_format_hash, self->conditional_formats, element);
7103     }
7104 
7105     lxw_xml_end_tag(self->file, "x14:conditionalFormattings");
7106     lxw_xml_end_tag(self->file, "ext");
7107 }
7108 
7109 /*
7110  * Write the <extLst> element.
7111  */
7112 STATIC void
_worksheet_write_ext_list(lxw_worksheet * self)7113 _worksheet_write_ext_list(lxw_worksheet *self)
7114 {
7115     if (self->data_bar_2010_index == 0)
7116         return;
7117 
7118     lxw_xml_start_tag(self->file, "extLst", NULL);
7119 
7120     _worksheet_write_ext_list_data_bars(self);
7121 
7122     lxw_xml_end_tag(self->file, "extLst");
7123 }
7124 
7125 /*
7126  * Write the <ignoredError> element.
7127  */
7128 STATIC void
_worksheet_write_ignored_error(lxw_worksheet * self,char * ignore_error,char * range)7129 _worksheet_write_ignored_error(lxw_worksheet *self, char *ignore_error,
7130                                char *range)
7131 {
7132     struct xml_attribute_list attributes;
7133     struct xml_attribute *attribute;
7134 
7135     LXW_INIT_ATTRIBUTES();
7136     LXW_PUSH_ATTRIBUTES_STR("sqref", range);
7137     LXW_PUSH_ATTRIBUTES_STR(ignore_error, "1");
7138 
7139     lxw_xml_empty_tag(self->file, "ignoredError", &attributes);
7140 
7141     LXW_FREE_ATTRIBUTES();
7142 }
7143 
7144 lxw_error
_validate_conditional_icons(lxw_conditional_format * user)7145 _validate_conditional_icons(lxw_conditional_format *user)
7146 {
7147     if (user->icon_style > LXW_CONDITIONAL_ICONS_5_QUARTERS) {
7148 
7149         LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
7150                          "For type = LXW_CONDITIONAL_TYPE_ICON_SETS, "
7151                          "invalid icon_style (%d).", user->icon_style);
7152 
7153         return LXW_ERROR_PARAMETER_VALIDATION;
7154     }
7155     else {
7156         return LXW_NO_ERROR;
7157     }
7158 }
7159 
7160 lxw_error
_validate_conditional_data_bar(lxw_worksheet * self,lxw_cond_format_obj * cond_format,lxw_conditional_format * user_options)7161 _validate_conditional_data_bar(lxw_worksheet *self,
7162                                lxw_cond_format_obj *cond_format,
7163                                lxw_conditional_format *user_options)
7164 {
7165     uint8_t min_rule_type = user_options->min_rule_type;
7166     uint8_t max_rule_type = user_options->max_rule_type;
7167 
7168     if (user_options->data_bar_2010
7169         || user_options->bar_solid
7170         || user_options->bar_no_border
7171         || user_options->bar_direction
7172         || user_options->bar_axis_position
7173         || user_options->bar_negative_color_same
7174         || user_options->bar_negative_border_color_same
7175         || user_options->bar_negative_color
7176         || user_options->bar_border_color
7177         || user_options->bar_negative_border_color
7178         || user_options->bar_axis_color) {
7179 
7180         cond_format->data_bar_2010 = LXW_TRUE;
7181         self->excel_version = 2010;
7182     }
7183 
7184     if (min_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM
7185         && min_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) {
7186         cond_format->min_rule_type = min_rule_type;
7187         cond_format->min_value = user_options->min_value;
7188         cond_format->min_value_string =
7189             lxw_strdup_formula(user_options->min_value_string);
7190     }
7191     else {
7192         cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_MINIMUM;
7193         cond_format->min_value = 0;
7194     }
7195 
7196     if (max_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM
7197         && max_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) {
7198         cond_format->max_rule_type = max_rule_type;
7199         cond_format->max_value = user_options->max_value;
7200         cond_format->max_value_string =
7201             lxw_strdup_formula(user_options->max_value_string);
7202     }
7203     else {
7204         cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_MAXIMUM;
7205         cond_format->max_value = 0;
7206     }
7207 
7208     if (cond_format->data_bar_2010) {
7209         if (min_rule_type == LXW_CONDITIONAL_RULE_TYPE_NONE)
7210             cond_format->auto_min = LXW_TRUE;
7211         if (max_rule_type == LXW_CONDITIONAL_RULE_TYPE_NONE)
7212             cond_format->auto_max = LXW_TRUE;
7213     }
7214 
7215     cond_format->bar_only = user_options->bar_only;
7216     cond_format->bar_solid = user_options->bar_solid;
7217     cond_format->bar_no_border = user_options->bar_no_border;
7218     cond_format->bar_direction = user_options->bar_direction;
7219     cond_format->bar_axis_position = user_options->bar_axis_position;
7220     cond_format->bar_negative_color_same =
7221         user_options->bar_negative_color_same;
7222     cond_format->bar_negative_border_color_same =
7223         user_options->bar_negative_border_color_same;
7224 
7225     if (user_options->bar_color != LXW_COLOR_UNSET)
7226         cond_format->bar_color = user_options->bar_color;
7227     else
7228         cond_format->bar_color = 0x638EC6;
7229 
7230     if (user_options->bar_negative_color != LXW_COLOR_UNSET)
7231         cond_format->bar_negative_color = user_options->bar_negative_color;
7232     else
7233         cond_format->bar_negative_color = 0xFF0000;
7234 
7235     if (user_options->bar_border_color != LXW_COLOR_UNSET)
7236         cond_format->bar_border_color = user_options->bar_border_color;
7237     else
7238         cond_format->bar_border_color = cond_format->bar_color;
7239 
7240     if (user_options->bar_negative_border_color != LXW_COLOR_UNSET)
7241         cond_format->bar_negative_border_color =
7242             user_options->bar_negative_border_color;
7243     else
7244         cond_format->bar_negative_border_color = 0xFF0000;
7245 
7246     if (user_options->bar_axis_color != LXW_COLOR_UNSET)
7247         cond_format->bar_axis_color = user_options->bar_axis_color;
7248     else
7249         cond_format->bar_axis_color = 0x000000;
7250 
7251     return LXW_NO_ERROR;
7252 }
7253 
7254 lxw_error
_validate_conditional_scale(lxw_cond_format_obj * cond_format,lxw_conditional_format * user_options)7255 _validate_conditional_scale(lxw_cond_format_obj *cond_format,
7256                             lxw_conditional_format *user_options)
7257 {
7258     uint8_t min_rule_type = user_options->min_rule_type;
7259     uint8_t mid_rule_type = user_options->mid_rule_type;
7260     uint8_t max_rule_type = user_options->max_rule_type;
7261 
7262     if (min_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM
7263         && min_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) {
7264         cond_format->min_rule_type = min_rule_type;
7265         cond_format->min_value = user_options->min_value;
7266         cond_format->min_value_string =
7267             lxw_strdup_formula(user_options->min_value_string);
7268     }
7269     else {
7270         cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_MINIMUM;
7271         cond_format->min_value = 0;
7272     }
7273 
7274     if (max_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM
7275         && max_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) {
7276         cond_format->max_rule_type = max_rule_type;
7277         cond_format->max_value = user_options->max_value;
7278         cond_format->max_value_string =
7279             lxw_strdup_formula(user_options->max_value_string);
7280     }
7281     else {
7282         cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_MAXIMUM;
7283         cond_format->max_value = 0;
7284     }
7285 
7286     if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) {
7287         if (mid_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM
7288             && mid_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) {
7289             cond_format->mid_rule_type = mid_rule_type;
7290             cond_format->mid_value = user_options->mid_value;
7291             cond_format->mid_value_string =
7292                 lxw_strdup_formula(user_options->mid_value_string);
7293         }
7294         else {
7295             cond_format->mid_rule_type = LXW_CONDITIONAL_RULE_TYPE_PERCENTILE;
7296             cond_format->mid_value = 50;
7297         }
7298     }
7299 
7300     if (user_options->min_color != LXW_COLOR_UNSET)
7301         cond_format->min_color = user_options->min_color;
7302     else
7303         cond_format->min_color = 0xFF7128;
7304 
7305     if (user_options->max_color != LXW_COLOR_UNSET)
7306         cond_format->max_color = user_options->max_color;
7307     else
7308         cond_format->max_color = 0xFFEF9C;
7309 
7310     if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) {
7311         if (user_options->min_color == LXW_COLOR_UNSET)
7312             cond_format->min_color = 0xF8696B;
7313 
7314         if (user_options->mid_color != LXW_COLOR_UNSET)
7315             cond_format->mid_color = user_options->mid_color;
7316         else
7317             cond_format->mid_color = 0xFFEB84;
7318 
7319         if (user_options->max_color == LXW_COLOR_UNSET)
7320             cond_format->max_color = 0x63BE7B;
7321     }
7322 
7323     return LXW_NO_ERROR;
7324 }
7325 
7326 lxw_error
_validate_conditional_top(lxw_cond_format_obj * cond_format,lxw_conditional_format * user_options)7327 _validate_conditional_top(lxw_cond_format_obj *cond_format,
7328                           lxw_conditional_format *user_options)
7329 {
7330     /* Restrict the range of rank values to Excel's allowed range. */
7331     if (user_options->criteria ==
7332         LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT) {
7333         if (user_options->value < 0.0 || user_options->value > 100.0) {
7334 
7335             LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
7336                              "For type = LXW_CONDITIONAL_TYPE_TOP/BOTTOM, "
7337                              "top/bottom percent (%g%%) must by in range 0-100",
7338                              user_options->value);
7339 
7340             return LXW_ERROR_PARAMETER_VALIDATION;
7341         }
7342     }
7343     else {
7344         if (user_options->value < 1.0 || user_options->value > 1000.0) {
7345 
7346             LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
7347                              "For type = LXW_CONDITIONAL_TYPE_TOP/BOTTOM, "
7348                              "top/bottom items (%g) must by in range 1-1000",
7349                              user_options->value);
7350 
7351             return LXW_ERROR_PARAMETER_VALIDATION;
7352         }
7353     }
7354 
7355     cond_format->min_value = (uint16_t) user_options->value;
7356 
7357     return LXW_NO_ERROR;
7358 }
7359 
7360 lxw_error
_validate_conditional_average(lxw_conditional_format * user)7361 _validate_conditional_average(lxw_conditional_format *user)
7362 {
7363     if (user->criteria < LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE ||
7364         user->criteria > LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) {
7365 
7366         LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
7367                          "For type = LXW_CONDITIONAL_TYPE_AVERAGE, "
7368                          "invalid criteria value (%d).", user->criteria);
7369 
7370         return LXW_ERROR_PARAMETER_VALIDATION;
7371     }
7372     else {
7373         return LXW_NO_ERROR;
7374     }
7375 }
7376 
7377 lxw_error
_validate_conditional_time_period(lxw_conditional_format * user)7378 _validate_conditional_time_period(lxw_conditional_format *user)
7379 {
7380     if (user->criteria < LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY ||
7381         user->criteria > LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH) {
7382 
7383         LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
7384                          "For type = LXW_CONDITIONAL_TYPE_TIME_PERIOD, "
7385                          "invalid criteria value (%d).", user->criteria);
7386 
7387         return LXW_ERROR_PARAMETER_VALIDATION;
7388     }
7389     else {
7390         return LXW_NO_ERROR;
7391     }
7392 }
7393 
7394 lxw_error
_validate_conditional_text(lxw_cond_format_obj * cond_format,lxw_conditional_format * user_options)7395 _validate_conditional_text(lxw_cond_format_obj *cond_format,
7396                            lxw_conditional_format *user_options)
7397 {
7398     if (!user_options->value_string) {
7399 
7400         LXW_WARN_FORMAT("worksheet_conditional_format_cell()/_range(): "
7401                         "For type = LXW_CONDITIONAL_TYPE_TEXT, "
7402                         "value_string can not be NULL. "
7403                         "Text must be specified.");
7404 
7405         return LXW_ERROR_PARAMETER_VALIDATION;
7406     }
7407 
7408     if (strlen(user_options->value_string) >= LXW_MAX_ATTRIBUTE_LENGTH) {
7409 
7410         LXW_WARN_FORMAT2("worksheet_conditional_format_cell()/_range(): "
7411                          "For type = LXW_CONDITIONAL_TYPE_TEXT, "
7412                          "value_string length (%d) must be less than %d.",
7413                          (uint16_t) strlen(user_options->value_string),
7414                          LXW_MAX_ATTRIBUTE_LENGTH);
7415 
7416         return LXW_ERROR_PARAMETER_VALIDATION;
7417     }
7418 
7419     if (user_options->criteria < LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING ||
7420         user_options->criteria > LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) {
7421 
7422         LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
7423                          "For type = LXW_CONDITIONAL_TYPE_TEXT, "
7424                          "invalid criteria value (%d).",
7425                          user_options->criteria);
7426 
7427         return LXW_ERROR_PARAMETER_VALIDATION;
7428     }
7429 
7430     cond_format->min_value_string =
7431         lxw_strdup_formula(user_options->value_string);
7432 
7433     return LXW_NO_ERROR;
7434 }
7435 
7436 lxw_error
_validate_conditional_formula(lxw_cond_format_obj * cond_format,lxw_conditional_format * user_options)7437 _validate_conditional_formula(lxw_cond_format_obj *cond_format,
7438                               lxw_conditional_format *user_options)
7439 {
7440     if (!user_options->value_string) {
7441 
7442         LXW_WARN_FORMAT("worksheet_conditional_format_cell()/_range(): "
7443                         "For type = LXW_CONDITIONAL_TYPE_FORMULA, "
7444                         "value_string can not be NULL. "
7445                         "Formula must be specified.");
7446 
7447         return LXW_ERROR_PARAMETER_VALIDATION;
7448     }
7449 
7450     cond_format->min_value_string =
7451         lxw_strdup_formula(user_options->value_string);
7452 
7453     return LXW_NO_ERROR;
7454 }
7455 
7456 lxw_error
_validate_conditional_cell(lxw_cond_format_obj * cond_format,lxw_conditional_format * user_options)7457 _validate_conditional_cell(lxw_cond_format_obj *cond_format,
7458                            lxw_conditional_format *user_options)
7459 {
7460     cond_format->min_value = user_options->value;
7461     cond_format->min_value_string =
7462         lxw_strdup_formula(user_options->value_string);
7463 
7464     if (cond_format->criteria == LXW_CONDITIONAL_CRITERIA_BETWEEN
7465         || cond_format->criteria == LXW_CONDITIONAL_CRITERIA_NOT_BETWEEN) {
7466         cond_format->has_max = LXW_TRUE;
7467         cond_format->min_value = user_options->min_value;
7468         cond_format->max_value = user_options->max_value;
7469         cond_format->min_value_string =
7470             lxw_strdup_formula(user_options->min_value_string);
7471         cond_format->max_value_string =
7472             lxw_strdup_formula(user_options->max_value_string);
7473     }
7474 
7475     return LXW_NO_ERROR;
7476 }
7477 
7478 /*
7479  * Write the <ignoredErrors> element.
7480  */
7481 STATIC void
_worksheet_write_ignored_errors(lxw_worksheet * self)7482 _worksheet_write_ignored_errors(lxw_worksheet *self)
7483 {
7484     if (!self->has_ignore_errors)
7485         return;
7486 
7487     lxw_xml_start_tag(self->file, "ignoredErrors", NULL);
7488 
7489     if (self->ignore_number_stored_as_text) {
7490         _worksheet_write_ignored_error(self, "numberStoredAsText",
7491                                        self->ignore_number_stored_as_text);
7492     }
7493 
7494     if (self->ignore_eval_error) {
7495         _worksheet_write_ignored_error(self, "evalError",
7496                                        self->ignore_eval_error);
7497     }
7498 
7499     if (self->ignore_formula_differs) {
7500         _worksheet_write_ignored_error(self, "formula",
7501                                        self->ignore_formula_differs);
7502     }
7503 
7504     if (self->ignore_formula_range) {
7505         _worksheet_write_ignored_error(self, "formulaRange",
7506                                        self->ignore_formula_range);
7507     }
7508 
7509     if (self->ignore_formula_unlocked) {
7510         _worksheet_write_ignored_error(self, "unlockedFormula",
7511                                        self->ignore_formula_unlocked);
7512     }
7513 
7514     if (self->ignore_empty_cell_reference) {
7515         _worksheet_write_ignored_error(self, "emptyCellReference",
7516                                        self->ignore_empty_cell_reference);
7517     }
7518 
7519     if (self->ignore_list_data_validation) {
7520         _worksheet_write_ignored_error(self, "listDataValidation",
7521                                        self->ignore_list_data_validation);
7522     }
7523 
7524     if (self->ignore_calculated_column) {
7525         _worksheet_write_ignored_error(self, "calculatedColumn",
7526                                        self->ignore_calculated_column);
7527     }
7528 
7529     if (self->ignore_two_digit_text_year) {
7530         _worksheet_write_ignored_error(self, "twoDigitTextYear",
7531                                        self->ignore_two_digit_text_year);
7532     }
7533 
7534     lxw_xml_end_tag(self->file, "ignoredErrors");
7535 }
7536 
7537 /*
7538  * Write the <tablePart> element.
7539  */
7540 STATIC void
_worksheet_write_table_part(lxw_worksheet * self,uint16_t id)7541 _worksheet_write_table_part(lxw_worksheet *self, uint16_t id)
7542 {
7543     struct xml_attribute_list attributes;
7544     struct xml_attribute *attribute;
7545     char r_id[LXW_MAX_ATTRIBUTE_LENGTH];
7546 
7547     lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id);
7548 
7549     LXW_INIT_ATTRIBUTES();
7550     LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
7551 
7552     lxw_xml_empty_tag(self->file, "tablePart", &attributes);
7553 
7554     LXW_FREE_ATTRIBUTES();
7555 }
7556 
7557 /*
7558  * Write the <tableParts> element.
7559  */
7560 STATIC void
_worksheet_write_table_parts(lxw_worksheet * self)7561 _worksheet_write_table_parts(lxw_worksheet *self)
7562 {
7563     struct xml_attribute_list attributes;
7564     struct xml_attribute *attribute;
7565     lxw_table_obj *table_obj;
7566 
7567     if (!self->table_count)
7568         return;
7569 
7570     LXW_INIT_ATTRIBUTES();
7571     LXW_PUSH_ATTRIBUTES_INT("count", self->table_count);
7572 
7573     lxw_xml_start_tag(self->file, "tableParts", &attributes);
7574 
7575     STAILQ_FOREACH(table_obj, self->table_objs, list_pointers) {
7576         self->rel_count++;
7577 
7578         /* Write the tablePart element. */
7579         _worksheet_write_table_part(self, self->rel_count);
7580     }
7581 
7582     lxw_xml_end_tag(self->file, "tableParts");
7583 
7584     LXW_FREE_ATTRIBUTES();
7585 }
7586 
7587 /*
7588  * External functions to call intern XML methods shared with chartsheet.
7589  */
7590 void
lxw_worksheet_write_sheet_views(lxw_worksheet * self)7591 lxw_worksheet_write_sheet_views(lxw_worksheet *self)
7592 {
7593     _worksheet_write_sheet_views(self);
7594 }
7595 
7596 void
lxw_worksheet_write_page_margins(lxw_worksheet * self)7597 lxw_worksheet_write_page_margins(lxw_worksheet *self)
7598 {
7599     _worksheet_write_page_margins(self);
7600 }
7601 
7602 void
lxw_worksheet_write_drawings(lxw_worksheet * self)7603 lxw_worksheet_write_drawings(lxw_worksheet *self)
7604 {
7605     _worksheet_write_drawings(self);
7606 }
7607 
7608 void
lxw_worksheet_write_sheet_protection(lxw_worksheet * self,lxw_protection_obj * protect)7609 lxw_worksheet_write_sheet_protection(lxw_worksheet *self,
7610                                      lxw_protection_obj *protect)
7611 {
7612     _worksheet_write_sheet_protection(self, protect);
7613 }
7614 
7615 void
lxw_worksheet_write_sheet_pr(lxw_worksheet * self)7616 lxw_worksheet_write_sheet_pr(lxw_worksheet *self)
7617 {
7618     _worksheet_write_sheet_pr(self);
7619 }
7620 
7621 void
lxw_worksheet_write_page_setup(lxw_worksheet * self)7622 lxw_worksheet_write_page_setup(lxw_worksheet *self)
7623 {
7624     _worksheet_write_page_setup(self);
7625 }
7626 
7627 void
lxw_worksheet_write_header_footer(lxw_worksheet * self)7628 lxw_worksheet_write_header_footer(lxw_worksheet *self)
7629 {
7630     _worksheet_write_header_footer(self);
7631 }
7632 
7633 /*
7634  * Assemble and write the XML file.
7635  */
7636 void
lxw_worksheet_assemble_xml_file(lxw_worksheet * self)7637 lxw_worksheet_assemble_xml_file(lxw_worksheet *self)
7638 {
7639     /* Write the XML declaration. */
7640     _worksheet_xml_declaration(self);
7641 
7642     /* Write the worksheet element. */
7643     _worksheet_write_worksheet(self);
7644 
7645     /* Write the worksheet properties. */
7646     _worksheet_write_sheet_pr(self);
7647 
7648     /* Write the worksheet dimensions. */
7649     _worksheet_write_dimension(self);
7650 
7651     /* Write the sheet view properties. */
7652     _worksheet_write_sheet_views(self);
7653 
7654     /* Write the sheet format properties. */
7655     _worksheet_write_sheet_format_pr(self);
7656 
7657     /* Write the sheet column info. */
7658     _worksheet_write_cols(self);
7659 
7660     /* Write the sheetData element. */
7661     if (!self->optimize)
7662         _worksheet_write_sheet_data(self);
7663     else
7664         _worksheet_write_optimized_sheet_data(self);
7665 
7666     /* Write the sheetProtection element. */
7667     _worksheet_write_sheet_protection(self, &self->protection);
7668 
7669     /* Write the autoFilter element. */
7670     _worksheet_write_auto_filter(self);
7671 
7672     /* Write the mergeCells element. */
7673     _worksheet_write_merge_cells(self);
7674 
7675     /* Write the conditionalFormatting elements. */
7676     _worksheet_write_conditional_formats(self);
7677 
7678     /* Write the dataValidations element. */
7679     _worksheet_write_data_validations(self);
7680 
7681     /* Write the hyperlink element. */
7682     _worksheet_write_hyperlinks(self);
7683 
7684     /* Write the printOptions element. */
7685     _worksheet_write_print_options(self);
7686 
7687     /* Write the worksheet page_margins. */
7688     _worksheet_write_page_margins(self);
7689 
7690     /* Write the worksheet page setup. */
7691     _worksheet_write_page_setup(self);
7692 
7693     /* Write the headerFooter element. */
7694     _worksheet_write_header_footer(self);
7695 
7696     /* Write the rowBreaks element. */
7697     _worksheet_write_row_breaks(self);
7698 
7699     /* Write the colBreaks element. */
7700     _worksheet_write_col_breaks(self);
7701 
7702     /* Write the ignoredErrors element. */
7703     _worksheet_write_ignored_errors(self);
7704 
7705     /* Write the drawing element. */
7706     _worksheet_write_drawings(self);
7707 
7708     /* Write the legacyDrawing element. */
7709     _worksheet_write_legacy_drawing(self);
7710 
7711     /* Write the legacyDrawingHF element. */
7712     _worksheet_write_legacy_drawing_hf(self);
7713 
7714     /* Write the picture element. */
7715     _worksheet_write_picture(self);
7716 
7717     /* Write the tableParts element. */
7718     _worksheet_write_table_parts(self);
7719 
7720     /* Write the extLst element. */
7721     _worksheet_write_ext_list(self);
7722 
7723     /* Close the worksheet tag. */
7724     lxw_xml_end_tag(self->file, "worksheet");
7725 }
7726 
7727 /*****************************************************************************
7728  *
7729  * Public functions.
7730  *
7731  ****************************************************************************/
7732 
7733 /*
7734  * Write a number to a cell in Excel.
7735  */
7736 lxw_error
worksheet_write_number(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,double value,lxw_format * format)7737 worksheet_write_number(lxw_worksheet *self,
7738                        lxw_row_t row_num,
7739                        lxw_col_t col_num, double value, lxw_format *format)
7740 {
7741     lxw_cell *cell;
7742     lxw_error err;
7743 
7744     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
7745     if (err)
7746         return err;
7747 
7748     cell = _new_number_cell(row_num, col_num, value, format);
7749 
7750     _insert_cell(self, row_num, col_num, cell);
7751 
7752     return LXW_NO_ERROR;
7753 }
7754 
7755 /*
7756  * Write a string to an Excel file.
7757  */
7758 lxw_error
worksheet_write_string(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,const char * string,lxw_format * format)7759 worksheet_write_string(lxw_worksheet *self,
7760                        lxw_row_t row_num,
7761                        lxw_col_t col_num, const char *string,
7762                        lxw_format *format)
7763 {
7764     lxw_cell *cell;
7765     int32_t string_id;
7766     char *string_copy;
7767     struct sst_element *sst_element;
7768     lxw_error err;
7769 
7770     if (!string || !*string) {
7771         /* Treat a NULL or empty string with formatting as a blank cell. */
7772         /* Null strings without formats should be ignored.      */
7773         if (format)
7774             return worksheet_write_blank(self, row_num, col_num, format);
7775         else
7776             return LXW_NO_ERROR;
7777     }
7778 
7779     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
7780     if (err)
7781         return err;
7782 
7783     if (lxw_utf8_strlen(string) > LXW_STR_MAX)
7784         return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED;
7785 
7786     if (!self->optimize) {
7787         /* Get the SST element and string id. */
7788         sst_element = lxw_get_sst_index(self->sst, string, LXW_FALSE);
7789 
7790         if (!sst_element)
7791             return LXW_ERROR_SHARED_STRING_INDEX_NOT_FOUND;
7792 
7793         string_id = sst_element->index;
7794         cell = _new_string_cell(row_num, col_num, string_id,
7795                                 sst_element->string, format);
7796     }
7797     else {
7798         /* Look for and escape control chars in the string. */
7799         if (lxw_has_control_characters(string)) {
7800             string_copy = lxw_escape_control_characters(string);
7801         }
7802         else {
7803             string_copy = lxw_strdup(string);
7804         }
7805         cell = _new_inline_string_cell(row_num, col_num, string_copy, format);
7806     }
7807 
7808     _insert_cell(self, row_num, col_num, cell);
7809 
7810     return LXW_NO_ERROR;
7811 }
7812 
7813 /*
7814  * Write a formula with a numerical result to a cell in Excel.
7815  */
7816 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)7817 worksheet_write_formula_num(lxw_worksheet *self,
7818                             lxw_row_t row_num,
7819                             lxw_col_t col_num,
7820                             const char *formula,
7821                             lxw_format *format, double result)
7822 {
7823     lxw_cell *cell;
7824     char *formula_copy;
7825     lxw_error err;
7826 
7827     if (!formula)
7828         return LXW_ERROR_NULL_PARAMETER_IGNORED;
7829 
7830     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
7831     if (err)
7832         return err;
7833 
7834     /* Strip leading "=" from formula. */
7835     if (formula[0] == '=')
7836         formula_copy = lxw_strdup(formula + 1);
7837     else
7838         formula_copy = lxw_strdup(formula);
7839 
7840     cell = _new_formula_cell(row_num, col_num, formula_copy, format);
7841     cell->formula_result = result;
7842 
7843     _insert_cell(self, row_num, col_num, cell);
7844 
7845     return LXW_NO_ERROR;
7846 }
7847 
7848 /*
7849  * Write a formula with a string result to a cell in Excel.
7850  */
7851 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)7852 worksheet_write_formula_str(lxw_worksheet *self,
7853                             lxw_row_t row_num,
7854                             lxw_col_t col_num,
7855                             const char *formula,
7856                             lxw_format *format, const char *result)
7857 {
7858     lxw_cell *cell;
7859     char *formula_copy;
7860     lxw_error err;
7861 
7862     if (!formula)
7863         return LXW_ERROR_NULL_PARAMETER_IGNORED;
7864 
7865     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
7866     if (err)
7867         return err;
7868 
7869     /* Strip leading "=" from formula. */
7870     if (formula[0] == '=')
7871         formula_copy = lxw_strdup(formula + 1);
7872     else
7873         formula_copy = lxw_strdup(formula);
7874 
7875     cell = _new_formula_cell(row_num, col_num, formula_copy, format);
7876     cell->user_data2 = lxw_strdup(result);
7877 
7878     _insert_cell(self, row_num, col_num, cell);
7879 
7880     return LXW_NO_ERROR;
7881 }
7882 
7883 /*
7884  * Write a formula with a default result to a cell in Excel .
7885  */
7886 lxw_error
worksheet_write_formula(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,const char * formula,lxw_format * format)7887 worksheet_write_formula(lxw_worksheet *self,
7888                         lxw_row_t row_num,
7889                         lxw_col_t col_num, const char *formula,
7890                         lxw_format *format)
7891 {
7892     return worksheet_write_formula_num(self, row_num, col_num, formula,
7893                                        format, 0);
7894 }
7895 
7896 /*
7897  * Internal shared function for various array formula functions.
7898  */
7899 lxw_error
_store_array_formula(lxw_worksheet * self,lxw_row_t first_row,lxw_col_t first_col,lxw_row_t last_row,lxw_col_t last_col,const char * formula,lxw_format * format,double result,uint8_t is_dynamic)7900 _store_array_formula(lxw_worksheet *self,
7901                      lxw_row_t first_row,
7902                      lxw_col_t first_col,
7903                      lxw_row_t last_row,
7904                      lxw_col_t last_col,
7905                      const char *formula, lxw_format *format, double result,
7906                      uint8_t is_dynamic)
7907 {
7908     lxw_cell *cell;
7909     lxw_row_t tmp_row;
7910     lxw_col_t tmp_col;
7911     char *formula_copy;
7912     char *range;
7913     lxw_error err;
7914 
7915     /* Swap last row/col with first row/col as necessary */
7916     if (first_row > last_row) {
7917         tmp_row = last_row;
7918         last_row = first_row;
7919         first_row = tmp_row;
7920     }
7921     if (first_col > last_col) {
7922         tmp_col = last_col;
7923         last_col = first_col;
7924         first_col = tmp_col;
7925     }
7926 
7927     if (!formula)
7928         return LXW_ERROR_NULL_PARAMETER_IGNORED;
7929 
7930     /* Check that row and col are valid and store max and min values. */
7931     err = _check_dimensions(self, first_row, first_col, LXW_FALSE, LXW_FALSE);
7932     if (err)
7933         return err;
7934 
7935     err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
7936     if (err)
7937         return err;
7938 
7939     /* Define the array range. */
7940     range = calloc(1, LXW_MAX_CELL_RANGE_LENGTH);
7941     RETURN_ON_MEM_ERROR(range, LXW_ERROR_MEMORY_MALLOC_FAILED);
7942 
7943     if (first_row == last_row && first_col == last_col)
7944         lxw_rowcol_to_cell(range, first_row, last_col);
7945     else
7946         lxw_rowcol_to_range(range, first_row, first_col, last_row, last_col);
7947 
7948     /* Copy and trip leading "{=" from formula. */
7949     if (formula[0] == '{')
7950         if (formula[1] == '=')
7951             formula_copy = lxw_strdup(formula + 2);
7952         else
7953             formula_copy = lxw_strdup(formula + 1);
7954     else
7955         formula_copy = lxw_strdup_formula(formula);
7956 
7957     /* Strip trailing "}" from formula. */
7958     if (formula_copy[strlen(formula_copy) - 1] == '}')
7959         formula_copy[strlen(formula_copy) - 1] = '\0';
7960 
7961     /* Create a new array formula cell object. */
7962     cell = _new_array_formula_cell(first_row, first_col,
7963                                    formula_copy, range, format, is_dynamic);
7964 
7965     cell->formula_result = result;
7966 
7967     _insert_cell(self, first_row, first_col, cell);
7968 
7969     if (is_dynamic)
7970         self->has_dynamic_arrays = LXW_TRUE;
7971 
7972     /* Pad out the rest of the area with formatted zeroes. */
7973     if (!self->optimize) {
7974         for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) {
7975             for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) {
7976                 if (tmp_row == first_row && tmp_col == first_col)
7977                     continue;
7978 
7979                 worksheet_write_number(self, tmp_row, tmp_col, 0, format);
7980             }
7981         }
7982     }
7983 
7984     return LXW_NO_ERROR;
7985 }
7986 
7987 /*
7988  * Write an array formula with a numerical result to a cell in Excel.
7989  */
7990 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)7991 worksheet_write_array_formula_num(lxw_worksheet *self,
7992                                   lxw_row_t first_row,
7993                                   lxw_col_t first_col,
7994                                   lxw_row_t last_row,
7995                                   lxw_col_t last_col,
7996                                   const char *formula,
7997                                   lxw_format *format, double result)
7998 {
7999     return _store_array_formula(self, first_row, first_col,
8000                                 last_row, last_col, formula, format, result,
8001                                 LXW_FALSE);
8002 }
8003 
8004 /*
8005  * Write an array formula with a default result to a cell in Excel.
8006  */
8007 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)8008 worksheet_write_array_formula(lxw_worksheet *self,
8009                               lxw_row_t first_row,
8010                               lxw_col_t first_col,
8011                               lxw_row_t last_row,
8012                               lxw_col_t last_col,
8013                               const char *formula, lxw_format *format)
8014 {
8015     return _store_array_formula(self, first_row, first_col,
8016                                 last_row, last_col, formula, format, 0,
8017                                 LXW_FALSE);
8018 }
8019 
8020 /*
8021  * Write a single cell dynamic array formula with a default result to a cell.
8022  */
8023 lxw_error
worksheet_write_dynamic_formula(lxw_worksheet * self,lxw_row_t row,lxw_col_t col,const char * formula,lxw_format * format)8024 worksheet_write_dynamic_formula(lxw_worksheet *self,
8025                                 lxw_row_t row,
8026                                 lxw_col_t col,
8027                                 const char *formula, lxw_format *format)
8028 {
8029     return _store_array_formula(self, row, col, row, col, formula, format, 0,
8030                                 LXW_TRUE);
8031 }
8032 
8033 /*
8034  * Write a single cell dynamic array formula with a numerical result to a cell.
8035  */
8036 lxw_error
worksheet_write_dynamic_formula_num(lxw_worksheet * self,lxw_row_t row,lxw_col_t col,const char * formula,lxw_format * format,double result)8037 worksheet_write_dynamic_formula_num(lxw_worksheet *self,
8038                                     lxw_row_t row,
8039                                     lxw_col_t col,
8040                                     const char *formula,
8041                                     lxw_format *format, double result)
8042 {
8043     return _store_array_formula(self, row, col, row, col, formula, format,
8044                                 result, LXW_TRUE);
8045 }
8046 
8047 /*
8048  * Write a dynamic array formula with a numerical result to a cell in Excel.
8049  */
8050 lxw_error
worksheet_write_dynamic_array_formula_num(lxw_worksheet * self,lxw_row_t first_row,lxw_col_t first_col,lxw_row_t last_row,lxw_col_t last_col,const char * formula,lxw_format * format,double result)8051 worksheet_write_dynamic_array_formula_num(lxw_worksheet *self,
8052                                           lxw_row_t first_row,
8053                                           lxw_col_t first_col,
8054                                           lxw_row_t last_row,
8055                                           lxw_col_t last_col,
8056                                           const char *formula,
8057                                           lxw_format *format, double result)
8058 {
8059     return _store_array_formula(self, first_row, first_col,
8060                                 last_row, last_col, formula, format, result,
8061                                 LXW_TRUE);
8062 }
8063 
8064 /*
8065  * Write a dynamic array formula with a default result to a cell in Excel.
8066  */
8067 lxw_error
worksheet_write_dynamic_array_formula(lxw_worksheet * self,lxw_row_t first_row,lxw_col_t first_col,lxw_row_t last_row,lxw_col_t last_col,const char * formula,lxw_format * format)8068 worksheet_write_dynamic_array_formula(lxw_worksheet *self,
8069                                       lxw_row_t first_row,
8070                                       lxw_col_t first_col,
8071                                       lxw_row_t last_row,
8072                                       lxw_col_t last_col,
8073                                       const char *formula, lxw_format *format)
8074 {
8075     return _store_array_formula(self, first_row, first_col,
8076                                 last_row, last_col, formula, format, 0,
8077                                 LXW_TRUE);
8078 }
8079 
8080 /*
8081  * Write a blank cell with a format to a cell in Excel.
8082  */
8083 lxw_error
worksheet_write_blank(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_format * format)8084 worksheet_write_blank(lxw_worksheet *self,
8085                       lxw_row_t row_num, lxw_col_t col_num,
8086                       lxw_format *format)
8087 {
8088     lxw_cell *cell;
8089     lxw_error err;
8090 
8091     /* Blank cells without formatting are ignored by Excel. */
8092     if (!format)
8093         return LXW_NO_ERROR;
8094 
8095     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8096     if (err)
8097         return err;
8098 
8099     cell = _new_blank_cell(row_num, col_num, format);
8100 
8101     _insert_cell(self, row_num, col_num, cell);
8102 
8103     return LXW_NO_ERROR;
8104 }
8105 
8106 /*
8107  * Write a boolean cell with a format to a cell in Excel.
8108  */
8109 lxw_error
worksheet_write_boolean(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,int value,lxw_format * format)8110 worksheet_write_boolean(lxw_worksheet *self,
8111                         lxw_row_t row_num, lxw_col_t col_num,
8112                         int value, lxw_format *format)
8113 {
8114     lxw_cell *cell;
8115     lxw_error err;
8116 
8117     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8118     if (err)
8119         return err;
8120 
8121     cell = _new_boolean_cell(row_num, col_num, value, format);
8122 
8123     _insert_cell(self, row_num, col_num, cell);
8124 
8125     return LXW_NO_ERROR;
8126 }
8127 
8128 /*
8129  * Write a date and or time to a cell in Excel.
8130  */
8131 lxw_error
worksheet_write_datetime(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_datetime * datetime,lxw_format * format)8132 worksheet_write_datetime(lxw_worksheet *self,
8133                          lxw_row_t row_num,
8134                          lxw_col_t col_num, lxw_datetime *datetime,
8135                          lxw_format *format)
8136 {
8137     lxw_cell *cell;
8138     double excel_date;
8139     lxw_error err;
8140 
8141     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8142     if (err)
8143         return err;
8144 
8145     excel_date = lxw_datetime_to_excel_date_epoch(datetime, LXW_EPOCH_1900);
8146 
8147     cell = _new_number_cell(row_num, col_num, excel_date, format);
8148 
8149     _insert_cell(self, row_num, col_num, cell);
8150 
8151     return LXW_NO_ERROR;
8152 }
8153 
8154 /*
8155  * Write a date and or time to a cell in Excel.
8156  */
8157 lxw_error
worksheet_write_unixtime(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,int64_t unixtime,lxw_format * format)8158 worksheet_write_unixtime(lxw_worksheet *self,
8159                          lxw_row_t row_num,
8160                          lxw_col_t col_num,
8161                          int64_t unixtime, lxw_format *format)
8162 {
8163     lxw_cell *cell;
8164     double excel_date;
8165     lxw_error err;
8166 
8167     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8168     if (err)
8169         return err;
8170 
8171     excel_date = lxw_unixtime_to_excel_date_epoch(unixtime, LXW_EPOCH_1900);
8172 
8173     cell = _new_number_cell(row_num, col_num, excel_date, format);
8174 
8175     _insert_cell(self, row_num, col_num, cell);
8176 
8177     return LXW_NO_ERROR;
8178 }
8179 
8180 /*
8181  * Write a hyperlink/url to an Excel file.
8182  */
8183 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)8184 worksheet_write_url_opt(lxw_worksheet *self,
8185                         lxw_row_t row_num,
8186                         lxw_col_t col_num, const char *url,
8187                         lxw_format *user_format, const char *string,
8188                         const char *tooltip)
8189 {
8190     lxw_cell *link;
8191     char *string_copy = NULL;
8192     char *url_copy = NULL;
8193     char *url_external = NULL;
8194     char *url_string = NULL;
8195     char *tooltip_copy = NULL;
8196     char *found_string;
8197     char *tmp_string = NULL;
8198     lxw_format *format = NULL;
8199     size_t string_size;
8200     size_t i;
8201     lxw_error err = LXW_ERROR_MEMORY_MALLOC_FAILED;
8202     enum cell_types link_type = HYPERLINK_URL;
8203 
8204     if (!url || !*url)
8205         return LXW_ERROR_NULL_PARAMETER_IGNORED;
8206 
8207     /* Check the Excel limit of URLS per worksheet. */
8208     if (self->hlink_count > LXW_MAX_NUMBER_URLS) {
8209         LXW_WARN("worksheet_write_url()/_opt(): URL ignored since it exceeds "
8210                  "the maximum number of allowed worksheet URLs (65530).");
8211         return LXW_ERROR_WORKSHEET_MAX_NUMBER_URLS_EXCEEDED;
8212     }
8213 
8214     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8215     if (err)
8216         return err;
8217 
8218     /* Reset default error condition. */
8219     err = LXW_ERROR_MEMORY_MALLOC_FAILED;
8220 
8221     /* Set the URI scheme from internal links. */
8222     found_string = strstr(url, "internal:");
8223     if (found_string)
8224         link_type = HYPERLINK_INTERNAL;
8225 
8226     /* Set the URI scheme from external links. */
8227     found_string = strstr(url, "external:");
8228     if (found_string)
8229         link_type = HYPERLINK_EXTERNAL;
8230 
8231     if (string) {
8232         string_copy = lxw_strdup(string);
8233         GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error);
8234     }
8235     else {
8236         if (link_type == HYPERLINK_URL) {
8237             /* Strip the mailto header. */
8238             found_string = strstr(url, "mailto:");
8239             if (found_string)
8240                 string_copy = lxw_strdup(url + sizeof("mailto"));
8241             else
8242                 string_copy = lxw_strdup(url);
8243         }
8244         else {
8245             string_copy = lxw_strdup(url + sizeof("__ternal"));
8246         }
8247         GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error);
8248     }
8249 
8250     if (url) {
8251         if (link_type == HYPERLINK_URL)
8252             url_copy = lxw_strdup(url);
8253         else
8254             url_copy = lxw_strdup(url + sizeof("__ternal"));
8255 
8256         GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
8257     }
8258 
8259     if (tooltip) {
8260         tooltip_copy = lxw_strdup(tooltip);
8261         GOTO_LABEL_ON_MEM_ERROR(tooltip_copy, mem_error);
8262     }
8263 
8264     if (link_type == HYPERLINK_INTERNAL) {
8265         url_string = lxw_strdup(string_copy);
8266         GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error);
8267     }
8268 
8269     /* Split url into the link and optional anchor/location. */
8270     found_string = strchr(url_copy, '#');
8271 
8272     if (found_string) {
8273         free(url_string);
8274         url_string = lxw_strdup(found_string + 1);
8275         GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error);
8276 
8277         *found_string = '\0';
8278     }
8279 
8280     /* Escape the URL. */
8281     if (link_type == HYPERLINK_URL || link_type == HYPERLINK_EXTERNAL) {
8282         tmp_string = lxw_escape_url_characters(url_copy, LXW_FALSE);
8283         GOTO_LABEL_ON_MEM_ERROR(tmp_string, mem_error);
8284 
8285         free(url_copy);
8286         url_copy = tmp_string;
8287     }
8288 
8289     if (link_type == HYPERLINK_EXTERNAL) {
8290         /* External Workbook links need to be modified into the right format.
8291          * The URL will look something like "c:\temp\file.xlsx#Sheet!A1". */
8292 
8293         /* For external links change the dir separator from Unix to DOS. */
8294         for (i = 0; i <= strlen(url_copy); i++)
8295             if (url_copy[i] == '/')
8296                 url_copy[i] = '\\';
8297 
8298         for (i = 0; i <= strlen(string_copy); i++)
8299             if (string_copy[i] == '/')
8300                 string_copy[i] = '\\';
8301 
8302         /* Look for Windows style "C:/" link or Windows share "\\" link. */
8303         found_string = strchr(url_copy, ':');
8304         if (!found_string)
8305             found_string = strstr(url_copy, "\\\\");
8306 
8307         if (found_string) {
8308             /* Add the file:/// URI to the url if non-local. */
8309             string_size = sizeof("file:///") + strlen(url_copy);
8310             url_external = calloc(1, string_size);
8311             GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error);
8312 
8313             lxw_snprintf(url_external, string_size, "file:///%s", url_copy);
8314 
8315         }
8316 
8317         /* Convert a ./dir/file.xlsx link to dir/file.xlsx. */
8318         found_string = strstr(url_copy, ".\\");
8319         if (found_string == url_copy)
8320             memmove(url_copy, url_copy + 2, strlen(url_copy) - 1);
8321 
8322         if (url_external) {
8323             free(url_copy);
8324             url_copy = lxw_strdup(url_external);
8325             GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
8326 
8327             free(url_external);
8328             url_external = NULL;
8329         }
8330 
8331     }
8332 
8333     /* Check if URL exceeds Excel's length limit. */
8334     if (lxw_utf8_strlen(url_copy) > self->max_url_length) {
8335         LXW_WARN_FORMAT2("worksheet_write_url()/_opt(): URL exceeds "
8336                          "Excel's allowable length of %d characters: %s",
8337                          self->max_url_length, url_copy);
8338         err = LXW_ERROR_WORKSHEET_MAX_URL_LENGTH_EXCEEDED;
8339         goto mem_error;
8340     }
8341 
8342     /* Use the default URL format if none is specified. */
8343     if (!user_format)
8344         format = self->default_url_format;
8345     else
8346         format = user_format;
8347 
8348     err = worksheet_write_string(self, row_num, col_num, string_copy, format);
8349     if (err)
8350         goto mem_error;
8351 
8352     /* Reset default error condition. */
8353     err = LXW_ERROR_MEMORY_MALLOC_FAILED;
8354 
8355     link = _new_hyperlink_cell(row_num, col_num, link_type, url_copy,
8356                                url_string, tooltip_copy);
8357     GOTO_LABEL_ON_MEM_ERROR(link, mem_error);
8358 
8359     _insert_hyperlink(self, row_num, col_num, link);
8360 
8361     free(string_copy);
8362     self->hlink_count++;
8363     return LXW_NO_ERROR;
8364 
8365 mem_error:
8366     free(string_copy);
8367     free(url_copy);
8368     free(url_external);
8369     free(url_string);
8370     free(tooltip_copy);
8371     return err;
8372 }
8373 
8374 /*
8375  * Write a hyperlink/url to an Excel file.
8376  */
8377 lxw_error
worksheet_write_url(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,const char * url,lxw_format * format)8378 worksheet_write_url(lxw_worksheet *self,
8379                     lxw_row_t row_num,
8380                     lxw_col_t col_num, const char *url, lxw_format *format)
8381 {
8382     return worksheet_write_url_opt(self, row_num, col_num, url, format, NULL,
8383                                    NULL);
8384 }
8385 
8386 /*
8387  * Write a rich string to an Excel file.
8388  *
8389  * Rather than duplicate several of the styles.c font xml methods of styles.c
8390  * and write the data to a memory buffer this function creates a temporary
8391  * styles object and uses it to write the data to a file. It then reads that
8392  * data back into memory and closes the file.
8393  */
8394 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)8395 worksheet_write_rich_string(lxw_worksheet *self,
8396                             lxw_row_t row_num,
8397                             lxw_col_t col_num,
8398                             lxw_rich_string_tuple *rich_strings[],
8399                             lxw_format *format)
8400 {
8401     lxw_cell *cell;
8402     int32_t string_id;
8403     struct sst_element *sst_element;
8404     lxw_error err;
8405     uint8_t i;
8406     long file_size;
8407     char *rich_string = NULL;
8408     char *string_copy = NULL;
8409     lxw_styles *styles = NULL;
8410     lxw_format *default_format = NULL;
8411     lxw_rich_string_tuple *rich_string_tuple = NULL;
8412     FILE *tmpfile;
8413 
8414     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8415     if (err)
8416         return err;
8417 
8418     /* Iterate through rich string fragments to check for input errors. */
8419     i = 0;
8420     err = LXW_NO_ERROR;
8421     while ((rich_string_tuple = rich_strings[i++]) != NULL) {
8422 
8423         /* Check for NULL or empty strings. */
8424         if (!rich_string_tuple->string || !*rich_string_tuple->string) {
8425             err = LXW_ERROR_PARAMETER_VALIDATION;
8426         }
8427     }
8428 
8429     /* If there are less than 2 fragments it isn't a rich string. */
8430     if (i <= 2)
8431         err = LXW_ERROR_PARAMETER_VALIDATION;
8432 
8433     if (err)
8434         return err;
8435 
8436     /* Create a tmp file for the styles object. */
8437     tmpfile = lxw_tmpfile(self->tmpdir);
8438     if (!tmpfile)
8439         return LXW_ERROR_CREATING_TMPFILE;
8440 
8441     /* Create a temp styles object for writing the font data. */
8442     styles = lxw_styles_new();
8443     GOTO_LABEL_ON_MEM_ERROR(styles, mem_error);
8444     styles->file = tmpfile;
8445 
8446     /* Create a default format for non-formatted text. */
8447     default_format = lxw_format_new();
8448     GOTO_LABEL_ON_MEM_ERROR(default_format, mem_error);
8449 
8450     /* Iterate through the rich string fragments and write each one out. */
8451     i = 0;
8452     while ((rich_string_tuple = rich_strings[i++]) != NULL) {
8453         lxw_xml_start_tag(tmpfile, "r", NULL);
8454 
8455         if (rich_string_tuple->format) {
8456             /* Write the user defined font format. */
8457             lxw_styles_write_rich_font(styles, rich_string_tuple->format);
8458         }
8459         else {
8460             /* Write a default font format. Except for the first fragment. */
8461             if (i > 1)
8462                 lxw_styles_write_rich_font(styles, default_format);
8463         }
8464 
8465         lxw_styles_write_string_fragment(styles, rich_string_tuple->string);
8466         lxw_xml_end_tag(tmpfile, "r");
8467     }
8468 
8469     /* Free the temp objects. */
8470     lxw_styles_free(styles);
8471     lxw_format_free(default_format);
8472 
8473     /* Flush the file and read the size to calculate the required memory. */
8474     fflush(tmpfile);
8475     file_size = ftell(tmpfile);
8476 
8477     /* Allocate a buffer for the rich string xml data. */
8478     rich_string = calloc(file_size + 1, 1);
8479     GOTO_LABEL_ON_MEM_ERROR(rich_string, mem_error);
8480 
8481     /* Rewind the file and read the data into the memory buffer. */
8482     rewind(tmpfile);
8483     if (fread(rich_string, file_size, 1, tmpfile) < 1) {
8484         fclose(tmpfile);
8485         free(rich_string);
8486         return LXW_ERROR_READING_TMPFILE;
8487     }
8488 
8489     /* Close the temp file. */
8490     fclose(tmpfile);
8491 
8492     if (lxw_utf8_strlen(rich_string) > LXW_STR_MAX) {
8493         free(rich_string);
8494         return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED;
8495     }
8496 
8497     if (!self->optimize) {
8498         /* Get the SST element and string id. */
8499         sst_element = lxw_get_sst_index(self->sst, rich_string, LXW_TRUE);
8500         free(rich_string);
8501 
8502         if (!sst_element)
8503             return LXW_ERROR_SHARED_STRING_INDEX_NOT_FOUND;
8504 
8505         string_id = sst_element->index;
8506         cell = _new_string_cell(row_num, col_num, string_id,
8507                                 sst_element->string, format);
8508     }
8509     else {
8510         /* Look for and escape control chars in the string. */
8511         if (lxw_has_control_characters(rich_string)) {
8512             string_copy = lxw_escape_control_characters(rich_string);
8513             free(rich_string);
8514         }
8515         else {
8516             string_copy = rich_string;
8517         }
8518         cell = _new_inline_rich_string_cell(row_num, col_num, string_copy,
8519                                             format);
8520     }
8521 
8522     _insert_cell(self, row_num, col_num, cell);
8523 
8524     return LXW_NO_ERROR;
8525 
8526 mem_error:
8527     lxw_styles_free(styles);
8528     lxw_format_free(default_format);
8529     fclose(tmpfile);
8530 
8531     return LXW_ERROR_MEMORY_MALLOC_FAILED;
8532 }
8533 
8534 /*
8535  * Write a comment to a worksheet cell in Excel.
8536  */
8537 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)8538 worksheet_write_comment_opt(lxw_worksheet *self,
8539                             lxw_row_t row_num, lxw_col_t col_num,
8540                             const char *text, lxw_comment_options *options)
8541 {
8542     lxw_cell *cell;
8543     lxw_error err;
8544     lxw_vml_obj *comment;
8545 
8546     err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
8547     if (err)
8548         return err;
8549 
8550     if (!text)
8551         return LXW_ERROR_NULL_PARAMETER_IGNORED;
8552 
8553     if (lxw_utf8_strlen(text) > LXW_STR_MAX)
8554         return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED;
8555 
8556     comment = calloc(1, sizeof(lxw_vml_obj));
8557     GOTO_LABEL_ON_MEM_ERROR(comment, mem_error);
8558 
8559     comment->text = lxw_strdup(text);
8560     GOTO_LABEL_ON_MEM_ERROR(comment->text, mem_error);
8561 
8562     comment->row = row_num;
8563     comment->col = col_num;
8564 
8565     cell = _new_comment_cell(row_num, col_num, comment);
8566     GOTO_LABEL_ON_MEM_ERROR(cell, mem_error);
8567 
8568     _insert_comment(self, row_num, col_num, cell);
8569 
8570     /* Set user and default parameters for the comment. */
8571     _get_comment_params(comment, options);
8572 
8573     self->has_vml = LXW_TRUE;
8574     self->has_comments = LXW_TRUE;
8575 
8576     /* Insert a placeholder in the cell RB table in the same position so
8577      * that the worksheet row "spans" calculations are correct. */
8578     _insert_cell_placeholder(self, row_num, col_num);
8579 
8580     return LXW_NO_ERROR;
8581 
8582 mem_error:
8583     if (comment)
8584         _free_vml_object(comment);
8585 
8586     return LXW_ERROR_MEMORY_MALLOC_FAILED;
8587 }
8588 
8589 /*
8590  * Write a comment to a worksheet cell in Excel.
8591  */
8592 lxw_error
worksheet_write_comment(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,const char * string)8593 worksheet_write_comment(lxw_worksheet *self,
8594                         lxw_row_t row_num, lxw_col_t col_num,
8595                         const char *string)
8596 {
8597     return worksheet_write_comment_opt(self, row_num, col_num, string, NULL);
8598 }
8599 
8600 /*
8601  * Set the properties of a single column or a range of columns with options.
8602  */
8603 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)8604 worksheet_set_column_opt(lxw_worksheet *self,
8605                          lxw_col_t firstcol,
8606                          lxw_col_t lastcol,
8607                          double width,
8608                          lxw_format *format,
8609                          lxw_row_col_options *user_options)
8610 {
8611     lxw_col_options *copied_options;
8612     uint8_t ignore_row = LXW_TRUE;
8613     uint8_t ignore_col = LXW_TRUE;
8614     uint8_t hidden = LXW_FALSE;
8615     uint8_t level = 0;
8616     uint8_t collapsed = LXW_FALSE;
8617     lxw_col_t col;
8618     lxw_error err;
8619 
8620     if (user_options) {
8621         hidden = user_options->hidden;
8622         level = user_options->level;
8623         collapsed = user_options->collapsed;
8624     }
8625 
8626     /* Ensure second col is larger than first. */
8627     if (firstcol > lastcol) {
8628         lxw_col_t tmp = firstcol;
8629         firstcol = lastcol;
8630         lastcol = tmp;
8631     }
8632 
8633     /* Ensure that the cols are valid and store max and min values.
8634      * NOTE: The check shouldn't modify the row dimensions and should only
8635      *       modify the column dimensions in certain cases. */
8636     if (format != NULL || (width != LXW_DEF_COL_WIDTH && hidden))
8637         ignore_col = LXW_FALSE;
8638 
8639     err = _check_dimensions(self, 0, firstcol, ignore_row, ignore_col);
8640 
8641     if (!err)
8642         err = _check_dimensions(self, 0, lastcol, ignore_row, ignore_col);
8643 
8644     if (err)
8645         return err;
8646 
8647     /* Resize the col_options array if required. */
8648     if (firstcol >= self->col_options_max) {
8649         lxw_col_t col_tmp;
8650         lxw_col_t old_size = self->col_options_max;
8651         lxw_col_t new_size = _next_power_of_two(firstcol + 1);
8652         lxw_col_options **new_ptr = realloc(self->col_options,
8653                                             new_size *
8654                                             sizeof(lxw_col_options *));
8655 
8656         if (new_ptr) {
8657             for (col_tmp = old_size; col_tmp < new_size; col_tmp++)
8658                 new_ptr[col_tmp] = NULL;
8659 
8660             self->col_options = new_ptr;
8661             self->col_options_max = new_size;
8662         }
8663         else {
8664             return LXW_ERROR_MEMORY_MALLOC_FAILED;
8665         }
8666     }
8667 
8668     /* Resize the col_formats array if required. */
8669     if (lastcol >= self->col_formats_max) {
8670         lxw_col_t col;
8671         lxw_col_t old_size = self->col_formats_max;
8672         lxw_col_t new_size = _next_power_of_two(lastcol + 1);
8673         lxw_format **new_ptr = realloc(self->col_formats,
8674                                        new_size * sizeof(lxw_format *));
8675 
8676         if (new_ptr) {
8677             for (col = old_size; col < new_size; col++)
8678                 new_ptr[col] = NULL;
8679 
8680             self->col_formats = new_ptr;
8681             self->col_formats_max = new_size;
8682         }
8683         else {
8684             return LXW_ERROR_MEMORY_MALLOC_FAILED;
8685         }
8686     }
8687 
8688     /* Store the column options. */
8689     copied_options = calloc(1, sizeof(lxw_col_options));
8690     RETURN_ON_MEM_ERROR(copied_options, LXW_ERROR_MEMORY_MALLOC_FAILED);
8691 
8692     /* Ensure the level is <= 7). */
8693     if (level > 7)
8694         level = 7;
8695 
8696     if (level > self->outline_col_level)
8697         self->outline_col_level = level;
8698 
8699     /* Set the column properties. */
8700     copied_options->firstcol = firstcol;
8701     copied_options->lastcol = lastcol;
8702     copied_options->width = width;
8703     copied_options->format = format;
8704     copied_options->hidden = hidden;
8705     copied_options->level = level;
8706     copied_options->collapsed = collapsed;
8707 
8708     free(self->col_options[firstcol]);
8709     self->col_options[firstcol] = copied_options;
8710 
8711     /* Store the column formats for use when writing cell data. */
8712     for (col = firstcol; col <= lastcol; col++) {
8713         self->col_formats[col] = format;
8714     }
8715 
8716     /* Store the column change to allow optimizations. */
8717     self->col_size_changed = LXW_TRUE;
8718 
8719     return LXW_NO_ERROR;
8720 }
8721 
8722 /*
8723  * Set the properties of a single column or a range of columns.
8724  */
8725 lxw_error
worksheet_set_column(lxw_worksheet * self,lxw_col_t firstcol,lxw_col_t lastcol,double width,lxw_format * format)8726 worksheet_set_column(lxw_worksheet *self,
8727                      lxw_col_t firstcol,
8728                      lxw_col_t lastcol, double width, lxw_format *format)
8729 {
8730     return worksheet_set_column_opt(self, firstcol, lastcol, width, format,
8731                                     NULL);
8732 }
8733 
8734 /*
8735  * Set the properties of a single column or a range of columns, with the
8736  * width in pixels.
8737  */
8738 lxw_error
worksheet_set_column_pixels(lxw_worksheet * self,lxw_col_t firstcol,lxw_col_t lastcol,uint32_t pixels,lxw_format * format)8739 worksheet_set_column_pixels(lxw_worksheet *self,
8740                             lxw_col_t firstcol,
8741                             lxw_col_t lastcol,
8742                             uint32_t pixels, lxw_format *format)
8743 {
8744     double width = _pixels_to_width(pixels);
8745 
8746     return worksheet_set_column_opt(self, firstcol, lastcol, width, format,
8747                                     NULL);
8748 }
8749 
8750 /*
8751  * Set the properties of a single column or a range of columns with options,
8752  * with the width in pixels.
8753  */
8754 lxw_error
worksheet_set_column_pixels_opt(lxw_worksheet * self,lxw_col_t firstcol,lxw_col_t lastcol,uint32_t pixels,lxw_format * format,lxw_row_col_options * user_options)8755 worksheet_set_column_pixels_opt(lxw_worksheet *self,
8756                                 lxw_col_t firstcol,
8757                                 lxw_col_t lastcol,
8758                                 uint32_t pixels,
8759                                 lxw_format *format,
8760                                 lxw_row_col_options *user_options)
8761 {
8762     double width = _pixels_to_width(pixels);
8763 
8764     return worksheet_set_column_opt(self, firstcol, lastcol, width, format,
8765                                     user_options);
8766 }
8767 
8768 /*
8769  * Set the properties of a row with options.
8770  */
8771 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)8772 worksheet_set_row_opt(lxw_worksheet *self,
8773                       lxw_row_t row_num,
8774                       double height,
8775                       lxw_format *format, lxw_row_col_options *user_options)
8776 {
8777 
8778     lxw_col_t min_col;
8779     uint8_t hidden = LXW_FALSE;
8780     uint8_t level = 0;
8781     uint8_t collapsed = LXW_FALSE;
8782     lxw_row *row;
8783     lxw_error err;
8784 
8785     if (user_options) {
8786         hidden = user_options->hidden;
8787         level = user_options->level;
8788         collapsed = user_options->collapsed;
8789     }
8790 
8791     /* Use minimum col in _check_dimensions(). */
8792     if (self->dim_colmin != LXW_COL_MAX)
8793         min_col = self->dim_colmin;
8794     else
8795         min_col = 0;
8796 
8797     err = _check_dimensions(self, row_num, min_col, LXW_FALSE, LXW_FALSE);
8798     if (err)
8799         return err;
8800 
8801     /* If the height is 0 the row is hidden and the height is the default. */
8802     if (height == 0) {
8803         hidden = LXW_TRUE;
8804         height = self->default_row_height;
8805     }
8806 
8807     /* Ensure the level is <= 7). */
8808     if (level > 7)
8809         level = 7;
8810 
8811     if (level > self->outline_row_level)
8812         self->outline_row_level = level;
8813 
8814     /* Store the row properties. */
8815     row = _get_row(self, row_num);
8816 
8817     row->height = height;
8818     row->format = format;
8819     row->hidden = hidden;
8820     row->level = level;
8821     row->collapsed = collapsed;
8822     row->row_changed = LXW_TRUE;
8823 
8824     if (height != self->default_row_height)
8825         row->height_changed = LXW_TRUE;
8826 
8827     return LXW_NO_ERROR;
8828 }
8829 
8830 /*
8831  * Set the properties of a row.
8832  */
8833 lxw_error
worksheet_set_row(lxw_worksheet * self,lxw_row_t row_num,double height,lxw_format * format)8834 worksheet_set_row(lxw_worksheet *self,
8835                   lxw_row_t row_num, double height, lxw_format *format)
8836 {
8837     return worksheet_set_row_opt(self, row_num, height, format, NULL);
8838 }
8839 
8840 /*
8841  * Set the properties of a row, with the height in pixels.
8842  */
8843 lxw_error
worksheet_set_row_pixels(lxw_worksheet * self,lxw_row_t row_num,uint32_t pixels,lxw_format * format)8844 worksheet_set_row_pixels(lxw_worksheet *self,
8845                          lxw_row_t row_num, uint32_t pixels,
8846                          lxw_format *format)
8847 {
8848     double height = _pixels_to_height(pixels);
8849 
8850     return worksheet_set_row_opt(self, row_num, height, format, NULL);
8851 }
8852 
8853 /*
8854  * Set the properties of a row with options, with the height in pixels.
8855  */
8856 lxw_error
worksheet_set_row_pixels_opt(lxw_worksheet * self,lxw_row_t row_num,uint32_t pixels,lxw_format * format,lxw_row_col_options * user_options)8857 worksheet_set_row_pixels_opt(lxw_worksheet *self,
8858                              lxw_row_t row_num,
8859                              uint32_t pixels,
8860                              lxw_format *format,
8861                              lxw_row_col_options *user_options)
8862 {
8863     double height = _pixels_to_height(pixels);
8864 
8865     return worksheet_set_row_opt(self, row_num, height, format, user_options);
8866 }
8867 
8868 /*
8869  * Merge a range of cells. The first cell should contain the data and the others
8870  * should be blank. All cells should contain the same format.
8871  */
8872 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)8873 worksheet_merge_range(lxw_worksheet *self, lxw_row_t first_row,
8874                       lxw_col_t first_col, lxw_row_t last_row,
8875                       lxw_col_t last_col, const char *string,
8876                       lxw_format *format)
8877 {
8878     lxw_merged_range *merged_range;
8879     lxw_row_t tmp_row;
8880     lxw_col_t tmp_col;
8881     lxw_error err;
8882 
8883     /* Excel doesn't allow a single cell to be merged */
8884     if (first_row == last_row && first_col == last_col)
8885         return LXW_ERROR_PARAMETER_VALIDATION;
8886 
8887     /* Swap last row/col with first row/col as necessary */
8888     if (first_row > last_row) {
8889         tmp_row = last_row;
8890         last_row = first_row;
8891         first_row = tmp_row;
8892     }
8893     if (first_col > last_col) {
8894         tmp_col = last_col;
8895         last_col = first_col;
8896         first_col = tmp_col;
8897     }
8898 
8899     /* Check that column number is valid and store the max value */
8900     err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
8901     if (err)
8902         return err;
8903 
8904     /* Store the merge range. */
8905     merged_range = calloc(1, sizeof(lxw_merged_range));
8906     RETURN_ON_MEM_ERROR(merged_range, LXW_ERROR_MEMORY_MALLOC_FAILED);
8907 
8908     merged_range->first_row = first_row;
8909     merged_range->first_col = first_col;
8910     merged_range->last_row = last_row;
8911     merged_range->last_col = last_col;
8912 
8913     STAILQ_INSERT_TAIL(self->merged_ranges, merged_range, list_pointers);
8914     self->merged_range_count++;
8915 
8916     /* Write the first cell */
8917     worksheet_write_string(self, first_row, first_col, string, format);
8918 
8919     /* Pad out the rest of the area with formatted blank cells. */
8920     for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) {
8921         for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) {
8922             if (tmp_row == first_row && tmp_col == first_col)
8923                 continue;
8924 
8925             worksheet_write_blank(self, tmp_row, tmp_col, format);
8926         }
8927     }
8928 
8929     return LXW_NO_ERROR;
8930 }
8931 
8932 /*
8933  * Set the autofilter area in the worksheet.
8934  */
8935 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)8936 worksheet_autofilter(lxw_worksheet *self, lxw_row_t first_row,
8937                      lxw_col_t first_col, lxw_row_t last_row,
8938                      lxw_col_t last_col)
8939 {
8940     lxw_row_t tmp_row;
8941     lxw_col_t tmp_col;
8942     lxw_error err;
8943     lxw_filter_rule_obj **filter_rules;
8944     lxw_col_t num_filter_rules;
8945 
8946     /* Swap last row/col with first row/col as necessary */
8947     if (first_row > last_row) {
8948         tmp_row = last_row;
8949         last_row = first_row;
8950         first_row = tmp_row;
8951     }
8952     if (first_col > last_col) {
8953         tmp_col = last_col;
8954         last_col = first_col;
8955         first_col = tmp_col;
8956     }
8957 
8958     /* Check that column number is valid and store the max value */
8959     err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
8960     if (err)
8961         return err;
8962 
8963     /* Create a array to hold filter rules. */
8964     self->autofilter.in_use = LXW_FALSE;
8965     self->autofilter.has_rules = LXW_FALSE;
8966     _free_filter_rules(self);
8967     num_filter_rules = last_col - first_col + 1;
8968     filter_rules = calloc(num_filter_rules, sizeof(lxw_filter_rule_obj *));
8969     RETURN_ON_MEM_ERROR(filter_rules, LXW_ERROR_MEMORY_MALLOC_FAILED);
8970 
8971     self->autofilter.in_use = LXW_TRUE;
8972     self->autofilter.first_row = first_row;
8973     self->autofilter.first_col = first_col;
8974     self->autofilter.last_row = last_row;
8975     self->autofilter.last_col = last_col;
8976 
8977     self->filter_rules = filter_rules;
8978     self->num_filter_rules = num_filter_rules;
8979 
8980     return LXW_NO_ERROR;
8981 }
8982 
8983 /*
8984  * Set a autofilter rule for a filter column.
8985  */
8986 lxw_error
worksheet_filter_column(lxw_worksheet * self,lxw_col_t col,lxw_filter_rule * rule)8987 worksheet_filter_column(lxw_worksheet *self, lxw_col_t col,
8988                         lxw_filter_rule *rule)
8989 {
8990     lxw_filter_rule_obj *rule_obj;
8991     uint16_t rule_index;
8992 
8993     if (!rule) {
8994         LXW_WARN("worksheet_filter_column(): rule parameter cannot be NULL");
8995         return LXW_ERROR_PARAMETER_VALIDATION;
8996     }
8997 
8998     if (self->autofilter.in_use == LXW_FALSE) {
8999         LXW_WARN("worksheet_filter_column(): "
9000                  "Worksheet autofilter range hasn't been defined. "
9001                  "Use worksheet_autofilter() first.");
9002         return LXW_ERROR_PARAMETER_VALIDATION;
9003     }
9004 
9005     if (col < self->autofilter.first_col || col > self->autofilter.last_col) {
9006         LXW_WARN_FORMAT3("worksheet_filter_column(): "
9007                          "Column '%d' is outside autofilter range "
9008                          "'%d <= col <= %d'.", col,
9009                          self->autofilter.first_col,
9010                          self->autofilter.last_col);
9011         return LXW_ERROR_PARAMETER_VALIDATION;
9012     }
9013 
9014     /* Free any previous rule in the column slot. */
9015     rule_index = col - self->autofilter.first_col;
9016     _free_filter_rule(self->filter_rules[rule_index]);
9017 
9018     /* Create a new rule and copy user input. */
9019     rule_obj = calloc(1, sizeof(lxw_filter_rule_obj));
9020     RETURN_ON_MEM_ERROR(rule_obj, LXW_ERROR_MEMORY_MALLOC_FAILED);
9021 
9022     rule_obj->col_num = rule_index;
9023     rule_obj->type = LXW_FILTER_TYPE_SINGLE;
9024     rule_obj->criteria1 = rule->criteria;
9025     rule_obj->value1 = rule->value;
9026 
9027     if (rule_obj->criteria1 != LXW_FILTER_CRITERIA_NON_BLANKS) {
9028         rule_obj->value1_string = lxw_strdup(rule->value_string);
9029     }
9030     else {
9031         rule_obj->criteria1 = LXW_FILTER_CRITERIA_NOT_EQUAL_TO;
9032         rule_obj->value1_string = lxw_strdup(" ");
9033     }
9034 
9035     if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS)
9036         rule_obj->has_blanks = LXW_TRUE;
9037 
9038     _set_custom_filter(rule_obj);
9039 
9040     self->filter_rules[rule_index] = rule_obj;
9041     self->filter_on = LXW_TRUE;
9042     self->autofilter.has_rules = LXW_TRUE;
9043 
9044     return LXW_NO_ERROR;
9045 }
9046 
9047 /*
9048  * Set two autofilter rules for a filter column.
9049  */
9050 lxw_error
worksheet_filter_column2(lxw_worksheet * self,lxw_col_t col,lxw_filter_rule * rule1,lxw_filter_rule * rule2,uint8_t and_or)9051 worksheet_filter_column2(lxw_worksheet *self, lxw_col_t col,
9052                          lxw_filter_rule *rule1, lxw_filter_rule *rule2,
9053                          uint8_t and_or)
9054 {
9055     lxw_filter_rule_obj *rule_obj;
9056     uint16_t rule_index;
9057 
9058     if (!rule1 || !rule2) {
9059         LXW_WARN("worksheet_filter_column2(): rule parameter cannot be NULL");
9060         return LXW_ERROR_PARAMETER_VALIDATION;
9061     }
9062 
9063     if (self->autofilter.in_use == LXW_FALSE) {
9064         LXW_WARN("worksheet_filter_column2(): "
9065                  "Worksheet autofilter range hasn't been defined. "
9066                  "Use worksheet_autofilter() first.");
9067         return LXW_ERROR_PARAMETER_VALIDATION;
9068     }
9069 
9070     if (col < self->autofilter.first_col || col > self->autofilter.last_col) {
9071         LXW_WARN_FORMAT3("worksheet_filter_column2(): "
9072                          "Column '%d' is outside autofilter range "
9073                          "'%d <= col <= %d'.", col,
9074                          self->autofilter.first_col,
9075                          self->autofilter.last_col);
9076         return LXW_ERROR_PARAMETER_VALIDATION;
9077     }
9078 
9079     /* Free any previous rule in the column slot. */
9080     rule_index = col - self->autofilter.first_col;
9081     _free_filter_rule(self->filter_rules[rule_index]);
9082 
9083     /* Create a new rule and copy user input. */
9084     rule_obj = calloc(1, sizeof(lxw_filter_rule_obj));
9085     RETURN_ON_MEM_ERROR(rule_obj, LXW_ERROR_MEMORY_MALLOC_FAILED);
9086 
9087     if (and_or == LXW_FILTER_AND)
9088         rule_obj->type = LXW_FILTER_TYPE_AND;
9089     else
9090         rule_obj->type = LXW_FILTER_TYPE_OR;
9091 
9092     rule_obj->col_num = rule_index;
9093 
9094     rule_obj->criteria1 = rule1->criteria;
9095     rule_obj->value1 = rule1->value;
9096 
9097     rule_obj->criteria2 = rule2->criteria;
9098     rule_obj->value2 = rule2->value;
9099 
9100     if (rule_obj->criteria1 != LXW_FILTER_CRITERIA_NON_BLANKS) {
9101         rule_obj->value1_string = lxw_strdup(rule1->value_string);
9102     }
9103     else {
9104         rule_obj->criteria1 = LXW_FILTER_CRITERIA_NOT_EQUAL_TO;
9105         rule_obj->value1_string = lxw_strdup(" ");
9106     }
9107 
9108     if (rule_obj->criteria2 != LXW_FILTER_CRITERIA_NON_BLANKS) {
9109         rule_obj->value2_string = lxw_strdup(rule2->value_string);
9110     }
9111     else {
9112         rule_obj->criteria2 = LXW_FILTER_CRITERIA_NOT_EQUAL_TO;
9113         rule_obj->value2_string = lxw_strdup(" ");
9114     }
9115 
9116     if (rule_obj->criteria1 == LXW_FILTER_CRITERIA_BLANKS)
9117         rule_obj->has_blanks = LXW_TRUE;
9118 
9119     if (rule_obj->criteria2 == LXW_FILTER_CRITERIA_BLANKS)
9120         rule_obj->has_blanks = LXW_TRUE;
9121 
9122     _set_custom_filter(rule_obj);
9123 
9124     self->filter_rules[rule_index] = rule_obj;
9125     self->filter_on = LXW_TRUE;
9126     self->autofilter.has_rules = LXW_TRUE;
9127 
9128     return LXW_NO_ERROR;
9129 }
9130 
9131 /*
9132  * Set two autofilter rules for a filter column.
9133  */
9134 lxw_error
worksheet_filter_list(lxw_worksheet * self,lxw_col_t col,char ** list)9135 worksheet_filter_list(lxw_worksheet *self, lxw_col_t col, char **list)
9136 {
9137     lxw_filter_rule_obj *rule_obj;
9138     uint16_t rule_index;
9139     uint8_t has_blanks = LXW_FALSE;
9140     uint16_t num_filters = 0;
9141     uint16_t input_list_index;
9142     uint16_t rule_obj_list_index;
9143     const char *str;
9144     char **tmp_list;
9145 
9146     if (!list) {
9147         LXW_WARN("worksheet_filter_list(): list parameter cannot be NULL");
9148         return LXW_ERROR_PARAMETER_VALIDATION;
9149     }
9150 
9151     if (self->autofilter.in_use == LXW_FALSE) {
9152         LXW_WARN("worksheet_filter_list(): "
9153                  "Worksheet autofilter range hasn't been defined. "
9154                  "Use worksheet_autofilter() first.");
9155         return LXW_ERROR_PARAMETER_VALIDATION;
9156     }
9157 
9158     if (col < self->autofilter.first_col || col > self->autofilter.last_col) {
9159         LXW_WARN_FORMAT3("worksheet_filter_list(): "
9160                          "Column '%d' is outside autofilter range "
9161                          "'%d <= col <= %d'.", col,
9162                          self->autofilter.first_col,
9163                          self->autofilter.last_col);
9164         return LXW_ERROR_PARAMETER_VALIDATION;
9165     }
9166 
9167     /* Count the number of non "Blanks" strings in the input list. */
9168     input_list_index = 0;
9169     while ((str = list[input_list_index]) != NULL) {
9170         if (strncmp(str, "Blanks", 6) == 0)
9171             has_blanks = LXW_TRUE;
9172         else
9173             num_filters++;
9174 
9175         input_list_index++;
9176     }
9177 
9178     /* There should be at least one filter string. */
9179     if (num_filters == 0) {
9180         LXW_WARN("worksheet_filter_list(): "
9181                  "list must have at least 1 non-blanks item.");
9182         return LXW_ERROR_PARAMETER_VALIDATION;
9183     }
9184 
9185     /* Free any previous rule in the column slot. */
9186     rule_index = col - self->autofilter.first_col;
9187     _free_filter_rule(self->filter_rules[rule_index]);
9188 
9189     /* Create a new rule and copy user input. */
9190     rule_obj = calloc(1, sizeof(lxw_filter_rule_obj));
9191     RETURN_ON_MEM_ERROR(rule_obj, LXW_ERROR_MEMORY_MALLOC_FAILED);
9192 
9193     tmp_list = calloc(num_filters + 1, sizeof(char *));
9194     GOTO_LABEL_ON_MEM_ERROR(tmp_list, mem_error);
9195 
9196     /* Copy input list (without any "Blanks" command) to an internal list. */
9197     input_list_index = 0;
9198     rule_obj_list_index = 0;
9199     while ((str = list[input_list_index]) != NULL) {
9200         if (strncmp(str, "Blanks", 6) != 0) {
9201             tmp_list[rule_obj_list_index] = lxw_strdup(str);
9202             rule_obj_list_index++;
9203         }
9204 
9205         input_list_index++;
9206     }
9207 
9208     rule_obj->list = tmp_list;
9209     rule_obj->num_list_filters = num_filters;
9210     rule_obj->is_custom = LXW_FALSE;
9211     rule_obj->col_num = rule_index;
9212     rule_obj->type = LXW_FILTER_TYPE_STRING_LIST;
9213     rule_obj->has_blanks = has_blanks;
9214 
9215     self->filter_rules[rule_index] = rule_obj;
9216     self->filter_on = LXW_TRUE;
9217     self->autofilter.has_rules = LXW_TRUE;
9218 
9219     return LXW_NO_ERROR;
9220 
9221 mem_error:
9222     free(rule_obj);
9223     return LXW_ERROR_MEMORY_MALLOC_FAILED;
9224 
9225 }
9226 
9227 /*
9228  * Add an Excel table to the worksheet.
9229  */
9230 lxw_error
worksheet_add_table(lxw_worksheet * self,lxw_row_t first_row,lxw_col_t first_col,lxw_row_t last_row,lxw_col_t last_col,lxw_table_options * user_options)9231 worksheet_add_table(lxw_worksheet *self, lxw_row_t first_row,
9232                     lxw_col_t first_col, lxw_row_t last_row,
9233                     lxw_col_t last_col, lxw_table_options *user_options)
9234 {
9235     lxw_row_t tmp_row;
9236     lxw_col_t tmp_col;
9237     lxw_col_t num_cols;
9238     lxw_error err;
9239     lxw_table_obj *table_obj;
9240     lxw_table_column **columns;
9241 
9242     if (self->optimize) {
9243         LXW_WARN_FORMAT("worksheet_add_table(): "
9244                         "worksheet tables aren't supported in "
9245                         "'constant_memory' mode");
9246         return LXW_ERROR_FEATURE_NOT_SUPPORTED;
9247     }
9248 
9249     /* Swap last row/col with first row/col as necessary */
9250     if (first_row > last_row) {
9251         tmp_row = last_row;
9252         last_row = first_row;
9253         first_row = tmp_row;
9254     }
9255     if (first_col > last_col) {
9256         tmp_col = last_col;
9257         last_col = first_col;
9258         first_col = tmp_col;
9259     }
9260 
9261     /* Check that column number is valid and store the max value */
9262     err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE);
9263     if (err)
9264         return err;
9265 
9266     num_cols = last_col - first_col + 1;
9267 
9268     /* Check that there are sufficient data rows. */
9269     err = _check_table_rows(first_row, last_row, user_options);
9270     if (err)
9271         return err;
9272 
9273     /* Check that the the table name is valid. */
9274     err = _check_table_name(user_options);
9275     if (err)
9276         return err;
9277 
9278     /* Create a table object to copy from the user options. */
9279     table_obj = calloc(1, sizeof(lxw_table_obj));
9280     RETURN_ON_MEM_ERROR(table_obj, LXW_ERROR_MEMORY_MALLOC_FAILED);
9281 
9282     columns = calloc(num_cols, sizeof(lxw_table_column *));
9283     GOTO_LABEL_ON_MEM_ERROR(columns, error);
9284 
9285     table_obj->columns = columns;
9286     table_obj->num_cols = num_cols;
9287     table_obj->first_row = first_row;
9288     table_obj->first_col = first_col;
9289     table_obj->last_row = last_row;
9290     table_obj->last_col = last_col;
9291 
9292     err = _set_default_table_columns(table_obj);
9293     if (err)
9294         goto error;
9295 
9296     /* Create the table range. */
9297     lxw_rowcol_to_range(table_obj->sqref,
9298                         first_row, first_col, last_row, last_col);
9299     lxw_rowcol_to_range(table_obj->filter_sqref,
9300                         first_row, first_col, last_row, last_col);
9301 
9302     /* Validate and copy user options to an internal object. */
9303     if (user_options) {
9304 
9305         _check_and_copy_table_style(table_obj, user_options);
9306 
9307         table_obj->total_row = user_options->total_row;
9308         table_obj->last_column = user_options->last_column;
9309         table_obj->first_column = user_options->first_column;
9310         table_obj->no_autofilter = user_options->no_autofilter;
9311         table_obj->no_header_row = user_options->no_header_row;
9312         table_obj->no_banded_rows = user_options->no_banded_rows;
9313         table_obj->banded_columns = user_options->banded_columns;
9314 
9315         if (user_options->no_header_row)
9316             table_obj->no_autofilter = LXW_TRUE;
9317 
9318         if (user_options->columns) {
9319             err = _set_custom_table_columns(table_obj, user_options);
9320             if (err)
9321                 goto error;
9322         }
9323 
9324         if (user_options->total_row) {
9325             lxw_rowcol_to_range(table_obj->filter_sqref,
9326                                 first_row, first_col, last_row - 1, last_col);
9327         }
9328 
9329         if (user_options->name) {
9330             table_obj->name = lxw_strdup(user_options->name);
9331             if (!table_obj->name) {
9332                 err = LXW_ERROR_MEMORY_MALLOC_FAILED;
9333                 goto error;
9334             }
9335         }
9336     }
9337 
9338     _write_table_column_data(self, table_obj);
9339 
9340     STAILQ_INSERT_TAIL(self->table_objs, table_obj, list_pointers);
9341     self->table_count++;
9342 
9343     return LXW_NO_ERROR;
9344 
9345 error:
9346     _free_worksheet_table(table_obj);
9347     return err;
9348 
9349 }
9350 
9351 /*
9352  * Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
9353  * highlighted.
9354  */
9355 void
worksheet_select(lxw_worksheet * self)9356 worksheet_select(lxw_worksheet *self)
9357 {
9358     self->selected = LXW_TRUE;
9359 
9360     /* Selected worksheet can't be hidden. */
9361     self->hidden = LXW_FALSE;
9362 }
9363 
9364 /*
9365  * Set this worksheet as the active worksheet, i.e. the worksheet that is
9366  * displayed when the workbook is opened. Also set it as selected.
9367  */
9368 void
worksheet_activate(lxw_worksheet * self)9369 worksheet_activate(lxw_worksheet *self)
9370 {
9371     self->selected = LXW_TRUE;
9372     self->active = LXW_TRUE;
9373 
9374     /* Active worksheet can't be hidden. */
9375     self->hidden = LXW_FALSE;
9376 
9377     *self->active_sheet = self->index;
9378 }
9379 
9380 /*
9381  * Set this worksheet as the first visible sheet. This is necessary
9382  * when there are a large number of worksheets and the activated
9383  * worksheet is not visible on the screen.
9384  */
9385 void
worksheet_set_first_sheet(lxw_worksheet * self)9386 worksheet_set_first_sheet(lxw_worksheet *self)
9387 {
9388     /* Active worksheet can't be hidden. */
9389     self->hidden = LXW_FALSE;
9390 
9391     *self->first_sheet = self->index;
9392 }
9393 
9394 /*
9395  * Hide this worksheet.
9396  */
9397 void
worksheet_hide(lxw_worksheet * self)9398 worksheet_hide(lxw_worksheet *self)
9399 {
9400     self->hidden = LXW_TRUE;
9401 
9402     /* A hidden worksheet shouldn't be active or selected. */
9403     self->selected = LXW_FALSE;
9404 
9405     /* If this is active_sheet or first_sheet reset the workbook value. */
9406     if (*self->first_sheet == self->index)
9407         *self->first_sheet = 0;
9408 
9409     if (*self->active_sheet == self->index)
9410         *self->active_sheet = 0;
9411 }
9412 
9413 /*
9414  * Set which cell or cells are selected in a worksheet.
9415  */
9416 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)9417 worksheet_set_selection(lxw_worksheet *self,
9418                         lxw_row_t first_row, lxw_col_t first_col,
9419                         lxw_row_t last_row, lxw_col_t last_col)
9420 {
9421     lxw_selection *selection;
9422     lxw_row_t tmp_row;
9423     lxw_col_t tmp_col;
9424     char active_cell[LXW_MAX_CELL_RANGE_LENGTH];
9425     char sqref[LXW_MAX_CELL_RANGE_LENGTH];
9426 
9427     /* Only allow selection to be set once to avoid freeing/re-creating it. */
9428     if (!STAILQ_EMPTY(self->selections))
9429         return;
9430 
9431     /* Excel doesn't set a selection for cell A1 since it is the default. */
9432     if (first_row == 0 && first_col == 0 && last_row == 0 && last_col == 0)
9433         return;
9434 
9435     selection = calloc(1, sizeof(lxw_selection));
9436     RETURN_VOID_ON_MEM_ERROR(selection);
9437 
9438     /* Set the cell range selection. Do this before swapping max/min to  */
9439     /* allow the selection direction to be reversed. */
9440     lxw_rowcol_to_cell(active_cell, first_row, first_col);
9441 
9442     /* Swap last row/col for first row/col if necessary. */
9443     if (first_row > last_row) {
9444         tmp_row = first_row;
9445         first_row = last_row;
9446         last_row = tmp_row;
9447     }
9448 
9449     if (first_col > last_col) {
9450         tmp_col = first_col;
9451         first_col = last_col;
9452         last_col = tmp_col;
9453     }
9454 
9455     /* If the first and last cell are the same write a single cell. */
9456     if ((first_row == last_row) && (first_col == last_col))
9457         lxw_rowcol_to_cell(sqref, first_row, first_col);
9458     else
9459         lxw_rowcol_to_range(sqref, first_row, first_col, last_row, last_col);
9460 
9461     lxw_strcpy(selection->pane, "");
9462     lxw_strcpy(selection->active_cell, active_cell);
9463     lxw_strcpy(selection->sqref, sqref);
9464 
9465     STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
9466 }
9467 
9468 /*
9469  * Set panes and mark them as frozen. With extra options.
9470  */
9471 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)9472 worksheet_freeze_panes_opt(lxw_worksheet *self,
9473                            lxw_row_t first_row, lxw_col_t first_col,
9474                            lxw_row_t top_row, lxw_col_t left_col,
9475                            uint8_t type)
9476 {
9477     self->panes.first_row = first_row;
9478     self->panes.first_col = first_col;
9479     self->panes.top_row = top_row;
9480     self->panes.left_col = left_col;
9481     self->panes.x_split = 0.0;
9482     self->panes.y_split = 0.0;
9483 
9484     if (type)
9485         self->panes.type = FREEZE_SPLIT_PANES;
9486     else
9487         self->panes.type = FREEZE_PANES;
9488 }
9489 
9490 /*
9491  * Set panes and mark them as frozen.
9492  */
9493 void
worksheet_freeze_panes(lxw_worksheet * self,lxw_row_t first_row,lxw_col_t first_col)9494 worksheet_freeze_panes(lxw_worksheet *self,
9495                        lxw_row_t first_row, lxw_col_t first_col)
9496 {
9497     worksheet_freeze_panes_opt(self, first_row, first_col,
9498                                first_row, first_col, 0);
9499 }
9500 
9501 /*
9502  * Set panes and mark them as split.With extra options.
9503  */
9504 void
worksheet_split_panes_opt(lxw_worksheet * self,double y_split,double x_split,lxw_row_t top_row,lxw_col_t left_col)9505 worksheet_split_panes_opt(lxw_worksheet *self,
9506                           double y_split, double x_split,
9507                           lxw_row_t top_row, lxw_col_t left_col)
9508 {
9509     self->panes.first_row = 0;
9510     self->panes.first_col = 0;
9511     self->panes.top_row = top_row;
9512     self->panes.left_col = left_col;
9513     self->panes.x_split = x_split;
9514     self->panes.y_split = y_split;
9515     self->panes.type = SPLIT_PANES;
9516 }
9517 
9518 /*
9519  * Set panes and mark them as split.
9520  */
9521 void
worksheet_split_panes(lxw_worksheet * self,double y_split,double x_split)9522 worksheet_split_panes(lxw_worksheet *self, double y_split, double x_split)
9523 {
9524     worksheet_split_panes_opt(self, y_split, x_split, 0, 0);
9525 }
9526 
9527 /*
9528  * Set the page orientation as portrait.
9529  */
9530 void
worksheet_set_portrait(lxw_worksheet * self)9531 worksheet_set_portrait(lxw_worksheet *self)
9532 {
9533     self->orientation = LXW_PORTRAIT;
9534     self->page_setup_changed = LXW_TRUE;
9535 }
9536 
9537 /*
9538  * Set the page orientation as landscape.
9539  */
9540 void
worksheet_set_landscape(lxw_worksheet * self)9541 worksheet_set_landscape(lxw_worksheet *self)
9542 {
9543     self->orientation = LXW_LANDSCAPE;
9544     self->page_setup_changed = LXW_TRUE;
9545 }
9546 
9547 /*
9548  * Set the page view mode for Mac Excel.
9549  */
9550 void
worksheet_set_page_view(lxw_worksheet * self)9551 worksheet_set_page_view(lxw_worksheet *self)
9552 {
9553     self->page_view = LXW_TRUE;
9554 }
9555 
9556 /*
9557  * Set the paper type. Example. 1 = US Letter, 9 = A4
9558  */
9559 void
worksheet_set_paper(lxw_worksheet * self,uint8_t paper_size)9560 worksheet_set_paper(lxw_worksheet *self, uint8_t paper_size)
9561 {
9562     self->paper_size = paper_size;
9563     self->page_setup_changed = LXW_TRUE;
9564 }
9565 
9566 /*
9567  * Set the order in which pages are printed.
9568  */
9569 void
worksheet_print_across(lxw_worksheet * self)9570 worksheet_print_across(lxw_worksheet *self)
9571 {
9572     self->page_order = LXW_PRINT_ACROSS;
9573     self->page_setup_changed = LXW_TRUE;
9574 }
9575 
9576 /*
9577  * Set all the page margins in inches.
9578  */
9579 void
worksheet_set_margins(lxw_worksheet * self,double left,double right,double top,double bottom)9580 worksheet_set_margins(lxw_worksheet *self, double left, double right,
9581                       double top, double bottom)
9582 {
9583 
9584     if (left >= 0)
9585         self->margin_left = left;
9586 
9587     if (right >= 0)
9588         self->margin_right = right;
9589 
9590     if (top >= 0)
9591         self->margin_top = top;
9592 
9593     if (bottom >= 0)
9594         self->margin_bottom = bottom;
9595 }
9596 
9597 /*
9598  * Set the page header caption and options.
9599  */
9600 lxw_error
worksheet_set_header_opt(lxw_worksheet * self,const char * string,lxw_header_footer_options * options)9601 worksheet_set_header_opt(lxw_worksheet *self, const char *string,
9602                          lxw_header_footer_options *options)
9603 {
9604     lxw_error err;
9605     char *found_string;
9606     char *offset_string;
9607     uint8_t placeholder_count = 0;
9608     uint8_t image_count = 0;
9609 
9610     if (!string)
9611         return LXW_ERROR_NULL_PARAMETER_IGNORED;
9612 
9613     if (lxw_utf8_strlen(string) > LXW_HEADER_FOOTER_MAX)
9614         return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
9615 
9616     /* Clear existing header. */
9617     free(self->header);
9618 
9619     self->header = lxw_strdup(string);
9620     RETURN_ON_MEM_ERROR(self->header, LXW_ERROR_MEMORY_MALLOC_FAILED);
9621 
9622     /* Replace &[Picture] with &G which is used internally by Excel. */
9623     while ((found_string = strstr(self->header, "&[Picture]"))) {
9624         found_string++;
9625         *found_string = 'G';
9626 
9627         do {
9628             found_string++;
9629             offset_string = found_string + sizeof("Picture");
9630             *found_string = *offset_string;
9631         } while (*offset_string);
9632     }
9633 
9634     /* Count &G placeholders and ensure there are sufficient images. */
9635     found_string = self->header;
9636     while (*found_string) {
9637         if (*found_string == '&' && *(found_string + 1) == 'G')
9638             placeholder_count++;
9639         found_string++;
9640     }
9641 
9642     if (placeholder_count > 0 && !options) {
9643         LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): "
9644                          "the number of &G/&[Picture] placeholders in option "
9645                          "string \"%s\" does not match the number of supplied "
9646                          "images.", string);
9647 
9648         /* Reset the header string. */
9649         free(self->header);
9650 
9651         return LXW_ERROR_PARAMETER_VALIDATION;
9652     }
9653 
9654     if (options) {
9655         /* Ensure there are enough images to match the placeholders. There is
9656          * a potential bug where there are sufficient images but in the wrong
9657          * positions but we don't currently try to deal with that.*/
9658         if (options->image_left)
9659             image_count++;
9660         if (options->image_center)
9661             image_count++;
9662         if (options->image_right)
9663             image_count++;
9664 
9665         if (placeholder_count != image_count) {
9666             LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): "
9667                              "the number of &G/&[Picture] placeholders in option "
9668                              "string \"%s\" does not match the number of supplied "
9669                              "images.", string);
9670 
9671             /* Reset the header string. */
9672             free(self->header);
9673 
9674             return LXW_ERROR_PARAMETER_VALIDATION;
9675         }
9676 
9677         /* Free any existing header image objects. */
9678         _free_object_properties(self->header_left_object_props);
9679         _free_object_properties(self->header_center_object_props);
9680         _free_object_properties(self->header_right_object_props);
9681 
9682         if (options->margin > 0.0)
9683             self->margin_header = options->margin;
9684 
9685         err = _worksheet_set_header_footer_image(self,
9686                                                  options->image_left,
9687                                                  HEADER_LEFT);
9688         if (err) {
9689             free(self->header);
9690             return err;
9691         }
9692 
9693         err = _worksheet_set_header_footer_image(self,
9694                                                  options->image_center,
9695                                                  HEADER_CENTER);
9696         if (err) {
9697             free(self->header);
9698             return err;
9699         }
9700 
9701         err = _worksheet_set_header_footer_image(self,
9702                                                  options->image_right,
9703                                                  HEADER_RIGHT);
9704         if (err) {
9705             free(self->header);
9706             return err;
9707         }
9708     }
9709 
9710     self->header_footer_changed = 1;
9711 
9712     return LXW_NO_ERROR;
9713 }
9714 
9715 /*
9716  * Set the page footer caption and options.
9717  */
9718 lxw_error
worksheet_set_footer_opt(lxw_worksheet * self,const char * string,lxw_header_footer_options * options)9719 worksheet_set_footer_opt(lxw_worksheet *self, const char *string,
9720                          lxw_header_footer_options *options)
9721 {
9722     lxw_error err;
9723     char *found_string;
9724     char *offset_string;
9725     uint8_t placeholder_count = 0;
9726     uint8_t image_count = 0;
9727 
9728     if (!string)
9729         return LXW_ERROR_NULL_PARAMETER_IGNORED;
9730 
9731     if (lxw_utf8_strlen(string) > LXW_HEADER_FOOTER_MAX)
9732         return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
9733 
9734     /* Clear existing header. */
9735     free(self->footer);
9736 
9737     self->footer = lxw_strdup(string);
9738     RETURN_ON_MEM_ERROR(self->footer, LXW_ERROR_MEMORY_MALLOC_FAILED);
9739 
9740     /* Replace &[Picture] with &G which is used internally by Excel. */
9741     while ((found_string = strstr(self->footer, "&[Picture]"))) {
9742         found_string++;
9743         *found_string = 'G';
9744 
9745         do {
9746             found_string++;
9747             offset_string = found_string + sizeof("Picture");
9748             *found_string = *offset_string;
9749         } while (*offset_string);
9750     }
9751 
9752     /* Count &G placeholders and ensure there are sufficient images. */
9753     found_string = self->footer;
9754     while (*found_string) {
9755         if (*found_string == '&' && *(found_string + 1) == 'G')
9756             placeholder_count++;
9757         found_string++;
9758     }
9759 
9760     if (placeholder_count > 0 && !options) {
9761         LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): "
9762                          "the number of &G/&[Picture] placeholders in option "
9763                          "string \"%s\" does not match the number of supplied "
9764                          "images.", string);
9765 
9766         /* Reset the footer string. */
9767         free(self->footer);
9768 
9769         return LXW_ERROR_PARAMETER_VALIDATION;
9770     }
9771 
9772     if (options) {
9773         /* Ensure there are enough images to match the placeholders. There is
9774          * a potential bug where there are sufficient images but in the wrong
9775          * positions but we don't currently try to deal with that.*/
9776         if (options->image_left)
9777             image_count++;
9778         if (options->image_center)
9779             image_count++;
9780         if (options->image_right)
9781             image_count++;
9782 
9783         if (placeholder_count != image_count) {
9784             LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): "
9785                              "the number of &G/&[Picture] placeholders in option "
9786                              "string \"%s\" does not match the number of supplied "
9787                              "images.", string);
9788 
9789             /* Reset the header string. */
9790             free(self->footer);
9791 
9792             return LXW_ERROR_PARAMETER_VALIDATION;
9793         }
9794 
9795         /* Free any existing footer image objects. */
9796         _free_object_properties(self->footer_left_object_props);
9797         _free_object_properties(self->footer_center_object_props);
9798         _free_object_properties(self->footer_right_object_props);
9799 
9800         if (options->margin > 0.0)
9801             self->margin_footer = options->margin;
9802 
9803         err = _worksheet_set_header_footer_image(self,
9804                                                  options->image_left,
9805                                                  FOOTER_LEFT);
9806         if (err) {
9807             free(self->footer);
9808             return err;
9809         }
9810 
9811         err = _worksheet_set_header_footer_image(self,
9812                                                  options->image_center,
9813                                                  FOOTER_CENTER);
9814         if (err) {
9815             free(self->footer);
9816             return err;
9817         }
9818 
9819         err = _worksheet_set_header_footer_image(self,
9820                                                  options->image_right,
9821                                                  FOOTER_RIGHT);
9822         if (err) {
9823             free(self->footer);
9824             return err;
9825         }
9826     }
9827 
9828     self->header_footer_changed = 1;
9829 
9830     return LXW_NO_ERROR;
9831 }
9832 
9833 /*
9834  * Set the page header caption.
9835  */
9836 lxw_error
worksheet_set_header(lxw_worksheet * self,const char * string)9837 worksheet_set_header(lxw_worksheet *self, const char *string)
9838 {
9839     return worksheet_set_header_opt(self, string, NULL);
9840 }
9841 
9842 /*
9843  * Set the page footer caption.
9844  */
9845 lxw_error
worksheet_set_footer(lxw_worksheet * self,const char * string)9846 worksheet_set_footer(lxw_worksheet *self, const char *string)
9847 {
9848     return worksheet_set_footer_opt(self, string, NULL);
9849 }
9850 
9851 /*
9852  * Set the option to show/hide gridlines on the screen and the printed page.
9853  */
9854 void
worksheet_gridlines(lxw_worksheet * self,uint8_t option)9855 worksheet_gridlines(lxw_worksheet *self, uint8_t option)
9856 {
9857     if (option == LXW_HIDE_ALL_GRIDLINES) {
9858         self->print_gridlines = 0;
9859         self->screen_gridlines = 0;
9860     }
9861 
9862     if (option & LXW_SHOW_SCREEN_GRIDLINES) {
9863         self->screen_gridlines = 1;
9864     }
9865 
9866     if (option & LXW_SHOW_PRINT_GRIDLINES) {
9867         self->print_gridlines = 1;
9868         self->print_options_changed = 1;
9869     }
9870 }
9871 
9872 /*
9873  * Center the page horizontally.
9874  */
9875 void
worksheet_center_horizontally(lxw_worksheet * self)9876 worksheet_center_horizontally(lxw_worksheet *self)
9877 {
9878     self->print_options_changed = 1;
9879     self->hcenter = 1;
9880 }
9881 
9882 /*
9883  * Center the page horizontally.
9884  */
9885 void
worksheet_center_vertically(lxw_worksheet * self)9886 worksheet_center_vertically(lxw_worksheet *self)
9887 {
9888     self->print_options_changed = 1;
9889     self->vcenter = 1;
9890 }
9891 
9892 /*
9893  * Set the option to print the row and column headers on the printed page.
9894  */
9895 void
worksheet_print_row_col_headers(lxw_worksheet * self)9896 worksheet_print_row_col_headers(lxw_worksheet *self)
9897 {
9898     self->print_headers = 1;
9899     self->print_options_changed = 1;
9900 }
9901 
9902 /*
9903  * Set the rows to repeat at the top of each printed page.
9904  */
9905 lxw_error
worksheet_repeat_rows(lxw_worksheet * self,lxw_row_t first_row,lxw_row_t last_row)9906 worksheet_repeat_rows(lxw_worksheet *self, lxw_row_t first_row,
9907                       lxw_row_t last_row)
9908 {
9909     lxw_row_t tmp_row;
9910     lxw_error err;
9911 
9912     if (first_row > last_row) {
9913         tmp_row = last_row;
9914         last_row = first_row;
9915         first_row = tmp_row;
9916     }
9917 
9918     err = _check_dimensions(self, last_row, 0, LXW_IGNORE, LXW_IGNORE);
9919     if (err)
9920         return err;
9921 
9922     self->repeat_rows.in_use = LXW_TRUE;
9923     self->repeat_rows.first_row = first_row;
9924     self->repeat_rows.last_row = last_row;
9925 
9926     return LXW_NO_ERROR;
9927 }
9928 
9929 /*
9930  * Set the columns to repeat at the left hand side of each printed page.
9931  */
9932 lxw_error
worksheet_repeat_columns(lxw_worksheet * self,lxw_col_t first_col,lxw_col_t last_col)9933 worksheet_repeat_columns(lxw_worksheet *self, lxw_col_t first_col,
9934                          lxw_col_t last_col)
9935 {
9936     lxw_col_t tmp_col;
9937     lxw_error err;
9938 
9939     if (first_col > last_col) {
9940         tmp_col = last_col;
9941         last_col = first_col;
9942         first_col = tmp_col;
9943     }
9944 
9945     err = _check_dimensions(self, last_col, 0, LXW_IGNORE, LXW_IGNORE);
9946     if (err)
9947         return err;
9948 
9949     self->repeat_cols.in_use = LXW_TRUE;
9950     self->repeat_cols.first_col = first_col;
9951     self->repeat_cols.last_col = last_col;
9952 
9953     return LXW_NO_ERROR;
9954 }
9955 
9956 /*
9957  * Set the print area in the current worksheet.
9958  */
9959 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)9960 worksheet_print_area(lxw_worksheet *self, lxw_row_t first_row,
9961                      lxw_col_t first_col, lxw_row_t last_row,
9962                      lxw_col_t last_col)
9963 {
9964     lxw_row_t tmp_row;
9965     lxw_col_t tmp_col;
9966     lxw_error err;
9967 
9968     if (first_row > last_row) {
9969         tmp_row = last_row;
9970         last_row = first_row;
9971         first_row = tmp_row;
9972     }
9973 
9974     if (first_col > last_col) {
9975         tmp_col = last_col;
9976         last_col = first_col;
9977         first_col = tmp_col;
9978     }
9979 
9980     err = _check_dimensions(self, last_row, last_col, LXW_IGNORE, LXW_IGNORE);
9981     if (err)
9982         return err;
9983 
9984     /* Ignore max area since it is the same as no print area in Excel. */
9985     if (first_row == 0 && first_col == 0 && last_row == LXW_ROW_MAX - 1
9986         && last_col == LXW_COL_MAX - 1) {
9987         return LXW_NO_ERROR;
9988     }
9989 
9990     self->print_area.in_use = LXW_TRUE;
9991     self->print_area.first_row = first_row;
9992     self->print_area.last_row = last_row;
9993     self->print_area.first_col = first_col;
9994     self->print_area.last_col = last_col;
9995 
9996     return LXW_NO_ERROR;
9997 }
9998 
9999 /* Store the vertical and horizontal number of pages that will define the
10000  * maximum area printed.
10001  */
10002 void
worksheet_fit_to_pages(lxw_worksheet * self,uint16_t width,uint16_t height)10003 worksheet_fit_to_pages(lxw_worksheet *self, uint16_t width, uint16_t height)
10004 {
10005     self->fit_page = 1;
10006     self->fit_width = width;
10007     self->fit_height = height;
10008     self->page_setup_changed = 1;
10009 }
10010 
10011 /*
10012  * Set the start page number.
10013  */
10014 void
worksheet_set_start_page(lxw_worksheet * self,uint16_t start_page)10015 worksheet_set_start_page(lxw_worksheet *self, uint16_t start_page)
10016 {
10017     self->page_start = start_page;
10018 }
10019 
10020 /*
10021  * Set the scale factor for the printed page.
10022  */
10023 void
worksheet_set_print_scale(lxw_worksheet * self,uint16_t scale)10024 worksheet_set_print_scale(lxw_worksheet *self, uint16_t scale)
10025 {
10026     /* Confine the scale to Excel"s range */
10027     if (scale < 10 || scale > 400)
10028         return;
10029 
10030     /* Turn off "fit to page" option. */
10031     self->fit_page = LXW_FALSE;
10032 
10033     self->print_scale = scale;
10034     self->page_setup_changed = LXW_TRUE;
10035 }
10036 
10037 /*
10038  * Store the horizontal page breaks on a worksheet.
10039  */
10040 lxw_error
worksheet_set_h_pagebreaks(lxw_worksheet * self,lxw_row_t hbreaks[])10041 worksheet_set_h_pagebreaks(lxw_worksheet *self, lxw_row_t hbreaks[])
10042 {
10043     uint16_t count = 0;
10044 
10045     if (hbreaks == NULL)
10046         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10047 
10048     while (hbreaks[count])
10049         count++;
10050 
10051     /* The Excel 2007 specification says that the maximum number of page
10052      * breaks is 1026. However, in practice it is actually 1023. */
10053     if (count > LXW_BREAKS_MAX)
10054         count = LXW_BREAKS_MAX;
10055 
10056     self->hbreaks = calloc(count, sizeof(lxw_row_t));
10057     RETURN_ON_MEM_ERROR(self->hbreaks, LXW_ERROR_MEMORY_MALLOC_FAILED);
10058     memcpy(self->hbreaks, hbreaks, count * sizeof(lxw_row_t));
10059     self->hbreaks_count = count;
10060 
10061     return LXW_NO_ERROR;
10062 }
10063 
10064 /*
10065  * Store the vertical page breaks on a worksheet.
10066  */
10067 lxw_error
worksheet_set_v_pagebreaks(lxw_worksheet * self,lxw_col_t vbreaks[])10068 worksheet_set_v_pagebreaks(lxw_worksheet *self, lxw_col_t vbreaks[])
10069 {
10070     uint16_t count = 0;
10071 
10072     if (vbreaks == NULL)
10073         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10074 
10075     while (vbreaks[count])
10076         count++;
10077 
10078     /* The Excel 2007 specification says that the maximum number of page
10079      * breaks is 1026. However, in practice it is actually 1023. */
10080     if (count > LXW_BREAKS_MAX)
10081         count = LXW_BREAKS_MAX;
10082 
10083     self->vbreaks = calloc(count, sizeof(lxw_col_t));
10084     RETURN_ON_MEM_ERROR(self->vbreaks, LXW_ERROR_MEMORY_MALLOC_FAILED);
10085     memcpy(self->vbreaks, vbreaks, count * sizeof(lxw_col_t));
10086     self->vbreaks_count = count;
10087 
10088     return LXW_NO_ERROR;
10089 }
10090 
10091 /*
10092  * Set the worksheet zoom factor.
10093  */
10094 void
worksheet_set_zoom(lxw_worksheet * self,uint16_t scale)10095 worksheet_set_zoom(lxw_worksheet *self, uint16_t scale)
10096 {
10097     /* Confine the scale to Excel"s range */
10098     if (scale < 10 || scale > 400) {
10099         LXW_WARN("worksheet_set_zoom(): "
10100                  "Zoom factor scale outside range: 10 <= zoom <= 400.");
10101         return;
10102     }
10103 
10104     self->zoom = scale;
10105 }
10106 
10107 /*
10108  * Hide cell zero values.
10109  */
10110 void
worksheet_hide_zero(lxw_worksheet * self)10111 worksheet_hide_zero(lxw_worksheet *self)
10112 {
10113     self->show_zeros = LXW_FALSE;
10114 }
10115 
10116 /*
10117  * Display the worksheet right to left for some eastern versions of Excel.
10118  */
10119 void
worksheet_right_to_left(lxw_worksheet * self)10120 worksheet_right_to_left(lxw_worksheet *self)
10121 {
10122     self->right_to_left = LXW_TRUE;
10123 }
10124 
10125 /*
10126  * Set the color of the worksheet tab.
10127  */
10128 void
worksheet_set_tab_color(lxw_worksheet * self,lxw_color_t color)10129 worksheet_set_tab_color(lxw_worksheet *self, lxw_color_t color)
10130 {
10131     self->tab_color = color;
10132 }
10133 
10134 /*
10135  * Set the worksheet protection flags to prevent modification of worksheet
10136  * objects.
10137  */
10138 void
worksheet_protect(lxw_worksheet * self,const char * password,lxw_protection * options)10139 worksheet_protect(lxw_worksheet *self, const char *password,
10140                   lxw_protection *options)
10141 {
10142     struct lxw_protection_obj *protect = &self->protection;
10143 
10144     /* Copy any user parameters to the internal structure. */
10145     if (options) {
10146         protect->no_select_locked_cells = options->no_select_locked_cells;
10147         protect->no_select_unlocked_cells = options->no_select_unlocked_cells;
10148         protect->format_cells = options->format_cells;
10149         protect->format_columns = options->format_columns;
10150         protect->format_rows = options->format_rows;
10151         protect->insert_columns = options->insert_columns;
10152         protect->insert_rows = options->insert_rows;
10153         protect->insert_hyperlinks = options->insert_hyperlinks;
10154         protect->delete_columns = options->delete_columns;
10155         protect->delete_rows = options->delete_rows;
10156         protect->sort = options->sort;
10157         protect->autofilter = options->autofilter;
10158         protect->pivot_tables = options->pivot_tables;
10159         protect->scenarios = options->scenarios;
10160         protect->objects = options->objects;
10161     }
10162 
10163     if (password) {
10164         uint16_t hash = lxw_hash_password(password);
10165         lxw_snprintf(protect->hash, 5, "%X", hash);
10166     }
10167 
10168     protect->no_sheet = LXW_FALSE;
10169     protect->no_content = LXW_TRUE;
10170     protect->is_configured = LXW_TRUE;
10171 }
10172 
10173 /*
10174  * Set the worksheet properties for outlines and grouping.
10175  */
10176 void
worksheet_outline_settings(lxw_worksheet * self,uint8_t visible,uint8_t symbols_below,uint8_t symbols_right,uint8_t auto_style)10177 worksheet_outline_settings(lxw_worksheet *self,
10178                            uint8_t visible,
10179                            uint8_t symbols_below,
10180                            uint8_t symbols_right, uint8_t auto_style)
10181 {
10182     self->outline_on = visible;
10183     self->outline_below = symbols_below;
10184     self->outline_right = symbols_right;
10185     self->outline_style = auto_style;
10186 
10187     self->outline_changed = LXW_TRUE;
10188 }
10189 
10190 /*
10191  * Set the default row properties
10192  */
10193 void
worksheet_set_default_row(lxw_worksheet * self,double height,uint8_t hide_unused_rows)10194 worksheet_set_default_row(lxw_worksheet *self, double height,
10195                           uint8_t hide_unused_rows)
10196 {
10197     if (height < 0)
10198         height = self->default_row_height;
10199 
10200     if (height != self->default_row_height) {
10201         self->default_row_height = height;
10202         self->row_size_changed = LXW_TRUE;
10203     }
10204 
10205     if (hide_unused_rows)
10206         self->default_row_zeroed = LXW_TRUE;
10207 
10208     self->default_row_set = LXW_TRUE;
10209 }
10210 
10211 /*
10212  * Insert an image with options into the worksheet.
10213  */
10214 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)10215 worksheet_insert_image_opt(lxw_worksheet *self,
10216                            lxw_row_t row_num, lxw_col_t col_num,
10217                            const char *filename,
10218                            lxw_image_options *user_options)
10219 {
10220     FILE *image_stream;
10221     const char *description;
10222     lxw_object_properties *object_props;
10223 
10224     if (!filename) {
10225         LXW_WARN("worksheet_insert_image()/_opt(): "
10226                  "filename must be specified.");
10227         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10228     }
10229 
10230     /* Check that the image file exists and can be opened. */
10231     image_stream = lxw_fopen(filename, "rb");
10232     if (!image_stream) {
10233         LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
10234                          "file doesn't exist or can't be opened: %s.",
10235                          filename);
10236         return LXW_ERROR_PARAMETER_VALIDATION;
10237     }
10238 
10239     /* Use the filename as the default description, like Excel. */
10240     description = lxw_basename(filename);
10241     if (!description) {
10242         LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
10243                          "couldn't get basename for file: %s.", filename);
10244         fclose(image_stream);
10245         return LXW_ERROR_PARAMETER_VALIDATION;
10246     }
10247 
10248     /* Create a new object to hold the image properties. */
10249     object_props = calloc(1, sizeof(lxw_object_properties));
10250     if (!object_props) {
10251         fclose(image_stream);
10252         return LXW_ERROR_MEMORY_MALLOC_FAILED;
10253     }
10254 
10255     if (user_options) {
10256         object_props->x_offset = user_options->x_offset;
10257         object_props->y_offset = user_options->y_offset;
10258         object_props->x_scale = user_options->x_scale;
10259         object_props->y_scale = user_options->y_scale;
10260         object_props->object_position = user_options->object_position;
10261         object_props->url = lxw_strdup(user_options->url);
10262         object_props->tip = lxw_strdup(user_options->tip);
10263         object_props->decorative = user_options->decorative;
10264 
10265         if (user_options->description)
10266             description = user_options->description;
10267     }
10268 
10269     /* Copy other options or set defaults. */
10270     object_props->filename = lxw_strdup(filename);
10271     object_props->description = lxw_strdup(description);
10272     object_props->stream = image_stream;
10273     object_props->row = row_num;
10274     object_props->col = col_num;
10275 
10276     if (object_props->x_scale == 0.0)
10277         object_props->x_scale = 1;
10278 
10279     if (object_props->y_scale == 0.0)
10280         object_props->y_scale = 1;
10281 
10282     if (_get_image_properties(object_props) == LXW_NO_ERROR) {
10283         STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers);
10284         fclose(image_stream);
10285         return LXW_NO_ERROR;
10286     }
10287     else {
10288         _free_object_properties(object_props);
10289         fclose(image_stream);
10290         return LXW_ERROR_IMAGE_DIMENSIONS;
10291     }
10292 }
10293 
10294 /*
10295  * Insert an image into the worksheet.
10296  */
10297 lxw_error
worksheet_insert_image(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,const char * filename)10298 worksheet_insert_image(lxw_worksheet *self,
10299                        lxw_row_t row_num, lxw_col_t col_num,
10300                        const char *filename)
10301 {
10302     return worksheet_insert_image_opt(self, row_num, col_num, filename, NULL);
10303 }
10304 
10305 /*
10306  * Insert an image buffer, with options, into the worksheet.
10307  */
10308 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)10309 worksheet_insert_image_buffer_opt(lxw_worksheet *self,
10310                                   lxw_row_t row_num,
10311                                   lxw_col_t col_num,
10312                                   const unsigned char *image_buffer,
10313                                   size_t image_size,
10314                                   lxw_image_options *user_options)
10315 {
10316     FILE *image_stream;
10317     lxw_object_properties *object_props;
10318 
10319     if (!image_size) {
10320         LXW_WARN("worksheet_insert_image_buffer()/_opt(): "
10321                  "size must be non-zero.");
10322         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10323     }
10324 
10325     /* Write the image buffer to a file (preferably in memory) so we can read
10326      * the dimensions like an ordinary file. */
10327 #ifdef USE_FMEMOPEN
10328     image_stream = fmemopen(NULL, image_size, "w+b");
10329 #else
10330     image_stream = lxw_tmpfile(self->tmpdir);
10331 #endif
10332 
10333     if (!image_stream)
10334         return LXW_ERROR_CREATING_TMPFILE;
10335 
10336     if (fwrite(image_buffer, 1, image_size, image_stream) != image_size) {
10337         fclose(image_stream);
10338         return LXW_ERROR_CREATING_TMPFILE;
10339     }
10340 
10341     rewind(image_stream);
10342 
10343     /* Create a new object to hold the image properties. */
10344     object_props = calloc(1, sizeof(lxw_object_properties));
10345     if (!object_props) {
10346         fclose(image_stream);
10347         return LXW_ERROR_MEMORY_MALLOC_FAILED;
10348     }
10349 
10350     /* Store the image data in the properties structure. */
10351     object_props->image_buffer = calloc(1, image_size);
10352     if (!object_props->image_buffer) {
10353         _free_object_properties(object_props);
10354         fclose(image_stream);
10355         return LXW_ERROR_MEMORY_MALLOC_FAILED;
10356     }
10357     else {
10358         memcpy(object_props->image_buffer, image_buffer, image_size);
10359         object_props->image_buffer_size = image_size;
10360         object_props->is_image_buffer = LXW_TRUE;
10361     }
10362 
10363     if (user_options) {
10364         object_props->x_offset = user_options->x_offset;
10365         object_props->y_offset = user_options->y_offset;
10366         object_props->x_scale = user_options->x_scale;
10367         object_props->y_scale = user_options->y_scale;
10368         object_props->url = lxw_strdup(user_options->url);
10369         object_props->tip = lxw_strdup(user_options->tip);
10370         object_props->object_position = user_options->object_position;
10371         object_props->description = lxw_strdup(user_options->description);
10372         object_props->decorative = user_options->decorative;
10373     }
10374 
10375     /* Copy other options or set defaults. */
10376     object_props->filename = lxw_strdup("image_buffer");
10377     object_props->stream = image_stream;
10378     object_props->row = row_num;
10379     object_props->col = col_num;
10380 
10381     if (object_props->x_scale == 0.0)
10382         object_props->x_scale = 1;
10383 
10384     if (object_props->y_scale == 0.0)
10385         object_props->y_scale = 1;
10386 
10387     if (_get_image_properties(object_props) == LXW_NO_ERROR) {
10388         STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers);
10389         fclose(image_stream);
10390         return LXW_NO_ERROR;
10391     }
10392     else {
10393         _free_object_properties(object_props);
10394         fclose(image_stream);
10395         return LXW_ERROR_IMAGE_DIMENSIONS;
10396     }
10397 }
10398 
10399 /*
10400  * Insert an image buffer into the worksheet.
10401  */
10402 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)10403 worksheet_insert_image_buffer(lxw_worksheet *self,
10404                               lxw_row_t row_num,
10405                               lxw_col_t col_num,
10406                               const unsigned char *image_buffer,
10407                               size_t image_size)
10408 {
10409     return worksheet_insert_image_buffer_opt(self, row_num, col_num,
10410                                              image_buffer, image_size, NULL);
10411 }
10412 
10413 /*
10414  * Set an image as a worksheet background.
10415  */
10416 lxw_error
worksheet_set_background(lxw_worksheet * self,const char * filename)10417 worksheet_set_background(lxw_worksheet *self, const char *filename)
10418 {
10419     FILE *image_stream;
10420     lxw_object_properties *object_props;
10421 
10422     if (!filename) {
10423         LXW_WARN("worksheet_set_background(): "
10424                  "filename must be specified.");
10425         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10426     }
10427 
10428     /* Check that the image file exists and can be opened. */
10429     image_stream = lxw_fopen(filename, "rb");
10430     if (!image_stream) {
10431         LXW_WARN_FORMAT1("worksheet_set_background(): "
10432                          "file doesn't exist or can't be opened: %s.",
10433                          filename);
10434         return LXW_ERROR_PARAMETER_VALIDATION;
10435     }
10436 
10437     /* Create a new object to hold the image properties. */
10438     object_props = calloc(1, sizeof(lxw_object_properties));
10439     if (!object_props) {
10440         fclose(image_stream);
10441         return LXW_ERROR_MEMORY_MALLOC_FAILED;
10442     }
10443 
10444     /* Copy other options or set defaults. */
10445     object_props->filename = lxw_strdup(filename);
10446     object_props->stream = image_stream;
10447     object_props->is_background = LXW_TRUE;
10448 
10449     if (_get_image_properties(object_props) == LXW_NO_ERROR) {
10450         _free_object_properties(self->background_image);
10451         self->background_image = object_props;
10452         self->has_background_image = LXW_TRUE;
10453         fclose(image_stream);
10454         return LXW_NO_ERROR;
10455     }
10456     else {
10457         _free_object_properties(object_props);
10458         fclose(image_stream);
10459         return LXW_ERROR_IMAGE_DIMENSIONS;
10460     }
10461 }
10462 
10463 /*
10464  * Set an image buffer as a worksheet background.
10465  */
10466 lxw_error
worksheet_set_background_buffer(lxw_worksheet * self,const unsigned char * image_buffer,size_t image_size)10467 worksheet_set_background_buffer(lxw_worksheet *self,
10468                                 const unsigned char *image_buffer,
10469                                 size_t image_size)
10470 {
10471     FILE *image_stream;
10472     lxw_object_properties *object_props;
10473 
10474     if (!image_size) {
10475         LXW_WARN("worksheet_set_background(): " "size must be non-zero.");
10476         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10477     }
10478 
10479     /* Write the image buffer to a file (preferably in memory) so we can read
10480      * the dimensions like an ordinary file. */
10481 #ifdef USE_FMEMOPEN
10482     image_stream = fmemopen(NULL, image_size, "w+b");
10483 #else
10484     image_stream = lxw_tmpfile(self->tmpdir);
10485 #endif
10486 
10487     if (!image_stream)
10488         return LXW_ERROR_CREATING_TMPFILE;
10489 
10490     if (fwrite(image_buffer, 1, image_size, image_stream) != image_size) {
10491         fclose(image_stream);
10492         return LXW_ERROR_CREATING_TMPFILE;
10493     }
10494 
10495     rewind(image_stream);
10496 
10497     /* Create a new object to hold the image properties. */
10498     object_props = calloc(1, sizeof(lxw_object_properties));
10499     if (!object_props) {
10500         fclose(image_stream);
10501         return LXW_ERROR_MEMORY_MALLOC_FAILED;
10502     }
10503 
10504     /* Store the image data in the properties structure. */
10505     object_props->image_buffer = calloc(1, image_size);
10506     if (!object_props->image_buffer) {
10507         _free_object_properties(object_props);
10508         fclose(image_stream);
10509         return LXW_ERROR_MEMORY_MALLOC_FAILED;
10510     }
10511     else {
10512         memcpy(object_props->image_buffer, image_buffer, image_size);
10513         object_props->image_buffer_size = image_size;
10514         object_props->is_image_buffer = LXW_TRUE;
10515     }
10516 
10517     /* Copy other options or set defaults. */
10518     object_props->filename = lxw_strdup("image_buffer");
10519     object_props->stream = image_stream;
10520     object_props->is_background = LXW_TRUE;
10521 
10522     if (_get_image_properties(object_props) == LXW_NO_ERROR) {
10523         _free_object_properties(self->background_image);
10524         self->background_image = object_props;
10525         self->has_background_image = LXW_TRUE;
10526         fclose(image_stream);
10527         return LXW_NO_ERROR;
10528     }
10529     else {
10530         _free_object_properties(object_props);
10531         fclose(image_stream);
10532         return LXW_ERROR_IMAGE_DIMENSIONS;
10533     }
10534 }
10535 
10536 /*
10537  * Insert an chart into the worksheet.
10538  */
10539 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)10540 worksheet_insert_chart_opt(lxw_worksheet *self,
10541                            lxw_row_t row_num, lxw_col_t col_num,
10542                            lxw_chart *chart, lxw_chart_options *user_options)
10543 {
10544     lxw_object_properties *object_props;
10545     lxw_chart_series *series;
10546 
10547     if (!chart) {
10548         LXW_WARN("worksheet_insert_chart()/_opt(): chart must be non-NULL.");
10549         return LXW_ERROR_NULL_PARAMETER_IGNORED;
10550     }
10551 
10552     /* Check that the chart isn't being used more than once. */
10553     if (chart->in_use) {
10554         LXW_WARN("worksheet_insert_chart()/_opt(): the same chart object "
10555                  "cannot be inserted in a worksheet more than once.");
10556 
10557         return LXW_ERROR_PARAMETER_VALIDATION;
10558     }
10559 
10560     /* Check that the chart has a data series. */
10561     if (STAILQ_EMPTY(chart->series_list)) {
10562         LXW_WARN
10563             ("worksheet_insert_chart()/_opt(): chart must have a series.");
10564 
10565         return LXW_ERROR_PARAMETER_VALIDATION;
10566     }
10567 
10568     /* Check that the chart has a 'values' series. */
10569     STAILQ_FOREACH(series, chart->series_list, list_pointers) {
10570         if (!series->values->formula && !series->values->sheetname) {
10571             LXW_WARN("worksheet_insert_chart()/_opt(): chart must have a "
10572                      "'values' series.");
10573 
10574             return LXW_ERROR_PARAMETER_VALIDATION;
10575         }
10576     }
10577 
10578     /* Create a new object to hold the chart image properties. */
10579     object_props = calloc(1, sizeof(lxw_object_properties));
10580     RETURN_ON_MEM_ERROR(object_props, LXW_ERROR_MEMORY_MALLOC_FAILED);
10581 
10582     if (user_options) {
10583         object_props->x_offset = user_options->x_offset;
10584         object_props->y_offset = user_options->y_offset;
10585         object_props->x_scale = user_options->x_scale;
10586         object_props->y_scale = user_options->y_scale;
10587         object_props->object_position = user_options->object_position;
10588         object_props->description = lxw_strdup(user_options->description);
10589         object_props->decorative = user_options->decorative;
10590     }
10591 
10592     /* Copy other options or set defaults. */
10593     object_props->row = row_num;
10594     object_props->col = col_num;
10595 
10596     object_props->width = 480;
10597     object_props->height = 288;
10598 
10599     if (object_props->x_scale == 0.0)
10600         object_props->x_scale = 1;
10601 
10602     if (object_props->y_scale == 0.0)
10603         object_props->y_scale = 1;
10604 
10605     /* Store chart references so they can be ordered in the workbook. */
10606     object_props->chart = chart;
10607 
10608     STAILQ_INSERT_TAIL(self->chart_data, object_props, list_pointers);
10609 
10610     chart->in_use = LXW_TRUE;
10611 
10612     return LXW_NO_ERROR;
10613 }
10614 
10615 /*
10616  * Insert an image into the worksheet.
10617  */
10618 lxw_error
worksheet_insert_chart(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_chart * chart)10619 worksheet_insert_chart(lxw_worksheet *self,
10620                        lxw_row_t row_num, lxw_col_t col_num, lxw_chart *chart)
10621 {
10622     return worksheet_insert_chart_opt(self, row_num, col_num, chart, NULL);
10623 }
10624 
10625 /*
10626  * Add a data validation to a worksheet, for a range. Ironically this requires
10627  * a lot of validation of the user input.
10628  */
10629 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)10630 worksheet_data_validation_range(lxw_worksheet *self, lxw_row_t first_row,
10631                                 lxw_col_t first_col,
10632                                 lxw_row_t last_row,
10633                                 lxw_col_t last_col,
10634                                 lxw_data_validation *validation)
10635 {
10636     lxw_data_val_obj *copy;
10637     uint8_t is_between = LXW_FALSE;
10638     uint8_t is_formula = LXW_FALSE;
10639     uint8_t has_criteria = LXW_TRUE;
10640     lxw_error err;
10641     lxw_row_t tmp_row;
10642     lxw_col_t tmp_col;
10643     size_t length;
10644 
10645     /* No action is required for validation type 'any' unless there are
10646      * input messages to display.*/
10647     if (validation->validate == LXW_VALIDATION_TYPE_ANY
10648         && !(validation->input_title || validation->input_message)) {
10649 
10650         return LXW_NO_ERROR;
10651     }
10652 
10653     /* Check for formula types. */
10654     switch (validation->validate) {
10655         case LXW_VALIDATION_TYPE_INTEGER_FORMULA:
10656         case LXW_VALIDATION_TYPE_DECIMAL_FORMULA:
10657         case LXW_VALIDATION_TYPE_LIST_FORMULA:
10658         case LXW_VALIDATION_TYPE_LENGTH_FORMULA:
10659         case LXW_VALIDATION_TYPE_DATE_FORMULA:
10660         case LXW_VALIDATION_TYPE_TIME_FORMULA:
10661         case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
10662             is_formula = LXW_TRUE;
10663             break;
10664     }
10665 
10666     /* Check for types without a criteria. */
10667     switch (validation->validate) {
10668         case LXW_VALIDATION_TYPE_LIST:
10669         case LXW_VALIDATION_TYPE_LIST_FORMULA:
10670         case LXW_VALIDATION_TYPE_ANY:
10671         case LXW_VALIDATION_TYPE_CUSTOM_FORMULA:
10672             has_criteria = LXW_FALSE;
10673             break;
10674     }
10675 
10676     /* Check that a validation parameter has been specified
10677      * except for 'list', 'any' and 'custom'. */
10678     if (has_criteria && validation->criteria == LXW_VALIDATION_CRITERIA_NONE) {
10679 
10680         LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
10681                         "criteria parameter must be specified.");
10682         return LXW_ERROR_PARAMETER_VALIDATION;
10683     }
10684 
10685     /* Check for "between" criteria so we can do additional checks. */
10686     if (has_criteria
10687         && (validation->criteria == LXW_VALIDATION_CRITERIA_BETWEEN
10688             || validation->criteria == LXW_VALIDATION_CRITERIA_NOT_BETWEEN)) {
10689 
10690         is_between = LXW_TRUE;
10691     }
10692 
10693     /* Check that formula values are non NULL. */
10694     if (is_formula) {
10695         if (is_between) {
10696             if (!validation->minimum_formula) {
10697                 LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
10698                                 "minimum_formula parameter cannot be NULL.");
10699                 return LXW_ERROR_PARAMETER_VALIDATION;
10700             }
10701             if (!validation->maximum_formula) {
10702                 LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
10703                                 "maximum_formula parameter cannot be NULL.");
10704                 return LXW_ERROR_PARAMETER_VALIDATION;
10705             }
10706         }
10707         else {
10708             if (!validation->value_formula) {
10709                 LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
10710                                 "formula parameter cannot be NULL.");
10711                 return LXW_ERROR_PARAMETER_VALIDATION;
10712             }
10713         }
10714     }
10715 
10716     /* Check Excel limitations on input strings. */
10717     if (validation->input_title) {
10718         length = lxw_utf8_strlen(validation->input_title);
10719         if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) {
10720             LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
10721                              "input_title length > Excel limit of %d.",
10722                              LXW_VALIDATION_MAX_TITLE_LENGTH);
10723             return LXW_ERROR_32_STRING_LENGTH_EXCEEDED;
10724         }
10725     }
10726 
10727     if (validation->error_title) {
10728         length = lxw_utf8_strlen(validation->error_title);
10729         if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) {
10730             LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
10731                              "error_title length > Excel limit of %d.",
10732                              LXW_VALIDATION_MAX_TITLE_LENGTH);
10733             return LXW_ERROR_32_STRING_LENGTH_EXCEEDED;
10734         }
10735     }
10736 
10737     if (validation->input_message) {
10738         length = lxw_utf8_strlen(validation->input_message);
10739         if (length > LXW_VALIDATION_MAX_STRING_LENGTH) {
10740             LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
10741                              "input_message length > Excel limit of %d.",
10742                              LXW_VALIDATION_MAX_STRING_LENGTH);
10743             return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
10744         }
10745     }
10746 
10747     if (validation->error_message) {
10748         length = lxw_utf8_strlen(validation->error_message);
10749         if (length > LXW_VALIDATION_MAX_STRING_LENGTH) {
10750             LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
10751                              "error_message length > Excel limit of %d.",
10752                              LXW_VALIDATION_MAX_STRING_LENGTH);
10753             return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
10754         }
10755     }
10756 
10757     if (validation->validate == LXW_VALIDATION_TYPE_LIST) {
10758         length = _validation_list_length(validation->value_list);
10759 
10760         if (length == 0) {
10761             LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): "
10762                             "list parameters cannot be zero.");
10763             return LXW_ERROR_PARAMETER_VALIDATION;
10764         }
10765 
10766         if (length > LXW_VALIDATION_MAX_STRING_LENGTH) {
10767             LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): "
10768                              "list length with commas > Excel limit of %d.",
10769                              LXW_VALIDATION_MAX_STRING_LENGTH);
10770             return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
10771         }
10772     }
10773 
10774     /* Swap last row/col with first row/col as necessary */
10775     if (first_row > last_row) {
10776         tmp_row = last_row;
10777         last_row = first_row;
10778         first_row = tmp_row;
10779     }
10780     if (first_col > last_col) {
10781         tmp_col = last_col;
10782         last_col = first_col;
10783         first_col = tmp_col;
10784     }
10785 
10786     /* Check that dimensions are valid but don't store them. */
10787     err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE);
10788     if (err)
10789         return err;
10790 
10791     /* Create a copy of the parameters from the user data validation. */
10792     copy = calloc(1, sizeof(lxw_data_val_obj));
10793     GOTO_LABEL_ON_MEM_ERROR(copy, mem_error);
10794 
10795     /* Create the data validation range. */
10796     if (first_row == last_row && first_col == last_col)
10797         lxw_rowcol_to_cell(copy->sqref, first_row, last_col);
10798     else
10799         lxw_rowcol_to_range(copy->sqref, first_row, first_col, last_row,
10800                             last_col);
10801 
10802     /* Copy the parameters from the user data validation. */
10803     copy->validate = validation->validate;
10804     copy->value_number = validation->value_number;
10805     copy->error_type = validation->error_type;
10806     copy->dropdown = validation->dropdown;
10807 
10808     if (has_criteria)
10809         copy->criteria = validation->criteria;
10810 
10811     if (is_between) {
10812         copy->value_number = validation->minimum_number;
10813         copy->maximum_number = validation->maximum_number;
10814     }
10815 
10816     /* Copy the input/error titles and messages. */
10817     if (validation->input_title) {
10818         copy->input_title = lxw_strdup_formula(validation->input_title);
10819         GOTO_LABEL_ON_MEM_ERROR(copy->input_title, mem_error);
10820     }
10821 
10822     if (validation->input_message) {
10823         copy->input_message = lxw_strdup_formula(validation->input_message);
10824         GOTO_LABEL_ON_MEM_ERROR(copy->input_message, mem_error);
10825     }
10826 
10827     if (validation->error_title) {
10828         copy->error_title = lxw_strdup_formula(validation->error_title);
10829         GOTO_LABEL_ON_MEM_ERROR(copy->error_title, mem_error);
10830     }
10831 
10832     if (validation->error_message) {
10833         copy->error_message = lxw_strdup_formula(validation->error_message);
10834         GOTO_LABEL_ON_MEM_ERROR(copy->error_message, mem_error);
10835     }
10836 
10837     /* Copy the formula strings. */
10838     if (is_formula) {
10839         if (is_between) {
10840             copy->value_formula =
10841                 lxw_strdup_formula(validation->minimum_formula);
10842             GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
10843             copy->maximum_formula =
10844                 lxw_strdup_formula(validation->maximum_formula);
10845             GOTO_LABEL_ON_MEM_ERROR(copy->maximum_formula, mem_error);
10846         }
10847         else {
10848             copy->value_formula =
10849                 lxw_strdup_formula(validation->value_formula);
10850             GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
10851         }
10852     }
10853 
10854     /* Copy the validation list as a csv string. */
10855     if (validation->validate == LXW_VALIDATION_TYPE_LIST) {
10856         copy->value_formula = _validation_list_to_csv(validation->value_list);
10857         GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
10858     }
10859 
10860     if (validation->validate == LXW_VALIDATION_TYPE_LIST_FORMULA) {
10861         copy->value_formula = lxw_strdup_formula(validation->value_formula);
10862         GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error);
10863     }
10864 
10865     if (validation->validate == LXW_VALIDATION_TYPE_DATE
10866         || validation->validate == LXW_VALIDATION_TYPE_TIME) {
10867         if (is_between) {
10868             copy->value_number =
10869                 lxw_datetime_to_excel_date_epoch
10870                 (&validation->minimum_datetime, LXW_EPOCH_1900);
10871             copy->maximum_number =
10872                 lxw_datetime_to_excel_date_epoch
10873                 (&validation->maximum_datetime, LXW_EPOCH_1900);
10874         }
10875         else {
10876             copy->value_number =
10877                 lxw_datetime_to_excel_date_epoch(&validation->value_datetime,
10878                                                  LXW_EPOCH_1900);
10879         }
10880     }
10881 
10882     /* These options are on by default so we can't take plain booleans. */
10883     copy->ignore_blank = validation->ignore_blank ^ 1;
10884     copy->show_input = validation->show_input ^ 1;
10885     copy->show_error = validation->show_error ^ 1;
10886 
10887     STAILQ_INSERT_TAIL(self->data_validations, copy, list_pointers);
10888 
10889     self->num_validations++;
10890 
10891     return LXW_NO_ERROR;
10892 
10893 mem_error:
10894     _free_data_validation(copy);
10895     return LXW_ERROR_MEMORY_MALLOC_FAILED;
10896 }
10897 
10898 /*
10899  * Add a data validation to a worksheet, for a cell.
10900  */
10901 lxw_error
worksheet_data_validation_cell(lxw_worksheet * self,lxw_row_t row,lxw_col_t col,lxw_data_validation * validation)10902 worksheet_data_validation_cell(lxw_worksheet *self, lxw_row_t row,
10903                                lxw_col_t col, lxw_data_validation *validation)
10904 {
10905     return worksheet_data_validation_range(self, row, col,
10906                                            row, col, validation);
10907 }
10908 
10909 /*
10910  * Add a conditional format to a worksheet, for a range.
10911  */
10912 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)10913 worksheet_conditional_format_range(lxw_worksheet *self, lxw_row_t first_row,
10914                                    lxw_col_t first_col,
10915                                    lxw_row_t last_row,
10916                                    lxw_col_t last_col,
10917                                    lxw_conditional_format *user_options)
10918 {
10919     lxw_cond_format_obj *cond_format;
10920     lxw_row_t tmp_row;
10921     lxw_col_t tmp_col;
10922     lxw_error err = LXW_NO_ERROR;
10923     char *type_strings[] = {
10924         "none",
10925         "cellIs",
10926         "containsText",
10927         "timePeriod",
10928         "aboveAverage",
10929         "duplicateValues",
10930         "uniqueValues",
10931         "top10",
10932         "top10",
10933         "containsBlanks",
10934         "notContainsBlanks",
10935         "containsErrors",
10936         "notContainsErrors",
10937         "expression",
10938         "colorScale",
10939         "colorScale",
10940         "dataBar",
10941         "iconSet",
10942     };
10943 
10944     /* Swap last row/col with first row/col as necessary */
10945     if (first_row > last_row) {
10946         tmp_row = last_row;
10947         last_row = first_row;
10948         first_row = tmp_row;
10949     }
10950     if (first_col > last_col) {
10951         tmp_col = last_col;
10952         last_col = first_col;
10953         first_col = tmp_col;
10954     }
10955 
10956     /* Check that dimensions are valid but don't store them. */
10957     err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE);
10958     if (err)
10959         return err;
10960 
10961     /* Check the validation type is in correct enum range. */
10962     if (user_options->type <= LXW_CONDITIONAL_TYPE_NONE ||
10963         user_options->type >= LXW_CONDITIONAL_TYPE_LAST) {
10964 
10965         LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
10966                          "invalid type value (%d).", user_options->type);
10967 
10968         return LXW_ERROR_PARAMETER_VALIDATION;
10969     }
10970 
10971     /* Create a copy of the parameters from the user data validation. */
10972     cond_format = calloc(1, sizeof(lxw_cond_format_obj));
10973     GOTO_LABEL_ON_MEM_ERROR(cond_format, error);
10974 
10975     /* Create the data validation range. */
10976     if (first_row == last_row && first_col == last_col)
10977         lxw_rowcol_to_cell(cond_format->sqref, first_row, last_col);
10978     else
10979         lxw_rowcol_to_range(cond_format->sqref, first_row, first_col,
10980                             last_row, last_col);
10981 
10982     /* Store the first cell string for text and date rules. */
10983     lxw_rowcol_to_cell(cond_format->first_cell, first_row, last_col);
10984 
10985     /* Overwrite the sqref range with a user supplied set of ranges. */
10986     if (user_options->multi_range) {
10987 
10988         if (strlen(user_options->multi_range) >= LXW_MAX_ATTRIBUTE_LENGTH) {
10989             LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): "
10990                              "multi_range >= limit = %d.",
10991                              LXW_MAX_ATTRIBUTE_LENGTH);
10992             err = LXW_ERROR_PARAMETER_VALIDATION;
10993             goto error;
10994         }
10995 
10996         LXW_ATTRIBUTE_COPY(cond_format->sqref, user_options->multi_range);
10997     }
10998 
10999     /* Get the conditional format dxf format index. */
11000     if (user_options->format)
11001         cond_format->dxf_index =
11002             lxw_format_get_dxf_index(user_options->format);
11003     else
11004         cond_format->dxf_index = LXW_PROPERTY_UNSET;
11005 
11006     /* Set some common option for all validation types. */
11007     cond_format->type = user_options->type;
11008     cond_format->criteria = user_options->criteria;
11009     cond_format->stop_if_true = user_options->stop_if_true;
11010     cond_format->type_string = lxw_strdup(type_strings[cond_format->type]);
11011 
11012     /* Validate the user input for various types of rules. */
11013     if (user_options->type == LXW_CONDITIONAL_TYPE_CELL
11014         || cond_format->type == LXW_CONDITIONAL_TYPE_DUPLICATE
11015         || cond_format->type == LXW_CONDITIONAL_TYPE_UNIQUE) {
11016 
11017         err = _validate_conditional_cell(cond_format, user_options);
11018         if (err)
11019             goto error;
11020     }
11021     else if (user_options->type == LXW_CONDITIONAL_TYPE_TEXT) {
11022 
11023         err = _validate_conditional_text(cond_format, user_options);
11024         if (err)
11025             goto error;
11026     }
11027     else if (user_options->type == LXW_CONDITIONAL_TYPE_TIME_PERIOD) {
11028 
11029         err = _validate_conditional_time_period(user_options);
11030         if (err)
11031             goto error;
11032     }
11033     else if (user_options->type == LXW_CONDITIONAL_TYPE_AVERAGE) {
11034 
11035         err = _validate_conditional_average(user_options);
11036         if (err)
11037             goto error;
11038     }
11039     else if (cond_format->type == LXW_CONDITIONAL_TYPE_TOP
11040              || cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) {
11041 
11042         err = _validate_conditional_top(cond_format, user_options);
11043         if (err)
11044             goto error;
11045     }
11046     else if (user_options->type == LXW_CONDITIONAL_TYPE_FORMULA) {
11047 
11048         err = _validate_conditional_formula(cond_format, user_options);
11049         if (err)
11050             goto error;
11051     }
11052     else if (cond_format->type == LXW_CONDITIONAL_2_COLOR_SCALE
11053              || cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) {
11054 
11055         err = _validate_conditional_scale(cond_format, user_options);
11056         if (err)
11057             goto error;
11058     }
11059     else if (cond_format->type == LXW_CONDITIONAL_DATA_BAR) {
11060 
11061         err = _validate_conditional_data_bar(self, cond_format, user_options);
11062         if (err)
11063             goto error;
11064     }
11065     else if (cond_format->type == LXW_CONDITIONAL_TYPE_ICON_SETS) {
11066 
11067         err = _validate_conditional_icons(user_options);
11068         if (err)
11069             goto error;
11070 
11071         cond_format->icon_style = user_options->icon_style;
11072         cond_format->reverse_icons = user_options->reverse_icons;
11073         cond_format->icons_only = user_options->icons_only;
11074     }
11075 
11076     /* Set the priority based on the order of adding. */
11077     cond_format->dxf_priority = ++self->dxf_priority;
11078 
11079     /* Store the conditional format object. */
11080     err = _store_conditional_format_object(self, cond_format);
11081 
11082     if (err)
11083         goto error;
11084     else
11085         return LXW_NO_ERROR;
11086 
11087 error:
11088     _free_cond_format(cond_format);
11089     return err;
11090 }
11091 
11092 /*
11093  * Add a conditional format to a worksheet, for a cell.
11094  */
11095 lxw_error
worksheet_conditional_format_cell(lxw_worksheet * self,lxw_row_t row,lxw_col_t col,lxw_conditional_format * options)11096 worksheet_conditional_format_cell(lxw_worksheet *self,
11097                                   lxw_row_t row,
11098                                   lxw_col_t col,
11099                                   lxw_conditional_format *options)
11100 {
11101     return worksheet_conditional_format_range(self, row, col,
11102                                               row, col, options);
11103 }
11104 
11105 /*
11106  * Insert a button object into the worksheet.
11107  */
11108 lxw_error
worksheet_insert_button(lxw_worksheet * self,lxw_row_t row_num,lxw_col_t col_num,lxw_button_options * options)11109 worksheet_insert_button(lxw_worksheet *self, lxw_row_t row_num,
11110                         lxw_col_t col_num, lxw_button_options *options)
11111 {
11112     lxw_error err;
11113     lxw_vml_obj *button;
11114 
11115     err = _check_dimensions(self, row_num, col_num, LXW_TRUE, LXW_TRUE);
11116     if (err)
11117         return err;
11118 
11119     button = calloc(1, sizeof(lxw_vml_obj));
11120     GOTO_LABEL_ON_MEM_ERROR(button, mem_error);
11121 
11122     button->row = row_num;
11123     button->col = col_num;
11124 
11125     /* Set user and default parameters for the button. */
11126     err = _get_button_params(button, 1 + self->num_buttons, options);
11127     if (err)
11128         goto mem_error;
11129 
11130     /* Calculate the worksheet position of the button. */
11131     _worksheet_position_vml_object(self, button);
11132 
11133     self->has_vml = LXW_TRUE;
11134     self->has_buttons = LXW_TRUE;
11135     self->num_buttons++;
11136 
11137     STAILQ_INSERT_TAIL(self->button_objs, button, list_pointers);
11138 
11139     return LXW_NO_ERROR;
11140 
11141 mem_error:
11142     if (button)
11143         _free_vml_object(button);
11144 
11145     return LXW_ERROR_MEMORY_MALLOC_FAILED;
11146 }
11147 
11148 /*
11149  * Set the VBA name for the worksheet.
11150  */
11151 lxw_error
worksheet_set_vba_name(lxw_worksheet * self,const char * name)11152 worksheet_set_vba_name(lxw_worksheet *self, const char *name)
11153 {
11154     if (!name) {
11155         LXW_WARN("worksheet_set_vba_name(): " "name must be specified.");
11156         return LXW_ERROR_NULL_PARAMETER_IGNORED;
11157     }
11158 
11159     self->vba_codename = lxw_strdup(name);
11160 
11161     return LXW_NO_ERROR;
11162 }
11163 
11164 /*
11165  * Set the default author of the cell comments.
11166  */
11167 void
worksheet_set_comments_author(lxw_worksheet * self,const char * author)11168 worksheet_set_comments_author(lxw_worksheet *self, const char *author)
11169 {
11170     self->comment_author = lxw_strdup(author);
11171 }
11172 
11173 /*
11174  * Make any comments in the worksheet visible, unless explicitly hidden.
11175  */
11176 void
worksheet_show_comments(lxw_worksheet * self)11177 worksheet_show_comments(lxw_worksheet *self)
11178 {
11179     self->comment_display_default = LXW_COMMENT_DISPLAY_VISIBLE;
11180 }
11181 
11182 /*
11183  * Ignore various Excel errors/warnings in a worksheet for user defined ranges.
11184  */
11185 lxw_error
worksheet_ignore_errors(lxw_worksheet * self,uint8_t type,const char * range)11186 worksheet_ignore_errors(lxw_worksheet *self, uint8_t type, const char *range)
11187 {
11188     if (!range) {
11189         LXW_WARN("worksheet_ignore_errors(): " "'range' must be specified.");
11190         return LXW_ERROR_NULL_PARAMETER_IGNORED;
11191     }
11192 
11193     if (type <= 0 || type >= LXW_IGNORE_LAST_OPTION) {
11194         LXW_WARN("worksheet_ignore_errors(): " "unknown option in 'type'.");
11195         return LXW_ERROR_NULL_PARAMETER_IGNORED;
11196     }
11197 
11198     /* Set the ranges to be ignored. */
11199     if (type == LXW_IGNORE_NUMBER_STORED_AS_TEXT) {
11200         free(self->ignore_number_stored_as_text);
11201         self->ignore_number_stored_as_text = lxw_strdup(range);
11202     }
11203     else if (type == LXW_IGNORE_EVAL_ERROR) {
11204         free(self->ignore_eval_error);
11205         self->ignore_eval_error = lxw_strdup(range);
11206     }
11207     else if (type == LXW_IGNORE_FORMULA_DIFFERS) {
11208         free(self->ignore_formula_differs);
11209         self->ignore_formula_differs = lxw_strdup(range);
11210     }
11211     else if (type == LXW_IGNORE_FORMULA_RANGE) {
11212         free(self->ignore_formula_range);
11213         self->ignore_formula_range = lxw_strdup(range);
11214     }
11215     else if (type == LXW_IGNORE_FORMULA_UNLOCKED) {
11216         free(self->ignore_formula_unlocked);
11217         self->ignore_formula_unlocked = lxw_strdup(range);
11218     }
11219     else if (type == LXW_IGNORE_EMPTY_CELL_REFERENCE) {
11220         free(self->ignore_empty_cell_reference);
11221         self->ignore_empty_cell_reference = lxw_strdup(range);
11222     }
11223     else if (type == LXW_IGNORE_LIST_DATA_VALIDATION) {
11224         free(self->ignore_list_data_validation);
11225         self->ignore_list_data_validation = lxw_strdup(range);
11226     }
11227     else if (type == LXW_IGNORE_CALCULATED_COLUMN) {
11228         free(self->ignore_calculated_column);
11229         self->ignore_calculated_column = lxw_strdup(range);
11230     }
11231     else if (type == LXW_IGNORE_TWO_DIGIT_TEXT_YEAR) {
11232         free(self->ignore_two_digit_text_year);
11233         self->ignore_two_digit_text_year = lxw_strdup(range);
11234     }
11235 
11236     self->has_ignore_errors = LXW_TRUE;
11237 
11238     return LXW_NO_ERROR;
11239 }
11240