Coverage Report

Created: 2026-01-09 06:28

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