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);
}