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