1 /*****************************************************************************
2 * workbook - A library for creating Excel XLSX workbook files.
3 *
4 * Used in conjunction with the libxlsxwriter library.
5 *
6 * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
7 *
8 */
9
10 #include "xlsxwriter/xmlwriter.h"
11 #include "xlsxwriter/workbook.h"
12 #include "xlsxwriter/utility.h"
13 #include "xlsxwriter/packager.h"
14 #include "xlsxwriter/hash_table.h"
15
16 STATIC int _worksheet_name_cmp(lxw_worksheet_name *name1,
17 lxw_worksheet_name *name2);
18 STATIC int _chartsheet_name_cmp(lxw_chartsheet_name *name1,
19 lxw_chartsheet_name *name2);
20 STATIC int _image_md5_cmp(lxw_image_md5 *tuple1, lxw_image_md5 *tuple2);
21
22 #ifndef __clang_analyzer__
23 LXW_RB_GENERATE_WORKSHEET_NAMES(lxw_worksheet_names, lxw_worksheet_name,
24 tree_pointers, _worksheet_name_cmp);
25 LXW_RB_GENERATE_CHARTSHEET_NAMES(lxw_chartsheet_names, lxw_chartsheet_name,
26 tree_pointers, _chartsheet_name_cmp);
27 LXW_RB_GENERATE_IMAGE_MD5S(lxw_image_md5s, lxw_image_md5,
28 tree_pointers, _image_md5_cmp);
29 #endif
30
31 /*
32 * Forward declarations.
33 */
34
35 /*****************************************************************************
36 *
37 * Private functions.
38 *
39 ****************************************************************************/
40
41 /*
42 * Comparators for the sheet names structure red/black tree.
43 */
44 STATIC int
_worksheet_name_cmp(lxw_worksheet_name * name1,lxw_worksheet_name * name2)45 _worksheet_name_cmp(lxw_worksheet_name *name1, lxw_worksheet_name *name2)
46 {
47 return lxw_strcasecmp(name1->name, name2->name);
48 }
49
50 STATIC int
_chartsheet_name_cmp(lxw_chartsheet_name * name1,lxw_chartsheet_name * name2)51 _chartsheet_name_cmp(lxw_chartsheet_name *name1, lxw_chartsheet_name *name2)
52 {
53 return lxw_strcasecmp(name1->name, name2->name);
54 }
55
56 STATIC int
_image_md5_cmp(lxw_image_md5 * tuple1,lxw_image_md5 * tuple2)57 _image_md5_cmp(lxw_image_md5 *tuple1, lxw_image_md5 *tuple2)
58 {
59 return strcmp(tuple1->md5, tuple2->md5);
60 }
61
62 /*
63 * Free workbook properties.
64 */
65 STATIC void
_free_doc_properties(lxw_doc_properties * properties)66 _free_doc_properties(lxw_doc_properties *properties)
67 {
68 if (properties) {
69 free(properties->title);
70 free(properties->subject);
71 free(properties->author);
72 free(properties->manager);
73 free(properties->company);
74 free(properties->category);
75 free(properties->keywords);
76 free(properties->comments);
77 free(properties->status);
78 free(properties->hyperlink_base);
79 }
80
81 free(properties);
82 }
83
84 /*
85 * Free workbook custom property.
86 */
87 STATIC void
_free_custom_doc_property(lxw_custom_property * custom_property)88 _free_custom_doc_property(lxw_custom_property *custom_property)
89 {
90 if (custom_property) {
91 free(custom_property->name);
92 if (custom_property->type == LXW_CUSTOM_STRING)
93 free(custom_property->u.string);
94 }
95
96 free(custom_property);
97 }
98
99 /*
100 * Free a workbook object.
101 */
102 void
lxw_workbook_free(lxw_workbook * workbook)103 lxw_workbook_free(lxw_workbook *workbook)
104 {
105 lxw_sheet *sheet;
106 struct lxw_worksheet_name *worksheet_name;
107 struct lxw_worksheet_name *next_worksheet_name;
108 struct lxw_chartsheet_name *chartsheet_name;
109 struct lxw_chartsheet_name *next_chartsheet_name;
110 struct lxw_image_md5 *image_md5;
111 struct lxw_image_md5 *next_image_md5;
112 lxw_chart *chart;
113 lxw_format *format;
114 lxw_defined_name *defined_name;
115 lxw_defined_name *defined_name_tmp;
116 lxw_custom_property *custom_property;
117
118 if (!workbook)
119 return;
120
121 _free_doc_properties(workbook->properties);
122
123 free(workbook->filename);
124
125 /* Free the sheets in the workbook. */
126 if (workbook->sheets) {
127 while (!STAILQ_EMPTY(workbook->sheets)) {
128 sheet = STAILQ_FIRST(workbook->sheets);
129
130 if (sheet->is_chartsheet)
131 lxw_chartsheet_free(sheet->u.chartsheet);
132 else
133 lxw_worksheet_free(sheet->u.worksheet);
134
135 STAILQ_REMOVE_HEAD(workbook->sheets, list_pointers);
136 free(sheet);
137 }
138 free(workbook->sheets);
139 }
140
141 /* Free the sheet lists. The worksheet objects are freed above. */
142 free(workbook->worksheets);
143 free(workbook->chartsheets);
144
145 /* Free the charts in the workbook. */
146 if (workbook->charts) {
147 while (!STAILQ_EMPTY(workbook->charts)) {
148 chart = STAILQ_FIRST(workbook->charts);
149 STAILQ_REMOVE_HEAD(workbook->charts, list_pointers);
150 lxw_chart_free(chart);
151 }
152 free(workbook->charts);
153 }
154
155 /* Free the formats in the workbook. */
156 if (workbook->formats) {
157 while (!STAILQ_EMPTY(workbook->formats)) {
158 format = STAILQ_FIRST(workbook->formats);
159 STAILQ_REMOVE_HEAD(workbook->formats, list_pointers);
160 lxw_format_free(format);
161 }
162 free(workbook->formats);
163 }
164
165 /* Free the defined_names in the workbook. */
166 if (workbook->defined_names) {
167 defined_name = TAILQ_FIRST(workbook->defined_names);
168 while (defined_name) {
169
170 defined_name_tmp = TAILQ_NEXT(defined_name, list_pointers);
171 free(defined_name);
172 defined_name = defined_name_tmp;
173 }
174 free(workbook->defined_names);
175 }
176
177 /* Free the custom_properties in the workbook. */
178 if (workbook->custom_properties) {
179 while (!STAILQ_EMPTY(workbook->custom_properties)) {
180 custom_property = STAILQ_FIRST(workbook->custom_properties);
181 STAILQ_REMOVE_HEAD(workbook->custom_properties, list_pointers);
182 _free_custom_doc_property(custom_property);
183 }
184 free(workbook->custom_properties);
185 }
186
187 if (workbook->worksheet_names) {
188 for (worksheet_name =
189 RB_MIN(lxw_worksheet_names, workbook->worksheet_names);
190 worksheet_name; worksheet_name = next_worksheet_name) {
191
192 next_worksheet_name = RB_NEXT(lxw_worksheet_names,
193 workbook->worksheet_name,
194 worksheet_name);
195 RB_REMOVE(lxw_worksheet_names, workbook->worksheet_names,
196 worksheet_name);
197 free(worksheet_name);
198 }
199
200 free(workbook->worksheet_names);
201 }
202
203 if (workbook->chartsheet_names) {
204 for (chartsheet_name =
205 RB_MIN(lxw_chartsheet_names, workbook->chartsheet_names);
206 chartsheet_name; chartsheet_name = next_chartsheet_name) {
207
208 next_chartsheet_name = RB_NEXT(lxw_chartsheet_names,
209 workbook->chartsheet_name,
210 chartsheet_name);
211 RB_REMOVE(lxw_chartsheet_names, workbook->chartsheet_names,
212 chartsheet_name);
213 free(chartsheet_name);
214 }
215
216 free(workbook->chartsheet_names);
217 }
218
219 if (workbook->image_md5s) {
220 for (image_md5 = RB_MIN(lxw_image_md5s, workbook->image_md5s);
221 image_md5; image_md5 = next_image_md5) {
222
223 next_image_md5 =
224 RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5);
225 RB_REMOVE(lxw_image_md5s, workbook->image_md5s, image_md5);
226 free(image_md5->md5);
227 free(image_md5);
228 }
229
230 free(workbook->image_md5s);
231 }
232
233 if (workbook->header_image_md5s) {
234 for (image_md5 = RB_MIN(lxw_image_md5s, workbook->header_image_md5s);
235 image_md5; image_md5 = next_image_md5) {
236
237 next_image_md5 =
238 RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5);
239 RB_REMOVE(lxw_image_md5s, workbook->header_image_md5s, image_md5);
240 free(image_md5->md5);
241 free(image_md5);
242 }
243
244 free(workbook->header_image_md5s);
245 }
246
247 if (workbook->background_md5s) {
248 for (image_md5 = RB_MIN(lxw_image_md5s, workbook->background_md5s);
249 image_md5; image_md5 = next_image_md5) {
250
251 next_image_md5 =
252 RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5);
253 RB_REMOVE(lxw_image_md5s, workbook->background_md5s, image_md5);
254 free(image_md5->md5);
255 free(image_md5);
256 }
257
258 free(workbook->background_md5s);
259 }
260
261 lxw_hash_free(workbook->used_xf_formats);
262 lxw_hash_free(workbook->used_dxf_formats);
263 lxw_sst_free(workbook->sst);
264 free(workbook->options.tmpdir);
265 free(workbook->ordered_charts);
266 free(workbook->vba_project);
267 free(workbook->vba_codename);
268 free(workbook);
269 }
270
271 /*
272 * Set the default index for each format. This is only used for testing.
273 */
274 void
lxw_workbook_set_default_xf_indices(lxw_workbook * self)275 lxw_workbook_set_default_xf_indices(lxw_workbook *self)
276 {
277 lxw_format *format;
278 int32_t index = 0;
279
280 STAILQ_FOREACH(format, self->formats, list_pointers) {
281
282 /* Skip the hyperlink format. */
283 if (index != 1)
284 lxw_format_get_xf_index(format);
285
286 index++;
287 }
288 }
289
290 /*
291 * Iterate through the XF Format objects and give them an index to non-default
292 * font elements.
293 */
294 STATIC void
_prepare_fonts(lxw_workbook * self)295 _prepare_fonts(lxw_workbook *self)
296 {
297
298 lxw_hash_table *fonts = lxw_hash_new(128, 1, 1);
299 lxw_hash_element *hash_element;
300 lxw_hash_element *used_format_element;
301 uint16_t index = 0;
302
303 LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
304 lxw_format *format = (lxw_format *) used_format_element->value;
305 lxw_font *key = lxw_format_get_font_key(format);
306
307 if (key) {
308 /* Look up the format in the hash table. */
309 hash_element = lxw_hash_key_exists(fonts, key, sizeof(lxw_font));
310
311 if (hash_element) {
312 /* Font has already been used. */
313 format->font_index = *(uint16_t *) hash_element->value;
314 format->has_font = LXW_FALSE;
315 free(key);
316 }
317 else {
318 /* This is a new font. */
319 uint16_t *font_index = calloc(1, sizeof(uint16_t));
320 *font_index = index;
321 format->font_index = index;
322 format->has_font = LXW_TRUE;
323 lxw_insert_hash_element(fonts, key, font_index,
324 sizeof(lxw_font));
325 index++;
326 }
327 }
328 }
329
330 lxw_hash_free(fonts);
331
332 /* For DXF formats we only need to check if the properties have changed. */
333 LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
334 lxw_format *format = (lxw_format *) used_format_element->value;
335
336 /* The only font properties that can change for a DXF format are:
337 * color, bold, italic, underline and strikethrough. */
338 if (format->font_color || format->bold || format->italic
339 || format->underline || format->font_strikeout) {
340 format->has_dxf_font = LXW_TRUE;
341 }
342 }
343
344 self->font_count = index;
345 }
346
347 /*
348 * Iterate through the XF Format objects and give them an index to non-default
349 * border elements.
350 */
351 STATIC void
_prepare_borders(lxw_workbook * self)352 _prepare_borders(lxw_workbook *self)
353 {
354
355 lxw_hash_table *borders = lxw_hash_new(128, 1, 1);
356 lxw_hash_element *hash_element;
357 lxw_hash_element *used_format_element;
358 uint16_t index = 0;
359
360 LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
361 lxw_format *format = (lxw_format *) used_format_element->value;
362 lxw_border *key = lxw_format_get_border_key(format);
363
364 if (key) {
365 /* Look up the format in the hash table. */
366 hash_element =
367 lxw_hash_key_exists(borders, key, sizeof(lxw_border));
368
369 if (hash_element) {
370 /* Border has already been used. */
371 format->border_index = *(uint16_t *) hash_element->value;
372 format->has_border = LXW_FALSE;
373 free(key);
374 }
375 else {
376 /* This is a new border. */
377 uint16_t *border_index = calloc(1, sizeof(uint16_t));
378 *border_index = index;
379 format->border_index = index;
380 format->has_border = 1;
381 lxw_insert_hash_element(borders, key, border_index,
382 sizeof(lxw_border));
383 index++;
384 }
385 }
386 }
387
388 /* For DXF formats we only need to check if the properties have changed. */
389 LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
390 lxw_format *format = (lxw_format *) used_format_element->value;
391
392 if (format->left || format->right || format->top || format->bottom) {
393 format->has_dxf_border = LXW_TRUE;
394 }
395 }
396
397 lxw_hash_free(borders);
398
399 self->border_count = index;
400 }
401
402 /*
403 * Iterate through the XF Format objects and give them an index to non-default
404 * fill elements.
405 */
406 STATIC void
_prepare_fills(lxw_workbook * self)407 _prepare_fills(lxw_workbook *self)
408 {
409
410 lxw_hash_table *fills = lxw_hash_new(128, 1, 1);
411 lxw_hash_element *hash_element;
412 lxw_hash_element *used_format_element;
413 uint16_t index = 2;
414 lxw_fill *default_fill_1 = NULL;
415 lxw_fill *default_fill_2 = NULL;
416 uint16_t *fill_index1 = NULL;
417 uint16_t *fill_index2 = NULL;
418
419 default_fill_1 = calloc(1, sizeof(lxw_fill));
420 GOTO_LABEL_ON_MEM_ERROR(default_fill_1, mem_error);
421
422 default_fill_2 = calloc(1, sizeof(lxw_fill));
423 GOTO_LABEL_ON_MEM_ERROR(default_fill_2, mem_error);
424
425 fill_index1 = calloc(1, sizeof(uint16_t));
426 GOTO_LABEL_ON_MEM_ERROR(fill_index1, mem_error);
427
428 fill_index2 = calloc(1, sizeof(uint16_t));
429 GOTO_LABEL_ON_MEM_ERROR(fill_index2, mem_error);
430
431 /* Add the default fills. */
432 default_fill_1->pattern = LXW_PATTERN_NONE;
433 default_fill_1->fg_color = LXW_COLOR_UNSET;
434 default_fill_1->bg_color = LXW_COLOR_UNSET;
435 *fill_index1 = 0;
436 lxw_insert_hash_element(fills, default_fill_1, fill_index1,
437 sizeof(lxw_fill));
438
439 default_fill_2->pattern = LXW_PATTERN_GRAY_125;
440 default_fill_2->fg_color = LXW_COLOR_UNSET;
441 default_fill_2->bg_color = LXW_COLOR_UNSET;
442 *fill_index2 = 1;
443 lxw_insert_hash_element(fills, default_fill_2, fill_index2,
444 sizeof(lxw_fill));
445
446 /* For DXF formats we only need to check if the properties have changed. */
447 LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
448 lxw_format *format = (lxw_format *) used_format_element->value;
449
450 if (format->pattern || format->bg_color || format->fg_color) {
451 format->has_dxf_fill = LXW_TRUE;
452 format->dxf_bg_color = format->bg_color;
453 format->dxf_fg_color = format->fg_color;
454 }
455 }
456
457 LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
458 lxw_format *format = (lxw_format *) used_format_element->value;
459 lxw_fill *key = lxw_format_get_fill_key(format);
460
461 /* The following logical statements jointly take care of special */
462 /* cases in relation to cell colors and patterns: */
463 /* 1. For a solid fill (pattern == 1) Excel reverses the role of */
464 /* foreground and background colors, and */
465 /* 2. If the user specifies a foreground or background color */
466 /* without a pattern they probably wanted a solid fill, so */
467 /* we fill in the defaults. */
468 if (format->pattern == LXW_PATTERN_SOLID
469 && format->bg_color != LXW_COLOR_UNSET
470 && format->fg_color != LXW_COLOR_UNSET) {
471 lxw_color_t tmp = format->fg_color;
472 format->fg_color = format->bg_color;
473 format->bg_color = tmp;
474 }
475
476 if (format->pattern <= LXW_PATTERN_SOLID
477 && format->bg_color != LXW_COLOR_UNSET
478 && format->fg_color == LXW_COLOR_UNSET) {
479 format->fg_color = format->bg_color;
480 format->bg_color = LXW_COLOR_UNSET;
481 format->pattern = LXW_PATTERN_SOLID;
482 }
483
484 if (format->pattern <= LXW_PATTERN_SOLID
485 && format->bg_color == LXW_COLOR_UNSET
486 && format->fg_color != LXW_COLOR_UNSET) {
487 format->bg_color = LXW_COLOR_UNSET;
488 format->pattern = LXW_PATTERN_SOLID;
489 }
490
491 if (key) {
492 /* Look up the format in the hash table. */
493 hash_element = lxw_hash_key_exists(fills, key, sizeof(lxw_fill));
494
495 if (hash_element) {
496 /* Fill has already been used. */
497 format->fill_index = *(uint16_t *) hash_element->value;
498 format->has_fill = LXW_FALSE;
499 free(key);
500 }
501 else {
502 /* This is a new fill. */
503 uint16_t *fill_index = calloc(1, sizeof(uint16_t));
504 *fill_index = index;
505 format->fill_index = index;
506 format->has_fill = 1;
507 lxw_insert_hash_element(fills, key, fill_index,
508 sizeof(lxw_fill));
509 index++;
510 }
511 }
512 }
513
514 lxw_hash_free(fills);
515
516 self->fill_count = index;
517
518 return;
519
520 mem_error:
521 free(fill_index2);
522 free(fill_index1);
523 free(default_fill_2);
524 free(default_fill_1);
525 lxw_hash_free(fills);
526 }
527
528 /*
529 * Iterate through the XF Format objects and give them an index to non-default
530 * number format elements. Note, user defined records start from index 0xA4.
531 */
532 STATIC void
_prepare_num_formats(lxw_workbook * self)533 _prepare_num_formats(lxw_workbook *self)
534 {
535
536 lxw_hash_table *num_formats = lxw_hash_new(128, 0, 1);
537 lxw_hash_element *hash_element;
538 lxw_hash_element *used_format_element;
539 uint16_t index = 0xA4;
540 uint16_t num_format_count = 0;
541 uint16_t *num_format_index;
542
543 LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) {
544 lxw_format *format = (lxw_format *) used_format_element->value;
545
546 /* Format already has a number format index. */
547 if (format->num_format_index)
548 continue;
549
550 /* Check if there is a user defined number format string. */
551 if (*format->num_format) {
552 char num_format[LXW_FORMAT_FIELD_LEN] = { 0 };
553 lxw_snprintf(num_format, LXW_FORMAT_FIELD_LEN, "%s",
554 format->num_format);
555
556 /* Look up the num_format in the hash table. */
557 hash_element = lxw_hash_key_exists(num_formats, num_format,
558 LXW_FORMAT_FIELD_LEN);
559
560 if (hash_element) {
561 /* Num_Format has already been used. */
562 format->num_format_index = *(uint16_t *) hash_element->value;
563 }
564 else {
565 /* This is a new num_format. */
566 num_format_index = calloc(1, sizeof(uint16_t));
567 *num_format_index = index;
568 format->num_format_index = index;
569 lxw_insert_hash_element(num_formats, format->num_format,
570 num_format_index,
571 LXW_FORMAT_FIELD_LEN);
572 index++;
573 num_format_count++;
574 }
575 }
576 }
577
578 LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) {
579 lxw_format *format = (lxw_format *) used_format_element->value;
580
581 /* Format already has a number format index. */
582 if (format->num_format_index)
583 continue;
584
585 /* Check if there is a user defined number format string. */
586 if (*format->num_format) {
587 char num_format[LXW_FORMAT_FIELD_LEN] = { 0 };
588 lxw_snprintf(num_format, LXW_FORMAT_FIELD_LEN, "%s",
589 format->num_format);
590
591 /* Look up the num_format in the hash table. */
592 hash_element = lxw_hash_key_exists(num_formats, num_format,
593 LXW_FORMAT_FIELD_LEN);
594
595 if (hash_element) {
596 /* Num_Format has already been used. */
597 format->num_format_index = *(uint16_t *) hash_element->value;
598 }
599 else {
600 /* This is a new num_format. */
601 num_format_index = calloc(1, sizeof(uint16_t));
602 *num_format_index = index;
603 format->num_format_index = index;
604 lxw_insert_hash_element(num_formats, format->num_format,
605 num_format_index,
606 LXW_FORMAT_FIELD_LEN);
607 index++;
608 /* Don't update num_format_count for DXF formats. */
609 }
610 }
611 }
612
613 lxw_hash_free(num_formats);
614
615 self->num_format_count = num_format_count;
616 }
617
618 /*
619 * Prepare workbook and sub-objects for writing.
620 */
621 STATIC void
_prepare_workbook(lxw_workbook * self)622 _prepare_workbook(lxw_workbook *self)
623 {
624 /* Set the font index for the format objects. */
625 _prepare_fonts(self);
626
627 /* Set the number format index for the format objects. */
628 _prepare_num_formats(self);
629
630 /* Set the border index for the format objects. */
631 _prepare_borders(self);
632
633 /* Set the fill index for the format objects. */
634 _prepare_fills(self);
635
636 }
637
638 /*
639 * Compare two defined_name structures.
640 */
641 static int
_compare_defined_names(lxw_defined_name * a,lxw_defined_name * b)642 _compare_defined_names(lxw_defined_name *a, lxw_defined_name *b)
643 {
644 int res = strcmp(a->normalised_name, b->normalised_name);
645
646 /* Primary comparison based on defined name. */
647 if (res)
648 return res;
649
650 /* Secondary comparison based on worksheet name. */
651 res = strcmp(a->normalised_sheetname, b->normalised_sheetname);
652
653 return res;
654 }
655
656 /*
657 * Process and store the defined names. The defined names are stored with
658 * the Workbook.xml but also with the App.xml if they refer to a sheet
659 * range like "Sheet1!:A1". The defined names are store in sorted
660 * order for consistency with Excel. The names need to be normalized before
661 * sorting.
662 */
663 STATIC lxw_error
_store_defined_name(lxw_workbook * self,const char * name,const char * app_name,const char * formula,int16_t index,uint8_t hidden)664 _store_defined_name(lxw_workbook *self, const char *name,
665 const char *app_name, const char *formula, int16_t index,
666 uint8_t hidden)
667 {
668 lxw_sheet *sheet;
669 lxw_worksheet *worksheet;
670 lxw_defined_name *defined_name;
671 lxw_defined_name *list_defined_name;
672 char name_copy[LXW_DEFINED_NAME_LENGTH];
673 char *tmp_str;
674 char *worksheet_name;
675
676 /* Do some checks on the input data */
677 if (!name || !formula)
678 return LXW_ERROR_NULL_PARAMETER_IGNORED;
679
680 if (lxw_utf8_strlen(name) > LXW_DEFINED_NAME_LENGTH ||
681 lxw_utf8_strlen(formula) > LXW_DEFINED_NAME_LENGTH) {
682 return LXW_ERROR_128_STRING_LENGTH_EXCEEDED;
683 }
684
685 /* Allocate a new defined_name to be added to the linked list of names. */
686 defined_name = calloc(1, sizeof(struct lxw_defined_name));
687 RETURN_ON_MEM_ERROR(defined_name, LXW_ERROR_MEMORY_MALLOC_FAILED);
688
689 /* Copy the user input string. */
690 lxw_strcpy(name_copy, name);
691
692 /* Set the worksheet index or -1 for a global defined name. */
693 defined_name->index = index;
694 defined_name->hidden = hidden;
695
696 /* Check for local defined names like like "Sheet1!name". */
697 tmp_str = strchr(name_copy, '!');
698
699 if (tmp_str == NULL) {
700 /* The name is global. We just store the defined name string. */
701 lxw_strcpy(defined_name->name, name_copy);
702 }
703 else {
704 /* The name is worksheet local. We need to extract the sheet name
705 * and map it to a sheet index. */
706
707 /* Split the into the worksheet name and defined name. */
708 *tmp_str = '\0';
709 tmp_str++;
710 worksheet_name = name_copy;
711
712 /* Remove any worksheet quoting. */
713 if (worksheet_name[0] == '\'')
714 worksheet_name++;
715 if (worksheet_name[strlen(worksheet_name) - 1] == '\'')
716 worksheet_name[strlen(worksheet_name) - 1] = '\0';
717
718 /* Search for worksheet name to get the equivalent worksheet index. */
719 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
720 if (sheet->is_chartsheet)
721 continue;
722 else
723 worksheet = sheet->u.worksheet;
724
725 if (strcmp(worksheet_name, worksheet->name) == 0) {
726 defined_name->index = worksheet->index;
727 lxw_strcpy(defined_name->normalised_sheetname,
728 worksheet_name);
729 }
730 }
731
732 /* If we didn't find the worksheet name we exit. */
733 if (defined_name->index == -1)
734 goto mem_error;
735
736 lxw_strcpy(defined_name->name, tmp_str);
737 }
738
739 /* Print titles and repeat title pass in the name used for App.xml. */
740 if (app_name) {
741 lxw_strcpy(defined_name->app_name, app_name);
742 lxw_strcpy(defined_name->normalised_sheetname, app_name);
743 }
744 else {
745 lxw_strcpy(defined_name->app_name, name);
746 }
747
748 /* We need to normalize the defined names for sorting. This involves
749 * removing any _xlnm namespace and converting it to lowercase. */
750 tmp_str = strstr(name_copy, "_xlnm.");
751
752 if (tmp_str)
753 lxw_strcpy(defined_name->normalised_name, defined_name->name + 6);
754 else
755 lxw_strcpy(defined_name->normalised_name, defined_name->name);
756
757 lxw_str_tolower(defined_name->normalised_name);
758 lxw_str_tolower(defined_name->normalised_sheetname);
759
760 /* Strip leading "=" from the formula. */
761 if (formula[0] == '=')
762 lxw_strcpy(defined_name->formula, formula + 1);
763 else
764 lxw_strcpy(defined_name->formula, formula);
765
766 /* We add the defined name to the list in sorted order. */
767 list_defined_name = TAILQ_FIRST(self->defined_names);
768
769 if (list_defined_name == NULL ||
770 _compare_defined_names(defined_name, list_defined_name) < 1) {
771 /* List is empty or defined name goes to the head. */
772 TAILQ_INSERT_HEAD(self->defined_names, defined_name, list_pointers);
773 return LXW_NO_ERROR;
774 }
775
776 TAILQ_FOREACH(list_defined_name, self->defined_names, list_pointers) {
777 int res = _compare_defined_names(defined_name, list_defined_name);
778
779 /* The entry already exists. We exit and don't overwrite. */
780 if (res == 0)
781 goto mem_error;
782
783 /* New defined name is inserted in sorted order before other entries. */
784 if (res < 0) {
785 TAILQ_INSERT_BEFORE(list_defined_name, defined_name,
786 list_pointers);
787 return LXW_NO_ERROR;
788 }
789 }
790
791 /* If the entry wasn't less than any of the entries in the list we add it
792 * to the end. */
793 TAILQ_INSERT_TAIL(self->defined_names, defined_name, list_pointers);
794 return LXW_NO_ERROR;
795
796 mem_error:
797 free(defined_name);
798 return LXW_ERROR_MEMORY_MALLOC_FAILED;
799 }
800
801 /*
802 * Populate the data cache of a chart data series by reading the data from the
803 * relevant worksheet and adding it to the cached in the range object as a
804 * list of points.
805 *
806 * Note, the data cache isn't strictly required by Excel but it helps if the
807 * chart is embedded in another application such as PowerPoint and it also
808 * helps with comparison testing.
809 */
810 STATIC void
_populate_range_data_cache(lxw_workbook * self,lxw_series_range * range)811 _populate_range_data_cache(lxw_workbook *self, lxw_series_range *range)
812 {
813 lxw_worksheet *worksheet;
814 lxw_row_t row_num;
815 lxw_col_t col_num;
816 lxw_row *row_obj;
817 lxw_cell *cell_obj;
818 struct lxw_series_data_point *data_point;
819 uint16_t num_data_points = 0;
820
821 /* If ignore_cache is set then don't try to populate the cache. This flag
822 * may be set manually, for testing, or due to a case where the cache
823 * can't be calculated.
824 */
825 if (range->ignore_cache)
826 return;
827
828 /* Currently we only handle 2D ranges so ensure either the rows or cols
829 * are the same.
830 */
831 if (range->first_row != range->last_row
832 && range->first_col != range->last_col) {
833 range->ignore_cache = LXW_TRUE;
834 return;
835 }
836
837 /* Check that the sheetname exists. */
838 worksheet = workbook_get_worksheet_by_name(self, range->sheetname);
839 if (!worksheet) {
840 LXW_WARN_FORMAT2("workbook_add_chart(): worksheet name '%s' "
841 "in chart formula '%s' doesn't exist.",
842 range->sheetname, range->formula);
843 range->ignore_cache = LXW_TRUE;
844 return;
845 }
846
847 /* We can't read the data when worksheet optimization is on. */
848 if (worksheet->optimize) {
849 range->ignore_cache = LXW_TRUE;
850 return;
851 }
852
853 /* Iterate through the worksheet data and populate the range cache. */
854 for (row_num = range->first_row; row_num <= range->last_row; row_num++) {
855 row_obj = lxw_worksheet_find_row(worksheet, row_num);
856
857 for (col_num = range->first_col; col_num <= range->last_col;
858 col_num++) {
859
860 data_point = calloc(1, sizeof(struct lxw_series_data_point));
861 if (!data_point) {
862 range->ignore_cache = LXW_TRUE;
863 return;
864 }
865
866 cell_obj = lxw_worksheet_find_cell_in_row(row_obj, col_num);
867
868 if (cell_obj) {
869 if (cell_obj->type == NUMBER_CELL) {
870 data_point->number = cell_obj->u.number;
871 }
872
873 if (cell_obj->type == STRING_CELL) {
874 data_point->string = lxw_strdup(cell_obj->sst_string);
875 data_point->is_string = LXW_TRUE;
876 range->has_string_cache = LXW_TRUE;
877 }
878 }
879 else {
880 data_point->no_data = LXW_TRUE;
881 }
882
883 STAILQ_INSERT_TAIL(range->data_cache, data_point, list_pointers);
884 num_data_points++;
885 }
886 }
887
888 range->num_data_points = num_data_points;
889
890 }
891
892 /* Convert a chart range such as Sheet1!$A$1:$A$5 to a sheet name and row-col
893 * dimensions, or vice-versa. This gives us the dimensions to read data back
894 * from the worksheet.
895 */
896 STATIC void
_populate_range_dimensions(lxw_workbook * self,lxw_series_range * range)897 _populate_range_dimensions(lxw_workbook *self, lxw_series_range *range)
898 {
899
900 char formula[LXW_MAX_FORMULA_RANGE_LENGTH] = { 0 };
901 char *tmp_str;
902 char *sheetname;
903
904 /* If neither the range formula or sheetname is defined then this probably
905 * isn't a valid range.
906 */
907 if (!range->formula && !range->sheetname) {
908 range->ignore_cache = LXW_TRUE;
909 return;
910 }
911
912 /* If the sheetname is already defined it was already set via
913 * chart_series_set_categories() or chart_series_set_values().
914 */
915 if (range->sheetname)
916 return;
917
918 /* Ignore non-contiguous range like (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5) */
919 if (range->formula[0] == '(') {
920 range->ignore_cache = LXW_TRUE;
921 return;
922 }
923
924 /* Create a copy of the formula to modify and parse into parts. */
925 lxw_snprintf(formula, LXW_MAX_FORMULA_RANGE_LENGTH, "%s", range->formula);
926
927 /* Check for valid formula. Note, This needs stronger validation. */
928 tmp_str = strchr(formula, '!');
929
930 if (tmp_str == NULL) {
931 range->ignore_cache = LXW_TRUE;
932 return;
933 }
934 else {
935 /* Split the formulas into sheetname and row-col data. */
936 *tmp_str = '\0';
937 tmp_str++;
938 sheetname = formula;
939
940 /* Remove any worksheet quoting. */
941 if (sheetname[0] == '\'')
942 sheetname++;
943 if (sheetname[strlen(sheetname) - 1] == '\'')
944 sheetname[strlen(sheetname) - 1] = '\0';
945
946 /* Check that the sheetname exists. */
947 if (!workbook_get_worksheet_by_name(self, sheetname)) {
948 LXW_WARN_FORMAT2("workbook_add_chart(): worksheet name '%s' "
949 "in chart formula '%s' doesn't exist.",
950 sheetname, range->formula);
951 range->ignore_cache = LXW_TRUE;
952 return;
953 }
954
955 range->sheetname = lxw_strdup(sheetname);
956 range->first_row = lxw_name_to_row(tmp_str);
957 range->first_col = lxw_name_to_col(tmp_str);
958
959 if (strchr(tmp_str, ':')) {
960 /* 2D range. */
961 range->last_row = lxw_name_to_row_2(tmp_str);
962 range->last_col = lxw_name_to_col_2(tmp_str);
963 }
964 else {
965 /* 1D range. */
966 range->last_row = range->first_row;
967 range->last_col = range->first_col;
968 }
969
970 }
971 }
972
973 /* Set the range dimensions and set the data cache.
974 */
975 STATIC void
_populate_range(lxw_workbook * self,lxw_series_range * range)976 _populate_range(lxw_workbook *self, lxw_series_range *range)
977 {
978 if (!range)
979 return;
980
981 _populate_range_dimensions(self, range);
982 _populate_range_data_cache(self, range);
983 }
984
985 /*
986 * Add "cached" data to charts to provide the numCache and strCache data for
987 * series and title/axis ranges.
988 */
989 STATIC void
_add_chart_cache_data(lxw_workbook * self)990 _add_chart_cache_data(lxw_workbook *self)
991 {
992 lxw_chart *chart;
993 lxw_chart_series *series;
994 uint16_t i;
995
996 STAILQ_FOREACH(chart, self->ordered_charts, ordered_list_pointers) {
997
998 _populate_range(self, chart->title.range);
999 _populate_range(self, chart->x_axis->title.range);
1000 _populate_range(self, chart->y_axis->title.range);
1001
1002 if (STAILQ_EMPTY(chart->series_list))
1003 continue;
1004
1005 STAILQ_FOREACH(series, chart->series_list, list_pointers) {
1006 _populate_range(self, series->categories);
1007 _populate_range(self, series->values);
1008 _populate_range(self, series->title.range);
1009
1010 for (i = 0; i < series->data_label_count; i++) {
1011 lxw_chart_custom_label *data_label = &series->data_labels[i];
1012 _populate_range(self, data_label->range);
1013 }
1014 }
1015 }
1016 }
1017
1018 /*
1019 * Store the image types used in the workbook to update the content types.
1020 */
1021 STATIC void
_store_image_type(lxw_workbook * self,uint8_t image_type)1022 _store_image_type(lxw_workbook *self, uint8_t image_type)
1023 {
1024 if (image_type == LXW_IMAGE_PNG)
1025 self->has_png = LXW_TRUE;
1026
1027 if (image_type == LXW_IMAGE_JPEG)
1028 self->has_jpeg = LXW_TRUE;
1029
1030 if (image_type == LXW_IMAGE_BMP)
1031 self->has_bmp = LXW_TRUE;
1032
1033 if (image_type == LXW_IMAGE_GIF)
1034 self->has_gif = LXW_TRUE;
1035 }
1036
1037 /*
1038 * Iterate through the worksheets and set up any chart or image drawings.
1039 */
1040 STATIC void
_prepare_drawings(lxw_workbook * self)1041 _prepare_drawings(lxw_workbook *self)
1042 {
1043 lxw_sheet *sheet;
1044 lxw_worksheet *worksheet;
1045 lxw_object_properties *object_props;
1046 uint32_t chart_ref_id = 0;
1047 uint32_t image_ref_id = 0;
1048 uint32_t ref_id = 0;
1049 uint32_t drawing_id = 0;
1050 uint8_t is_chartsheet;
1051 lxw_image_md5 tmp_image_md5;
1052 lxw_image_md5 *new_image_md5 = NULL;
1053 lxw_image_md5 *found_duplicate_image = NULL;
1054 uint8_t i;
1055
1056 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
1057 if (sheet->is_chartsheet) {
1058 worksheet = sheet->u.chartsheet->worksheet;
1059 is_chartsheet = LXW_TRUE;
1060 }
1061 else {
1062 worksheet = sheet->u.worksheet;
1063 is_chartsheet = LXW_FALSE;
1064 }
1065
1066 if (STAILQ_EMPTY(worksheet->image_props)
1067 && STAILQ_EMPTY(worksheet->chart_data)
1068 && !worksheet->has_header_vml && !worksheet->has_background_image) {
1069 continue;
1070 }
1071
1072 drawing_id++;
1073
1074 /* Prepare background images. */
1075 if (worksheet->has_background_image) {
1076
1077 object_props = worksheet->background_image;
1078
1079 _store_image_type(self, object_props->image_type);
1080
1081 /* Check for duplicate images and only store the first instance. */
1082 if (object_props->md5) {
1083 tmp_image_md5.md5 = object_props->md5;
1084 found_duplicate_image = RB_FIND(lxw_image_md5s,
1085 self->background_md5s,
1086 &tmp_image_md5);
1087 }
1088
1089 if (found_duplicate_image) {
1090 ref_id = found_duplicate_image->id;
1091 object_props->is_duplicate = LXW_TRUE;
1092 }
1093 else {
1094 image_ref_id++;
1095 ref_id = image_ref_id;
1096
1097 #ifndef USE_NO_MD5
1098 new_image_md5 = calloc(1, sizeof(lxw_image_md5));
1099 #endif
1100 if (new_image_md5 && object_props->md5) {
1101 new_image_md5->id = ref_id;
1102 new_image_md5->md5 = lxw_strdup(object_props->md5);
1103
1104 RB_INSERT(lxw_image_md5s, self->background_md5s,
1105 new_image_md5);
1106 }
1107 }
1108
1109 lxw_worksheet_prepare_background(worksheet, ref_id, object_props);
1110 }
1111
1112 /* Prepare worksheet images. */
1113 STAILQ_FOREACH(object_props, worksheet->image_props, list_pointers) {
1114
1115 /* Ignore background image added above. */
1116 if (object_props->is_background)
1117 continue;
1118
1119 _store_image_type(self, object_props->image_type);
1120
1121 /* Check for duplicate images and only store the first instance. */
1122 if (object_props->md5) {
1123 tmp_image_md5.md5 = object_props->md5;
1124 found_duplicate_image = RB_FIND(lxw_image_md5s,
1125 self->image_md5s,
1126 &tmp_image_md5);
1127 }
1128
1129 if (found_duplicate_image) {
1130 ref_id = found_duplicate_image->id;
1131 object_props->is_duplicate = LXW_TRUE;
1132 }
1133 else {
1134 image_ref_id++;
1135 ref_id = image_ref_id;
1136
1137 #ifndef USE_NO_MD5
1138 new_image_md5 = calloc(1, sizeof(lxw_image_md5));
1139 #endif
1140 if (new_image_md5 && object_props->md5) {
1141 new_image_md5->id = ref_id;
1142 new_image_md5->md5 = lxw_strdup(object_props->md5);
1143
1144 RB_INSERT(lxw_image_md5s, self->image_md5s,
1145 new_image_md5);
1146 }
1147 }
1148
1149 lxw_worksheet_prepare_image(worksheet, ref_id, drawing_id,
1150 object_props);
1151 }
1152
1153 /* Prepare worksheet charts. */
1154 STAILQ_FOREACH(object_props, worksheet->chart_data, list_pointers) {
1155 chart_ref_id++;
1156 lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id,
1157 object_props, is_chartsheet);
1158 if (object_props->chart)
1159 STAILQ_INSERT_TAIL(self->ordered_charts, object_props->chart,
1160 ordered_list_pointers);
1161 }
1162
1163 /* Prepare worksheet header/footer images. */
1164 for (i = 0; i < LXW_HEADER_FOOTER_OBJS_MAX; i++) {
1165
1166 object_props = *worksheet->header_footer_objs[i];
1167 if (!object_props)
1168 continue;
1169
1170 _store_image_type(self, object_props->image_type);
1171
1172 /* Check for duplicate images and only store the first instance. */
1173 if (object_props->md5) {
1174 tmp_image_md5.md5 = object_props->md5;
1175 found_duplicate_image = RB_FIND(lxw_image_md5s,
1176 self->header_image_md5s,
1177 &tmp_image_md5);
1178 }
1179
1180 if (found_duplicate_image) {
1181 ref_id = found_duplicate_image->id;
1182 object_props->is_duplicate = LXW_TRUE;
1183 }
1184 else {
1185 image_ref_id++;
1186 ref_id = image_ref_id;
1187
1188 #ifndef USE_NO_MD5
1189 new_image_md5 = calloc(1, sizeof(lxw_image_md5));
1190 #endif
1191 if (new_image_md5 && object_props->md5) {
1192 new_image_md5->id = ref_id;
1193 new_image_md5->md5 = lxw_strdup(object_props->md5);
1194
1195 RB_INSERT(lxw_image_md5s, self->header_image_md5s,
1196 new_image_md5);
1197 }
1198 }
1199
1200 lxw_worksheet_prepare_header_image(worksheet, ref_id,
1201 object_props);
1202 }
1203
1204 }
1205
1206 self->drawing_count = drawing_id;
1207 }
1208
1209 /*
1210 * Iterate through the worksheets and set up the VML objects.
1211 */
1212 STATIC void
_prepare_vml(lxw_workbook * self)1213 _prepare_vml(lxw_workbook *self)
1214 {
1215 lxw_worksheet *worksheet;
1216 lxw_sheet *sheet;
1217 uint32_t comment_id = 0;
1218 uint32_t vml_drawing_id = 0;
1219 uint32_t vml_data_id = 1;
1220 uint32_t vml_header_id = 0;
1221 uint32_t vml_shape_id = 1024;
1222 uint32_t comment_count = 0;
1223
1224 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
1225 if (sheet->is_chartsheet)
1226 continue;
1227 else
1228 worksheet = sheet->u.worksheet;
1229
1230 if (!worksheet->has_vml && !worksheet->has_header_vml)
1231 continue;
1232
1233 if (worksheet->has_vml) {
1234 self->has_vml = LXW_TRUE;
1235 if (worksheet->has_comments) {
1236 self->comment_count++;
1237 comment_id++;
1238 self->has_comments = LXW_TRUE;
1239 }
1240
1241 vml_drawing_id++;
1242
1243 comment_count = lxw_worksheet_prepare_vml_objects(worksheet,
1244 vml_data_id,
1245 vml_shape_id,
1246 vml_drawing_id,
1247 comment_id);
1248
1249 /* Each VML should start with a shape id incremented by 1024. */
1250 vml_data_id += 1 * ((1024 + comment_count) / 1024);
1251 vml_shape_id += 1024 * ((1024 + comment_count) / 1024);
1252 }
1253
1254 if (worksheet->has_header_vml) {
1255 self->has_vml = LXW_TRUE;
1256 vml_drawing_id++;
1257 vml_header_id++;
1258 lxw_worksheet_prepare_header_vml_objects(worksheet,
1259 vml_header_id,
1260 vml_drawing_id);
1261 }
1262 }
1263 }
1264
1265 /*
1266 * Iterate through the worksheets and store any defined names used for print
1267 * ranges or repeat rows/columns.
1268 */
1269 STATIC void
_prepare_defined_names(lxw_workbook * self)1270 _prepare_defined_names(lxw_workbook *self)
1271 {
1272 lxw_worksheet *worksheet;
1273 lxw_sheet *sheet;
1274 char app_name[LXW_DEFINED_NAME_LENGTH];
1275 char range[LXW_DEFINED_NAME_LENGTH];
1276 char area[LXW_MAX_CELL_RANGE_LENGTH];
1277 char first_col[8];
1278 char last_col[8];
1279
1280 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
1281 if (sheet->is_chartsheet)
1282 continue;
1283 else
1284 worksheet = sheet->u.worksheet;
1285 /*
1286 * Check for autofilter settings and store them.
1287 */
1288 if (worksheet->autofilter.in_use) {
1289
1290 lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
1291 "%s!_FilterDatabase", worksheet->quoted_name);
1292
1293 lxw_rowcol_to_range_abs(area,
1294 worksheet->autofilter.first_row,
1295 worksheet->autofilter.first_col,
1296 worksheet->autofilter.last_row,
1297 worksheet->autofilter.last_col);
1298
1299 lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s",
1300 worksheet->quoted_name, area);
1301
1302 /* Autofilters are the only defined name to set the hidden flag. */
1303 _store_defined_name(self, "_xlnm._FilterDatabase", app_name,
1304 range, worksheet->index, LXW_TRUE);
1305 }
1306
1307 /*
1308 * Check for Print Area settings and store them.
1309 */
1310 if (worksheet->print_area.in_use) {
1311
1312 lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
1313 "%s!Print_Area", worksheet->quoted_name);
1314
1315 /* Check for print area that is the max row range. */
1316 if (worksheet->print_area.first_row == 0
1317 && worksheet->print_area.last_row == LXW_ROW_MAX - 1) {
1318
1319 lxw_col_to_name(first_col,
1320 worksheet->print_area.first_col, LXW_FALSE);
1321
1322 lxw_col_to_name(last_col,
1323 worksheet->print_area.last_col, LXW_FALSE);
1324
1325 lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%s:$%s",
1326 first_col, last_col);
1327
1328 }
1329 /* Check for print area that is the max column range. */
1330 else if (worksheet->print_area.first_col == 0
1331 && worksheet->print_area.last_col == LXW_COL_MAX - 1) {
1332
1333 lxw_snprintf(area, LXW_MAX_CELL_RANGE_LENGTH - 1, "$%d:$%d",
1334 worksheet->print_area.first_row + 1,
1335 worksheet->print_area.last_row + 1);
1336
1337 }
1338 else {
1339 lxw_rowcol_to_range_abs(area,
1340 worksheet->print_area.first_row,
1341 worksheet->print_area.first_col,
1342 worksheet->print_area.last_row,
1343 worksheet->print_area.last_col);
1344 }
1345
1346 lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH, "%s!%s",
1347 worksheet->quoted_name, area);
1348
1349 _store_defined_name(self, "_xlnm.Print_Area", app_name,
1350 range, worksheet->index, LXW_FALSE);
1351 }
1352
1353 /*
1354 * Check for repeat rows/cols. aka, Print Titles and store them.
1355 */
1356 if (worksheet->repeat_rows.in_use || worksheet->repeat_cols.in_use) {
1357 if (worksheet->repeat_rows.in_use
1358 && worksheet->repeat_cols.in_use) {
1359 lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
1360 "%s!Print_Titles", worksheet->quoted_name);
1361
1362 lxw_col_to_name(first_col,
1363 worksheet->repeat_cols.first_col, LXW_FALSE);
1364
1365 lxw_col_to_name(last_col,
1366 worksheet->repeat_cols.last_col, LXW_FALSE);
1367
1368 lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
1369 "%s!$%s:$%s,%s!$%d:$%d",
1370 worksheet->quoted_name, first_col,
1371 last_col, worksheet->quoted_name,
1372 worksheet->repeat_rows.first_row + 1,
1373 worksheet->repeat_rows.last_row + 1);
1374
1375 _store_defined_name(self, "_xlnm.Print_Titles", app_name,
1376 range, worksheet->index, LXW_FALSE);
1377 }
1378 else if (worksheet->repeat_rows.in_use) {
1379
1380 lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
1381 "%s!Print_Titles", worksheet->quoted_name);
1382
1383 lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
1384 "%s!$%d:$%d", worksheet->quoted_name,
1385 worksheet->repeat_rows.first_row + 1,
1386 worksheet->repeat_rows.last_row + 1);
1387
1388 _store_defined_name(self, "_xlnm.Print_Titles", app_name,
1389 range, worksheet->index, LXW_FALSE);
1390 }
1391 else if (worksheet->repeat_cols.in_use) {
1392 lxw_snprintf(app_name, LXW_DEFINED_NAME_LENGTH,
1393 "%s!Print_Titles", worksheet->quoted_name);
1394
1395 lxw_col_to_name(first_col,
1396 worksheet->repeat_cols.first_col, LXW_FALSE);
1397
1398 lxw_col_to_name(last_col,
1399 worksheet->repeat_cols.last_col, LXW_FALSE);
1400
1401 lxw_snprintf(range, LXW_DEFINED_NAME_LENGTH,
1402 "%s!$%s:$%s", worksheet->quoted_name,
1403 first_col, last_col);
1404
1405 _store_defined_name(self, "_xlnm.Print_Titles", app_name,
1406 range, worksheet->index, LXW_FALSE);
1407 }
1408 }
1409 }
1410 }
1411
1412 /*
1413 * Iterate through the worksheets and set up the table objects.
1414 */
1415 STATIC void
_prepare_tables(lxw_workbook * self)1416 _prepare_tables(lxw_workbook *self)
1417 {
1418 lxw_worksheet *worksheet;
1419 lxw_sheet *sheet;
1420 uint32_t table_id = 0;
1421 uint32_t table_count = 0;
1422
1423 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
1424 if (sheet->is_chartsheet)
1425 continue;
1426 else
1427 worksheet = sheet->u.worksheet;
1428
1429 table_count = worksheet->table_count;
1430
1431 if (table_count == 0)
1432 continue;
1433
1434 lxw_worksheet_prepare_tables(worksheet, table_id + 1);
1435
1436 table_id += table_count;
1437 }
1438 }
1439
1440 /*****************************************************************************
1441 *
1442 * XML functions.
1443 *
1444 ****************************************************************************/
1445
1446 /*
1447 * Write the XML declaration.
1448 */
1449 STATIC void
_workbook_xml_declaration(lxw_workbook * self)1450 _workbook_xml_declaration(lxw_workbook *self)
1451 {
1452 lxw_xml_declaration(self->file);
1453 }
1454
1455 /*
1456 * Write the <workbook> element.
1457 */
1458 STATIC void
_write_workbook(lxw_workbook * self)1459 _write_workbook(lxw_workbook *self)
1460 {
1461 struct xml_attribute_list attributes;
1462 struct xml_attribute *attribute;
1463 char xmlns[] = "http://schemas.openxmlformats.org"
1464 "/spreadsheetml/2006/main";
1465 char xmlns_r[] = "http://schemas.openxmlformats.org"
1466 "/officeDocument/2006/relationships";
1467
1468 LXW_INIT_ATTRIBUTES();
1469 LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
1470 LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
1471
1472 lxw_xml_start_tag(self->file, "workbook", &attributes);
1473
1474 LXW_FREE_ATTRIBUTES();
1475 }
1476
1477 /*
1478 * Write the <fileVersion> element.
1479 */
1480 STATIC void
_write_file_version(lxw_workbook * self)1481 _write_file_version(lxw_workbook *self)
1482 {
1483 struct xml_attribute_list attributes;
1484 struct xml_attribute *attribute;
1485
1486 LXW_INIT_ATTRIBUTES();
1487 LXW_PUSH_ATTRIBUTES_STR("appName", "xl");
1488 LXW_PUSH_ATTRIBUTES_STR("lastEdited", "4");
1489 LXW_PUSH_ATTRIBUTES_STR("lowestEdited", "4");
1490 LXW_PUSH_ATTRIBUTES_STR("rupBuild", "4505");
1491
1492 if (self->vba_project)
1493 LXW_PUSH_ATTRIBUTES_STR("codeName",
1494 "{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}");
1495
1496 lxw_xml_empty_tag(self->file, "fileVersion", &attributes);
1497
1498 LXW_FREE_ATTRIBUTES();
1499 }
1500
1501 /*
1502 * Write the <fileSharing> element.
1503 */
1504 STATIC void
_workbook_write_file_sharing(lxw_workbook * self)1505 _workbook_write_file_sharing(lxw_workbook *self)
1506 {
1507 struct xml_attribute_list attributes;
1508 struct xml_attribute *attribute;
1509
1510 if (self->read_only == 0)
1511 return;
1512
1513 LXW_INIT_ATTRIBUTES();
1514 LXW_PUSH_ATTRIBUTES_STR("readOnlyRecommended", "1");
1515
1516 lxw_xml_empty_tag(self->file, "fileSharing", &attributes);
1517
1518 LXW_FREE_ATTRIBUTES();
1519 }
1520
1521 /*
1522 * Write the <workbookPr> element.
1523 */
1524 STATIC void
_write_workbook_pr(lxw_workbook * self)1525 _write_workbook_pr(lxw_workbook *self)
1526 {
1527 struct xml_attribute_list attributes;
1528 struct xml_attribute *attribute;
1529
1530 LXW_INIT_ATTRIBUTES();
1531
1532 if (self->vba_codename)
1533 LXW_PUSH_ATTRIBUTES_STR("codeName", self->vba_codename);
1534
1535 LXW_PUSH_ATTRIBUTES_STR("defaultThemeVersion", "124226");
1536
1537 lxw_xml_empty_tag(self->file, "workbookPr", &attributes);
1538
1539 LXW_FREE_ATTRIBUTES();
1540 }
1541
1542 /*
1543 * Write the <workbookView> element.
1544 */
1545 STATIC void
_write_workbook_view(lxw_workbook * self)1546 _write_workbook_view(lxw_workbook *self)
1547 {
1548 struct xml_attribute_list attributes;
1549 struct xml_attribute *attribute;
1550
1551 LXW_INIT_ATTRIBUTES();
1552 LXW_PUSH_ATTRIBUTES_STR("xWindow", "240");
1553 LXW_PUSH_ATTRIBUTES_STR("yWindow", "15");
1554 LXW_PUSH_ATTRIBUTES_STR("windowWidth", "16095");
1555 LXW_PUSH_ATTRIBUTES_STR("windowHeight", "9660");
1556
1557 if (self->first_sheet)
1558 LXW_PUSH_ATTRIBUTES_INT("firstSheet", self->first_sheet);
1559
1560 if (self->active_sheet)
1561 LXW_PUSH_ATTRIBUTES_INT("activeTab", self->active_sheet);
1562
1563 lxw_xml_empty_tag(self->file, "workbookView", &attributes);
1564
1565 LXW_FREE_ATTRIBUTES();
1566 }
1567
1568 /*
1569 * Write the <bookViews> element.
1570 */
1571 STATIC void
_write_book_views(lxw_workbook * self)1572 _write_book_views(lxw_workbook *self)
1573 {
1574 lxw_xml_start_tag(self->file, "bookViews", NULL);
1575
1576 _write_workbook_view(self);
1577
1578 lxw_xml_end_tag(self->file, "bookViews");
1579 }
1580
1581 /*
1582 * Write the <sheet> element.
1583 */
1584 STATIC void
_write_sheet(lxw_workbook * self,const char * name,uint32_t sheet_id,uint8_t hidden)1585 _write_sheet(lxw_workbook *self, const char *name, uint32_t sheet_id,
1586 uint8_t hidden)
1587 {
1588 struct xml_attribute_list attributes;
1589 struct xml_attribute *attribute;
1590 char r_id[LXW_MAX_ATTRIBUTE_LENGTH] = "rId1";
1591
1592 lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", sheet_id);
1593
1594 LXW_INIT_ATTRIBUTES();
1595 LXW_PUSH_ATTRIBUTES_STR("name", name);
1596 LXW_PUSH_ATTRIBUTES_INT("sheetId", sheet_id);
1597
1598 if (hidden)
1599 LXW_PUSH_ATTRIBUTES_STR("state", "hidden");
1600
1601 LXW_PUSH_ATTRIBUTES_STR("r:id", r_id);
1602
1603 lxw_xml_empty_tag(self->file, "sheet", &attributes);
1604
1605 LXW_FREE_ATTRIBUTES();
1606 }
1607
1608 /*
1609 * Write the <sheets> element.
1610 */
1611 STATIC void
_write_sheets(lxw_workbook * self)1612 _write_sheets(lxw_workbook *self)
1613 {
1614 lxw_sheet *sheet;
1615 lxw_worksheet *worksheet;
1616 lxw_chartsheet *chartsheet;
1617
1618 lxw_xml_start_tag(self->file, "sheets", NULL);
1619
1620 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
1621 if (sheet->is_chartsheet) {
1622 chartsheet = sheet->u.chartsheet;
1623 _write_sheet(self, chartsheet->name, chartsheet->index + 1,
1624 chartsheet->hidden);
1625 }
1626 else {
1627 worksheet = sheet->u.worksheet;
1628 _write_sheet(self, worksheet->name, worksheet->index + 1,
1629 worksheet->hidden);
1630 }
1631 }
1632
1633 lxw_xml_end_tag(self->file, "sheets");
1634 }
1635
1636 /*
1637 * Write the <calcPr> element.
1638 */
1639 STATIC void
_write_calc_pr(lxw_workbook * self)1640 _write_calc_pr(lxw_workbook *self)
1641 {
1642 struct xml_attribute_list attributes;
1643 struct xml_attribute *attribute;
1644
1645 LXW_INIT_ATTRIBUTES();
1646 LXW_PUSH_ATTRIBUTES_STR("calcId", "124519");
1647 LXW_PUSH_ATTRIBUTES_STR("fullCalcOnLoad", "1");
1648
1649 lxw_xml_empty_tag(self->file, "calcPr", &attributes);
1650
1651 LXW_FREE_ATTRIBUTES();
1652 }
1653
1654 /*
1655 * Write the <definedName> element.
1656 */
1657 STATIC void
_write_defined_name(lxw_workbook * self,lxw_defined_name * defined_name)1658 _write_defined_name(lxw_workbook *self, lxw_defined_name *defined_name)
1659 {
1660 struct xml_attribute_list attributes;
1661 struct xml_attribute *attribute;
1662
1663 LXW_INIT_ATTRIBUTES();
1664 LXW_PUSH_ATTRIBUTES_STR("name", defined_name->name);
1665
1666 if (defined_name->index != -1)
1667 LXW_PUSH_ATTRIBUTES_INT("localSheetId", defined_name->index);
1668
1669 if (defined_name->hidden)
1670 LXW_PUSH_ATTRIBUTES_INT("hidden", 1);
1671
1672 lxw_xml_data_element(self->file, "definedName", defined_name->formula,
1673 &attributes);
1674
1675 LXW_FREE_ATTRIBUTES();
1676 }
1677
1678 STATIC void
_write_defined_names(lxw_workbook * self)1679 _write_defined_names(lxw_workbook *self)
1680 {
1681 lxw_defined_name *defined_name;
1682
1683 if (TAILQ_EMPTY(self->defined_names))
1684 return;
1685
1686 lxw_xml_start_tag(self->file, "definedNames", NULL);
1687
1688 TAILQ_FOREACH(defined_name, self->defined_names, list_pointers) {
1689 _write_defined_name(self, defined_name);
1690 }
1691
1692 lxw_xml_end_tag(self->file, "definedNames");
1693 }
1694
1695 /*****************************************************************************
1696 *
1697 * XML file assembly functions.
1698 *
1699 ****************************************************************************/
1700
1701 /*
1702 * Assemble and write the XML file.
1703 */
1704 void
lxw_workbook_assemble_xml_file(lxw_workbook * self)1705 lxw_workbook_assemble_xml_file(lxw_workbook *self)
1706 {
1707 /* Prepare workbook and sub-objects for writing. */
1708 _prepare_workbook(self);
1709
1710 /* Write the XML declaration. */
1711 _workbook_xml_declaration(self);
1712
1713 /* Write the root workbook element. */
1714 _write_workbook(self);
1715
1716 /* Write the XLSX file version. */
1717 _write_file_version(self);
1718
1719 /* Write the fileSharing element. */
1720 _workbook_write_file_sharing(self);
1721
1722 /* Write the workbook properties. */
1723 _write_workbook_pr(self);
1724
1725 /* Write the workbook view properties. */
1726 _write_book_views(self);
1727
1728 /* Write the worksheet names and ids. */
1729 _write_sheets(self);
1730
1731 /* Write the workbook defined names. */
1732 _write_defined_names(self);
1733
1734 /* Write the workbook calculation properties. */
1735 _write_calc_pr(self);
1736
1737 /* Close the workbook tag. */
1738 lxw_xml_end_tag(self->file, "workbook");
1739 }
1740
1741 /*****************************************************************************
1742 *
1743 * Public functions.
1744 *
1745 ****************************************************************************/
1746
1747 /*
1748 * Create a new workbook object.
1749 */
1750 lxw_workbook *
workbook_new(const char * filename)1751 workbook_new(const char *filename)
1752 {
1753 return workbook_new_opt(filename, NULL);
1754 }
1755
1756 /* Deprecated function name for backwards compatibility. */
1757 lxw_workbook *
new_workbook(const char * filename)1758 new_workbook(const char *filename)
1759 {
1760 return workbook_new_opt(filename, NULL);
1761 }
1762
1763 /* Deprecated function name for backwards compatibility. */
1764 lxw_workbook *
new_workbook_opt(const char * filename,lxw_workbook_options * options)1765 new_workbook_opt(const char *filename, lxw_workbook_options *options)
1766 {
1767 return workbook_new_opt(filename, options);
1768 }
1769
1770 /*
1771 * Create a new workbook object with options.
1772 */
1773 lxw_workbook *
workbook_new_opt(const char * filename,lxw_workbook_options * options)1774 workbook_new_opt(const char *filename, lxw_workbook_options *options)
1775 {
1776 lxw_format *format;
1777 lxw_workbook *workbook;
1778
1779 /* Create the workbook object. */
1780 workbook = calloc(1, sizeof(lxw_workbook));
1781 GOTO_LABEL_ON_MEM_ERROR(workbook, mem_error);
1782 workbook->filename = lxw_strdup(filename);
1783
1784 /* Add the sheets list. */
1785 workbook->sheets = calloc(1, sizeof(struct lxw_sheets));
1786 GOTO_LABEL_ON_MEM_ERROR(workbook->sheets, mem_error);
1787 STAILQ_INIT(workbook->sheets);
1788
1789 /* Add the worksheets list. */
1790 workbook->worksheets = calloc(1, sizeof(struct lxw_worksheets));
1791 GOTO_LABEL_ON_MEM_ERROR(workbook->worksheets, mem_error);
1792 STAILQ_INIT(workbook->worksheets);
1793
1794 /* Add the chartsheets list. */
1795 workbook->chartsheets = calloc(1, sizeof(struct lxw_chartsheets));
1796 GOTO_LABEL_ON_MEM_ERROR(workbook->chartsheets, mem_error);
1797 STAILQ_INIT(workbook->chartsheets);
1798
1799 /* Add the worksheet names tree. */
1800 workbook->worksheet_names = calloc(1, sizeof(struct lxw_worksheet_names));
1801 GOTO_LABEL_ON_MEM_ERROR(workbook->worksheet_names, mem_error);
1802 RB_INIT(workbook->worksheet_names);
1803
1804 /* Add the chartsheet names tree. */
1805 workbook->chartsheet_names = calloc(1,
1806 sizeof(struct lxw_chartsheet_names));
1807 GOTO_LABEL_ON_MEM_ERROR(workbook->chartsheet_names, mem_error);
1808 RB_INIT(workbook->chartsheet_names);
1809
1810 /* Add the image MD5 tree. */
1811 workbook->image_md5s = calloc(1, sizeof(struct lxw_image_md5s));
1812 GOTO_LABEL_ON_MEM_ERROR(workbook->image_md5s, mem_error);
1813 RB_INIT(workbook->image_md5s);
1814
1815 /* Add the header image MD5 tree. */
1816 workbook->header_image_md5s = calloc(1, sizeof(struct lxw_image_md5s));
1817 GOTO_LABEL_ON_MEM_ERROR(workbook->header_image_md5s, mem_error);
1818 RB_INIT(workbook->header_image_md5s);
1819
1820 /* Add the background image MD5 tree. */
1821 workbook->background_md5s = calloc(1, sizeof(struct lxw_image_md5s));
1822 GOTO_LABEL_ON_MEM_ERROR(workbook->background_md5s, mem_error);
1823 RB_INIT(workbook->background_md5s);
1824
1825 /* Add the charts list. */
1826 workbook->charts = calloc(1, sizeof(struct lxw_charts));
1827 GOTO_LABEL_ON_MEM_ERROR(workbook->charts, mem_error);
1828 STAILQ_INIT(workbook->charts);
1829
1830 /* Add the ordered charts list to track chart insertion order. */
1831 workbook->ordered_charts = calloc(1, sizeof(struct lxw_charts));
1832 GOTO_LABEL_ON_MEM_ERROR(workbook->ordered_charts, mem_error);
1833 STAILQ_INIT(workbook->ordered_charts);
1834
1835 /* Add the formats list. */
1836 workbook->formats = calloc(1, sizeof(struct lxw_formats));
1837 GOTO_LABEL_ON_MEM_ERROR(workbook->formats, mem_error);
1838 STAILQ_INIT(workbook->formats);
1839
1840 /* Add the defined_names list. */
1841 workbook->defined_names = calloc(1, sizeof(struct lxw_defined_names));
1842 GOTO_LABEL_ON_MEM_ERROR(workbook->defined_names, mem_error);
1843 TAILQ_INIT(workbook->defined_names);
1844
1845 /* Add the shared strings table. */
1846 workbook->sst = lxw_sst_new();
1847 GOTO_LABEL_ON_MEM_ERROR(workbook->sst, mem_error);
1848
1849 /* Add the default workbook properties. */
1850 workbook->properties = calloc(1, sizeof(lxw_doc_properties));
1851 GOTO_LABEL_ON_MEM_ERROR(workbook->properties, mem_error);
1852
1853 /* Add a hash table to track format indices. */
1854 workbook->used_xf_formats = lxw_hash_new(128, 1, 0);
1855 GOTO_LABEL_ON_MEM_ERROR(workbook->used_xf_formats, mem_error);
1856
1857 /* Add a hash table to track format indices. */
1858 workbook->used_dxf_formats = lxw_hash_new(128, 1, 0);
1859 GOTO_LABEL_ON_MEM_ERROR(workbook->used_dxf_formats, mem_error);
1860
1861 /* Add the worksheets list. */
1862 workbook->custom_properties =
1863 calloc(1, sizeof(struct lxw_custom_properties));
1864 GOTO_LABEL_ON_MEM_ERROR(workbook->custom_properties, mem_error);
1865 STAILQ_INIT(workbook->custom_properties);
1866
1867 /* Add the default cell format. */
1868 format = workbook_add_format(workbook);
1869 GOTO_LABEL_ON_MEM_ERROR(format, mem_error);
1870
1871 /* Initialize its index. */
1872 lxw_format_get_xf_index(format);
1873
1874 /* Add the default hyperlink format. */
1875 format = workbook_add_format(workbook);
1876 GOTO_LABEL_ON_MEM_ERROR(format, mem_error);
1877 format_set_hyperlink(format);
1878 workbook->default_url_format = format;
1879
1880 if (options) {
1881 workbook->options.constant_memory = options->constant_memory;
1882 workbook->options.tmpdir = lxw_strdup(options->tmpdir);
1883 workbook->options.use_zip64 = options->use_zip64;
1884 }
1885
1886 workbook->max_url_length = 2079;
1887
1888 return workbook;
1889
1890 mem_error:
1891 lxw_workbook_free(workbook);
1892 workbook = NULL;
1893 return NULL;
1894 }
1895
1896 /*
1897 * Add a new worksheet to the Excel workbook.
1898 */
1899 lxw_worksheet *
workbook_add_worksheet(lxw_workbook * self,const char * sheetname)1900 workbook_add_worksheet(lxw_workbook *self, const char *sheetname)
1901 {
1902 lxw_sheet *sheet = NULL;
1903 lxw_worksheet *worksheet = NULL;
1904 lxw_worksheet_name *worksheet_name = NULL;
1905 lxw_error error;
1906 lxw_worksheet_init_data init_data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1907 char *new_name = NULL;
1908
1909 if (sheetname) {
1910 /* Use the user supplied name. */
1911 init_data.name = lxw_strdup(sheetname);
1912 init_data.quoted_name = lxw_quote_sheetname(sheetname);
1913 }
1914 else {
1915 /* Use the default SheetN name. */
1916 new_name = malloc(LXW_MAX_SHEETNAME_LENGTH);
1917 GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error);
1918
1919 lxw_snprintf(new_name, LXW_MAX_SHEETNAME_LENGTH, "Sheet%d",
1920 self->num_worksheets + 1);
1921 init_data.name = new_name;
1922 init_data.quoted_name = lxw_strdup(new_name);
1923 }
1924
1925 /* Check that the worksheet name is valid. */
1926 error = workbook_validate_sheet_name(self, init_data.name);
1927 if (error) {
1928 LXW_WARN_FORMAT2("workbook_add_worksheet(): worksheet name '%s' has "
1929 "error: %s", init_data.name, lxw_strerror(error));
1930 goto mem_error;
1931 }
1932
1933 /* Create a struct to find/store the worksheet name/pointer. */
1934 worksheet_name = calloc(1, sizeof(struct lxw_worksheet_name));
1935 GOTO_LABEL_ON_MEM_ERROR(worksheet_name, mem_error);
1936
1937 /* Initialize the metadata to pass to the worksheet. */
1938 init_data.hidden = 0;
1939 init_data.index = self->num_sheets;
1940 init_data.sst = self->sst;
1941 init_data.optimize = self->options.constant_memory;
1942 init_data.active_sheet = &self->active_sheet;
1943 init_data.first_sheet = &self->first_sheet;
1944 init_data.tmpdir = self->options.tmpdir;
1945 init_data.default_url_format = self->default_url_format;
1946 init_data.max_url_length = self->max_url_length;
1947
1948 /* Create a new worksheet object. */
1949 worksheet = lxw_worksheet_new(&init_data);
1950 GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error);
1951
1952 /* Add it to the worksheet list. */
1953 self->num_worksheets++;
1954 STAILQ_INSERT_TAIL(self->worksheets, worksheet, list_pointers);
1955
1956 /* Create a new sheet object. */
1957 sheet = calloc(1, sizeof(lxw_sheet));
1958 GOTO_LABEL_ON_MEM_ERROR(sheet, mem_error);
1959 sheet->u.worksheet = worksheet;
1960
1961 /* Add it to the worksheet list. */
1962 self->num_sheets++;
1963 STAILQ_INSERT_TAIL(self->sheets, sheet, list_pointers);
1964
1965 /* Store the worksheet so we can look it up by name. */
1966 worksheet_name->name = init_data.name;
1967 worksheet_name->worksheet = worksheet;
1968 RB_INSERT(lxw_worksheet_names, self->worksheet_names, worksheet_name);
1969
1970 return worksheet;
1971
1972 mem_error:
1973 free(init_data.name);
1974 free(init_data.quoted_name);
1975 free(worksheet_name);
1976 free(worksheet);
1977 return NULL;
1978 }
1979
1980 /*
1981 * Add a new chartsheet to the Excel workbook.
1982 */
1983 lxw_chartsheet *
workbook_add_chartsheet(lxw_workbook * self,const char * sheetname)1984 workbook_add_chartsheet(lxw_workbook *self, const char *sheetname)
1985 {
1986 lxw_sheet *sheet = NULL;
1987 lxw_chartsheet *chartsheet = NULL;
1988 lxw_chartsheet_name *chartsheet_name = NULL;
1989 lxw_error error;
1990 lxw_worksheet_init_data init_data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1991 char *new_name = NULL;
1992
1993 if (sheetname) {
1994 /* Use the user supplied name. */
1995 init_data.name = lxw_strdup(sheetname);
1996 init_data.quoted_name = lxw_quote_sheetname(sheetname);
1997 }
1998 else {
1999 /* Use the default SheetN name. */
2000 new_name = malloc(LXW_MAX_SHEETNAME_LENGTH);
2001 GOTO_LABEL_ON_MEM_ERROR(new_name, mem_error);
2002
2003 lxw_snprintf(new_name, LXW_MAX_SHEETNAME_LENGTH, "Chart%d",
2004 self->num_chartsheets + 1);
2005 init_data.name = new_name;
2006 init_data.quoted_name = lxw_strdup(new_name);
2007 }
2008
2009 /* Check that the chartsheet name is valid. */
2010 error = workbook_validate_sheet_name(self, init_data.name);
2011 if (error) {
2012 LXW_WARN_FORMAT2
2013 ("workbook_add_chartsheet(): chartsheet name '%s' has "
2014 "error: %s", init_data.name, lxw_strerror(error));
2015 goto mem_error;
2016 }
2017
2018 /* Create a struct to find/store the chartsheet name/pointer. */
2019 chartsheet_name = calloc(1, sizeof(struct lxw_chartsheet_name));
2020 GOTO_LABEL_ON_MEM_ERROR(chartsheet_name, mem_error);
2021
2022 /* Initialize the metadata to pass to the chartsheet. */
2023 init_data.hidden = 0;
2024 init_data.index = self->num_sheets;
2025 init_data.sst = self->sst;
2026 init_data.optimize = self->options.constant_memory;
2027 init_data.active_sheet = &self->active_sheet;
2028 init_data.first_sheet = &self->first_sheet;
2029 init_data.tmpdir = self->options.tmpdir;
2030
2031 /* Create a new chartsheet object. */
2032 chartsheet = lxw_chartsheet_new(&init_data);
2033 GOTO_LABEL_ON_MEM_ERROR(chartsheet, mem_error);
2034
2035 /* Add it to the chartsheet list. */
2036 self->num_chartsheets++;
2037 STAILQ_INSERT_TAIL(self->chartsheets, chartsheet, list_pointers);
2038
2039 /* Create a new sheet object. */
2040 sheet = calloc(1, sizeof(lxw_sheet));
2041 GOTO_LABEL_ON_MEM_ERROR(sheet, mem_error);
2042 sheet->is_chartsheet = LXW_TRUE;
2043 sheet->u.chartsheet = chartsheet;
2044
2045 /* Add it to the chartsheet list. */
2046 self->num_sheets++;
2047 STAILQ_INSERT_TAIL(self->sheets, sheet, list_pointers);
2048
2049 /* Store the chartsheet so we can look it up by name. */
2050 chartsheet_name->name = init_data.name;
2051 chartsheet_name->chartsheet = chartsheet;
2052 RB_INSERT(lxw_chartsheet_names, self->chartsheet_names, chartsheet_name);
2053
2054 return chartsheet;
2055
2056 mem_error:
2057 free(init_data.name);
2058 free(init_data.quoted_name);
2059 free(chartsheet_name);
2060 free(chartsheet);
2061 return NULL;
2062 }
2063
2064 /*
2065 * Add a new chart to the Excel workbook.
2066 */
2067 lxw_chart *
workbook_add_chart(lxw_workbook * self,uint8_t type)2068 workbook_add_chart(lxw_workbook *self, uint8_t type)
2069 {
2070 lxw_chart *chart;
2071
2072 /* Create a new chart object. */
2073 chart = lxw_chart_new(type);
2074
2075 if (chart)
2076 STAILQ_INSERT_TAIL(self->charts, chart, list_pointers);
2077
2078 return chart;
2079 }
2080
2081 /*
2082 * Add a new format to the Excel workbook.
2083 */
2084 lxw_format *
workbook_add_format(lxw_workbook * self)2085 workbook_add_format(lxw_workbook *self)
2086 {
2087 /* Create a new format object. */
2088 lxw_format *format = lxw_format_new();
2089 RETURN_ON_MEM_ERROR(format, NULL);
2090
2091 format->xf_format_indices = self->used_xf_formats;
2092 format->dxf_format_indices = self->used_dxf_formats;
2093 format->num_xf_formats = &self->num_xf_formats;
2094
2095 STAILQ_INSERT_TAIL(self->formats, format, list_pointers);
2096
2097 return format;
2098 }
2099
2100 /*
2101 * Call finalization code and close file.
2102 */
2103 lxw_error
workbook_close(lxw_workbook * self)2104 workbook_close(lxw_workbook *self)
2105 {
2106 lxw_sheet *sheet = NULL;
2107 lxw_worksheet *worksheet = NULL;
2108 lxw_packager *packager = NULL;
2109 lxw_error error = LXW_NO_ERROR;
2110 char codename[LXW_MAX_SHEETNAME_LENGTH] = { 0 };
2111
2112 /* Add a default worksheet if non have been added. */
2113 if (!self->num_sheets)
2114 workbook_add_worksheet(self, NULL);
2115
2116 /* Ensure that at least one worksheet has been selected. */
2117 if (self->active_sheet == 0) {
2118 sheet = STAILQ_FIRST(self->sheets);
2119 if (!sheet->is_chartsheet) {
2120 worksheet = sheet->u.worksheet;
2121 worksheet->selected = 1;
2122 worksheet->hidden = 0;
2123 }
2124 }
2125
2126 /* Set the active sheet and check if a metadata file is needed. */
2127 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
2128 if (sheet->is_chartsheet)
2129 continue;
2130 else
2131 worksheet = sheet->u.worksheet;
2132
2133 if (worksheet->index == self->active_sheet)
2134 worksheet->active = 1;
2135
2136 if (worksheet->has_dynamic_arrays)
2137 self->has_metadata = LXW_TRUE;
2138 }
2139
2140 /* Set workbook and worksheet VBA codenames if a macro has been added. */
2141 if (self->vba_project) {
2142 if (!self->vba_codename)
2143 workbook_set_vba_name(self, "ThisWorkbook");
2144
2145 STAILQ_FOREACH(sheet, self->sheets, list_pointers) {
2146 if (sheet->is_chartsheet)
2147 continue;
2148 else
2149 worksheet = sheet->u.worksheet;
2150
2151 if (!worksheet->vba_codename) {
2152 lxw_snprintf(codename, LXW_MAX_SHEETNAME_LENGTH, "Sheet%d",
2153 worksheet->index + 1);
2154
2155 worksheet_set_vba_name(worksheet, codename);
2156 }
2157 }
2158 }
2159
2160 /* Prepare the worksheet VML elements such as comments. */
2161 _prepare_vml(self);
2162
2163 /* Set the defined names for the worksheets such as Print Titles. */
2164 _prepare_defined_names(self);
2165
2166 /* Prepare the drawings, charts and images. */
2167 _prepare_drawings(self);
2168
2169 /* Add cached data to charts. */
2170 _add_chart_cache_data(self);
2171
2172 /* Set the table ids for the worksheet tables. */
2173 _prepare_tables(self);
2174
2175 /* Create a packager object to assemble sub-elements into a zip file. */
2176 packager = lxw_packager_new(self->filename,
2177 self->options.tmpdir,
2178 self->options.use_zip64);
2179
2180 /* If the packager fails it is generally due to a zip permission error. */
2181 if (packager == NULL) {
2182 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2183 "Error creating '%s'. "
2184 "System error = %s\n", self->filename, strerror(errno));
2185
2186 error = LXW_ERROR_CREATING_XLSX_FILE;
2187 goto mem_error;
2188 }
2189
2190 /* Set the workbook object in the packager. */
2191 packager->workbook = self;
2192
2193 /* Assemble all the sub-files in the xlsx package. */
2194 error = lxw_create_package(packager);
2195
2196 /* Error and non-error conditions fall through to the cleanup code. */
2197 if (error == LXW_ERROR_CREATING_TMPFILE) {
2198 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2199 "Error creating tmpfile(s) to assemble '%s'. "
2200 "System error = %s\n", self->filename, strerror(errno));
2201 }
2202
2203 /* If LXW_ERROR_ZIP_FILE_OPERATION then errno is set by zip. */
2204 if (error == LXW_ERROR_ZIP_FILE_OPERATION) {
2205 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2206 "Zip ZIP_ERRNO error while creating xlsx file '%s'. "
2207 "System error = %s\n", self->filename, strerror(errno));
2208 }
2209
2210 /* If LXW_ERROR_ZIP_PARAMETER_ERROR then errno is set by zip. */
2211 if (error == LXW_ERROR_ZIP_PARAMETER_ERROR) {
2212 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2213 "Zip ZIP_PARAMERROR error while creating xlsx file '%s'. "
2214 "System error = %s\n", self->filename, strerror(errno));
2215 }
2216
2217 /* If LXW_ERROR_ZIP_BAD_ZIP_FILE then errno is set by zip. */
2218 if (error == LXW_ERROR_ZIP_BAD_ZIP_FILE) {
2219 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2220 "Zip ZIP_BADZIPFILE error while creating xlsx file '%s'. "
2221 "This may require the use_zip64 option for large files. "
2222 "System error = %s\n", self->filename, strerror(errno));
2223 }
2224
2225 /* If LXW_ERROR_ZIP_INTERNAL_ERROR then errno is set by zip. */
2226 if (error == LXW_ERROR_ZIP_INTERNAL_ERROR) {
2227 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2228 "Zip ZIP_INTERNALERROR error while creating xlsx file '%s'. "
2229 "System error = %s\n", self->filename, strerror(errno));
2230 }
2231
2232 /* The next 2 error conditions don't set errno. */
2233 if (error == LXW_ERROR_ZIP_FILE_ADD) {
2234 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2235 "Zip error adding file to xlsx file '%s'.\n",
2236 self->filename);
2237 }
2238
2239 if (error == LXW_ERROR_ZIP_CLOSE) {
2240 LXW_PRINTF(LXW_STDERR "[ERROR] workbook_close(): "
2241 "Zip error closing xlsx file '%s'.\n", self->filename);
2242 }
2243
2244 mem_error:
2245 lxw_packager_free(packager);
2246 lxw_workbook_free(self);
2247 return error;
2248 }
2249
2250 /*
2251 * Create a defined name in Excel. We handle global/workbook level names and
2252 * local/worksheet names.
2253 */
2254 lxw_error
workbook_define_name(lxw_workbook * self,const char * name,const char * formula)2255 workbook_define_name(lxw_workbook *self, const char *name,
2256 const char *formula)
2257 {
2258 return _store_defined_name(self, name, NULL, formula, -1, LXW_FALSE);
2259 }
2260
2261 /*
2262 * Set the document properties such as Title, Author etc.
2263 */
2264 lxw_error
workbook_set_properties(lxw_workbook * self,lxw_doc_properties * user_props)2265 workbook_set_properties(lxw_workbook *self, lxw_doc_properties *user_props)
2266 {
2267 lxw_doc_properties *doc_props;
2268
2269 /* Free any existing properties. */
2270 _free_doc_properties(self->properties);
2271
2272 doc_props = calloc(1, sizeof(lxw_doc_properties));
2273 GOTO_LABEL_ON_MEM_ERROR(doc_props, mem_error);
2274
2275 /* Copy the user properties to an internal structure. */
2276 if (user_props->title) {
2277 doc_props->title = lxw_strdup(user_props->title);
2278 GOTO_LABEL_ON_MEM_ERROR(doc_props->title, mem_error);
2279 }
2280
2281 if (user_props->subject) {
2282 doc_props->subject = lxw_strdup(user_props->subject);
2283 GOTO_LABEL_ON_MEM_ERROR(doc_props->subject, mem_error);
2284 }
2285
2286 if (user_props->author) {
2287 doc_props->author = lxw_strdup(user_props->author);
2288 GOTO_LABEL_ON_MEM_ERROR(doc_props->author, mem_error);
2289 }
2290
2291 if (user_props->manager) {
2292 doc_props->manager = lxw_strdup(user_props->manager);
2293 GOTO_LABEL_ON_MEM_ERROR(doc_props->manager, mem_error);
2294 }
2295
2296 if (user_props->company) {
2297 doc_props->company = lxw_strdup(user_props->company);
2298 GOTO_LABEL_ON_MEM_ERROR(doc_props->company, mem_error);
2299 }
2300
2301 if (user_props->category) {
2302 doc_props->category = lxw_strdup(user_props->category);
2303 GOTO_LABEL_ON_MEM_ERROR(doc_props->category, mem_error);
2304 }
2305
2306 if (user_props->keywords) {
2307 doc_props->keywords = lxw_strdup(user_props->keywords);
2308 GOTO_LABEL_ON_MEM_ERROR(doc_props->keywords, mem_error);
2309 }
2310
2311 if (user_props->comments) {
2312 doc_props->comments = lxw_strdup(user_props->comments);
2313 GOTO_LABEL_ON_MEM_ERROR(doc_props->comments, mem_error);
2314 }
2315
2316 if (user_props->status) {
2317 doc_props->status = lxw_strdup(user_props->status);
2318 GOTO_LABEL_ON_MEM_ERROR(doc_props->status, mem_error);
2319 }
2320
2321 if (user_props->hyperlink_base) {
2322 doc_props->hyperlink_base = lxw_strdup(user_props->hyperlink_base);
2323 GOTO_LABEL_ON_MEM_ERROR(doc_props->hyperlink_base, mem_error);
2324 }
2325
2326 doc_props->created = user_props->created;
2327
2328 self->properties = doc_props;
2329
2330 return LXW_NO_ERROR;
2331
2332 mem_error:
2333 _free_doc_properties(doc_props);
2334 return LXW_ERROR_MEMORY_MALLOC_FAILED;
2335 }
2336
2337 /*
2338 * Set a string custom document property.
2339 */
2340 lxw_error
workbook_set_custom_property_string(lxw_workbook * self,const char * name,const char * value)2341 workbook_set_custom_property_string(lxw_workbook *self, const char *name,
2342 const char *value)
2343 {
2344 lxw_custom_property *custom_property;
2345
2346 if (!name) {
2347 LXW_WARN_FORMAT("workbook_set_custom_property_string(): "
2348 "parameter 'name' cannot be NULL.");
2349 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2350 }
2351
2352 if (!value) {
2353 LXW_WARN_FORMAT("workbook_set_custom_property_string(): "
2354 "parameter 'value' cannot be NULL.");
2355 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2356 }
2357
2358 if (lxw_utf8_strlen(name) > 255) {
2359 LXW_WARN_FORMAT("workbook_set_custom_property_string(): parameter "
2360 "'name' exceeds Excel length limit of 255.");
2361 return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
2362 }
2363
2364 if (lxw_utf8_strlen(value) > 255) {
2365 LXW_WARN_FORMAT("workbook_set_custom_property_string(): parameter "
2366 "'value' exceeds Excel length limit of 255.");
2367 return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
2368 }
2369
2370 /* Create a struct to hold the custom property. */
2371 custom_property = calloc(1, sizeof(struct lxw_custom_property));
2372 RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
2373
2374 custom_property->name = lxw_strdup(name);
2375 custom_property->u.string = lxw_strdup(value);
2376 custom_property->type = LXW_CUSTOM_STRING;
2377
2378 STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
2379 list_pointers);
2380
2381 return LXW_NO_ERROR;
2382 }
2383
2384 /*
2385 * Set a double number custom document property.
2386 */
2387 lxw_error
workbook_set_custom_property_number(lxw_workbook * self,const char * name,double value)2388 workbook_set_custom_property_number(lxw_workbook *self, const char *name,
2389 double value)
2390 {
2391 lxw_custom_property *custom_property;
2392
2393 if (!name) {
2394 LXW_WARN_FORMAT("workbook_set_custom_property_number(): parameter "
2395 "'name' cannot be NULL.");
2396 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2397 }
2398
2399 if (lxw_utf8_strlen(name) > 255) {
2400 LXW_WARN_FORMAT("workbook_set_custom_property_number(): parameter "
2401 "'name' exceeds Excel length limit of 255.");
2402 return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
2403 }
2404
2405 /* Create a struct to hold the custom property. */
2406 custom_property = calloc(1, sizeof(struct lxw_custom_property));
2407 RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
2408
2409 custom_property->name = lxw_strdup(name);
2410 custom_property->u.number = value;
2411 custom_property->type = LXW_CUSTOM_DOUBLE;
2412
2413 STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
2414 list_pointers);
2415
2416 return LXW_NO_ERROR;
2417 }
2418
2419 /*
2420 * Set a integer number custom document property.
2421 */
2422 lxw_error
workbook_set_custom_property_integer(lxw_workbook * self,const char * name,int32_t value)2423 workbook_set_custom_property_integer(lxw_workbook *self, const char *name,
2424 int32_t value)
2425 {
2426 lxw_custom_property *custom_property;
2427
2428 if (!name) {
2429 LXW_WARN_FORMAT("workbook_set_custom_property_integer(): parameter "
2430 "'name' cannot be NULL.");
2431 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2432 }
2433
2434 if (strlen(name) > 255) {
2435 LXW_WARN_FORMAT("workbook_set_custom_property_integer(): parameter "
2436 "'name' exceeds Excel length limit of 255.");
2437 return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
2438 }
2439
2440 /* Create a struct to hold the custom property. */
2441 custom_property = calloc(1, sizeof(struct lxw_custom_property));
2442 RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
2443
2444 custom_property->name = lxw_strdup(name);
2445 custom_property->u.integer = value;
2446 custom_property->type = LXW_CUSTOM_INTEGER;
2447
2448 STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
2449 list_pointers);
2450
2451 return LXW_NO_ERROR;
2452 }
2453
2454 /*
2455 * Set a boolean custom document property.
2456 */
2457 lxw_error
workbook_set_custom_property_boolean(lxw_workbook * self,const char * name,uint8_t value)2458 workbook_set_custom_property_boolean(lxw_workbook *self, const char *name,
2459 uint8_t value)
2460 {
2461 lxw_custom_property *custom_property;
2462
2463 if (!name) {
2464 LXW_WARN_FORMAT("workbook_set_custom_property_boolean(): parameter "
2465 "'name' cannot be NULL.");
2466 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2467 }
2468
2469 if (lxw_utf8_strlen(name) > 255) {
2470 LXW_WARN_FORMAT("workbook_set_custom_property_boolean(): parameter "
2471 "'name' exceeds Excel length limit of 255.");
2472 return LXW_ERROR_255_STRING_LENGTH_EXCEEDED;
2473 }
2474
2475 /* Create a struct to hold the custom property. */
2476 custom_property = calloc(1, sizeof(struct lxw_custom_property));
2477 RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
2478
2479 custom_property->name = lxw_strdup(name);
2480 custom_property->u.boolean = value;
2481 custom_property->type = LXW_CUSTOM_BOOLEAN;
2482
2483 STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
2484 list_pointers);
2485
2486 return LXW_NO_ERROR;
2487 }
2488
2489 /*
2490 * Set a datetime custom document property.
2491 */
2492 lxw_error
workbook_set_custom_property_datetime(lxw_workbook * self,const char * name,lxw_datetime * datetime)2493 workbook_set_custom_property_datetime(lxw_workbook *self, const char *name,
2494 lxw_datetime *datetime)
2495 {
2496 lxw_custom_property *custom_property;
2497
2498 if (!name) {
2499 LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter "
2500 "'name' cannot be NULL.");
2501 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2502 }
2503
2504 if (lxw_utf8_strlen(name) > 255) {
2505 LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter "
2506 "'name' exceeds Excel length limit of 255.");
2507 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2508 }
2509
2510 if (!datetime) {
2511 LXW_WARN_FORMAT("workbook_set_custom_property_datetime(): parameter "
2512 "'datetime' cannot be NULL.");
2513 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2514 }
2515
2516 /* Create a struct to hold the custom property. */
2517 custom_property = calloc(1, sizeof(struct lxw_custom_property));
2518 RETURN_ON_MEM_ERROR(custom_property, LXW_ERROR_MEMORY_MALLOC_FAILED);
2519
2520 custom_property->name = lxw_strdup(name);
2521
2522 memcpy(&custom_property->u.datetime, datetime, sizeof(lxw_datetime));
2523 custom_property->type = LXW_CUSTOM_DATETIME;
2524
2525 STAILQ_INSERT_TAIL(self->custom_properties, custom_property,
2526 list_pointers);
2527
2528 return LXW_NO_ERROR;
2529 }
2530
2531 /*
2532 * Get a worksheet object from its name.
2533 */
2534 lxw_worksheet *
workbook_get_worksheet_by_name(lxw_workbook * self,const char * name)2535 workbook_get_worksheet_by_name(lxw_workbook *self, const char *name)
2536 {
2537 lxw_worksheet_name worksheet_name;
2538 lxw_worksheet_name *found;
2539
2540 if (!name)
2541 return NULL;
2542
2543 worksheet_name.name = name;
2544 found = RB_FIND(lxw_worksheet_names,
2545 self->worksheet_names, &worksheet_name);
2546
2547 if (found)
2548 return found->worksheet;
2549 else
2550 return NULL;
2551 }
2552
2553 /*
2554 * Get a chartsheet object from its name.
2555 */
2556 lxw_chartsheet *
workbook_get_chartsheet_by_name(lxw_workbook * self,const char * name)2557 workbook_get_chartsheet_by_name(lxw_workbook *self, const char *name)
2558 {
2559 lxw_chartsheet_name chartsheet_name;
2560 lxw_chartsheet_name *found;
2561
2562 if (!name)
2563 return NULL;
2564
2565 chartsheet_name.name = name;
2566 found = RB_FIND(lxw_chartsheet_names,
2567 self->chartsheet_names, &chartsheet_name);
2568
2569 if (found)
2570 return found->chartsheet;
2571 else
2572 return NULL;
2573 }
2574
2575 /*
2576 * Get the default URL format.
2577 */
2578 lxw_format *
workbook_get_default_url_format(lxw_workbook * self)2579 workbook_get_default_url_format(lxw_workbook *self)
2580 {
2581 return self->default_url_format;
2582 }
2583
2584 /*
2585 * Unset the default URL format.
2586 */
2587 void
workbook_unset_default_url_format(lxw_workbook * self)2588 workbook_unset_default_url_format(lxw_workbook *self)
2589 {
2590 self->default_url_format->hyperlink = LXW_FALSE;
2591 self->default_url_format->xf_id = 0;
2592 self->default_url_format->underline = LXW_UNDERLINE_NONE;
2593 self->default_url_format->theme = 0;
2594 }
2595
2596 /*
2597 * Validate the worksheet name based on Excel's rules.
2598 */
2599 lxw_error
workbook_validate_sheet_name(lxw_workbook * self,const char * sheetname)2600 workbook_validate_sheet_name(lxw_workbook *self, const char *sheetname)
2601 {
2602 /* Check the UTF-8 length of the worksheet name. */
2603 if (lxw_utf8_strlen(sheetname) > LXW_SHEETNAME_MAX)
2604 return LXW_ERROR_SHEETNAME_LENGTH_EXCEEDED;
2605
2606 /* Check that the worksheet name doesn't contain invalid characters. */
2607 if (strpbrk(sheetname, "[]:*?/\\"))
2608 return LXW_ERROR_INVALID_SHEETNAME_CHARACTER;
2609
2610 /* Check that the worksheet doesn't start or end with an apostrophe. */
2611 if (sheetname[0] == '\'' || sheetname[strlen(sheetname) - 1] == '\'')
2612 return LXW_ERROR_SHEETNAME_START_END_APOSTROPHE;
2613
2614 /* Check if the worksheet name is already in use. */
2615 if (workbook_get_worksheet_by_name(self, sheetname))
2616 return LXW_ERROR_SHEETNAME_ALREADY_USED;
2617
2618 /* Check if the chartsheet name is already in use. */
2619 if (workbook_get_chartsheet_by_name(self, sheetname))
2620 return LXW_ERROR_SHEETNAME_ALREADY_USED;
2621
2622 return LXW_NO_ERROR;
2623 }
2624
2625 /*
2626 * Add a vbaProject binary to the Excel workbook.
2627 */
2628 lxw_error
workbook_add_vba_project(lxw_workbook * self,const char * filename)2629 workbook_add_vba_project(lxw_workbook *self, const char *filename)
2630 {
2631 FILE *filehandle;
2632
2633 if (!filename) {
2634 LXW_WARN("workbook_add_vba_project(): "
2635 "filename must be specified.");
2636 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2637 }
2638
2639 /* Check that the vbaProject file exists and can be opened. */
2640 filehandle = lxw_fopen(filename, "rb");
2641 if (!filehandle) {
2642 LXW_WARN_FORMAT1("workbook_add_vba_project(): "
2643 "file doesn't exist or can't be opened: %s.",
2644 filename);
2645 return LXW_ERROR_PARAMETER_VALIDATION;
2646 }
2647 fclose(filehandle);
2648
2649 self->vba_project = lxw_strdup(filename);
2650
2651 return LXW_NO_ERROR;
2652 }
2653
2654 /*
2655 * Set the VBA name for the workbook.
2656 */
2657 lxw_error
workbook_set_vba_name(lxw_workbook * self,const char * name)2658 workbook_set_vba_name(lxw_workbook *self, const char *name)
2659 {
2660 if (!name) {
2661 LXW_WARN("workbook_set_vba_name(): " "name must be specified.");
2662 return LXW_ERROR_NULL_PARAMETER_IGNORED;
2663 }
2664
2665 self->vba_codename = lxw_strdup(name);
2666
2667 return LXW_NO_ERROR;
2668 }
2669
2670 /*
2671 * Set the Excel "Read-only recommended" save option.
2672 */
2673 void
workbook_read_only_recommended(lxw_workbook * self)2674 workbook_read_only_recommended(lxw_workbook *self)
2675 {
2676 self->read_only = 2;
2677 }
2678