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