libxlsxwriter/src/worksheet.c in fast_excel-0.2.3 vs libxlsxwriter/src/worksheet.c in fast_excel-0.2.5

- old
+ new

@@ -1,11 +1,11 @@ /***************************************************************************** * worksheet - A library for creating Excel XLSX worksheet files. * * Used in conjunction with the libxlsxwriter library. * - * Copyright 2014-2017, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * Copyright 2014-2018, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. * */ #include <ctype.h> @@ -13,15 +13,17 @@ #include "xlsxwriter/worksheet.h" #include "xlsxwriter/format.h" #include "xlsxwriter/utility.h" #include "xlsxwriter/relationships.h" -#define LXW_STR_MAX 32767 -#define LXW_BUFFER_SIZE 4096 -#define LXW_PORTRAIT 1 -#define LXW_LANDSCAPE 0 -#define LXW_PRINT_ACROSS 1 +#define LXW_STR_MAX 32767 +#define LXW_BUFFER_SIZE 4096 +#define LXW_PORTRAIT 1 +#define LXW_LANDSCAPE 0 +#define LXW_PRINT_ACROSS 1 +#define LXW_VALIDATION_MAX_TITLE_LENGTH 32 +#define LXW_VALIDATION_MAX_STRING_LENGTH 255 /* * Forward declarations. */ STATIC void _worksheet_write_rows(lxw_worksheet *self); @@ -121,10 +123,15 @@ worksheet->selections = calloc(1, sizeof(struct lxw_selections)); GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error); STAILQ_INIT(worksheet->selections); + worksheet->data_validations = + calloc(1, sizeof(struct lxw_data_validations)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->data_validations, mem_error); + STAILQ_INIT(worksheet->data_validations); + worksheet->external_hyperlinks = calloc(1, sizeof(struct lxw_rel_tuples)); GOTO_LABEL_ON_MEM_ERROR(worksheet->external_hyperlinks, mem_error); STAILQ_INIT(worksheet->external_hyperlinks); worksheet->external_drawing_links = @@ -185,10 +192,13 @@ worksheet->print_options_changed = 0; worksheet->zoom = 100; worksheet->zoom_scale_normal = LXW_TRUE; worksheet->show_zeros = LXW_TRUE; worksheet->outline_on = LXW_TRUE; + worksheet->outline_style = LXW_TRUE; + worksheet->outline_below = LXW_TRUE; + worksheet->outline_right = LXW_FALSE; worksheet->tab_color = LXW_COLOR_UNSET; if (init_data) { worksheet->name = init_data->name; worksheet->quoted_name = init_data->quoted_name; @@ -267,10 +277,30 @@ free(image->tip); free(image); } /* + * Free a worksheet data_validation. + */ +STATIC void +_free_data_validation(lxw_data_validation *data_validation) +{ + if (!data_validation) + return; + + free(data_validation->value_formula); + free(data_validation->maximum_formula); + free(data_validation->input_title); + free(data_validation->input_message); + free(data_validation->error_title); + free(data_validation->error_message); + free(data_validation->minimum_formula); + + free(data_validation); +} + +/* * Free a worksheet object. */ void lxw_worksheet_free(lxw_worksheet *worksheet) { @@ -278,10 +308,11 @@ lxw_row *next_row; lxw_col_t col; lxw_merged_range *merged_range; lxw_image_options *image_options; lxw_selection *selection; + lxw_data_validation *data_validation; lxw_rel_tuple *relationship; if (!worksheet) return; @@ -307,11 +338,10 @@ free(worksheet->table); } if (worksheet->hyperlinks) { - for (row = RB_MIN(lxw_table_rows, worksheet->hyperlinks); row; row = next_row) { next_row = RB_NEXT(lxw_table_rows, worksheet->hyperlinks, row); RB_REMOVE(lxw_table_rows, worksheet->hyperlinks, row); @@ -359,10 +389,20 @@ } free(worksheet->selections); } + if (worksheet->data_validations) { + while (!STAILQ_EMPTY(worksheet->data_validations)) { + data_validation = STAILQ_FIRST(worksheet->data_validations); + STAILQ_REMOVE_HEAD(worksheet->data_validations, list_pointers); + _free_data_validation(data_validation); + } + + free(worksheet->data_validations); + } + /* TODO. Add function for freeing the relationship lists. */ while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) { relationship = STAILQ_FIRST(worksheet->external_hyperlinks); STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers); free(relationship->type); @@ -858,10 +898,65 @@ return forward_slash + 1; else return back_slash + 1; } +/* Function to count the total concatenated length of the strings in a + * validation list array, including commas. */ +size_t +_validation_list_length(char **list) +{ + uint8_t i = 0; + size_t length = 0; + + if (!list || !list[0]) + return 0; + + while (list[i] && length <= LXW_VALIDATION_MAX_STRING_LENGTH) { + /* Include commas in the length. */ + length += 1 + lxw_utf8_strlen(list[i]); + i++; + } + + /* Adjust the count for extraneous comma at end. */ + length--; + + return length; +} + +/* Function to convert an array of strings into a CSV string for data + * validation lists. */ +char * +_validation_list_to_csv(char **list) +{ + uint8_t i = 0; + char *str; + + /* Create a buffer for the concatenated, and quoted, string. */ + /* Add +3 for quotes and EOL. */ + str = calloc(1, LXW_VALIDATION_MAX_STRING_LENGTH + 3); + if (!str) + return NULL; + + /* Add the start quote and first element. */ + strcat(str, "\""); + strcat(str, list[0]); + + /* Add the other elements preceded by a comma. */ + i = 1; + while (list[i]) { + strcat(str, ","); + strcat(str, list[i]); + i++; + } + + /* Add the end quote. */ + strcat(str, "\""); + + return str; +} + /***************************************************************************** * * XML functions. * ****************************************************************************/ @@ -1370,10 +1465,16 @@ LXW_PUSH_ATTRIBUTES_STR("customHeight", "1"); if (self->default_row_zeroed) LXW_PUSH_ATTRIBUTES_STR("zeroHeight", "1"); + if (self->outline_row_level) + LXW_PUSH_ATTRIBUTES_INT("outlineLevelRow", self->outline_row_level); + + if (self->outline_col_level) + LXW_PUSH_ATTRIBUTES_INT("outlineLevelCol", self->outline_col_level); + lxw_xml_empty_tag(self->file, "sheetFormatPr", &attributes); LXW_FREE_ATTRIBUTES(); } @@ -1606,10 +1707,13 @@ LXW_PUSH_ATTRIBUTES_STR("hidden", "1"); if (height != LXW_DEF_ROW_HEIGHT) LXW_PUSH_ATTRIBUTES_STR("customHeight", "1"); + if (row->level) + LXW_PUSH_ATTRIBUTES_INT("outlineLevel", row->level); + if (row->collapsed) LXW_PUSH_ATTRIBUTES_STR("collapsed", "1"); if (!row->data_changed) lxw_xml_empty_tag(self->file, "row", &attributes); @@ -2181,12 +2285,11 @@ /* Read back 2 bytes to the end of the initial 0xFFD8 marker. */ fseek_err = fseek(stream, -2, SEEK_CUR); if (fseek_err) goto file_error; - /* Search through the image data to read the height and width in the */ - /* 0xFFC0/C2 element. Also read the DPI in the 0xFFE0 element. */ + /* Search through the image data and read the JPEG markers. */ while (!feof(stream)) { /* Read the JPEG marker and length fields for the sub-section. */ if (fread(&marker, sizeof(marker), 1, stream) < 1) break; @@ -2199,11 +2302,14 @@ length = LXW_UINT16_NETWORK(length); /* The offset for next fseek() is the field length + type length. */ offset = length - 2; - if (marker == 0xFFC0 || marker == 0xFFC2) { + /* Read the height and width in the 0xFFCn elements (except C4, C8 */ + /* and CC which aren't SOF markers). */ + if ((marker & 0xFFF0) == 0xFFC0 && marker != 0xFFC4 + && marker != 0xFFC8 && marker != 0xFFCC) { /* Skip 1 byte to height and width. */ fseek_err = fseek(stream, 1, SEEK_CUR); if (fseek_err) goto file_error; @@ -2217,10 +2323,11 @@ width = LXW_UINT16_NETWORK(width); offset -= 9; } + /* Read the DPI in the 0xFFE0 element. */ if (marker == 0xFFE0) { uint16_t x_density = 0; uint16_t y_density = 0; uint8_t units = 1; @@ -2312,10 +2419,13 @@ /* Ensure that we read some valid data from the file. */ if (width == 0) goto file_error; + height = LXW_UINT32_HOST(height); + width = LXW_UINT32_HOST(width); + /* Set the image metadata. */ image_options->image_type = LXW_IMAGE_BMP; image_options->width = width; image_options->height = height; image_options->x_dpi = x_dpi; @@ -2383,27 +2493,42 @@ */ STATIC void _write_number_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { +#ifdef USE_DOUBLE_FUNCTION + char data[LXW_ATTR_32]; + + lxw_sprintf_dbl(data, cell->u.number); + if (style_index) fprintf(self->file, + "<c r=\"%s\" s=\"%d\"><v>%s</v></c>", + range, style_index, data); + else + fprintf(self->file, "<c r=\"%s\"><v>%s</v></c>", range, data); +#else + if (style_index) + fprintf(self->file, "<c r=\"%s\" s=\"%d\"><v>%.16g</v></c>", range, style_index, cell->u.number); else fprintf(self->file, "<c r=\"%s\"><v>%.16g</v></c>", range, cell->u.number); + +#endif } /* * Write out a string worksheet cell. Doesn't use the xml functions as an * optimization in the inner cell writing loop. */ STATIC void _write_string_cell(lxw_worksheet *self, char *range, int32_t style_index, lxw_cell *cell) { + if (style_index) fprintf(self->file, "<c r=\"%s\" s=\"%d\" t=\"s\"><v>%d</v></c>", range, style_index, cell->u.string_id); else @@ -2457,12 +2582,11 @@ STATIC void _write_formula_num_cell(lxw_worksheet *self, lxw_cell *cell) { char data[LXW_ATTR_32]; - lxw_snprintf(data, LXW_ATTR_32, "%.16g", cell->formula_result); - + lxw_sprintf_dbl(data, cell->formula_result); lxw_xml_data_element(self->file, "f", cell->u.string, NULL); lxw_xml_data_element(self->file, "v", data, NULL); } /* @@ -2477,11 +2601,11 @@ LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("t", "array"); LXW_PUSH_ATTRIBUTES_STR("ref", cell->user_data1); - lxw_snprintf(data, LXW_ATTR_32, "%.16g", cell->formula_result); + lxw_sprintf_dbl(data, cell->formula_result); lxw_xml_data_element(self->file, "f", cell->u.string, &attributes); lxw_xml_data_element(self->file, "v", data, NULL); LXW_FREE_ATTRIBUTES(); @@ -2513,23 +2637,27 @@ * The span is the same for each block of 16 rows. */ STATIC void _calculate_spans(struct lxw_row *row, char *span, int32_t *block_num) { - lxw_col_t span_col_min = RB_MIN(lxw_table_cells, row->cells)->col_num; - lxw_col_t span_col_max = RB_MAX(lxw_table_cells, row->cells)->col_num; + lxw_cell *cell_min = RB_MIN(lxw_table_cells, row->cells); + lxw_cell *cell_max = RB_MAX(lxw_table_cells, row->cells); + lxw_col_t span_col_min = cell_min->col_num; + lxw_col_t span_col_max = cell_max->col_num; lxw_col_t col_min; lxw_col_t col_max; *block_num = row->row_num / 16; row = RB_NEXT(lxw_table_rows, root, row); while (row && (int32_t) (row->row_num / 16) == *block_num) { if (!RB_EMPTY(row->cells)) { - col_min = RB_MIN(lxw_table_cells, row->cells)->col_num; - col_max = RB_MAX(lxw_table_cells, row->cells)->col_num; + cell_min = RB_MIN(lxw_table_cells, row->cells); + cell_max = RB_MAX(lxw_table_cells, row->cells); + col_min = cell_min->col_num; + col_max = cell_max->col_num; if (col_min < span_col_min) span_col_min = col_min; if (col_max > span_col_max) @@ -2921,10 +3049,41 @@ LXW_FREE_ATTRIBUTES(); } /* + * Write the <outlinePr> element. + */ +STATIC void +_worksheet_write_outline_pr(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + if (!self->outline_changed) + return; + + LXW_INIT_ATTRIBUTES(); + + if (self->outline_style) + LXW_PUSH_ATTRIBUTES_STR("applyStyles", "1"); + + if (!self->outline_below) + LXW_PUSH_ATTRIBUTES_STR("summaryBelow", "0"); + + if (!self->outline_right) + LXW_PUSH_ATTRIBUTES_STR("summaryRight", "0"); + + if (!self->outline_on) + LXW_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0"); + + lxw_xml_empty_tag(self->file, "outlinePr", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* * Write the <sheetPr> element for Sheet level properties. */ STATIC void _worksheet_write_sheet_pr(lxw_worksheet *self) { @@ -2948,11 +3107,11 @@ if (self->fit_page || self->tab_color != LXW_COLOR_UNSET || self->outline_changed) { lxw_xml_start_tag(self->file, "sheetPr", &attributes); _worksheet_write_tab_color(self); - /* _worksheet_write_outline_pr(self); */ + _worksheet_write_outline_pr(self); _worksheet_write_page_set_up_pr(self); lxw_xml_end_tag(self->file, "sheetPr"); } else { lxw_xml_empty_tag(self->file, "sheetPr", &attributes); @@ -3311,10 +3470,228 @@ _write_drawing(self, self->rel_count); } /* + * Write the <formula1> element for numbers. + */ +STATIC void +_worksheet_write_formula1_num(lxw_worksheet *self, double number) +{ + char data[LXW_ATTR_32]; + + lxw_sprintf_dbl(data, number); + + lxw_xml_data_element(self->file, "formula1", data, NULL); +} + +/* + * Write the <formula1> element for strings/formulas. + */ +STATIC void +_worksheet_write_formula1_str(lxw_worksheet *self, char *str) +{ + lxw_xml_data_element(self->file, "formula1", str, NULL); +} + +/* + * Write the <formula2> element for numbers. + */ +STATIC void +_worksheet_write_formula2_num(lxw_worksheet *self, double number) +{ + char data[LXW_ATTR_32]; + + lxw_sprintf_dbl(data, number); + + lxw_xml_data_element(self->file, "formula2", data, NULL); +} + +/* + * Write the <formula2> element for strings/formulas. + */ +STATIC void +_worksheet_write_formula2_str(lxw_worksheet *self, char *str) +{ + lxw_xml_data_element(self->file, "formula2", str, NULL); +} + +/* + * Write the <dataValidation> element. + */ +STATIC void +_worksheet_write_data_validation(lxw_worksheet *self, + lxw_data_validation *validation) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint8_t is_between = 0; + + LXW_INIT_ATTRIBUTES(); + + switch (validation->validate) { + case LXW_VALIDATION_TYPE_INTEGER: + case LXW_VALIDATION_TYPE_INTEGER_FORMULA: + LXW_PUSH_ATTRIBUTES_STR("type", "whole"); + break; + case LXW_VALIDATION_TYPE_DECIMAL: + case LXW_VALIDATION_TYPE_DECIMAL_FORMULA: + LXW_PUSH_ATTRIBUTES_STR("type", "decimal"); + break; + case LXW_VALIDATION_TYPE_LIST: + case LXW_VALIDATION_TYPE_LIST_FORMULA: + LXW_PUSH_ATTRIBUTES_STR("type", "list"); + break; + case LXW_VALIDATION_TYPE_DATE: + case LXW_VALIDATION_TYPE_DATE_FORMULA: + case LXW_VALIDATION_TYPE_DATE_NUMBER: + LXW_PUSH_ATTRIBUTES_STR("type", "date"); + break; + case LXW_VALIDATION_TYPE_TIME: + case LXW_VALIDATION_TYPE_TIME_FORMULA: + case LXW_VALIDATION_TYPE_TIME_NUMBER: + LXW_PUSH_ATTRIBUTES_STR("type", "time"); + break; + case LXW_VALIDATION_TYPE_LENGTH: + case LXW_VALIDATION_TYPE_LENGTH_FORMULA: + LXW_PUSH_ATTRIBUTES_STR("type", "textLength"); + break; + case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: + LXW_PUSH_ATTRIBUTES_STR("type", "custom"); + break; + } + + switch (validation->criteria) { + case LXW_VALIDATION_CRITERIA_EQUAL_TO: + LXW_PUSH_ATTRIBUTES_STR("operator", "equal"); + break; + case LXW_VALIDATION_CRITERIA_NOT_EQUAL_TO: + LXW_PUSH_ATTRIBUTES_STR("operator", "notEqual"); + break; + case LXW_VALIDATION_CRITERIA_LESS_THAN: + LXW_PUSH_ATTRIBUTES_STR("operator", "lessThan"); + break; + case LXW_VALIDATION_CRITERIA_LESS_THAN_OR_EQUAL_TO: + LXW_PUSH_ATTRIBUTES_STR("operator", "lessThanOrEqual"); + break; + case LXW_VALIDATION_CRITERIA_GREATER_THAN: + LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThan"); + break; + case LXW_VALIDATION_CRITERIA_GREATER_THAN_OR_EQUAL_TO: + LXW_PUSH_ATTRIBUTES_STR("operator", "greaterThanOrEqual"); + break; + case LXW_VALIDATION_CRITERIA_BETWEEN: + /* Between is the default for 2 formulas and isn't added. */ + is_between = 1; + break; + case LXW_VALIDATION_CRITERIA_NOT_BETWEEN: + is_between = 1; + LXW_PUSH_ATTRIBUTES_STR("operator", "notBetween"); + break; + } + + if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_WARNING) + LXW_PUSH_ATTRIBUTES_STR("errorStyle", "warning"); + + if (validation->error_type == LXW_VALIDATION_ERROR_TYPE_INFORMATION) + LXW_PUSH_ATTRIBUTES_STR("errorStyle", "information"); + + if (validation->ignore_blank) + LXW_PUSH_ATTRIBUTES_INT("allowBlank", 1); + + if (validation->dropdown == LXW_VALIDATION_OFF) + LXW_PUSH_ATTRIBUTES_INT("showDropDown", 1); + + if (validation->show_input) + LXW_PUSH_ATTRIBUTES_INT("showInputMessage", 1); + + if (validation->show_error) + LXW_PUSH_ATTRIBUTES_INT("showErrorMessage", 1); + + if (validation->error_title) + LXW_PUSH_ATTRIBUTES_STR("errorTitle", validation->error_title); + + if (validation->error_message) + LXW_PUSH_ATTRIBUTES_STR("error", validation->error_message); + + if (validation->input_title) + LXW_PUSH_ATTRIBUTES_STR("promptTitle", validation->input_title); + + if (validation->input_message) + LXW_PUSH_ATTRIBUTES_STR("prompt", validation->input_message); + + LXW_PUSH_ATTRIBUTES_STR("sqref", validation->sqref); + + if (validation->validate == LXW_VALIDATION_TYPE_ANY) + lxw_xml_empty_tag(self->file, "dataValidation", &attributes); + else + lxw_xml_start_tag(self->file, "dataValidation", &attributes); + + /* Write the formula1 and formula2 elements. */ + switch (validation->validate) { + case LXW_VALIDATION_TYPE_INTEGER: + case LXW_VALIDATION_TYPE_DECIMAL: + case LXW_VALIDATION_TYPE_LENGTH: + case LXW_VALIDATION_TYPE_DATE: + case LXW_VALIDATION_TYPE_TIME: + case LXW_VALIDATION_TYPE_DATE_NUMBER: + case LXW_VALIDATION_TYPE_TIME_NUMBER: + _worksheet_write_formula1_num(self, validation->value_number); + if (is_between) + _worksheet_write_formula2_num(self, + validation->maximum_number); + break; + case LXW_VALIDATION_TYPE_INTEGER_FORMULA: + case LXW_VALIDATION_TYPE_DECIMAL_FORMULA: + case LXW_VALIDATION_TYPE_LENGTH_FORMULA: + case LXW_VALIDATION_TYPE_DATE_FORMULA: + case LXW_VALIDATION_TYPE_TIME_FORMULA: + case LXW_VALIDATION_TYPE_LIST: + case LXW_VALIDATION_TYPE_LIST_FORMULA: + case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: + _worksheet_write_formula1_str(self, validation->value_formula); + if (is_between) + _worksheet_write_formula2_str(self, + validation->maximum_formula); + break; + } + + if (validation->validate != LXW_VALIDATION_TYPE_ANY) + lxw_xml_end_tag(self->file, "dataValidation"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the <dataValidations> element. + */ +STATIC void +_worksheet_write_data_validations(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_data_validation *data_validation; + + if (self->num_validations == 0) + return; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("count", self->num_validations); + + lxw_xml_start_tag(self->file, "dataValidations", &attributes); + + STAILQ_FOREACH(data_validation, self->data_validations, list_pointers) { + /* Write the dataValidation element. */ + _worksheet_write_data_validation(self, data_validation); + } + + lxw_xml_end_tag(self->file, "dataValidations"); + + LXW_FREE_ATTRIBUTES(); +} + +/* * Assemble and write the XML file. */ void lxw_worksheet_assemble_xml_file(lxw_worksheet *self) { @@ -3352,10 +3729,13 @@ _worksheet_write_auto_filter(self); /* Write the mergeCells element. */ _worksheet_write_merge_cells(self); + /* Write the dataValidations element. */ + _worksheet_write_data_validations(self); + /* Write the hyperlink element. */ _worksheet_write_hyperlinks(self); /* Write the printOptions element. */ _worksheet_write_print_options(self); @@ -3429,11 +3809,11 @@ /* Treat a NULL or empty string with formatting as a blank cell. */ /* Null strings without formats should be ignored. */ if (format) return worksheet_write_blank(self, row_num, col_num, format); else - return LXW_ERROR_NULL_PARAMETER_IGNORED; + return LXW_NO_ERROR; } err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; @@ -4008,10 +4388,18 @@ /* Store the column options. */ copied_options = calloc(1, sizeof(lxw_col_options)); RETURN_ON_MEM_ERROR(copied_options, LXW_ERROR_MEMORY_MALLOC_FAILED); + /* Ensure the level is <= 7). */ + if (level > 7) + level = 7; + + if (level > self->outline_col_level) + self->outline_col_level = level; + + /* Set the column properties. */ copied_options->firstcol = firstcol; copied_options->lastcol = lastcol; copied_options->width = width; copied_options->format = format; copied_options->hidden = hidden; @@ -4080,10 +4468,18 @@ if (height == 0) { hidden = LXW_TRUE; height = self->default_row_height; } + /* Ensure the level is <= 7). */ + if (level > 7) + level = 7; + + if (level > self->outline_row_level) + self->outline_row_level = level; + + /* Store the row properties. */ row = _get_row(self, row_num); row->height = height; row->format = format; row->hidden = hidden; @@ -4465,11 +4861,11 @@ lxw_error worksheet_set_header_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { if (options) { - if (options->margin > 0) + if (options->margin >= 0.0) self->margin_header = options->margin; } if (!string) return LXW_ERROR_NULL_PARAMETER_IGNORED; @@ -4489,11 +4885,11 @@ lxw_error worksheet_set_footer_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { if (options) { - if (options->margin > 0) + if (options->margin >= 0.0) self->margin_footer = options->margin; } if (!string) return LXW_ERROR_NULL_PARAMETER_IGNORED; @@ -4817,25 +5213,54 @@ lxw_protection *options) { struct lxw_protection *protect = &self->protection; /* Copy any user parameters to the internal structure. */ - if (options) - memcpy(protect, options, sizeof(lxw_protection)); + if (options) { + protect->no_select_locked_cells = options->no_select_locked_cells; + protect->no_select_unlocked_cells = options->no_select_unlocked_cells; + protect->format_cells = options->format_cells; + protect->format_columns = options->format_columns; + protect->format_rows = options->format_rows; + protect->insert_columns = options->insert_columns; + protect->insert_rows = options->insert_rows; + protect->insert_hyperlinks = options->insert_hyperlinks; + protect->delete_columns = options->delete_columns; + protect->delete_rows = options->delete_rows; + protect->sort = options->sort; + protect->autofilter = options->autofilter; + protect->pivot_tables = options->pivot_tables; + protect->scenarios = options->scenarios; + protect->objects = options->objects; + } - /* Zero the hash storage in case of copied initialization data. */ - protect->hash[0] = '\0'; - if (password) { uint16_t hash = _hash_password(password); lxw_snprintf(protect->hash, 5, "%X", hash); } protect->is_configured = LXW_TRUE; } /* + * Set the worksheet properties for outlines and grouping. + */ +void +worksheet_outline_settings(lxw_worksheet *self, + uint8_t visible, + uint8_t symbols_below, + uint8_t symbols_right, uint8_t auto_style) +{ + self->outline_on = visible; + self->outline_below = symbols_below; + self->outline_right = symbols_right; + self->outline_style = auto_style; + + self->outline_changed = LXW_TRUE; +} + +/* * Set the default row properties */ void worksheet_set_default_row(lxw_worksheet *self, double height, uint8_t hide_unused_rows) @@ -4897,13 +5322,14 @@ fclose(image_stream); return LXW_ERROR_MEMORY_MALLOC_FAILED; } if (user_options) { - memcpy(options, user_options, sizeof(lxw_image_options)); - options->url = lxw_strdup(user_options->url); - options->tip = lxw_strdup(user_options->tip); + options->x_offset = user_options->x_offset; + options->y_offset = user_options->y_offset; + options->x_scale = user_options->x_scale; + options->y_scale = user_options->y_scale; } /* Copy other options or set defaults. */ options->filename = lxw_strdup(filename); options->short_name = lxw_strdup(short_name); @@ -4917,14 +5343,16 @@ if (!options->y_scale) options->y_scale = 1; if (_get_image_properties(options) == LXW_NO_ERROR) { STAILQ_INSERT_TAIL(self->image_data, options, list_pointers); + fclose(image_stream); return LXW_NO_ERROR; } else { free(options); + fclose(image_stream); return LXW_ERROR_IMAGE_DIMENSIONS; } } /* @@ -4982,12 +5410,16 @@ /* Create a new object to hold the chart image options. */ options = calloc(1, sizeof(lxw_image_options)); RETURN_ON_MEM_ERROR(options, LXW_ERROR_MEMORY_MALLOC_FAILED); - if (user_options) - memcpy(options, user_options, sizeof(lxw_image_options)); + if (user_options) { + options->x_offset = user_options->x_offset; + options->y_offset = user_options->y_offset; + options->x_scale = user_options->x_scale; + options->y_scale = user_options->y_scale; + } /* Copy other options or set defaults. */ options->row = row_num; options->col = col_num; @@ -5017,6 +5449,291 @@ lxw_error worksheet_insert_chart(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, lxw_chart *chart) { return worksheet_insert_chart_opt(self, row_num, col_num, chart, NULL); +} + +/* + * Add a data validation to a worksheet, for a range. Ironically this requires + * a lot of validation of the user input. + */ +lxw_error +worksheet_data_validation_range(lxw_worksheet *self, lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + lxw_data_validation *validation) +{ + lxw_data_validation *copy; + uint8_t is_between = LXW_FALSE; + uint8_t is_formula = LXW_FALSE; + uint8_t has_criteria = LXW_TRUE; + lxw_error err; + lxw_row_t tmp_row; + lxw_col_t tmp_col; + size_t length; + + /* No action is required for validation type 'any' unless there are + * input messages to display.*/ + if (validation->validate == LXW_VALIDATION_TYPE_ANY + && !(validation->input_title || validation->input_message)) { + + return LXW_NO_ERROR; + } + + /* Check for formula types. */ + switch (validation->validate) { + case LXW_VALIDATION_TYPE_INTEGER_FORMULA: + case LXW_VALIDATION_TYPE_DECIMAL_FORMULA: + case LXW_VALIDATION_TYPE_LIST_FORMULA: + case LXW_VALIDATION_TYPE_LENGTH_FORMULA: + case LXW_VALIDATION_TYPE_DATE_FORMULA: + case LXW_VALIDATION_TYPE_TIME_FORMULA: + case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: + is_formula = LXW_TRUE; + break; + } + + /* Check for types without a criteria. */ + switch (validation->validate) { + case LXW_VALIDATION_TYPE_LIST: + case LXW_VALIDATION_TYPE_LIST_FORMULA: + case LXW_VALIDATION_TYPE_ANY: + case LXW_VALIDATION_TYPE_CUSTOM_FORMULA: + has_criteria = LXW_FALSE; + break; + } + + /* Check that a validation parameter has been specified + * except for 'list', 'any' and 'custom'. */ + if (has_criteria && validation->criteria == LXW_VALIDATION_CRITERIA_NONE) { + + LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " + "criteria parameter must be specified."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Check for "between" criteria so we can do additional checks. */ + if (has_criteria + && (validation->criteria == LXW_VALIDATION_CRITERIA_BETWEEN + || validation->criteria == LXW_VALIDATION_CRITERIA_NOT_BETWEEN)) { + + is_between = LXW_TRUE; + } + + /* Check that formula values are non NULL. */ + if (is_formula) { + if (is_between) { + if (!validation->minimum_formula) { + LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " + "minimum_formula parameter cannot be NULL."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + if (!validation->maximum_formula) { + LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " + "maximum_formula parameter cannot be NULL."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + else { + if (!validation->value_formula) { + LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " + "formula parameter cannot be NULL."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + } + + /* Check Excel limitations on input strings. */ + if (validation->input_title) { + length = lxw_utf8_strlen(validation->input_title); + if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) { + LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " + "input_title length > Excel limit of %d.", + LXW_VALIDATION_MAX_TITLE_LENGTH); + return LXW_ERROR_32_STRING_LENGTH_EXCEEDED; + } + } + + if (validation->error_title) { + length = lxw_utf8_strlen(validation->error_title); + if (length > LXW_VALIDATION_MAX_TITLE_LENGTH) { + LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " + "error_title length > Excel limit of %d.", + LXW_VALIDATION_MAX_TITLE_LENGTH); + return LXW_ERROR_32_STRING_LENGTH_EXCEEDED; + } + } + + if (validation->input_message) { + length = lxw_utf8_strlen(validation->input_message); + if (length > LXW_VALIDATION_MAX_STRING_LENGTH) { + LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " + "input_message length > Excel limit of %d.", + LXW_VALIDATION_MAX_STRING_LENGTH); + return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } + } + + if (validation->error_message) { + length = lxw_utf8_strlen(validation->error_message); + if (length > LXW_VALIDATION_MAX_STRING_LENGTH) { + LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " + "error_message length > Excel limit of %d.", + LXW_VALIDATION_MAX_STRING_LENGTH); + return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } + } + + if (validation->validate == LXW_VALIDATION_TYPE_LIST) { + length = _validation_list_length(validation->value_list); + + if (length == 0) { + LXW_WARN_FORMAT("worksheet_data_validation_cell()/_range(): " + "list parameters cannot be zero."); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (length > LXW_VALIDATION_MAX_STRING_LENGTH) { + LXW_WARN_FORMAT1("worksheet_data_validation_cell()/_range(): " + "list length with commas > Excel limit of %d.", + LXW_VALIDATION_MAX_STRING_LENGTH); + return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; + } + } + + /* Swap last row/col with first row/col as necessary */ + if (first_row > last_row) { + tmp_row = last_row; + last_row = first_row; + first_row = tmp_row; + } + if (first_col > last_col) { + tmp_col = last_col; + last_col = first_col; + first_col = tmp_col; + } + + /* Check that dimensions are valid but don't store them. */ + err = _check_dimensions(self, last_row, last_col, LXW_TRUE, LXW_TRUE); + if (err) + return err; + + /* Create a copy of the parameters from the user data validation. */ + copy = calloc(1, sizeof(lxw_data_validation)); + GOTO_LABEL_ON_MEM_ERROR(copy, mem_error); + + /* Create the data validation range. */ + if (first_row == last_row && first_col == last_col) + lxw_rowcol_to_cell(copy->sqref, first_row, last_col); + else + lxw_rowcol_to_range(copy->sqref, first_row, first_col, last_row, + last_col); + + /* Copy the parameters from the user data validation. */ + copy->validate = validation->validate; + copy->value_number = validation->value_number; + copy->error_type = validation->error_type; + copy->dropdown = validation->dropdown; + copy->is_between = is_between; + + if (has_criteria) + copy->criteria = validation->criteria; + + if (is_between) { + copy->value_number = validation->minimum_number; + copy->maximum_number = validation->maximum_number; + } + + /* Copy the input/error titles and messages. */ + if (validation->input_title) { + copy->input_title = lxw_strdup_formula(validation->input_title); + GOTO_LABEL_ON_MEM_ERROR(copy->input_title, mem_error); + } + + if (validation->input_message) { + copy->input_message = lxw_strdup_formula(validation->input_message); + GOTO_LABEL_ON_MEM_ERROR(copy->input_message, mem_error); + } + + if (validation->error_title) { + copy->error_title = lxw_strdup_formula(validation->error_title); + GOTO_LABEL_ON_MEM_ERROR(copy->error_title, mem_error); + } + + if (validation->error_message) { + copy->error_message = lxw_strdup_formula(validation->error_message); + GOTO_LABEL_ON_MEM_ERROR(copy->error_message, mem_error); + } + + /* Copy the formula strings. */ + if (is_formula) { + if (is_between) { + copy->value_formula = + lxw_strdup_formula(validation->minimum_formula); + GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); + copy->maximum_formula = + lxw_strdup_formula(validation->maximum_formula); + GOTO_LABEL_ON_MEM_ERROR(copy->maximum_formula, mem_error); + } + else { + copy->value_formula = + lxw_strdup_formula(validation->value_formula); + GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); + } + } + + /* Copy the validation list as a csv string. */ + if (validation->validate == LXW_VALIDATION_TYPE_LIST) { + copy->value_formula = _validation_list_to_csv(validation->value_list); + GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); + } + + if (validation->validate == LXW_VALIDATION_TYPE_LIST_FORMULA) { + copy->value_formula = lxw_strdup_formula(validation->value_formula); + GOTO_LABEL_ON_MEM_ERROR(copy->value_formula, mem_error); + } + + if (validation->validate == LXW_VALIDATION_TYPE_DATE + || validation->validate == LXW_VALIDATION_TYPE_TIME) { + if (is_between) { + copy->value_number = + lxw_datetime_to_excel_date(&validation->minimum_datetime, + LXW_EPOCH_1900); + copy->maximum_number = + lxw_datetime_to_excel_date(&validation->maximum_datetime, + LXW_EPOCH_1900); + } + else { + copy->value_number = + lxw_datetime_to_excel_date(&validation->value_datetime, + LXW_EPOCH_1900); + } + } + + /* These options are on by default so we can't take plain booleans. */ + copy->ignore_blank = validation->ignore_blank ^ 1; + copy->show_input = validation->show_input ^ 1; + copy->show_error = validation->show_error ^ 1; + + STAILQ_INSERT_TAIL(self->data_validations, copy, list_pointers); + + self->num_validations++; + + return LXW_NO_ERROR; + +mem_error: + _free_data_validation(copy); + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + +/* + * Add a data validation to a worksheet, for a cell. + */ +lxw_error +worksheet_data_validation_cell(lxw_worksheet *self, lxw_row_t row, + lxw_col_t col, lxw_data_validation *validation) +{ + return worksheet_data_validation_range(self, row, col, + row, col, validation); }