diff --git a/libxlsxwriter/examples/chart_data_labels.c b/libxlsxwriter/examples/chart_data_labels.c new file mode 100644 index 0000000..2744e1a --- /dev/null +++ b/libxlsxwriter/examples/chart_data_labels.c @@ -0,0 +1,386 @@ +/* + * A demo of an various Excel chart data label features that are available via + * a libxlsxwriter chart. + * + * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +/* + * Create a worksheet with examples charts. + */ +int main() { + + lxw_workbook *workbook = workbook_new("chart_data_labels.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + /* Add a bold format to use to highlight the header cells. */ + lxw_format *bold = workbook_add_format(workbook); + format_set_bold(bold); + + /* Some chart positioning options. */ + lxw_chart_options options = {.x_offset = 25, .y_offset = 10}; + + /* Write some data for the chart. */ + worksheet_write_string(worksheet, 0, 0, "Number", bold); + worksheet_write_number(worksheet, 1, 0, 2, NULL); + worksheet_write_number(worksheet, 2, 0, 3, NULL); + worksheet_write_number(worksheet, 3, 0, 4, NULL); + worksheet_write_number(worksheet, 4, 0, 5, NULL); + worksheet_write_number(worksheet, 5, 0, 6, NULL); + worksheet_write_number(worksheet, 6, 0, 7, NULL); + + worksheet_write_string(worksheet, 0, 1, "Data", bold); + worksheet_write_number(worksheet, 1, 1, 20, NULL); + worksheet_write_number(worksheet, 2, 1, 10, NULL); + worksheet_write_number(worksheet, 3, 1, 20, NULL); + worksheet_write_number(worksheet, 4, 1, 30, NULL); + worksheet_write_number(worksheet, 5, 1, 40, NULL); + worksheet_write_number(worksheet, 6, 1, 30, NULL); + + worksheet_write_string(worksheet, 0, 2, "Text", bold); + worksheet_write_string(worksheet, 1, 2, "Jan", NULL); + worksheet_write_string(worksheet, 2, 2, "Feb", NULL); + worksheet_write_string(worksheet, 3, 2, "Mar", NULL); + worksheet_write_string(worksheet, 4, 2, "Apr", NULL); + worksheet_write_string(worksheet, 5, 2, "May", NULL); + worksheet_write_string(worksheet, 6, 2, "Jun", NULL); + + + /* + * Chart 1. Example with standard data labels. + */ + lxw_chart *chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Chart with standard data labels"); + + /* Add a data series to the chart. */ + lxw_chart_series *series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D2"), chart, &options); + + + /* + * Chart 2. Example with value and category data labels. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Category and Value data labels"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Turn on Value and Category labels. */ + chart_series_set_labels_options(series, LXW_FALSE, LXW_TRUE, LXW_TRUE); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D18"), chart, &options); + + + /* + * Chart 3. Example with standard data labels with different font. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Data labels with user defined font"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + lxw_chart_font font1 = {.bold = LXW_TRUE, .color = LXW_COLOR_RED, .rotation = -30}; + chart_series_set_labels_font(series, &font1); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D34"), chart, &options); + + + /* + * Chart 4. Example with standard data labels and formatting. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Data labels with formatting"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Set the border/line and fill for the data labels. */ + lxw_chart_line line1 = {.color = LXW_COLOR_RED}; + lxw_chart_fill fill1 = {.color = LXW_COLOR_YELLOW}; + + chart_series_set_labels_line(series, &line1); + chart_series_set_labels_fill(series, &fill1); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D50"), chart, &options); + + + /* + * Chart 5.Example with custom string data labels. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Chart with custom string data labels"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Create some custom labels. */ + lxw_chart_data_label data_label5_1 = {.value = "Amy"}; + lxw_chart_data_label data_label5_2 = {.value = "Bea"}; + lxw_chart_data_label data_label5_3 = {.value = "Eva"}; + lxw_chart_data_label data_label5_4 = {.value = "Fay"}; + lxw_chart_data_label data_label5_5 = {.value = "Liv"}; + lxw_chart_data_label data_label5_6 = {.value = "Una"}; + + /* Create an array of label pointers. NULL indicates the end of the array. */ + lxw_chart_data_label *data_labels5[] = { + &data_label5_1, + &data_label5_2, + &data_label5_3, + &data_label5_4, + &data_label5_5, + &data_label5_6, + NULL + }; + + /* Set the custom labels. */ + chart_series_set_labels_custom(series, data_labels5); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D66"), chart, &options); + + + /* + * Chart 6. Example with custom data labels from cells. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Chart with custom data labels from cells"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Create some custom labels. */ + lxw_chart_data_label data_label6_1 = {.value = "=Sheet1!$C$2"}; + lxw_chart_data_label data_label6_2 = {.value = "=Sheet1!$C$3"}; + lxw_chart_data_label data_label6_3 = {.value = "=Sheet1!$C$4"}; + lxw_chart_data_label data_label6_4 = {.value = "=Sheet1!$C$5"}; + lxw_chart_data_label data_label6_5 = {.value = "=Sheet1!$C$6"}; + lxw_chart_data_label data_label6_6 = {.value = "=Sheet1!$C$7"}; + + /* Create an array of label pointers. NULL indicates the end of the array. */ + lxw_chart_data_label *data_labels6[] = { + &data_label6_1, + &data_label6_2, + &data_label6_3, + &data_label6_4, + &data_label6_5, + &data_label6_6, + NULL + }; + + /* Set the custom labels. */ + chart_series_set_labels_custom(series, data_labels6); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D82"), chart, &options); + + + /* + * Chart 7. Example with custom and default data labels. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Mixed custom and default data labels"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + lxw_chart_font font2 = {.color = LXW_COLOR_RED}; + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Create some custom labels. */ + + /* The following is used to get a mix of default and custom labels. The + * items initialized with '{0}' and items without a custom label (points 5 + * and 6 which come after NULL) will get the default value. We also set a + * font for the custom items as an extra example. + */ + lxw_chart_data_label data_label7_1 = {.value = "=Sheet1!$C$2", .font = &font2}; + lxw_chart_data_label data_label7_2 = {0}; + lxw_chart_data_label data_label7_3 = {.value = "=Sheet1!$C$4", .font = &font2}; + lxw_chart_data_label data_label7_4 = {.value = "=Sheet1!$C$5", .font = &font2}; + + /* Create an array of label pointers. NULL indicates the end of the array. */ + lxw_chart_data_label *data_labels7[] = { + &data_label7_1, + &data_label7_2, + &data_label7_3, + &data_label7_4, + NULL + }; + + /* Set the custom labels. */ + chart_series_set_labels_custom(series, data_labels7); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D98"), chart, &options); + + + /* + * Chart 8. Example with deleted/hidden custom data labels. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Chart with deleted data labels"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Create some custom labels. */ + lxw_chart_data_label hide = {.hide = LXW_TRUE}; + lxw_chart_data_label keep = {.hide = LXW_FALSE}; + + /* An initialized struct like this would also work: */ + /* lxw_chart_data_label keep = {0}; */ + + /* Create an array of label pointers. NULL indicates the end of the array. */ + lxw_chart_data_label *data_labels8[] = { + &hide, + &keep, + &hide, + &hide, + &keep, + &hide, + NULL + }; + + /* Set the custom labels. */ + chart_series_set_labels_custom(series, data_labels8); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D114"), chart, &options); + + + /* + * Chart 9.Example with custom string data labels and formatting. + */ + chart = workbook_add_chart(workbook, LXW_CHART_COLUMN); + + /* Add a chart title. */ + chart_title_set_name(chart, "Chart with custom labels and formatting"); + + /* Add a data series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", + "=Sheet1!$B$2:$B$7"); + + /* Add the series data labels. */ + chart_series_set_labels(series); + + /* Set the border/line and fill for the data labels. */ + lxw_chart_line line2 = {.color = LXW_COLOR_RED}; + lxw_chart_fill fill2 = {.color = LXW_COLOR_YELLOW}; + lxw_chart_line line3 = {.color = LXW_COLOR_BLUE}; + lxw_chart_fill fill3 = {.color = LXW_COLOR_GREEN}; + + /* Create some custom labels. */ + lxw_chart_data_label data_label9_1 = {.value = "Amy", .line = &line3}; + lxw_chart_data_label data_label9_2 = {.value = "Bea"}; + lxw_chart_data_label data_label9_3 = {.value = "Eva"}; + lxw_chart_data_label data_label9_4 = {.value = "Fay"}; + lxw_chart_data_label data_label9_5 = {.value = "Liv"}; + lxw_chart_data_label data_label9_6 = {.value = "Una", .fill = &fill3}; + + /* Set the default formatting for the data labels in the series. */ + chart_series_set_labels_line(series, &line2); + chart_series_set_labels_fill(series, &fill2); + + /* Create an array of label pointers. NULL indicates the end of the array. */ + lxw_chart_data_label *data_labels9[] = { + &data_label9_1, + &data_label9_2, + &data_label9_3, + &data_label9_4, + &data_label9_5, + &data_label9_6, + NULL + }; + + /* Set the custom labels. */ + chart_series_set_labels_custom(series, data_labels9); + + /* Turn off the legend. */ + chart_legend_set_position(chart, LXW_CHART_LEGEND_NONE); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart_opt(worksheet, CELL("D130"), chart, &options); + + return workbook_close(workbook); +} diff --git a/libxlsxwriter/examples/chart_line.c b/libxlsxwriter/examples/chart_line.c index 88814b8..a2a38e4 100644 --- a/libxlsxwriter/examples/chart_line.c +++ b/libxlsxwriter/examples/chart_line.c @@ -81,5 +81,62 @@ int main() { worksheet_insert_chart(worksheet, CELL("E2"), chart); + /* + * Chart 2. Create a stacked line chart. + */ + chart = workbook_add_chart(workbook, LXW_CHART_LINE_STACKED); + + /* Add the first series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", "=Sheet1!$B$2:$B$7"); + + /* Set the name for the series instead of the default "Series 1". */ + chart_series_set_name(series, "=Sheet1!$B$1"); + + /* Add the second series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", "=Sheet1!$C$2:$C$7"); + + /* Set the name for the series instead of the default "Series 2". */ + chart_series_set_name(series, "=Sheet1!$C$1"); + + /* Add a chart title and some axis labels. */ + chart_title_set_name(chart, "Results of sample analysis"); + chart_axis_set_name(chart->x_axis, "Test number"); + chart_axis_set_name(chart->y_axis, "Sample length (mm)"); + + /* Set an Excel chart style. */ + chart_set_style(chart, 12); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart(worksheet, CELL("E18"), chart); + + + /* + * Chart 3. Create a percent stacked line chart. + */ + chart = workbook_add_chart(workbook, LXW_CHART_LINE_STACKED_PERCENT); + + /* Add the first series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", "=Sheet1!$B$2:$B$7"); + + /* Set the name for the series instead of the default "Series 1". */ + chart_series_set_name(series, "=Sheet1!$B$1"); + + /* Add the second series to the chart. */ + series = chart_add_series(chart, "=Sheet1!$A$2:$A$7", "=Sheet1!$C$2:$C$7"); + + /* Set the name for the series instead of the default "Series 2". */ + chart_series_set_name(series, "=Sheet1!$C$1"); + + /* Add a chart title and some axis labels. */ + chart_title_set_name(chart, "Results of sample analysis"); + chart_axis_set_name(chart->x_axis, "Test number"); + chart_axis_set_name(chart->y_axis, "Sample length (mm)"); + + /* Set an Excel chart style. */ + chart_set_style(chart, 13); + + /* Insert the chart into the worksheet. */ + worksheet_insert_chart(worksheet, CELL("E34"), chart); + return workbook_close(workbook); } diff --git a/libxlsxwriter/examples/conditional_format1.c b/libxlsxwriter/examples/conditional_format1.c new file mode 100644 index 0000000..cedf330 --- /dev/null +++ b/libxlsxwriter/examples/conditional_format1.c @@ -0,0 +1,55 @@ +/* + * An a simple example of how to add conditional formatting to an + * libxlsxwriter file. + * + * See conditional_format.c for a more comprehensive example. + * + * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + + +int main() { + + lxw_workbook *workbook = workbook_new("conditional_format_simple.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + /* Write some sample data. */ + worksheet_write_number(worksheet, CELL("B1"), 34, NULL); + worksheet_write_number(worksheet, CELL("B2"), 32, NULL); + worksheet_write_number(worksheet, CELL("B3"), 31, NULL); + worksheet_write_number(worksheet, CELL("B4"), 35, NULL); + worksheet_write_number(worksheet, CELL("B5"), 36, NULL); + worksheet_write_number(worksheet, CELL("B6"), 30, NULL); + worksheet_write_number(worksheet, CELL("B7"), 38, NULL); + worksheet_write_number(worksheet, CELL("B8"), 38, NULL); + worksheet_write_number(worksheet, CELL("B9"), 32, NULL); + + /* Add a format with red text. */ + lxw_format *custom_format = workbook_add_format(workbook); + format_set_font_color(custom_format, LXW_COLOR_RED); + + /* Create a conditional format object. A static object would also work. */ + lxw_conditional_format *conditional_format = calloc(1, sizeof(lxw_conditional_format)); + + /* Set the format type: a cell conditional: */ + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + + /* Set the criteria to use: */ + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_LESS_THAN; + + /* Set the value to which the criteria will be applied: */ + conditional_format->value = 33; + + /* Set the format to use if the criteria/value applies: */ + conditional_format->format = custom_format; + + /* Now apply the format to data range. */ + worksheet_conditional_format_range(worksheet, RANGE("B1:B9"), conditional_format); + + /* Free the object and close the file. */ + free(conditional_format); + return workbook_close(workbook); +} diff --git a/libxlsxwriter/examples/conditional_format2.c b/libxlsxwriter/examples/conditional_format2.c new file mode 100644 index 0000000..da6d9e2 --- /dev/null +++ b/libxlsxwriter/examples/conditional_format2.c @@ -0,0 +1,400 @@ +/* + * An example of how to add conditional formatting to an libxlsxwriter file. + * + * Conditional formatting allows you to apply a format to a cell or a + * range of cells based on certain criteria. + * + * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +/* Write some data to the worksheet. */ +void write_worksheet_data(lxw_worksheet *worksheet) { + + uint8_t data[10][10] = { + {34, 72, 38, 30, 75, 48, 75, 66, 84, 86}, + {6, 24, 1, 84, 54, 62, 60, 3, 26, 59}, + {28, 79, 97, 13, 85, 93, 93, 22, 5, 14}, + {27, 71, 40, 17, 18, 79, 90, 93, 29, 47}, + {88, 25, 33, 23, 67, 1, 59, 79, 47, 36}, + {24, 100, 20, 88, 29, 33, 38, 54, 54, 88}, + {6, 57, 88, 28, 10, 26, 37, 7, 41, 48}, + {52, 78, 1, 96, 26, 45, 47, 33, 96, 36}, + {60, 54, 81, 66, 81, 90, 80, 93, 12, 55}, + {70, 5, 46, 14, 71, 19, 66, 36, 41, 21}, + }; + + int row, col; + for (row = 0; row < 10; row++) + for (col = 0; col < 10; col++) + worksheet_write_number(worksheet, row +2, col +1, data[row][col], NULL); +} + +/* Reset the conditional format options back to their initial state. */ +void reset_conditional_format(lxw_conditional_format *conditional_format) { + memset(conditional_format, 0, sizeof(lxw_conditional_format)); +} + +int main() { + + lxw_workbook *workbook = workbook_new("conditional_format.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet4 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet5 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet6 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet7 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet8 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet9 = workbook_add_worksheet(workbook, NULL); + + /* Add a format. Light red fill with dark red text. */ + lxw_format *format1 = workbook_add_format(workbook); + format_set_bg_color(format1, 0xFFC7CE); + format_set_font_color(format1, 0x9C0006); + + /* Add a format. Green fill with dark green text. */ + lxw_format *format2 = workbook_add_format(workbook); + format_set_bg_color(format2, 0xC6EFCE); + format_set_font_color(format2, 0x006100); + + /* Create a single conditional format object to reuse in the examples. */ + lxw_conditional_format *conditional_format = calloc(1, sizeof(lxw_conditional_format)); + + /* + * Example 1. Conditional formatting based on simple cell based criteria. + */ + + write_worksheet_data(worksheet1); + + worksheet_write_string(worksheet1, + CELL("A1"), + "Cells with values >= 50 are in light red. " + "Values < 50 are in light green.", + NULL); + + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_GREATER_THAN_OR_EQUAL_TO; + conditional_format->value = 50; + conditional_format->format = format1; + worksheet_conditional_format_range(worksheet1, RANGE("B3:K12"), conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_LESS_THAN; + conditional_format->value = 50; + conditional_format->format = format2; + worksheet_conditional_format_range(worksheet1, RANGE("B3:K12"), conditional_format); + + /* + * Example 2. Conditional formatting based on max and min values. + */ + + write_worksheet_data(worksheet2); + + worksheet_write_string(worksheet2, + CELL("A1"), + "Values between 30 and 70 are in light red. " + "Values outside that range are in light green.", + NULL); + + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_BETWEEN; + conditional_format->min_value = 30; + conditional_format->max_value = 70; + conditional_format->format = format1; + worksheet_conditional_format_range(worksheet2, RANGE("B3:K12"), conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_NOT_BETWEEN; + conditional_format->min_value = 30; + conditional_format->max_value = 70; + conditional_format->format = format2; + worksheet_conditional_format_range(worksheet2, RANGE("B3:K12"), conditional_format); + + + /* + * Example 3. Conditional formatting with duplicate and unique values. + */ + + write_worksheet_data(worksheet3); + + worksheet_write_string(worksheet3, + CELL("A1"), + "Duplicate values are in light red. " + "Unique values are in light green.", + NULL); + + conditional_format->type = LXW_CONDITIONAL_TYPE_DUPLICATE; + conditional_format->format = format1; + worksheet_conditional_format_range(worksheet3, RANGE("B3:K12"), conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_UNIQUE; + conditional_format->format = format2; + worksheet_conditional_format_range(worksheet3, RANGE("B3:K12"), conditional_format); + + + /* + * Example 4. Conditional formatting with above and below average values. + */ + + write_worksheet_data(worksheet4); + + worksheet_write_string(worksheet4, + CELL("A1"), + "Above average values are in light red. " + "Below average values are in light green.", + NULL); + + + conditional_format->type = LXW_CONDITIONAL_TYPE_AVERAGE; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE; + conditional_format->format = format1; + worksheet_conditional_format_range(worksheet4, RANGE("B3:K12"), conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_AVERAGE; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW; + conditional_format->format = format2; + worksheet_conditional_format_range(worksheet4, RANGE("B3:K12"), conditional_format); + + + /* + * Example 5. Conditional formatting with top and bottom values. + */ + + write_worksheet_data(worksheet5); + + worksheet_write_string(worksheet5, + CELL("A1"), + "Top 10 values are in light red. " + "Bottom 10 values are in light green.", + NULL); + + conditional_format->type = LXW_CONDITIONAL_TYPE_TOP; + conditional_format->value = 10; + conditional_format->format = format1; + worksheet_conditional_format_range(worksheet5, RANGE("B3:K12"), conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_BOTTOM; + conditional_format->value = 10; + conditional_format->format = format2; + worksheet_conditional_format_range(worksheet5, RANGE("B3:K12"), conditional_format); + + + /* + * Example 6. Conditional formatting with multiple ranges. + */ + + write_worksheet_data(worksheet6); + + worksheet_write_string(worksheet6, + CELL("A1"), + "Cells with values >= 50 are in light red." + "Values < 50 are in light green. Non-contiguous ranges.", + NULL); + + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_GREATER_THAN_OR_EQUAL_TO; + conditional_format->value = 50; + conditional_format->format = format1; + conditional_format->multi_range = "B3:K6 B9:K12"; + worksheet_conditional_format_range(worksheet6, RANGE("B3:K12"), conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_LESS_THAN; + conditional_format->value = 50; + conditional_format->format = format2; + conditional_format->multi_range = "B3:K6 B9:K12"; + worksheet_conditional_format_range(worksheet6, RANGE("B3:K12"), conditional_format); + + /* Reset the options before the next example. */ + reset_conditional_format(conditional_format); + + + /* + * Example 7. Conditional formatting with 2 color scales. + */ + + /* Write the worksheet data. */ + for (int i = 1; i <= 12; i++) { + worksheet_write_number(worksheet7, i + 1, 1, i, NULL); + worksheet_write_number(worksheet7, i + 1, 3, i, NULL); + worksheet_write_number(worksheet7, i + 1, 6, i, NULL); + worksheet_write_number(worksheet7, i + 1, 8, i, NULL); + } + + + worksheet_write_string(worksheet7, + CELL("A1"), + "Examples of color scales with default and user colors.", + NULL); + + worksheet_write_string(worksheet7, CELL("B2"), "2 Color Scale", NULL); + worksheet_write_string(worksheet7, CELL("D2"), "2 Color Scale + user colors", NULL); + worksheet_write_string(worksheet7, CELL("G2"), "3 Color Scale", NULL); + worksheet_write_string(worksheet7, CELL("I2"), "3 Color Scale + user colors", NULL); + + /* 2 color scale with standard colors. */ + conditional_format->type = LXW_CONDITIONAL_2_COLOR_SCALE; + worksheet_conditional_format_range(worksheet7, RANGE("B3:B14"), conditional_format); + + /* 2 color scale with user defined colors. */ + conditional_format->type = LXW_CONDITIONAL_2_COLOR_SCALE; + conditional_format->min_color = 0xFF0000; + conditional_format->max_color = 0x00FF00; + worksheet_conditional_format_range(worksheet7, RANGE("D3:D14"), conditional_format); + + /* Reset the colors before the next example. */ + reset_conditional_format(conditional_format); + + /* 3 color scale with standard colors. */ + conditional_format->type = LXW_CONDITIONAL_3_COLOR_SCALE; + worksheet_conditional_format_range(worksheet7, RANGE("G3:G14"), conditional_format); + + /* 3 color scale with user defined colors. */ + conditional_format->type = LXW_CONDITIONAL_3_COLOR_SCALE; + conditional_format->min_color = 0xC5D9F1; + conditional_format->mid_color = 0x8DB4E3; + conditional_format->max_color = 0x538ED5; + worksheet_conditional_format_range(worksheet7, RANGE("I3:I14"), conditional_format); + reset_conditional_format(conditional_format); + + /* + * Example 8. Conditional formatting with data bars. + */ + + /* Write the worksheet data. */ + for (int i = 1; i <= 12; i++) { + worksheet_write_number(worksheet8, i + 1, 1, i, NULL); + worksheet_write_number(worksheet8, i + 1, 3, i, NULL); + worksheet_write_number(worksheet8, i + 1, 5, i, NULL); + worksheet_write_number(worksheet8, i + 1, 7, i, NULL); + worksheet_write_number(worksheet8, i + 1, 9, i, NULL); + } + + int data[] = {-1, -2, -3, -2, -1, 0, 1, 2, 3, 2, 1, 0}; + for (int i = 1; i <= 12; i++) { + worksheet_write_number(worksheet8, i + 1, 11, data[i -1], NULL); + worksheet_write_number(worksheet8, i + 1, 13, data[i -1], NULL); + } + + worksheet_write_string(worksheet8, + CELL("A1"), + "Examples of data bars.", + NULL); + + worksheet_write_string(worksheet8, CELL("B2"), "Default data bars", NULL); + worksheet_write_string(worksheet8, CELL("D2"), "Bars only", NULL); + worksheet_write_string(worksheet8, CELL("F2"), "With user color", NULL); + worksheet_write_string(worksheet8, CELL("H2"), "Solid bars", NULL); + worksheet_write_string(worksheet8, CELL("J2"), "Right to left", NULL); + worksheet_write_string(worksheet8, CELL("L2"), "Excel 2010 style", NULL); + worksheet_write_string(worksheet8, CELL("N2"), "Negative same as positive", NULL); + + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + worksheet_conditional_format_range(worksheet8, RANGE("B3:B14"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + conditional_format->bar_only = LXW_TRUE; + worksheet_conditional_format_range(worksheet8, RANGE("D3:D14"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + conditional_format->bar_color = 0x63C384; + worksheet_conditional_format_range(worksheet8, RANGE("F3:F14"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + conditional_format->bar_solid = LXW_TRUE; + worksheet_conditional_format_range(worksheet8, RANGE("H3:H14"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + conditional_format->bar_direction = LXW_CONDITIONAL_BAR_DIRECTION_RIGHT_TO_LEFT; + worksheet_conditional_format_range(worksheet8, RANGE("J3:J14"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + conditional_format->data_bar_2010 = LXW_TRUE; + worksheet_conditional_format_range(worksheet8, RANGE("L3:L14"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_DATA_BAR; + conditional_format->bar_negative_color_same = LXW_TRUE; + conditional_format->bar_negative_border_color_same = LXW_TRUE; + worksheet_conditional_format_range(worksheet8, RANGE("N3:N14"), conditional_format); + reset_conditional_format(conditional_format); + + + /* + * Example 9. Conditional formatting with icon sets. + */ + + /* Write the worksheet data. */ + for (int i = 1; i <= 3; i++) { + worksheet_write_number(worksheet9, 2, i, i, NULL); + worksheet_write_number(worksheet9, 3, i, i, NULL); + worksheet_write_number(worksheet9, 4, i, i, NULL); + worksheet_write_number(worksheet9, 5, i, i, NULL); + } + + for (int i = 1; i <= 4; i++) { + worksheet_write_number(worksheet9, 6, i, i, NULL); + } + + for (int i = 1; i <= 5; i++) { + worksheet_write_number(worksheet9, 7, i, i, NULL); + worksheet_write_number(worksheet9, 8, i, i, NULL); + } + + + worksheet_write_string(worksheet9, + CELL("A1"), + "Examples of conditional formats with icon sets.", + NULL); + + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED; + worksheet_conditional_format_range(worksheet9, RANGE("B3:D3"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED; + conditional_format->reverse_icons = LXW_TRUE; + worksheet_conditional_format_range(worksheet9, RANGE("B4:D4"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED; + conditional_format->icons_only = LXW_TRUE; + worksheet_conditional_format_range(worksheet9, RANGE("B5:D5"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_3_ARROWS_COLORED; + worksheet_conditional_format_range(worksheet9, RANGE("B6:D6"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED; + worksheet_conditional_format_range(worksheet9, RANGE("B7:E7"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED; + worksheet_conditional_format_range(worksheet9, RANGE("B8:F8"), conditional_format); + reset_conditional_format(conditional_format); + + conditional_format->type = LXW_CONDITIONAL_TYPE_ICON_SETS; + conditional_format->icon_style = LXW_CONDITIONAL_ICONS_5_RATINGS; + worksheet_conditional_format_range(worksheet9, RANGE("B9:F9"), conditional_format); + reset_conditional_format(conditional_format); + + + free(conditional_format); + return workbook_close(workbook); +} diff --git a/libxlsxwriter/examples/headers_footers.c b/libxlsxwriter/examples/headers_footers.c index 768888e..8f30887 100644 --- a/libxlsxwriter/examples/headers_footers.c +++ b/libxlsxwriter/examples/headers_footers.c @@ -9,12 +9,14 @@ * &L Justification Left * &C Center * &R Right + * * &P Information Page number * &N Total number of pages * &D Date * &T Time * &F File name * &A Worksheet name + * * &fontsize Font Font size * &"font,style" Font name and style * &U Single underline @@ -22,8 +24,10 @@ * &S Strikethrough * &X Superscript * &Y Subscript + * * &[Picture] Images Image placeholder * &G Same as &[Picture] + * * && Miscellaneous Literal ampersand & * * Copyright 2014-2018, John McNamara, jmcnamara@cpan.org @@ -54,29 +58,25 @@ int main() { /* - * This is an example of some of the header/footer variables. + * A simple example to start */ - lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, "Variables"); - char header2[] = "&LPage &P of &N" "&CFilename: &F" "&RSheetname: &A"; - char footer2[] = "&LCurrent date: &D" "&RCurrent time: &T"; - lxw_row_t breaks[] = {20, 0}; + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, "Image"); + lxw_header_footer_options header_options = {.image_left = "logo_small.png"}; - worksheet_set_header(worksheet2, header2); - worksheet_set_footer(worksheet2, footer2); + worksheet_set_header_opt(worksheet2, "&L&[Picture]", &header_options); + worksheet_set_margins(worksheet2, -1, -1, 1.3, -1); worksheet_set_column(worksheet2, 0, 0, 50, NULL); worksheet_write_string(worksheet2, 0, 0, preview, NULL); - worksheet_set_h_pagebreaks(worksheet2, breaks); - worksheet_write_string(worksheet2, 20, 0, "Next page", NULL); - /* - * This example shows how to use more than one font. + * This is an example of some of the header/footer variables. */ - lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, "Mixed fonts"); - char header3[] = "&C&\"Courier New,Bold\"Hello &\"Arial,Italic\"World"; - char footer3[] = "&C&\"Symbol\"e&\"Arial\" = mc&X2"; + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, "Variables"); + char header3[] = "&LPage &P of &N" "&CFilename: &F" "&RSheetname: &A"; + char footer3[] = "&LCurrent date: &D" "&RCurrent time: &T"; + lxw_row_t breaks[] = {20, 0}; worksheet_set_header(worksheet3, header3); worksheet_set_footer(worksheet3, footer3); @@ -84,24 +84,29 @@ int main() { worksheet_set_column(worksheet3, 0, 0, 50, NULL); worksheet_write_string(worksheet3, 0, 0, preview, NULL); + worksheet_set_h_pagebreaks(worksheet3, breaks); + worksheet_write_string(worksheet3, 20, 0, "Next page", NULL); + /* - * Example of line wrapping. + * This example shows how to use more than one font. */ - lxw_worksheet *worksheet4 = workbook_add_worksheet(workbook, "Word wrap"); - char header4[] = "&CHeading 1\nHeading 2"; + lxw_worksheet *worksheet4 = workbook_add_worksheet(workbook, "Mixed fonts"); + char header4[] = "&C&\"Courier New,Bold\"Hello &\"Arial,Italic\"World"; + char footer4[] = "&C&\"Symbol\"e&\"Arial\" = mc&X2"; worksheet_set_header(worksheet4, header4); + worksheet_set_footer(worksheet4, footer4); worksheet_set_column(worksheet4, 0, 0, 50, NULL); worksheet_write_string(worksheet4, 0, 0, preview, NULL); /* - * Example of inserting a literal ampersand & + * Example of line wrapping. */ - lxw_worksheet *worksheet5 = workbook_add_worksheet(workbook, "Ampersand"); - char header5[] = "&CCuriouser && Curiouser - Attorneys at Law"; + lxw_worksheet *worksheet5 = workbook_add_worksheet(workbook, "Word wrap"); + char header5[] = "&CHeading 1\nHeading 2"; worksheet_set_header(worksheet5, header5); @@ -109,6 +114,18 @@ int main() { worksheet_write_string(worksheet5, 0, 0, preview, NULL); + /* + * Example of inserting a literal ampersand & + */ + lxw_worksheet *worksheet6 = workbook_add_worksheet(workbook, "Ampersand"); + char header6[] = "&CCuriouser && Curiouser - Attorneys at Law"; + + worksheet_set_header(worksheet6, header6); + + worksheet_set_column(worksheet6, 0, 0, 50, NULL); + worksheet_write_string(worksheet6, 0, 0, preview, NULL); + + workbook_close(workbook); return 0; diff --git a/libxlsxwriter/examples/ignore_errors.c b/libxlsxwriter/examples/ignore_errors.c new file mode 100644 index 0000000..cd40c97 --- /dev/null +++ b/libxlsxwriter/examples/ignore_errors.c @@ -0,0 +1,38 @@ +/* + * An example of turning off worksheet cells errors/warnings using + * libxlsxwriter. + * + * Copyright 2014-2020, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("ignore_errors.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + /* Write strings that looks like numbers. This will cause an Excel warning. */ + worksheet_write_string(worksheet, CELL("C2"), "123", NULL); + worksheet_write_string(worksheet, CELL("C3"), "123", NULL); + + /* Write a divide by zero formula. This will also cause an Excel warning. */ + worksheet_write_formula(worksheet, CELL("C5"), "=1/0", NULL); + worksheet_write_formula(worksheet, CELL("C6"), "=1/0", NULL); + + /* Turn off some of the warnings: */ + worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "C3"); + worksheet_ignore_errors(worksheet, LXW_IGNORE_EVAL_ERROR, "C6"); + + /* Write some descriptions for the cells and make the column wider for clarity. */ + worksheet_set_column(worksheet, 1, 1, 16, NULL); + worksheet_write_string(worksheet, CELL("B2"), "Warning:", NULL); + worksheet_write_string(worksheet, CELL("B3"), "Warning turned off:", NULL); + worksheet_write_string(worksheet, CELL("B5"), "Warning:", NULL); + worksheet_write_string(worksheet, CELL("B6"), "Warning turned off:", NULL); + + workbook_close(workbook); + + return 0; +} diff --git a/libxlsxwriter/examples/logo_small.png b/libxlsxwriter/examples/logo_small.png new file mode 100644 index 0000000..779f4d3 Binary files /dev/null and b/libxlsxwriter/examples/logo_small.png differ diff --git a/libxlsxwriter/include/xlsxwriter.h b/libxlsxwriter/include/xlsxwriter.h index 84cd367..0a5a026 100644 --- a/libxlsxwriter/include/xlsxwriter.h +++ b/libxlsxwriter/include/xlsxwriter.h @@ -18,7 +18,7 @@ #include "xlsxwriter/format.h" #include "xlsxwriter/utility.h" -#define LXW_VERSION "0.9.4" -#define LXW_VERSION_ID 94 +#define LXW_VERSION "1.0.0" +#define LXW_VERSION_ID 100 #endif /* __LXW_XLSXWRITER_H__ */ diff --git a/libxlsxwriter/include/xlsxwriter/chart.h b/libxlsxwriter/include/xlsxwriter/chart.h index 95771fc..3d0075b 100644 --- a/libxlsxwriter/include/xlsxwriter/chart.h +++ b/libxlsxwriter/include/xlsxwriter/chart.h @@ -124,6 +124,12 @@ typedef enum lxw_chart_type { /** Line chart. */ LXW_CHART_LINE, + /** Line chart - stacked. */ + LXW_CHART_LINE_STACKED, + + /** Line chart - percentage stacked. */ + LXW_CHART_LINE_STACKED_PERCENT, + /** Pie chart. */ LXW_CHART_PIE, @@ -781,6 +787,54 @@ typedef struct lxw_chart_point { } lxw_chart_point; +/** + * @brief Struct to represent an Excel chart data label. + * + * The lxw_chart_data_label struct is used to represent a data label in a + * chart series so that custom properties can be set for it. + */ +typedef struct lxw_chart_data_label { + + /** The string or formula value for the data label. See + * @ref chart_custom_labels. */ + char *value; + + /** Option to hide/delete the data label from the chart series. + * See @ref chart_custom_labels. */ + uint8_t hide; + + /** The font properties for the chart data label. @ref chart_fonts. */ + lxw_chart_font *font; + + /** The line/border for the chart data label. See @ref chart_lines. */ + lxw_chart_line *line; + + /** The fill for the chart data label. See @ref chart_fills. */ + lxw_chart_fill *fill; + + /** The pattern for the chart data label. See @ref chart_patterns.*/ + lxw_chart_pattern *pattern; + +} lxw_chart_data_label; + +/* Internal version of lxw_chart_data_label with more metadata. */ +typedef struct lxw_chart_custom_label { + + char *value; + uint8_t hide; + lxw_chart_font *font; + lxw_chart_line *line; + lxw_chart_fill *fill; + lxw_chart_pattern *pattern; + + /* We use a range to hold the label formula properties even though it + * will only have 1 point in order to re-use similar functions.*/ + lxw_series_range *range; + + struct lxw_series_data_point data_point; + +} lxw_chart_custom_label; + /** * @brief Define how blank values are displayed in a chart. */ @@ -910,7 +964,9 @@ typedef struct lxw_chart_series { lxw_chart_pattern *pattern; lxw_chart_marker *marker; lxw_chart_point *points; + lxw_chart_custom_label *data_labels; uint16_t point_count; + uint16_t data_label_count; uint8_t smooth; uint8_t invert_if_negative; @@ -928,6 +984,9 @@ typedef struct lxw_chart_series { uint8_t default_label_position; char *label_num_format; lxw_chart_font *label_font; + lxw_chart_line *label_line; + lxw_chart_fill *label_fill; + lxw_chart_pattern *label_pattern; lxw_series_error_bars *x_error_bars; lxw_series_error_bars *y_error_bars; @@ -1032,8 +1091,8 @@ typedef struct lxw_chart { uint8_t subtype; uint16_t series_index; - void (*write_chart_type) (struct lxw_chart *); - void (*write_plot_area) (struct lxw_chart *); + void (*write_chart_type)(struct lxw_chart *); + void (*write_plot_area)(struct lxw_chart *); /** * A pointer to the chart x_axis object which can be used in functions @@ -1616,11 +1675,11 @@ void chart_series_set_smooth(lxw_chart_series *series, uint8_t smooth); * chart_series_set_labels(series); * @endcode * - * @image html chart_labels1.png + * @image html chart_data_labels1.png * * By default data labels are displayed in Excel with only the values shown: * - * @image html chart_labels2.png + * @image html chart_data_labels2.png * * However, it is possible to configure other display options, as shown * in the functions below. @@ -1645,7 +1704,7 @@ void chart_series_set_labels(lxw_chart_series *series); * chart_series_set_labels_options(series, LXW_TRUE, LXW_TRUE, LXW_TRUE); * @endcode * - * @image html chart_labels3.png + * @image html chart_data_labels3.png * * For more information see @ref chart_labels. */ @@ -1653,6 +1712,63 @@ void chart_series_set_labels_options(lxw_chart_series *series, uint8_t show_name, uint8_t show_category, uint8_t show_value); +/** @brief Set the properties for data labels in a series. +* +* @param series A series object created via `chart_add_series()`. +* @param data_labels An NULL terminated array of #lxw_chart_data_label pointers. +* +* @return A #lxw_error. +* +* The `%chart_series_set_labels_custom()` function is used to set the properties +* for data labels in a series. It can also be used to delete individual data +* labels in a series. +* +* In general properties are set for all the data labels in a chart +* series. However, it is also possible to set properties for individual data +* labels in a series using `%chart_series_set_labels_custom()`. +* +* The `%chart_series_set_labels_custom()` function takes a pointer to an array +* of #lxw_chart_data_label pointers. The list should be `NULL` terminated: +* +* @code +* // Add the series data labels. +* chart_series_set_labels(series); +* +* // Create some custom labels. +* lxw_chart_data_label data_label1 = {.value = "Jan"}; +* lxw_chart_data_label data_label2 = {.value = "Feb"}; +* lxw_chart_data_label data_label3 = {.value = "Mar"}; +* lxw_chart_data_label data_label4 = {.value = "Apr"}; +* lxw_chart_data_label data_label5 = {.value = "May"}; +* lxw_chart_data_label data_label6 = {.value = "Jun"}; +* +* // Create an array of label pointers. NULL indicates the end of the array. +* lxw_chart_data_label *data_labels[] = { +* &data_label1, +* &data_label2, +* &data_label3, +* &data_label4, +* &data_label5, +* &data_label6, +* NULL +* }; +* +* // Set the custom labels. +* chart_series_set_labels_custom(series, data_labels); +* @endcode +* +* @image html chart_data_labels18.png +* +* @note The array of #lxw_chart_point pointers should be NULL terminated as +* shown in the example. Any #lxw_chart_data_label items set to a default +* initialization or omitted from the list will be assigned the default data +* label value. +* +* For more details see @ref chart_custom_labels. +*/ +lxw_error chart_series_set_labels_custom(lxw_chart_series *series, lxw_chart_data_label + *data_labels[]); + /** * @brief Set the separator for the data label captions. * @@ -1679,7 +1795,7 @@ void chart_series_set_labels_options(lxw_chart_series *series, * chart_series_set_labels_separator(series, LXW_CHART_LABEL_SEPARATOR_NEWLINE); * @endcode * - * @image html chart_labels4.png + * @image html chart_data_labels4.png * * For more information see @ref chart_labels. */ @@ -1700,7 +1816,7 @@ void chart_series_set_labels_separator(lxw_chart_series *series, * chart_series_set_labels_position(series, LXW_CHART_LABEL_POSITION_ABOVE); * @endcode * - * @image html chart_labels5.png + * @image html chart_data_labels5.png * * In Excel the allowable data label positions vary for different chart * types. The allowable, and default, positions are: @@ -1760,7 +1876,7 @@ void chart_series_set_labels_leader_line(lxw_chart_series *series); * chart_series_set_labels_legend(series); * @endcode * - * @image html chart_labels6.png + * @image html chart_data_labels6.png * * For more information see @ref chart_labels. */ @@ -1781,7 +1897,7 @@ void chart_series_set_labels_legend(lxw_chart_series *series); * chart_series_set_labels_percentage(series); * @endcode * - * @image html chart_labels7.png + * @image html chart_data_labels7.png * * For more information see @ref chart_labels. */ @@ -1801,7 +1917,7 @@ void chart_series_set_labels_percentage(lxw_chart_series *series); * chart_series_set_labels_num_format(series, "$0.00"); * @endcode * - * @image html chart_labels8.png + * @image html chart_data_labels8.png * * The number format is similar to the Worksheet Cell Format num_format, * see `format_set_num_format()`. @@ -1828,7 +1944,7 @@ void chart_series_set_labels_num_format(lxw_chart_series *series, * chart_series_set_labels_font(series, &font); * @endcode * - * @image html chart_labels9.png + * @image html chart_data_labels9.png * * For more information see @ref chart_fonts and @ref chart_labels. * @@ -1836,6 +1952,67 @@ void chart_series_set_labels_num_format(lxw_chart_series *series, void chart_series_set_labels_font(lxw_chart_series *series, lxw_chart_font *font); +/** + * @brief Set the line properties for the data labels in a chart series. + * + * @param series A series object created via `chart_add_series()`. + * @param line A #lxw_chart_line struct. + * + * Set the line/border properties of the data labels in a chart series: + * + * @code + * lxw_chart_line line = {.color = LXW_COLOR_RED}; + * lxw_chart_fill fill = {.color = LXW_COLOR_YELLOW}; + * + * chart_series_set_labels_line(series, &line); + * chart_series_set_labels_fill(series, &fill); + * + * @endcode + * + * @image html chart_data_labels24.png + * + * For more information see @ref chart_lines and @ref chart_labels. + */ +void chart_series_set_labels_line(lxw_chart_series *series, + lxw_chart_line *line); + +/** + * @brief Set the fill properties for the data labels in a chart series. + * + * @param series A series object created via `chart_add_series()`. + * @param fill A #lxw_chart_fill struct. + * + * Set the fill properties of the data labels in a chart series: + * + * @code + * lxw_chart_fill fill = {.color = LXW_COLOR_YELLOW}; + * + * chart_series_set_labels_fill(series, &fill); + * @endcode + * + * See the example and image above and also see @ref chart_fills and + * @ref chart_labels. + */ +void chart_series_set_labels_fill(lxw_chart_series *series, + lxw_chart_fill *fill); + +/** + * @brief Set the pattern properties for the data labels in a chart series. + * + * @param series A series object created via `chart_add_series()`. + * @param pattern A #lxw_chart_pattern struct. + * + * Set the pattern properties of the data labels in a chart series: + * + * @code + * chart_series_set_labels_pattern(series, &pattern); + * @endcode + * + * For more information see #lxw_chart_pattern_type and @ref chart_patterns. + */ +void chart_series_set_labels_pattern(lxw_chart_series *series, + lxw_chart_pattern *pattern); + /** * @brief Turn on a trendline for a chart data series. * diff --git a/libxlsxwriter/include/xlsxwriter/common.h b/libxlsxwriter/include/xlsxwriter/common.h index 3896ef8..62f9c61 100644 --- a/libxlsxwriter/include/xlsxwriter/common.h +++ b/libxlsxwriter/include/xlsxwriter/common.h @@ -115,9 +115,6 @@ typedef enum lxw_error { /** Worksheet name is already in use. */ LXW_ERROR_SHEETNAME_ALREADY_USED, - /** Worksheet name 'History' is reserved by Excel. */ - LXW_ERROR_SHEETNAME_RESERVED, - /** Parameter exceeds Excel's limit of 32 characters. */ LXW_ERROR_32_STRING_LENGTH_EXCEEDED, @@ -205,6 +202,9 @@ enum lxw_custom_property_types { /* Datetime string length. */ #define LXW_DATETIME_LENGTH sizeof("2016-12-12T23:00:00Z") +/* GUID string length. */ +#define LXW_GUID_LENGTH sizeof("{12345678-1234-1234-1234-1234567890AB}") + #define LXW_EPOCH_1900 0 #define LXW_EPOCH_1904 1 diff --git a/libxlsxwriter/include/xlsxwriter/format.h b/libxlsxwriter/include/xlsxwriter/format.h index ead11f2..2f7b52d 100644 --- a/libxlsxwriter/include/xlsxwriter/format.h +++ b/libxlsxwriter/include/xlsxwriter/format.h @@ -350,7 +350,9 @@ typedef struct lxw_format { FILE *file; lxw_hash_table *xf_format_indices; + lxw_hash_table *dxf_format_indices; uint16_t *num_xf_formats; + uint16_t *num_dxf_formats; int32_t xf_index; int32_t dxf_index; @@ -390,6 +392,8 @@ typedef struct lxw_format { lxw_color_t fg_color; lxw_color_t bg_color; + lxw_color_t dxf_fg_color; + lxw_color_t dxf_bg_color; uint8_t pattern; uint8_t has_fill; uint8_t has_dxf_fill; @@ -487,6 +491,7 @@ extern "C" { lxw_format *lxw_format_new(void); void lxw_format_free(lxw_format *format); int32_t lxw_format_get_xf_index(lxw_format *format); +int32_t lxw_format_get_dxf_index(lxw_format *format); lxw_font *lxw_format_get_font_key(lxw_format *format); lxw_border *lxw_format_get_border_key(lxw_format *format); lxw_fill *lxw_format_get_fill_key(lxw_format *format); diff --git a/libxlsxwriter/include/xlsxwriter/styles.h b/libxlsxwriter/include/xlsxwriter/styles.h index b2516c6..2757b02 100644 --- a/libxlsxwriter/include/xlsxwriter/styles.h +++ b/libxlsxwriter/include/xlsxwriter/styles.h @@ -58,12 +58,13 @@ STATIC void _write_font_name(lxw_styles *self, const char *font_name, uint8_t is_rich_string); STATIC void _write_font_family(lxw_styles *self, uint8_t font_family); STATIC void _write_font_scheme(lxw_styles *self, const char *font_scheme); -STATIC void _write_font(lxw_styles *self, lxw_format *format, +STATIC void _write_font(lxw_styles *self, lxw_format *format, uint8_t is_dxf, uint8_t is_rich_string); STATIC void _write_fonts(lxw_styles *self); STATIC void _write_default_fill(lxw_styles *self, const char *pattern); STATIC void _write_fills(lxw_styles *self); -STATIC void _write_border(lxw_styles *self, lxw_format *format); +STATIC void _write_border(lxw_styles *self, lxw_format *format, + uint8_t is_dxf); STATIC void _write_borders(lxw_styles *self); STATIC void _write_style_xf(lxw_styles *self, uint8_t has_hyperlink, uint16_t font_id); diff --git a/libxlsxwriter/include/xlsxwriter/utility.h b/libxlsxwriter/include/xlsxwriter/utility.h index 8583f1c..b5f8db2 100644 --- a/libxlsxwriter/include/xlsxwriter/utility.h +++ b/libxlsxwriter/include/xlsxwriter/utility.h @@ -171,10 +171,32 @@ void lxw_rowcol_to_formula_abs(char *formula, const char *sheetname, uint32_t lxw_name_to_row(const char *row_str); uint16_t lxw_name_to_col(const char *col_str); + uint32_t lxw_name_to_row_2(const char *row_str); uint16_t lxw_name_to_col_2(const char *col_str); -double lxw_datetime_to_excel_date(lxw_datetime *datetime, uint8_t date_1904); +/** + * @brief Converts a #lxw_datetime to an Excel datetime number. + * + * @param datetime A pointer to a #lxw_datetime struct. + * + * @return A double representing an Excel datetime. + * + * The `%lxw_datetime_to_excel_datetime()` function converts a datetime in + * #lxw_datetime to and Excel datetime number: + * + * @code + * lxw_datetime datetime = {2013, 2, 28, 12, 0, 0.0}; + * + * double excel_datetime = lxw_datetime_to_excel_date(&datetime); + * @endcode + * + * See @ref working_with_dates for more details on the Excel datetime format. + */ +double lxw_datetime_to_excel_datetime(lxw_datetime *datetime); + +double lxw_datetime_to_excel_date_epoch(lxw_datetime *datetime, + uint8_t date_1904); char *lxw_strdup(const char *str); char *lxw_strdup_formula(const char *formula); diff --git a/libxlsxwriter/include/xlsxwriter/workbook.h b/libxlsxwriter/include/xlsxwriter/workbook.h index 73fa3a0..aff1eee 100644 --- a/libxlsxwriter/include/xlsxwriter/workbook.h +++ b/libxlsxwriter/include/xlsxwriter/workbook.h @@ -279,6 +279,7 @@ typedef struct lxw_workbook { struct lxw_worksheet_names *worksheet_names; struct lxw_chartsheet_names *chartsheet_names; struct lxw_image_md5s *image_md5s; + struct lxw_image_md5s *header_image_md5s; struct lxw_charts *charts; struct lxw_charts *ordered_charts; struct lxw_formats *formats; @@ -296,6 +297,7 @@ typedef struct lxw_workbook { uint16_t first_sheet; uint16_t active_sheet; uint16_t num_xf_formats; + uint16_t num_dxf_formats; uint16_t num_format_count; uint16_t drawing_count; uint16_t comment_count; @@ -313,6 +315,7 @@ typedef struct lxw_workbook { uint8_t has_comments; lxw_hash_table *used_xf_formats; + lxw_hash_table *used_dxf_formats; char *vba_project; char *vba_codename; @@ -428,12 +431,15 @@ lxw_workbook *workbook_new_opt(const char *filename, * - The name is less than or equal to 31 UTF-8 characters. * - The name doesn't contain any of the characters: ` [ ] : * ? / \ ` * - The name doesn't start or end with an apostrophe. - * - The name isn't "History", which is reserved by Excel. (Case insensitive). * - The name isn't already in use. (Case insensitive). * * If any of these errors are encountered the function will return NULL. * You can check for valid name using the `workbook_validate_sheet_name()` * function. + * + * @note You should also avoid using the worksheet name "History" (case + * insensitive) which is reserved in English language versions of + * Excel. Non-English versions may have restrictions on the equivalent word. */ lxw_worksheet *workbook_add_worksheet(lxw_workbook *workbook, const char *sheetname); @@ -467,13 +473,16 @@ lxw_worksheet *workbook_add_worksheet(lxw_workbook *workbook, * - The name is less than or equal to 31 UTF-8 characters. * - The name doesn't contain any of the characters: ` [ ] : * ? / \ ` * - The name doesn't start or end with an apostrophe. - * - The name isn't "History", which is reserved by Excel. (Case insensitive). * - The name isn't already in use. (Case insensitive). * * If any of these errors are encountered the function will return NULL. * You can check for valid name using the `workbook_validate_sheet_name()` * function. * + * @note You should also avoid using the worksheet name "History" (case + * insensitive) which is reserved in English language versions of + * Excel. Non-English versions may have restrictions on the equivalent word. + * * At least one worksheet should be added to a new workbook when creating a * chartsheet in order to provide data for the chart. The @ref worksheet.h * "Worksheet" object is used to write data and configure a worksheet in the @@ -551,6 +560,8 @@ lxw_format *workbook_add_format(lxw_workbook *workbook); * | #LXW_CHART_COLUMN_STACKED_PERCENT | Column chart - percentage stacked. | * | #LXW_CHART_DOUGHNUT | Doughnut chart. | * | #LXW_CHART_LINE | Line chart. | + * | #LXW_CHART_LINE_STACKED | Line chart - stacked. | + * | #LXW_CHART_LINE_STACKED_PERCENT | Line chart - percentage stacked. | * | #LXW_CHART_PIE | Pie chart. | * | #LXW_CHART_SCATTER | Scatter chart. | * | #LXW_CHART_SCATTER_STRAIGHT | Scatter chart - straight. | @@ -878,7 +889,6 @@ lxw_chartsheet *workbook_get_chartsheet_by_name(lxw_workbook *workbook, * - The name is less than or equal to 31 UTF-8 characters. * - The name doesn't contain any of the characters: ` [ ] : * ? / \ ` * - The name doesn't start or end with an apostrophe. - * - The name isn't "History", which is reserved by Excel. (Case insensitive). * - The name isn't already in use. (Case insensitive, see the note below). * * @code @@ -889,6 +899,10 @@ lxw_chartsheet *workbook_get_chartsheet_by_name(lxw_workbook *workbook, * `workbook_add_chartsheet()` but it can be explicitly called by the user * beforehand to ensure that the sheet name is valid. * + * @note You should also avoid using the worksheet name "History" (case + * insensitive) which is reserved in English language versions of + * Excel. Non-English versions may have restrictions on the equivalent word. + * * @note This function does an ASCII lowercase string comparison to determine * if the sheet name is already in use. It doesn't take UTF-8 characters into * account. Thus it would flag "Café" and "café" as a duplicate (just like diff --git a/libxlsxwriter/include/xlsxwriter/worksheet.h b/libxlsxwriter/include/xlsxwriter/worksheet.h index c1afd10..456fbfc 100644 --- a/libxlsxwriter/include/xlsxwriter/worksheet.h +++ b/libxlsxwriter/include/xlsxwriter/worksheet.h @@ -46,7 +46,6 @@ #include #include #include -#include #include "shared_strings.h" #include "chart.h" @@ -57,13 +56,14 @@ #include "utility.h" #include "relationships.h" -#define LXW_ROW_MAX 1048576 -#define LXW_COL_MAX 16384 -#define LXW_COL_META_MAX 128 -#define LXW_HEADER_FOOTER_MAX 255 -#define LXW_MAX_NUMBER_URLS 65530 -#define LXW_PANE_NAME_LENGTH 12 /* bottomRight + 1 */ -#define LXW_IMAGE_BUFFER_SIZE 1024 +#define LXW_ROW_MAX 1048576 +#define LXW_COL_MAX 16384 +#define LXW_COL_META_MAX 128 +#define LXW_HEADER_FOOTER_MAX 255 +#define LXW_MAX_NUMBER_URLS 65530 +#define LXW_PANE_NAME_LENGTH 12 /* bottomRight + 1 */ +#define LXW_IMAGE_BUFFER_SIZE 1024 +#define LXW_HEADER_FOOTER_OBJS_MAX 6 /* Header/footer image objs. */ /* The Excel 2007 specification says that the maximum number of page * breaks is 1026. However, in practice it is actually 1023. */ @@ -218,6 +218,318 @@ enum lxw_comment_display_types { LXW_COMMENT_DISPLAY_VISIBLE }; +/** @brief Type definitions for conditional formats. + * + * Values used to set the "type" field of conditional format. + */ +enum lxw_conditional_format_types { + LXW_CONDITIONAL_TYPE_NONE, + + /** The Cell type is the most common conditional formatting type. It is + * used when a format is applied to a cell based on a simple + * criterion. */ + LXW_CONDITIONAL_TYPE_CELL, + + /** The Text type is used to specify Excel's "Specific Text" style + * conditional format. */ + LXW_CONDITIONAL_TYPE_TEXT, + + /** The Time Period type is used to specify Excel's "Dates Occurring" + * style conditional format. */ + LXW_CONDITIONAL_TYPE_TIME_PERIOD, + + /** The Average type is used to specify Excel's "Average" style + * conditional format. */ + LXW_CONDITIONAL_TYPE_AVERAGE, + + /** The Duplicate type is used to highlight duplicate cells in a range. */ + LXW_CONDITIONAL_TYPE_DUPLICATE, + + /** The Unique type is used to highlight unique cells in a range. */ + LXW_CONDITIONAL_TYPE_UNIQUE, + + /** The Top type is used to specify the top n values by number or + * percentage in a range. */ + LXW_CONDITIONAL_TYPE_TOP, + + /** The Bottom type is used to specify the bottom n values by number or + * percentage in a range. */ + LXW_CONDITIONAL_TYPE_BOTTOM, + + /** The Blanks type is used to highlight blank cells in a range. */ + LXW_CONDITIONAL_TYPE_BLANKS, + + /** The No Blanks type is used to highlight non blank cells in a range. */ + LXW_CONDITIONAL_TYPE_NO_BLANKS, + + /** The Errors type is used to highlight error cells in a range. */ + LXW_CONDITIONAL_TYPE_ERRORS, + + /** The No Errors type is used to highlight non error cells in a range. */ + LXW_CONDITIONAL_TYPE_NO_ERRORS, + + /** The Formula type is used to specify a conditional format based on a + * user defined formula. */ + LXW_CONDITIONAL_TYPE_FORMULA, + + /** The 2 Color Scale type is used to specify Excel's "2 Color Scale" + * style conditional format. */ + LXW_CONDITIONAL_2_COLOR_SCALE, + + /** The 3 Color Scale type is used to specify Excel's "3 Color Scale" + * style conditional format. */ + LXW_CONDITIONAL_3_COLOR_SCALE, + + /** The Data Bar type is used to specify Excel's "Data Bar" style + * conditional format. */ + LXW_CONDITIONAL_DATA_BAR, + + /** The Icon Set type is used to specify a conditional format with a set + * of icons such as traffic lights or arrows. */ + LXW_CONDITIONAL_TYPE_ICON_SETS, + + LXW_CONDITIONAL_TYPE_LAST +}; + +/** @brief The criteria used in a conditional format. + * + * Criteria used to define how a conditional format works. + */ +enum lxw_conditional_criteria { + LXW_CONDITIONAL_CRITERIA_NONE, + + /** Format cells equal to a value. */ + LXW_CONDITIONAL_CRITERIA_EQUAL_TO, + + /** Format cells not equal to a value. */ + LXW_CONDITIONAL_CRITERIA_NOT_EQUAL_TO, + + /** Format cells greater than a value. */ + LXW_CONDITIONAL_CRITERIA_GREATER_THAN, + + /** Format cells less than a value. */ + LXW_CONDITIONAL_CRITERIA_LESS_THAN, + + /** Format cells greater than or equal to a value. */ + LXW_CONDITIONAL_CRITERIA_GREATER_THAN_OR_EQUAL_TO, + + /** Format cells less than or equal to a value. */ + LXW_CONDITIONAL_CRITERIA_LESS_THAN_OR_EQUAL_TO, + + /** Format cells between two values. */ + LXW_CONDITIONAL_CRITERIA_BETWEEN, + + /** Format cells that is not between two values. */ + LXW_CONDITIONAL_CRITERIA_NOT_BETWEEN, + + /** Format cells that contain the specified text. */ + LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING, + + /** Format cells that don't contain the specified text. */ + LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING, + + /** Format cells that begin with the specified text. */ + LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH, + + /** Format cells that end with the specified text. */ + LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH, + + /** Format cells with a date of yesterday. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY, + + /** Format cells with a date of today. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TODAY, + + /** Format cells with a date of tomorrow. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TOMORROW, + + /** Format cells with a date in the last 7 days. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_7_DAYS, + + /** Format cells with a date in the last week. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_WEEK, + + /** Format cells with a date in the current week. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_WEEK, + + /** Format cells with a date in the next week. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_WEEK, + + /** Format cells with a date in the last month. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_MONTH, + + /** Format cells with a date in the current month. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_MONTH, + + /** Format cells with a date in the next month. */ + LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH, + + /** Format cells above the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE, + + /** Format cells below the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW, + + /** Format cells above or equal to the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE_OR_EQUAL, + + /** Format cells below or equal to the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL, + + /** Format cells 1 standard deviation above the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_ABOVE, + + /** Format cells 1 standard deviation below the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW, + + /** Format cells 2 standard deviation above the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_ABOVE, + + /** Format cells 2 standard deviation below the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW, + + /** Format cells 3 standard deviation above the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_ABOVE, + + /** Format cells 3 standard deviation below the average for the range. */ + LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW, + + /** Format cells in the top of bottom percentage. */ + LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT +}; + +/** @brief Conditional format rule types. + * + * Conditional format rule types that apply to Color Scale and Data Bars. + */ +enum lxw_conditional_format_rule_types { + LXW_CONDITIONAL_RULE_TYPE_NONE, + + /** Conditional format rule type: matches the minimum values in the + * range. Can only be applied to min_rule_type.*/ + LXW_CONDITIONAL_RULE_TYPE_MINIMUM, + + /** Conditional format rule type: use a number to set the bound.*/ + LXW_CONDITIONAL_RULE_TYPE_NUMBER, + + /** Conditional format rule type: use a percentage to set the bound.*/ + LXW_CONDITIONAL_RULE_TYPE_PERCENT, + + /** Conditional format rule type: use a percentile to set the bound.*/ + LXW_CONDITIONAL_RULE_TYPE_PERCENTILE, + + /** Conditional format rule type: use a formula to set the bound.*/ + LXW_CONDITIONAL_RULE_TYPE_FORMULA, + + /** Conditional format rule type: matches the maximum values in the + * range. Can only be applied to max_rule_type.*/ + LXW_CONDITIONAL_RULE_TYPE_MAXIMUM, + + /* Used internally for Excel2010 bars. Not documented. */ + LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN, + + /* Used internally for Excel2010 bars. Not documented. */ + LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX +}; + +/** @brief Conditional format data bar directions. + * + * Values used to set the bar direction of a conditional format data bar. + */ +enum lxw_conditional_format_bar_direction { + + /** Data bar direction is set by Excel based on the context of the data + * displayed. */ + LXW_CONDITIONAL_BAR_DIRECTION_CONTEXT, + + /** Data bar direction is from right to left. */ + LXW_CONDITIONAL_BAR_DIRECTION_RIGHT_TO_LEFT, + + /** Data bar direction is from left to right. */ + LXW_CONDITIONAL_BAR_DIRECTION_LEFT_TO_RIGHT +}; + +/** @brief Conditional format data bar axis options. + * + * Values used to set the position of the axis in a conditional format data + * bar. + */ +enum lxw_conditional_bar_axis_position { + + /** Data bar axis position is set by Excel based on the context of the + * data displayed. */ + LXW_CONDITIONAL_BAR_AXIS_AUTOMATIC, + + /** Data bar axis position is set at the midpoint. */ + LXW_CONDITIONAL_BAR_AXIS_MIDPOINT, + + /** Data bar axis is turned off. */ + LXW_CONDITIONAL_BAR_AXIS_NONE +}; + +/** @brief Icon types used in the #lxw_conditional_format icon_style field. + * + * Definitions of icon styles used with Icon Set conditional formats. + */ +enum lxw_conditional_icon_types { + + /** Icon style: 3 colored arrows showing up, sideways and down. */ + LXW_CONDITIONAL_ICONS_3_ARROWS_COLORED, + + /** Icon style: 3 gray arrows showing up, sideways and down. */ + LXW_CONDITIONAL_ICONS_3_ARROWS_GRAY, + + /** Icon style: 3 colored flags in red, yellow and green. */ + LXW_CONDITIONAL_ICONS_3_FLAGS, + + /** Icon style: 3 traffic lights - rounded. */ + LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED, + + /** Icon style: 3 traffic lights with a rim - squarish. */ + LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_RIMMED, + + /** Icon style: 3 colored shapes - a circle, triangle and diamond. */ + LXW_CONDITIONAL_ICONS_3_SIGNS, + + /** Icon style: 3 circled symbols with tick mark, exclamation and + * cross. */ + LXW_CONDITIONAL_ICONS_3_SYMBOLS_CIRCLED, + + /** Icon style: 3 symbols with tick mark, exclamation and cross. */ + LXW_CONDITIONAL_ICONS_3_SYMBOLS_UNCIRCLED, + + /** Icon style: 3 colored arrows showing up, diagonal up, diagonal down + * and down. */ + LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED, + + /** Icon style: 3 gray arrows showing up, diagonal up, diagonal down and + * down. */ + LXW_CONDITIONAL_ICONS_4_ARROWS_GRAY, + + /** Icon style: 4 circles in 4 colors going from red to black. */ + LXW_CONDITIONAL_ICONS_4_RED_TO_BLACK, + + /** Icon style: 4 histogram ratings. */ + LXW_CONDITIONAL_ICONS_4_RATINGS, + + /** Icon style: 4 traffic lights. */ + LXW_CONDITIONAL_ICONS_4_TRAFFIC_LIGHTS, + + /** Icon style: 5 colored arrows showing up, diagonal up, sideways, + * diagonal down and down. */ + LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED, + + /** Icon style: 5 gray arrows showing up, diagonal up, sideways, diagonal + * down and down. */ + LXW_CONDITIONAL_ICONS_5_ARROWS_GRAY, + + /** Icon style: 5 histogram ratings. */ + LXW_CONDITIONAL_ICONS_5_RATINGS, + + /** Icon style: 5 quarters, from 0 to 4 quadrants filled. */ + LXW_CONDITIONAL_ICONS_5_QUARTERS +}; + /** Options to control the positioning of worksheet objects such as images * or charts. See @ref working_with_object_positioning. */ enum lxw_object_position { @@ -239,6 +551,44 @@ enum lxw_object_position { LXW_OBJECT_MOVE_AND_SIZE_AFTER }; +/** Options for ignoring worksheet errors/warnings. See worksheet_ignore_errors(). */ +enum lxw_ignore_errors { + + /** Turn off errors/warnings for numbers stores as text. */ + LXW_IGNORE_NUMBER_STORED_AS_TEXT = 1, + + /** Turn off errors/warnings for formula errors (such as divide by + * zero). */ + LXW_IGNORE_EVAL_ERROR, + + /** Turn off errors/warnings for formulas that differ from surrounding + * formulas. */ + LXW_IGNORE_FORMULA_DIFFERS, + + /** Turn off errors/warnings for formulas that omit cells in a range. */ + LXW_IGNORE_FORMULA_RANGE, + + /** Turn off errors/warnings for unlocked cells that contain formulas. */ + LXW_IGNORE_FORMULA_UNLOCKED, + + /** Turn off errors/warnings for formulas that refer to empty cells. */ + LXW_IGNORE_EMPTY_CELL_REFERENCE, + + /** Turn off errors/warnings for cells in a table that do not comply with + * applicable data validation rules. */ + LXW_IGNORE_LIST_DATA_VALIDATION, + + /** Turn off errors/warnings for cell formulas that differ from the column + * formula. */ + LXW_IGNORE_CALCULATED_COLUMN, + + /** Turn off errors/warnings for formulas that contain a two digit text + * representation of a year. */ + LXW_IGNORE_TWO_DIGIT_TEXT_YEAR, + + LXW_IGNORE_LAST_OPTION +}; + enum cell_types { NUMBER_CELL = 1, STRING_CELL, @@ -261,9 +611,20 @@ enum pane_types { FREEZE_SPLIT_PANES }; +enum lxw_image_position { + HEADER_LEFT = 0, + HEADER_CENTER, + HEADER_RIGHT, + FOOTER_LEFT, + FOOTER_CENTER, + FOOTER_RIGHT +}; + /* Define the tree.h RB structs for the red-black head types. */ RB_HEAD(lxw_table_cells, lxw_cell); RB_HEAD(lxw_drawing_rel_ids, lxw_drawing_rel_id); +RB_HEAD(lxw_vml_drawing_rel_ids, lxw_drawing_rel_id); +RB_HEAD(lxw_cond_format_hash, lxw_cond_format_hash_element); /* Define a RB_TREE struct manually to add extra members. */ struct lxw_table_rows { @@ -307,9 +668,32 @@ struct lxw_table_rows { /* Add unused struct to allow adding a semicolon */ \ struct lxw_rb_generate_drawing_rel_ids{int unused;} +#define LXW_RB_GENERATE_VML_DRAWING_REL_IDS(name, type, field, cmp) \ + RB_GENERATE_INSERT_COLOR(name, type, field, static) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ + RB_GENERATE_INSERT(name, type, field, cmp, static) \ + RB_GENERATE_REMOVE(name, type, field, static) \ + RB_GENERATE_FIND(name, type, field, cmp, static) \ + RB_GENERATE_NEXT(name, type, field, static) \ + RB_GENERATE_MINMAX(name, type, field, static) \ + /* Add unused struct to allow adding a semicolon */ \ + struct lxw_rb_generate_vml_drawing_rel_ids{int unused;} + +#define LXW_RB_GENERATE_COND_FORMAT_HASH(name, type, field, cmp) \ + RB_GENERATE_INSERT_COLOR(name, type, field, static) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ + RB_GENERATE_INSERT(name, type, field, cmp, static) \ + RB_GENERATE_REMOVE(name, type, field, static) \ + RB_GENERATE_FIND(name, type, field, cmp, static) \ + RB_GENERATE_NEXT(name, type, field, static) \ + RB_GENERATE_MINMAX(name, type, field, static) \ + /* Add unused struct to allow adding a semicolon */ \ + struct lxw_rb_generate_cond_format_hash{int unused;} + STAILQ_HEAD(lxw_merged_ranges, lxw_merged_range); STAILQ_HEAD(lxw_selections, lxw_selection); STAILQ_HEAD(lxw_data_validations, lxw_data_val_obj); +STAILQ_HEAD(lxw_cond_format_list, lxw_cond_format_obj); STAILQ_HEAD(lxw_image_props, lxw_object_properties); STAILQ_HEAD(lxw_chart_props, lxw_object_properties); STAILQ_HEAD(lxw_comment_objs, lxw_vml_obj); @@ -603,6 +987,274 @@ typedef struct lxw_data_val_obj { STAILQ_ENTRY (lxw_data_val_obj) list_pointers; } lxw_data_val_obj; +/** + * @brief Worksheet conditional formatting options. + * + * The fields/options in the the lxw_conditional_format are used to define a + * worksheet conditional format. It is used in conjunction with + * `worksheet_conditional_format()`. + * + */ +typedef struct lxw_conditional_format { + + /** The type of conditional format such as #LXW_CONDITIONAL_TYPE_CELL or + * #LXW_CONDITIONAL_DATA_BAR. Should be a #lxw_conditional_format_types + * value.*/ + uint8_t type; + + /** The criteria parameter is used to set the criteria by which the cell + * data will be evaluated. For example in the expression `a > 5 the + * criteria is `>` or, in libxlsxwriter terms, + * #LXW_CONDITIONAL_CRITERIA_GREATER_THAN. The criteria that are + * applicable depend on the conditional format type. The criteria + * options are defined in #lxw_conditional_criteria. */ + uint8_t criteria; + + /** The number value to which the condition refers. For example in the + * expression `a > 5`, the value is 5.*/ + double value; + + /** The string value to which the condition refers, such as `"=A1"`. If a + * value_string exists in the struct then the number value is + * ignored. Note, if the condition refers to a text string then it must + * be double quoted like this `"foo"`. */ + char *value_string; + + /** The format field is used to specify the #lxw_format format that will + * be applied to the cell when the conditional formatting criterion is + * met. The #lxw_format is created using the `workbook_add_format()` + * method in the same way as cell formats. + * + * @note In Excel, a conditional format is superimposed over the existing + * cell format and not all cell format properties can be + * modified. Properties that @b cannot be modified, in Excel, by a + * conditional format are: font name, font size, superscript and + * subscript, diagonal borders, all alignment properties and all + * protection properties. */ + lxw_format *format; + + /** The minimum value used for Cell, Color Scale and Data Bar conditional + * formats. For Cell types this is usually used with a "Between" style criteria. */ + double min_value; + + /** The minimum string value used for Cell, Color Scale and Data Bar conditional + * formats. Usually used to set ranges like `=A1`. */ + char *min_value_string; + + /** The rule used for the minimum condition in Color Scale and Data Bar + * conditional formats. The rule types are defined in + * #lxw_conditional_format_rule_types. */ + uint8_t min_rule_type; + + /** The color used for the minimum Color Scale conditional format. + * See @ref working_with_colors. */ + lxw_color_t min_color; + + /** The middle value used for Color Scale and Data Bar conditional + * formats. */ + double mid_value; + + /** The middle string value used for Color Scale and Data Bar conditional + * formats. Usually used to set ranges like `=A1`. */ + char *mid_value_string; + + /** The rule used for the middle condition in Color Scale and Data Bar + * conditional formats. The rule types are defined in + * #lxw_conditional_format_rule_types. */ + uint8_t mid_rule_type; + + /** The color used for the middle Color Scale conditional format. + * See @ref working_with_colors. */ + lxw_color_t mid_color; + + /** The maximum value used for Cell, Color Scale and Data Bar conditional + * formats. For Cell types this is usually used with a "Between" style + * criteria. */ + double max_value; + + /** The maximum string value used for Cell, Color Scale and Data Bar conditional + * formats. Usually used to set ranges like `=A1`. */ + char *max_value_string; + + /** The rule used for the maximum condition in Color Scale and Data Bar + * conditional formats. The rule types are defined in + * #lxw_conditional_format_rule_types. */ + uint8_t max_rule_type; + + /** The color used for the maximum Color Scale conditional format. + * See @ref working_with_colors. */ + lxw_color_t max_color; + + /** The bar_color field sets the fill color for data bars. See @ref + * working_with_colors. */ + lxw_color_t bar_color; + + /** The bar_only field sets The bar_only field displays a bar data but + * not the data in the cells. */ + uint8_t bar_only; + + /** In Excel 2010 additional data bar properties were added such as solid + * (non-gradient) bars and control over how negative values are + * displayed. These properties can shown below. + * + * The data_bar_2010 field sets Excel 2010 style data bars even when + * Excel 2010 specific properties aren't used. */ + uint8_t data_bar_2010; + + /** The bar_solid field turns on a solid (non-gradient) fill for data + * bars. Set to LXW_TRUE to turn on. Excel 2010 only. */ + uint8_t bar_solid; + + /** The bar_negative_color field sets the color fill for the negative + * portion of a data bar. See @ref working_with_colors. Excel 2010 only. */ + lxw_color_t bar_negative_color; + + /** The bar_border_color field sets the color for the border line of a + * data bar. See @ref working_with_colors. Excel 2010 only. */ + lxw_color_t bar_border_color; + + /** The bar_negative_border_color field sets the color for the border of + * the negative portion of a data bar. See @ref + * working_with_colors. Excel 2010 only. */ + lxw_color_t bar_negative_border_color; + + /** The bar_negative_color_same field sets the fill color for the negative + * portion of a data bar to be the same as the fill color for the + * positive portion of the data bar. Set to LXW_TRUE to turn on. Excel + * 2010 only. */ + uint8_t bar_negative_color_same; + + /** The bar_negative_border_color_same field sets the border color for the + * negative portion of a data bar to be the same as the border color for + * the positive portion of the data bar. Set to LXW_TRUE to turn + * on. Excel 2010 only. */ + uint8_t bar_negative_border_color_same; + + /** The bar_no_border field turns off the border for data bars. Set to + * LXW_TRUE to enable. Excel 2010 only. */ + uint8_t bar_no_border; + + /** The bar_direction field sets the direction for data bars. This + * property can be either left for left-to-right or right for + * right-to-left. If the property isn't set then Excel will adjust the + * position automatically based on the context. Should be a + * #lxw_conditional_format_bar_direction value. Excel 2010 only. */ + uint8_t bar_direction; + + /** The bar_axis_position field sets the position within the cells for the + * axis that is shown in data bars when there are negative values to + * display. The property can be either middle or none. If the property + * isn't set then Excel will position the axis based on the range of + * positive and negative values. Should be a + * lxw_conditional_bar_axis_position value. Excel 2010 only. */ + uint8_t bar_axis_position; + + /** The bar_axis_color field sets the color for the axis that is shown + * in data bars when there are negative values to display. See @ref + * working_with_colors. Excel 2010 only. */ + lxw_color_t bar_axis_color; + + /** The Icons Sets style is specified by the icon_style parameter. Should + * be a #lxw_conditional_icon_types. */ + uint8_t icon_style; + + /** The order of Icon Sets icons can be reversed by setting reverse_icons + * to LXW_TRUE. */ + uint8_t reverse_icons; + + /** The icons can be displayed without the cell value by settings the + * icons_only parameter to LXW_TRUE. */ + uint8_t icons_only; + + /** The multi_range field is used to extend a conditional format over + * non-contiguous ranges. + * + * It is possible to apply the conditional format to different cell + * ranges in a worksheet using multiple calls to + * `worksheet_conditional_format()`. However, as a minor optimization it + * is also possible in Excel to apply the same conditional format to + * different non-contiguous cell ranges. + * + * This is replicated in `worksheet_conditional_format()` using the + * multi_range option. The range must contain the primary range for the + * conditional format and any others separated by spaces. For example + * `"A1 C1:C5 E2 G1:G100"`. + */ + char *multi_range; + + /** The stop_if_true parameter can be used to set the "stop if true" + * feature of a conditional formatting rule when more than one rule is + * applied to a cell or a range of cells. When this parameter is set then + * subsequent rules are not evaluated if the current rule is true. Set to + * LXW_TRUE to turn on. */ + uint8_t stop_if_true; + +} lxw_conditional_format; + +/* Internal */ +typedef struct lxw_cond_format_obj { + uint8_t type; + uint8_t criteria; + + double min_value; + char *min_value_string; + uint8_t min_rule_type; + lxw_color_t min_color; + + double mid_value; + char *mid_value_string; + uint8_t mid_value_type; + uint8_t mid_rule_type; + lxw_color_t mid_color; + + double max_value; + char *max_value_string; + uint8_t max_value_type; + uint8_t max_rule_type; + lxw_color_t max_color; + + uint8_t data_bar_2010; + uint8_t auto_min; + uint8_t auto_max; + uint8_t bar_only; + uint8_t bar_solid; + uint8_t bar_negative_color_same; + uint8_t bar_negative_border_color_same; + uint8_t bar_no_border; + uint8_t bar_direction; + uint8_t bar_axis_position; + lxw_color_t bar_color; + lxw_color_t bar_negative_color; + lxw_color_t bar_border_color; + lxw_color_t bar_negative_border_color; + lxw_color_t bar_axis_color; + + uint8_t icon_style; + uint8_t reverse_icons; + uint8_t icons_only; + + uint8_t stop_if_true; + uint8_t has_max; + char *type_string; + char *guid; + + int32_t dxf_index; + uint32_t dxf_priority; + + char first_cell[LXW_MAX_CELL_NAME_LENGTH]; + char sqref[LXW_MAX_ATTRIBUTE_LENGTH]; + + STAILQ_ENTRY (lxw_cond_format_obj) list_pointers; +} lxw_cond_format_obj; + +typedef struct lxw_cond_format_hash_element { + char sqref[LXW_MAX_ATTRIBUTE_LENGTH]; + + struct lxw_cond_format_list *cond_formats; + + RB_ENTRY (lxw_cond_format_hash_element) tree_pointers; +} lxw_cond_format_hash_element; + /** * @brief Options for inserted images. * @@ -694,6 +1346,7 @@ typedef struct lxw_object_properties { lxw_chart *chart; uint8_t is_duplicate; char *md5; + char *image_position; STAILQ_ENTRY (lxw_object_properties) list_pointers; } lxw_object_properties; @@ -792,16 +1445,21 @@ typedef struct lxw_vml_obj { uint32_t row_absolute; uint32_t width; uint32_t height; + double x_dpi; + double y_dpi; lxw_color_t color; uint8_t font_family; uint8_t visible; uint32_t author_id; + uint32_t rel_index; double font_size; struct lxw_drawing_coords from; struct lxw_drawing_coords to; char *author; char *font_name; char *text; + char *image_position; + char *name; STAILQ_ENTRY (lxw_vml_obj) list_pointers; } lxw_vml_obj; @@ -809,13 +1467,30 @@ typedef struct lxw_vml_obj { /** * @brief Header and footer options. * - * Optional parameters used in the worksheet_set_header_opt() and + * Optional parameters used in the `worksheet_set_header_opt()` and * worksheet_set_footer_opt() functions. * */ typedef struct lxw_header_footer_options { - /** Header or footer margin in inches. Excel default is 0.3. */ + /** Header or footer margin in inches. Excel default is 0.3. Must by + * larger than 0.0. See `worksheet_set_header_opt()`. */ double margin; + + /** The left header image filename, with path if required. This should + * have a corresponding `&G/&[Picture]` placeholder in the `&L` section of + * the header/footer string. See `worksheet_set_header_opt()`. */ + char *image_left; + + /** The center header image filename, with path if required. This should + * have a corresponding `&G/&[Picture]` placeholder in the `&C` section of + * the header/footer string. See `worksheet_set_header_opt()`. */ + char *image_center; + + /** The right header image filename, with path if required. This should + * have a corresponding `&G/&[Picture]` placeholder in the `&R` section of + * the header/footer string. See `worksheet_set_header_opt()`. */ + char *image_right; + } lxw_header_footer_options; /** @@ -936,10 +1611,13 @@ typedef struct lxw_worksheet { struct lxw_merged_ranges *merged_ranges; struct lxw_selections *selections; struct lxw_data_validations *data_validations; + struct lxw_cond_format_hash *conditional_formats; struct lxw_image_props *image_props; struct lxw_chart_props *chart_data; struct lxw_drawing_rel_ids *drawing_rel_ids; + struct lxw_vml_drawing_rel_ids *vml_drawing_rel_ids; struct lxw_comment_objs *comment_objs; + struct lxw_comment_objs *header_image_objs; lxw_row_t dim_rowmin; lxw_row_t dim_rowmax; @@ -1041,9 +1719,11 @@ typedef struct lxw_worksheet { uint16_t vbreaks_count; uint32_t drawing_rel_id; + uint32_t vml_drawing_rel_id; struct lxw_rel_tuples *external_hyperlinks; struct lxw_rel_tuples *external_drawing_links; struct lxw_rel_tuples *drawing_links; + struct lxw_rel_tuples *vml_drawing_links; struct lxw_panes panes; @@ -1057,10 +1737,36 @@ typedef struct lxw_worksheet { uint8_t has_header_vml; lxw_rel_tuple *external_vml_comment_link; lxw_rel_tuple *external_comment_link; + lxw_rel_tuple *external_vml_header_link; char *comment_author; char *vml_data_id_str; + char *vml_header_id_str; uint32_t vml_shape_id; + uint32_t vml_header_id; + uint32_t dxf_priority; uint8_t comment_display_default; + uint32_t data_bar_2010_index; + + uint8_t has_ignore_errors; + char *ignore_number_stored_as_text; + char *ignore_eval_error; + char *ignore_formula_differs; + char *ignore_formula_range; + char *ignore_formula_unlocked; + char *ignore_empty_cell_reference; + char *ignore_list_data_validation; + char *ignore_calculated_column; + char *ignore_two_digit_text_year; + + uint16_t excel_version; + + lxw_object_properties **header_footer_objs[LXW_HEADER_FOOTER_OBJS_MAX]; + lxw_object_properties *header_left_object_props; + lxw_object_properties *header_center_object_props; + lxw_object_properties *header_right_object_props; + lxw_object_properties *footer_left_object_props; + lxw_object_properties *footer_center_object_props; + lxw_object_properties *footer_right_object_props; STAILQ_ENTRY (lxw_worksheet) list_pointers; @@ -1133,6 +1839,8 @@ typedef struct lxw_drawing_rel_id { RB_ENTRY (lxw_drawing_rel_id) tree_pointers; } lxw_drawing_rel_id; + + /* *INDENT-OFF* */ #ifdef __cplusplus extern "C" { @@ -1586,20 +2294,21 @@ lxw_error worksheet_write_blank(lxw_worksheet *worksheet, lxw_format *format); /** - * @brief Write a formula to a worksheet cell with a user defined result. + * @brief Write a formula to a worksheet cell with a user defined numeric + * result. * * @param worksheet Pointer to a lxw_worksheet instance to be updated. * @param row The zero indexed row number. * @param col The zero indexed column number. * @param formula Formula string to write to cell. * @param format A pointer to a Format instance or NULL. - * @param result A user defined result for a formula. + * @param result A user defined numeric result for the formula. * * @return A #lxw_error code. * * The `%worksheet_write_formula_num()` function writes a formula or Excel * function to the cell specified by `row` and `column` with a user defined - * result: + * numeric result: * * @code * // Required as a workaround only. @@ -1634,6 +2343,51 @@ lxw_error worksheet_write_formula_num(lxw_worksheet *worksheet, const char *formula, lxw_format *format, double result); +/** + * @brief Write a formula to a worksheet cell with a user defined string + * result. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param row The zero indexed row number. + * @param col The zero indexed column number. + * @param formula Formula string to write to cell. + * @param format A pointer to a Format instance or NULL. + * @param result A user defined string result for the formula. + * + * @return A #lxw_error code. + * + * The `%worksheet_write_formula_str()` function writes a formula or Excel + * function to the cell specified by `row` and `column` with a user defined + * string result: + * + * @code + * // The example formula is A & B -> AB. + * worksheet_write_formula_str(worksheet, 0, 0, "=\"A\" & \"B\"", NULL, "AB"); + * @endcode + * + * The `%worksheet_write_formula_str()` function is similar to the + * `%worksheet_write_formula_num()` function except it writes a string result + * instead or a numeric result. See `worksheet_write_formula_num()` for more + * details on why/when these functions are required. + * + * One place where the `%worksheet_write_formula_str()` function may be required + * is to specify an empty result which will force a recalculation of the formula + * when loaded in LibreOffice. + * + * @code + * worksheet_write_formula_str(worksheet, 0, 0, "=Sheet1!$A$1", NULL, ""); + * @endcode + * + * See the FAQ @ref faq_formula_zero. + * + * See also @ref working_with_formulas. + */ +lxw_error worksheet_write_formula_str(lxw_worksheet *worksheet, + lxw_row_t row, + lxw_col_t col, + const char *formula, + lxw_format *format, const char *result); + /** * @brief Write a "Rich" multi-format string to a worksheet cell. * @@ -2349,7 +3103,7 @@ lxw_error worksheet_insert_chart_opt(lxw_worksheet *worksheet, * worksheet_write_number(worksheet, 1, 1, 123, format); * @endcode * - * @note Merged ranges generally don’t work in libxlsxwriter when the Workbook + * @note Merged ranges generally don't work in libxlsxwriter when the Workbook * #lxw_workbook_options `constant_memory` mode is enabled. */ lxw_error worksheet_merge_range(lxw_worksheet *worksheet, lxw_row_t first_row, @@ -2433,7 +3187,7 @@ lxw_error worksheet_data_validation_cell(lxw_worksheet *worksheet, lxw_data_validation *validation); /** - * @brief Add a data validation to a range cell. + * @brief Add a data validation to a range. * * @param worksheet Pointer to a lxw_worksheet instance to be updated. * @param first_row The first row of the range. (All zero indexed.) @@ -2474,6 +3228,85 @@ lxw_error worksheet_data_validation_range(lxw_worksheet *worksheet, lxw_col_t last_col, lxw_data_validation *validation); +/** + * @brief Add a conditional format to a worksheet cell. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param row The zero indexed row number. + * @param col The zero indexed column number. + * @param conditional_format A #lxw_conditional_format object to control the + * conditional format. + * + * @return A #lxw_error code. + * + * The `%worksheet_conditional_format_cell()` function is used to set a + * conditional format for a cell in a worksheet: + * + * @code + * conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + * conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_GREATER_THAN_OR_EQUAL_TO; + * conditional_format->value = 50; + * conditional_format->format = format1; + * worksheet_conditional_format_cell(worksheet, CELL("A1"), conditional_format); + * @endcode + * + * The conditional format parameters is specified in #lxw_conditional_format. + * + * See @ref working_with_conditional_formatting for full details. + */ +lxw_error worksheet_conditional_format_cell(lxw_worksheet *worksheet, + lxw_row_t row, + lxw_col_t col, + lxw_conditional_format + *conditional_format); + +/** + * @brief Add a conditional format to a worksheet range. + * + * @param worksheet Pointer to a lxw_worksheet instance to be updated. + * @param first_row The first row of the range. (All zero indexed.) + * @param first_col The first column of the range. + * @param last_row The last row of the range. + * @param last_col The last col of the range. + * @param conditional_format A #lxw_conditional_format object to control the + * conditional format. + * + * @return A #lxw_error code. + * + * The `%worksheet_conditional_format_cell()` function is used to set a + * conditional format for a range of cells in a worksheet: + * + * @code + * conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + * conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_GREATER_THAN_OR_EQUAL_TO; + * conditional_format->value = 50; + * conditional_format->format = format1; + * worksheet_conditional_format_range(worksheet1, RANGE("B3:K12"), conditional_format); + * + * conditional_format->type = LXW_CONDITIONAL_TYPE_CELL; + * conditional_format->criteria = LXW_CONDITIONAL_CRITERIA_LESS_THAN; + * conditional_format->value = 50; + * conditional_format->format = format2; + * worksheet_conditional_format_range(worksheet1, RANGE("B3:K12"), conditional_format); + * @endcode + * + * Output: + * + * @image html conditional_format1.png + * + * + * The conditional format parameters is specified in #lxw_conditional_format. + * + * See @ref working_with_conditional_formatting for full details. + */ +lxw_error worksheet_conditional_format_range(lxw_worksheet *worksheet, + lxw_row_t first_row, + lxw_col_t first_col, + lxw_row_t last_row, + lxw_col_t last_col, + lxw_conditional_format + *conditional_format); + /** * @brief Make a worksheet the active, i.e., visible worksheet. * @@ -2846,7 +3679,11 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * | `&S` | | Strikethrough | * | `&X` | | Superscript | * | `&Y` | | Subscript | + * | `&[Picture]` | Images | Image placeholder | + * | `&G` | | Same as `&[Picture]` | + * | `&&` | Miscellaneous | Literal ampersand & | * + * Note: inserting images requires the `worksheet_set_header_opt()` function. * * Text in headers and footers can be justified (aligned) to the left, center * and right by prefixing the text with the control characters `&L`, `&C` and @@ -2970,17 +3807,14 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * @code * * $ unzip myfile.xlsm -d myfile - * $ xmllint --format `find myfile -name "*.xml" | xargs` | egrep "Header|Footer" + * $ xmllint --format `find myfile -name "*.xml" | xargs` | egrep "Header|Footer" | sed 's/&/\&/g' * * - * &L&P + * &L&P * * * @endcode * - * Note that in this case you need to unescape the Html. In the above example - * the header string would be `&L&P`. - * * To include a single literal ampersand `&` in a header or footer you should * use a double ampersand `&&`: * @@ -2988,8 +3822,8 @@ void worksheet_set_margins(lxw_worksheet *worksheet, double left, * worksheet_set_header(worksheet, "&CCuriouser && Curiouser - Attorneys at Law"); * @endcode * - * Note, the header or footer string must be less than 255 characters. Strings - * longer than this will not be written. + * Note, Excel requires that the header or footer string must be less than 255 + * characters. Strings longer than this will not be written. * */ lxw_error worksheet_set_header(lxw_worksheet *worksheet, const char *string); @@ -3016,19 +3850,43 @@ lxw_error worksheet_set_footer(lxw_worksheet *worksheet, const char *string); * * @return A #lxw_error code. * - * The syntax of this function is the same as worksheet_set_header() with an + * The syntax of this function is the same as `worksheet_set_header()` with an * additional parameter to specify options for the header. * - * Currently, the only available option is the header margin: + * The #lxw_header_footer_options options are: + * + * - `margin`: Header or footer margin in inches. The value must by larger + * than 0.0. The Excel default is 0.3. + * + * - `image_left`: The left header image filename, with path if required. This + * should have a corresponding `&G/&[Picture]` placeholder in the `&L` + * section of the header/footer string. + * + * - `image_center`: The center header image filename, with path if + * required. This should have a corresponding `&G/&[Picture]` placeholder in + * the `&C` section of the header/footer string. + * + * - `image_right`: The right header image filename, with path if + * required. This should have a corresponding `&G/&[Picture]` placeholder in + * the `&R` section of the header/footer string. * * @code + * lxw_header_footer_options header_options = { .margin = 0.2 }; * - * lxw_header_footer_options header_options = { 0.2 }; + * worksheet_set_header_opt(worksheet, "Some text", &header_options); + * @endcode + * + * Images can be inserted in the header by specifying the `&[Picture]` + * placeholder and a filename/path to the image: * - * worksheet_set_header_opt(worksheet, "Some text", &header_options); + * @code + * lxw_header_footer_options header_options = {.image_left = "logo.png"}; * + * worksheet_set_header_opt(worksheet, "&L&[Picture]", &header_options); * @endcode * + * @image html headers_footers.png + * */ lxw_error worksheet_set_header_opt(lxw_worksheet *worksheet, const char *string, @@ -3043,7 +3901,7 @@ lxw_error worksheet_set_header_opt(lxw_worksheet *worksheet, * * @return A #lxw_error code. * - * The syntax of this function is the same as worksheet_set_header_opt(). + * The syntax of this function is the same as `worksheet_set_header_opt()`. * */ lxw_error worksheet_set_footer_opt(lxw_worksheet *worksheet, @@ -3641,6 +4499,8 @@ void worksheet_set_default_row(lxw_worksheet *worksheet, double height, * @param worksheet Pointer to a lxw_worksheet instance. * @param name Name of the worksheet used by VBA. * + * @return A #lxw_error. + * * The `worksheet_set_vba_name()` function can be used to set the VBA name for * the worksheet. This is sometimes required when a vbaProject macro included * via `workbook_add_vba_project()` refers to the worksheet by a name other @@ -3656,8 +4516,6 @@ void worksheet_set_default_row(lxw_worksheet *worksheet, double height, * extracted from a foreign language version of Excel. * * See also @ref working_with_macros - * - * @return A #lxw_error. */ lxw_error worksheet_set_vba_name(lxw_worksheet *worksheet, const char *name); @@ -3699,6 +4557,107 @@ void worksheet_show_comments(lxw_worksheet *worksheet); void worksheet_set_comments_author(lxw_worksheet *worksheet, const char *author); +/** + * @brief Ignore various Excel errors/warnings in a worksheet for user + * defined ranges. + * + * @param worksheet Pointer to a lxw_worksheet instance. + * @param type The type of error/warning to ignore. See #lxw_ignore_errors. + * @param range The range(s) for which the error/warning should be ignored. + * + * @return A #lxw_error. + * + * + * The `%worksheet_ignore_errors()` function can be used to ignore various + * worksheet cell errors/warnings. For example the following code writes a string + * that looks like a number: + * + * @code + * worksheet_write_string(worksheet, CELL("D2"), "123", NULL); + * @endcode + * + * This causes Excel to display a small green triangle in the top left hand + * corner of the cell to indicate an error/warning: + * + * @image html ignore_errors1.png + * + * Sometimes these warnings are useful indicators that there is an issue in + * the spreadsheet but sometimes it is preferable to turn them off. Warnings + * can be turned off at the Excel level for all workbooks and worksheets by + * using the using "Excel options -> Formulas -> Error checking + * rules". Alternatively you can turn them off for individual cells in a + * worksheet, or ranges of cells, using the `%worksheet_ignore_errors()` + * function with different #lxw_ignore_errors options and ranges like this: + * + * @code + * worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "C3"); + * worksheet_ignore_errors(worksheet, LXW_IGNORE_EVAL_ERROR, "C6"); + * @endcode + * + * The range can be a single cell, a range of cells, or multiple cells and ranges + * separated by spaces: + * + * @code + * // Single cell. + * worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "C6"); + * + * // Or a single range: + * worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "C6:G8"); + * + * // Or multiple cells and ranges: + * worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "C6 E6 G1:G20 J2:J6"); + * @endcode + * + * @note Calling `%worksheet_ignore_errors()` more than once for the same + * #lxw_ignore_errors type will overwrite the previous range. + * + * You can turn off warnings for an entire column by specifying the range from + * the first cell in the column to the last cell in the column: + * + * @code + * worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "A1:A1048576"); + * @endcode + * + * Or for the entire worksheet by specifying the range from the first cell in + * the worksheet to the last cell in the worksheet: + * + * @code + * worksheet_ignore_errors(worksheet, LXW_IGNORE_NUMBER_STORED_AS_TEXT, "A1:XFD1048576"); + * @endcode + * + * The worksheet errors/warnings that can be ignored are: + * + * - #LXW_IGNORE_NUMBER_STORED_AS_TEXT: Turn off errors/warnings for numbers + * stores as text. + * + * - #LXW_IGNORE_EVAL_ERROR: Turn off errors/warnings for formula errors (such + * as divide by zero). + * + * - #LXW_IGNORE_FORMULA_DIFFERS: Turn off errors/warnings for formulas that + * differ from surrounding formulas. + * + * - #LXW_IGNORE_FORMULA_RANGE: Turn off errors/warnings for formulas that + * omit cells in a range. + * + * - #LXW_IGNORE_FORMULA_UNLOCKED: Turn off errors/warnings for unlocked cells + * that contain formulas. + * + * - #LXW_IGNORE_EMPTY_CELL_REFERENCE: Turn off errors/warnings for formulas + * that refer to empty cells. + * + * - #LXW_IGNORE_LIST_DATA_VALIDATION: Turn off errors/warnings for cells in a + * table that do not comply with applicable data validation rules. + * + * - #LXW_IGNORE_CALCULATED_COLUMN: Turn off errors/warnings for cell formulas + * that differ from the column formula. + * + * - #LXW_IGNORE_TWO_DIGIT_TEXT_YEAR: Turn off errors/warnings for formulas + * that contain a two digit text representation of a year. + * + */ +lxw_error worksheet_ignore_errors(lxw_worksheet *worksheet, uint8_t type, + const char *range); + lxw_worksheet *lxw_worksheet_new(lxw_worksheet_init_data *init_data); void lxw_worksheet_free(lxw_worksheet *worksheet); void lxw_worksheet_assemble_xml_file(lxw_worksheet *worksheet); @@ -3708,6 +4667,10 @@ void lxw_worksheet_prepare_image(lxw_worksheet *worksheet, uint32_t image_ref_id, uint32_t drawing_id, lxw_object_properties *object_props); +void lxw_worksheet_prepare_header_image(lxw_worksheet *worksheet, + uint32_t image_ref_id, + lxw_object_properties *object_props); + void lxw_worksheet_prepare_chart(lxw_worksheet *worksheet, uint32_t chart_ref_id, uint32_t drawing_id, lxw_object_properties *object_props, @@ -3719,10 +4682,14 @@ uint32_t lxw_worksheet_prepare_vml_objects(lxw_worksheet *worksheet, uint32_t vml_drawing_id, uint32_t comment_id); +void lxw_worksheet_prepare_header_vml_objects(lxw_worksheet *self, + uint32_t vml_header_id, + uint32_t vml_drawing_id); + lxw_row *lxw_worksheet_find_row(lxw_worksheet *worksheet, lxw_row_t row_num); lxw_cell *lxw_worksheet_find_cell_in_row(lxw_row *row, lxw_col_t col_num); /* - * External functions to call intern XML methods shared with chartsheet. + * External functions to call intern XML functions shared with chartsheet. */ void lxw_worksheet_write_sheet_views(lxw_worksheet *worksheet); void lxw_worksheet_write_page_margins(lxw_worksheet *worksheet); diff --git a/libxlsxwriter/include/xlsxwriter/xmlwriter.h b/libxlsxwriter/include/xlsxwriter/xmlwriter.h index e2a3aa8..2878c0a 100644 --- a/libxlsxwriter/include/xlsxwriter/xmlwriter.h +++ b/libxlsxwriter/include/xlsxwriter/xmlwriter.h @@ -167,6 +167,7 @@ void lxw_xml_data_element(FILE * xmlfile, void lxw_xml_rich_si_element(FILE * xmlfile, const char *string); +uint8_t lxw_has_control_characters(const char *string); char *lxw_escape_control_characters(const char *string); char *lxw_escape_url_characters(const char *string, uint8_t escape_hash); diff --git a/libxlsxwriter/src/chart.c b/libxlsxwriter/src/chart.c index 8f24fe1..2124d2c 100644 --- a/libxlsxwriter/src/chart.c +++ b/libxlsxwriter/src/chart.c @@ -82,6 +82,26 @@ _chart_free_font(lxw_chart_font *font) free(font); } +STATIC void +_chart_free_data_labels(lxw_chart_series *series) +{ + uint16_t index; + + for (index = 0; index < series->data_label_count; index++) { + lxw_chart_custom_label *data_label = &series->data_labels[index]; + + free(data_label->value); + _chart_free_range(data_label->range); + _chart_free_font(data_label->font); + free(data_label->line); + free(data_label->fill); + free(data_label->pattern); + } + + series->data_label_count = 0; + free(series->data_labels); +} + /* * Free a series object. */ @@ -96,6 +116,10 @@ _chart_series_free(lxw_chart_series *series) free(series->fill); free(series->pattern); free(series->label_num_format); + free(series->label_line); + free(series->label_fill); + free(series->label_pattern); + _chart_free_font(series->label_font); if (series->marker) { @@ -109,6 +133,7 @@ _chart_series_free(lxw_chart_series *series) _chart_free_range(series->values); _chart_free_range(series->title.range); _chart_free_points(series); + _chart_free_data_labels(series); if (series->x_error_bars) { free(series->x_error_bars->line); @@ -1067,12 +1092,14 @@ _chart_write_a_p_pie(lxw_chart *self, lxw_chart_font *font) * Write the element. */ STATIC void -_chart_write_a_p_rich(lxw_chart *self, char *name, lxw_chart_font *font) +_chart_write_a_p_rich(lxw_chart *self, char *name, lxw_chart_font *font, + uint8_t ignore_rich_pr) { lxw_xml_start_tag(self->file, "a:p", NULL); /* Write the a:pPr element. */ - _chart_write_a_p_pr_rich(self, font); + if (!ignore_rich_pr) + _chart_write_a_p_pr_rich(self, font); /* Write the a:r element. */ _chart_write_a_r(self, name, font); @@ -1454,8 +1481,8 @@ _chart_write_axis_font(lxw_chart *self, lxw_chart_font *font) * Write the element. */ STATIC void -_chart_write_rich(lxw_chart *self, char *name, uint8_t is_horizontal, - lxw_chart_font *font) +_chart_write_rich(lxw_chart *self, char *name, lxw_chart_font *font, + uint8_t is_horizontal, uint8_t ignore_rich_pr) { int32_t rotation = 0; @@ -1471,7 +1498,7 @@ _chart_write_rich(lxw_chart *self, char *name, uint8_t is_horizontal, _chart_write_a_lst_style(self); /* Write the a:p element. */ - _chart_write_a_p_rich(self, name, font); + _chart_write_a_p_rich(self, name, font, ignore_rich_pr); lxw_xml_end_tag(self->file, "c:rich"); } @@ -1487,7 +1514,7 @@ _chart_write_tx_rich(lxw_chart *self, char *name, uint8_t is_horizontal, lxw_xml_start_tag(self->file, "c:tx", NULL); /* Write the c:rich element. */ - _chart_write_rich(self, name, is_horizontal, font); + _chart_write_rich(self, name, font, is_horizontal, LXW_FALSE); lxw_xml_end_tag(self->file, "c:tx"); } @@ -2256,6 +2283,133 @@ _chart_write_label_num_fmt(lxw_chart *self, char *format) LXW_FREE_ATTRIBUTES(); } +/* + * Write parts of the elements where only formatting is changed. + */ +STATIC void +_chart_write_custom_label_format_only(lxw_chart *self, + lxw_chart_custom_label *data_label) +{ + if (data_label->line || data_label->fill || data_label->pattern) { + _chart_write_sp_pr(self, data_label->line, data_label->fill, + data_label->pattern); + _chart_write_tx_pr(self, LXW_FALSE, data_label->font); + } + else if (data_label->font) { + lxw_xml_empty_tag(self->file, "c:spPr", NULL); + _chart_write_tx_pr(self, LXW_FALSE, data_label->font); + } +} + +/* + * Write parts of the elements for formula custom labels. + */ +STATIC void +_chart_write_custom_label_formula(lxw_chart *self, lxw_chart_series *series, + lxw_chart_custom_label *data_label) +{ + lxw_xml_empty_tag(self->file, "c:layout", NULL); + lxw_xml_start_tag(self->file, "c:tx", NULL); + + _chart_write_str_ref(self, data_label->range); + + lxw_xml_end_tag(self->file, "c:tx"); + + _chart_write_custom_label_format_only(self, data_label); + + /* Write the c:showVal element. */ + if (series->show_labels_value) + _chart_write_show_val(self); + + /* Write the c:showCatName element. */ + if (series->show_labels_category) + _chart_write_show_cat_name(self); + + /* Write the c:showSerName element. */ + if (series->show_labels_name) + _chart_write_show_ser_name(self); + +} + +/* + * Write parts of the elements for string custom labels. + */ +STATIC void +_chart_write_custom_label_str(lxw_chart *self, lxw_chart_series *series, + lxw_chart_custom_label *data_label) +{ + uint8_t ignore_rich_pr = LXW_TRUE; + + if (data_label->line || data_label->fill || data_label->pattern) + ignore_rich_pr = LXW_FALSE; + + lxw_xml_empty_tag(self->file, "c:layout", NULL); + lxw_xml_start_tag(self->file, "c:tx", NULL); + + /* Write the c:rich element. */ + _chart_write_rich(self, data_label->value, data_label->font, + LXW_FALSE, ignore_rich_pr); + + lxw_xml_end_tag(self->file, "c:tx"); + + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, data_label->line, data_label->fill, + data_label->pattern); + + /* Write the c:showVal element. */ + if (series->show_labels_value) + _chart_write_show_val(self); + + /* Write the c:showCatName element. */ + if (series->show_labels_category) + _chart_write_show_cat_name(self); + + /* Write the c:showSerName element. */ + if (series->show_labels_name) + _chart_write_show_ser_name(self); + +} + +/* + * Write the elements for custom labels. + */ +STATIC void +_chart_write_custom_labels(lxw_chart *self, lxw_chart_series *series) +{ + uint16_t index = 0; + + for (index = 0; index < series->data_label_count; index++) { + lxw_chart_custom_label *data_label = &series->data_labels[index]; + + if (!data_label->value && !data_label->range && !data_label->hide + && !data_label->font) { + + continue; + } + + lxw_xml_start_tag(self->file, "c:dLbl", NULL); + + /* Write the c:idx element. */ + _chart_write_idx(self, index); + + if (data_label->hide) { + /* Write the c:delete element. */ + _chart_write_delete(self); + } + else if (data_label->value) { + _chart_write_custom_label_str(self, series, data_label); + } + else if (data_label->range) { + _chart_write_custom_label_formula(self, series, data_label); + } + else if (data_label->font) { + _chart_write_custom_label_format_only(self, data_label); + } + + lxw_xml_end_tag(self->file, "c:dLbl"); + } +} + /* * Write the element. */ @@ -2267,10 +2421,17 @@ _chart_write_d_lbls(lxw_chart *self, lxw_chart_series *series) lxw_xml_start_tag(self->file, "c:dLbls", NULL); + if (series->data_labels) + _chart_write_custom_labels(self, series); + /* Write the c:numFmt element. */ if (series->label_num_format) _chart_write_label_num_fmt(self, series->label_num_format); + /* Write the c:spPr element. */ + _chart_write_sp_pr(self, series->label_line, series->label_fill, + series->label_pattern); + if (series->label_font) _chart_write_tx_pr(self, LXW_FALSE, series->label_font); @@ -3557,7 +3718,7 @@ _chart_write_legend_entry(lxw_chart *self, uint16_t index) /* Write the c:idx element. */ _chart_write_idx(self, self->delete_series[index]); - /* Write the c:delete element. */ + /* Write the c:dst_label element. */ _chart_write_delete(self); lxw_xml_end_tag(self->file, "c:legendEntry"); @@ -4784,7 +4945,7 @@ _chart_initialize_doughnut_chart(lxw_chart *self) * Initialize a line chart. */ STATIC void -_chart_initialize_line_chart(lxw_chart *self) +_chart_initialize_line_chart(lxw_chart *self, uint8_t type) { self->chart_group = LXW_CHART_LINE; _chart_set_default_marker_type(self, LXW_CHART_MARKER_NONE); @@ -4793,6 +4954,17 @@ _chart_initialize_line_chart(lxw_chart *self) self->y_axis->is_value = LXW_TRUE; self->default_label_position = LXW_CHART_LABEL_POSITION_RIGHT; + if (type == LXW_CHART_LINE_STACKED) { + self->grouping = LXW_GROUPING_STACKED; + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + + if (type == LXW_CHART_LINE_STACKED_PERCENT) { + self->grouping = LXW_GROUPING_PERCENTSTACKED; + _chart_axis_set_default_num_format(self->y_axis, "0%"); + self->subtype = LXW_CHART_SUBTYPE_STACKED; + } + /* Initialize the function pointers for this chart type. */ self->write_chart_type = _chart_write_line_chart; self->write_plot_area = _chart_write_plot_area; @@ -4887,7 +5059,9 @@ _chart_initialize(lxw_chart *self, uint8_t type) break; case LXW_CHART_LINE: - _chart_initialize_line_chart(self); + case LXW_CHART_LINE_STACKED: + case LXW_CHART_LINE_STACKED_PERCENT: + _chart_initialize_line_chart(self, type); break; case LXW_CHART_PIE: @@ -4986,7 +5160,7 @@ lxw_chart_add_data_cache(lxw_series_range *range, uint8_t *data, } /* - * Insert an image into the worksheet. + * Add a series to the chart. */ lxw_chart_series * chart_add_series(lxw_chart *self, const char *categories, const char *values) @@ -5293,7 +5467,7 @@ chart_series_set_marker_pattern(lxw_chart_series *series, } /* - * Store the horizontal page breaks on a worksheet. + * Store the points for a chart. */ lxw_error chart_series_set_points(lxw_chart_series *series, lxw_chart_point *points[]) @@ -5362,6 +5536,83 @@ chart_series_set_labels_options(lxw_chart_series *series, uint8_t show_name, series->show_labels_value = show_value; } +/* + * Store the custom data_labels for a chart. + */ +lxw_error +chart_series_set_labels_custom(lxw_chart_series *series, + lxw_chart_data_label *data_labels[]) +{ + uint16_t i = 0; + uint16_t data_label_count = 0; + + if (data_labels == NULL) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + while (data_labels[data_label_count]) + data_label_count++; + + if (data_label_count == 0) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + series->has_labels = LXW_TRUE; + + /* Set the Value label type if no other type is set. */ + if (!series->show_labels_name && !series->show_labels_category + && !series->show_labels_value) { + series->show_labels_value = LXW_TRUE; + } + + /* Free any existing resource. */ + _chart_free_data_labels(series); + + series->data_labels = calloc(data_label_count, + sizeof(lxw_chart_custom_label)); + RETURN_ON_MEM_ERROR(series->data_labels, LXW_ERROR_MEMORY_MALLOC_FAILED); + + /* Copy the user data into the array of new structs. The struct types + * are different since the internal version has more fields. */ + for (i = 0; i < data_label_count; i++) { + lxw_chart_data_label *user_label = data_labels[i]; + lxw_chart_custom_label *data_label = &series->data_labels[i]; + char *src_value = user_label->value; + + data_label->hide = user_label->hide; + data_label->font = _chart_convert_font_args(user_label->font); + data_label->line = _chart_convert_line_args(user_label->line); + data_label->fill = _chart_convert_fill_args(user_label->fill); + data_label->pattern = + _chart_convert_pattern_args(user_label->pattern); + + if (src_value) { + if (*src_value == '=') { + /* The value is a formula. Handle like other chart ranges. */ + data_label->range = calloc(1, sizeof(lxw_series_range)); + GOTO_LABEL_ON_MEM_ERROR(data_label->range, mem_error); + + data_label->range->formula = lxw_strdup(src_value + 1); + + /* Add the formula to the data cache to allow value to be looked + * up and filled in when the file is closed. */ + if (_chart_init_data_cache(data_label->range) != LXW_NO_ERROR) + goto mem_error; + } + else { + /* The value is a simple string. */ + data_label->value = lxw_strdup(src_value); + } + } + } + + series->data_label_count = data_label_count; + + return LXW_NO_ERROR; + +mem_error: + _chart_free_data_labels(series); + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + /* * Set the data labels separator for a series. */ @@ -5446,6 +5697,52 @@ chart_series_set_labels_font(lxw_chart_series *series, lxw_chart_font *font) series->label_font = _chart_convert_font_args(font); } +/* + * Set a line type for a series data labels. + */ +void +chart_series_set_labels_line(lxw_chart_series *series, lxw_chart_line *line) +{ + if (!line) + return; + + /* Free any previously allocated resource. */ + free(series->label_line); + + series->label_line = _chart_convert_line_args(line); +} + +/* + * Set a fill type for a series data labels. + */ +void +chart_series_set_labels_fill(lxw_chart_series *series, lxw_chart_fill *fill) +{ + if (!fill) + return; + + /* Free any previously allocated resource. */ + free(series->label_fill); + + series->label_fill = _chart_convert_fill_args(fill); +} + +/* + * Set a pattern type for a series data labels. + */ +void +chart_series_set_labels_pattern(lxw_chart_series *series, + lxw_chart_pattern *pattern) +{ + if (!pattern) + return; + + /* Free any previously allocated resource. */ + free(series->label_pattern); + + series->label_pattern = _chart_convert_pattern_args(pattern); +} + /* * Set the trendline for a chart series. */ diff --git a/libxlsxwriter/src/core.c b/libxlsxwriter/src/core.c index eed7275..b2a95c7 100644 --- a/libxlsxwriter/src/core.c +++ b/libxlsxwriter/src/core.c @@ -50,7 +50,7 @@ lxw_core_free(lxw_core *core) } /* - * Convert a time_t struct to a ISO 8601 style "2010-01-01T00:00:00Z" date. + * Convert a time_t to a ISO 8601 style "2010-01-01T00:00:00Z" date. */ static void _datetime_to_iso8601_date(time_t *timer, char *str, size_t size) diff --git a/libxlsxwriter/src/format.c b/libxlsxwriter/src/format.c index ef881c7..3931917 100644 --- a/libxlsxwriter/src/format.c +++ b/libxlsxwriter/src/format.c @@ -27,6 +27,7 @@ lxw_format_new(void) GOTO_LABEL_ON_MEM_ERROR(format, mem_error); format->xf_format_indices = NULL; + format->dxf_format_indices = NULL; format->xf_index = LXW_PROPERTY_UNSET; format->dxf_index = LXW_PROPERTY_UNSET; @@ -149,7 +150,9 @@ _get_format_key(lxw_format *self) /* Set pointer members to NULL since they aren't part of the comparison. */ key->xf_format_indices = NULL; + key->dxf_format_indices = NULL; key->num_xf_formats = NULL; + key->num_dxf_formats = NULL; key->list_pointers.stqe_next = NULL; return key; @@ -286,6 +289,57 @@ lxw_format_get_xf_index(lxw_format *self) } } +/* + * Returns the DXF index number used by Excel to identify a format. + */ +int32_t +lxw_format_get_dxf_index(lxw_format *self) +{ + lxw_format *format_key; + lxw_format *existing_format; + lxw_hash_element *hash_element; + lxw_hash_table *formats_hash_table = self->dxf_format_indices; + int32_t index; + + /* Note: The formats_hash_table/dxf_format_indices contains the unique and + * more importantly the *used* formats in the workbook. + */ + + /* Format already has an index number so return it. */ + if (self->dxf_index != LXW_PROPERTY_UNSET) { + return self->dxf_index; + } + + /* Otherwise, the format doesn't have an index number so we assign one. + * First generate a unique key to identify the format in the hash table. + */ + format_key = _get_format_key(self); + + /* Return the default format index if the key generation failed. */ + if (!format_key) + return 0; + + /* Look up the format in the hash table. */ + hash_element = + lxw_hash_key_exists(formats_hash_table, format_key, + sizeof(lxw_format)); + + if (hash_element) { + /* Format matches existing format with an index. */ + free(format_key); + existing_format = hash_element->value; + return existing_format->dxf_index; + } + else { + /* New format requiring an index. */ + index = formats_hash_table->unique_count; + self->dxf_index = index; + lxw_insert_hash_element(formats_hash_table, format_key, self, + sizeof(lxw_format)); + return index; + } +} + /* * Set the font_name property. */ diff --git a/libxlsxwriter/src/packager.c b/libxlsxwriter/src/packager.c index acda1e1..dcb07b7 100644 --- a/libxlsxwriter/src/packager.c +++ b/libxlsxwriter/src/packager.c @@ -18,6 +18,10 @@ STATIC lxw_error _add_file_to_zip(lxw_packager *self, FILE * file, STATIC lxw_error _add_buffer_to_zip(lxw_packager *self, unsigned char *buffer, size_t buffer_size, const char *filename); +STATIC lxw_error _write_vml_drawing_rels_file(lxw_packager *self, + lxw_worksheet *worksheet, + uint32_t index); + /* * Forward declarations. */ @@ -465,43 +469,87 @@ _write_vml_files(lxw_packager *self) else worksheet = sheet->u.worksheet; - if (!worksheet->has_vml) + if (!worksheet->has_vml && !worksheet->has_header_vml) continue; - vml = lxw_vml_new(); - if (!vml) - return LXW_ERROR_MEMORY_MALLOC_FAILED; + if (worksheet->has_vml) { - lxw_snprintf(filename, LXW_FILENAME_LENGTH, - "xl/drawings/vmlDrawing%d.vml", index++); + vml = lxw_vml_new(); + if (!vml) + return LXW_ERROR_MEMORY_MALLOC_FAILED; - vml->file = lxw_tmpfile(self->tmpdir); - if (!vml->file) { - lxw_vml_free(vml); - return LXW_ERROR_CREATING_TMPFILE; - } + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "xl/drawings/vmlDrawing%d.vml", index++); - vml->comment_objs = worksheet->comment_objs; - vml->vml_shape_id = worksheet->vml_shape_id; - vml->comment_display_default = worksheet->comment_display_default; + vml->file = lxw_tmpfile(self->tmpdir); + if (!vml->file) { + lxw_vml_free(vml); + return LXW_ERROR_CREATING_TMPFILE; + } + + vml->comment_objs = worksheet->comment_objs; + vml->vml_shape_id = worksheet->vml_shape_id; + vml->comment_display_default = worksheet->comment_display_default; + + if (worksheet->vml_data_id_str) { + vml->vml_data_id_str = worksheet->vml_data_id_str; + } + else { + fclose(vml->file); + lxw_vml_free(vml); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + lxw_vml_assemble_xml_file(vml); + + err = _add_file_to_zip(self, vml->file, filename); - if (worksheet->vml_data_id_str) { - vml->vml_data_id_str = worksheet->vml_data_id_str; - } - else { fclose(vml->file); lxw_vml_free(vml); - return LXW_ERROR_MEMORY_MALLOC_FAILED; + + RETURN_ON_ERROR(err); } - lxw_vml_assemble_xml_file(vml); + if (worksheet->has_header_vml) { - err = _add_file_to_zip(self, vml->file, filename); + err = _write_vml_drawing_rels_file(self, worksheet, index); + RETURN_ON_ERROR(err); - fclose(vml->file); - lxw_vml_free(vml); + vml = lxw_vml_new(); + if (!vml) + return LXW_ERROR_MEMORY_MALLOC_FAILED; + + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "xl/drawings/vmlDrawing%d.vml", index++); + + vml->file = lxw_tmpfile(self->tmpdir); + if (!vml->file) { + lxw_vml_free(vml); + return LXW_ERROR_CREATING_TMPFILE; + } + + vml->image_objs = worksheet->header_image_objs; + vml->vml_shape_id = worksheet->vml_header_id * 1024; + + if (worksheet->vml_header_id_str) { + vml->vml_data_id_str = worksheet->vml_header_id_str; + } + else { + fclose(vml->file); + lxw_vml_free(vml); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + lxw_vml_assemble_xml_file(vml); + + err = _add_file_to_zip(self, vml->file, filename); + + fclose(vml->file); + lxw_vml_free(vml); + + RETURN_ON_ERROR(err); + } - RETURN_ON_ERROR(err); } return LXW_NO_ERROR; @@ -809,11 +857,27 @@ _write_styles_file(lxw_packager *self) STAILQ_INSERT_TAIL(styles->xf_formats, style_format, list_pointers); } + /* Copy the unique and in-use dxf formats from the workbook to the styles + * dxf_format list. */ + LXW_FOREACH_ORDERED(hash_element, self->workbook->used_dxf_formats) { + lxw_format *workbook_format = (lxw_format *) hash_element->value; + lxw_format *style_format = lxw_format_new(); + + if (!style_format) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto mem_error; + } + + memcpy(style_format, workbook_format, sizeof(lxw_format)); + STAILQ_INSERT_TAIL(styles->dxf_formats, style_format, list_pointers); + } + styles->font_count = self->workbook->font_count; styles->border_count = self->workbook->border_count; styles->fill_count = self->workbook->fill_count; styles->num_format_count = self->workbook->num_format_count; styles->xf_count = self->workbook->used_xf_formats->unique_count; + styles->dxf_count = self->workbook->used_dxf_formats->unique_count; styles->has_comments = self->workbook->has_comments; styles->file = lxw_tmpfile(self->tmpdir); @@ -1023,6 +1087,7 @@ _write_worksheet_rels_file(lxw_packager *self) if (STAILQ_EMPTY(worksheet->external_hyperlinks) && STAILQ_EMPTY(worksheet->external_drawing_links) && + !worksheet->external_vml_header_link && !worksheet->external_vml_comment_link && !worksheet->external_comment_link) continue; @@ -1050,6 +1115,11 @@ _write_worksheet_rels_file(lxw_packager *self) lxw_add_worksheet_relationship(rels, rel->type, rel->target, rel->target_mode); + rel = worksheet->external_vml_header_link; + if (rel) + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + rel = worksheet->external_comment_link; if (rel) lxw_add_worksheet_relationship(rels, rel->type, rel->target, @@ -1188,6 +1258,46 @@ _write_drawing_rels_file(lxw_packager *self) return LXW_NO_ERROR; } +/* + * Write the vmlDrawing .rels files for worksheets that contain images in + * headers or footers. + */ +STATIC lxw_error +_write_vml_drawing_rels_file(lxw_packager *self, lxw_worksheet *worksheet, + uint32_t index) +{ + lxw_relationships *rels; + lxw_rel_tuple *rel; + char sheetname[LXW_FILENAME_LENGTH] = { 0 }; + lxw_error err = LXW_NO_ERROR; + + rels = lxw_relationships_new(); + + rels->file = lxw_tmpfile(self->tmpdir); + if (!rels->file) { + lxw_free_relationships(rels); + return LXW_ERROR_CREATING_TMPFILE; + } + + STAILQ_FOREACH(rel, worksheet->vml_drawing_links, list_pointers) { + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + + } + + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, + "xl/drawings/_rels/vmlDrawing%d.vml.rels", index); + + lxw_relationships_assemble_xml_file(rels); + + err = _add_file_to_zip(self, rels->file, sheetname); + + fclose(rels->file); + lxw_free_relationships(rels); + + return err; +} + /* * Write the _rels/.rels xml file. */ diff --git a/libxlsxwriter/src/shared_strings.c b/libxlsxwriter/src/shared_strings.c index 6a02eaf..2704242 100644 --- a/libxlsxwriter/src/shared_strings.c +++ b/libxlsxwriter/src/shared_strings.c @@ -145,9 +145,7 @@ _write_si(lxw_sst *self, char *string) lxw_xml_start_tag(self->file, "si", NULL); /* Look for and escape control chars in the string. */ - if (strpbrk(string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C" - "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16" - "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) { + if (lxw_has_control_characters(string)) { string = lxw_escape_control_characters(string); escaped_string = LXW_TRUE; } diff --git a/libxlsxwriter/src/styles.c b/libxlsxwriter/src/styles.c index 7724e9e..081687a 100644 --- a/libxlsxwriter/src/styles.c +++ b/libxlsxwriter/src/styles.c @@ -14,7 +14,7 @@ /* * Forward declarations. */ -STATIC void _write_font(lxw_styles *self, lxw_format *format, +STATIC void _write_font(lxw_styles *self, lxw_format *format, uint8_t is_dxf, uint8_t is_rich_string); /***************************************************************************** @@ -34,9 +34,12 @@ lxw_styles_new(void) styles->xf_formats = calloc(1, sizeof(struct lxw_formats)); GOTO_LABEL_ON_MEM_ERROR(styles->xf_formats, mem_error); - STAILQ_INIT(styles->xf_formats); + styles->dxf_formats = calloc(1, sizeof(struct lxw_formats)); + GOTO_LABEL_ON_MEM_ERROR(styles->dxf_formats, mem_error); + STAILQ_INIT(styles->dxf_formats); + return styles; mem_error: @@ -55,7 +58,7 @@ lxw_styles_free(lxw_styles *styles) if (!styles) return; - /* Free the formats in the styles. */ + /* Free the xf formats in the styles. */ if (styles->xf_formats) { while (!STAILQ_EMPTY(styles->xf_formats)) { format = STAILQ_FIRST(styles->xf_formats); @@ -65,6 +68,16 @@ lxw_styles_free(lxw_styles *styles) free(styles->xf_formats); } + /* Free the dxf formats in the styles. */ + if (styles->dxf_formats) { + while (!STAILQ_EMPTY(styles->dxf_formats)) { + format = STAILQ_FIRST(styles->dxf_formats); + STAILQ_REMOVE_HEAD(styles->dxf_formats, list_pointers); + free(format); + } + free(styles->dxf_formats); + } + free(styles); } @@ -93,7 +106,7 @@ void lxw_styles_write_rich_font(lxw_styles *self, lxw_format *format) { - _write_font(self, format, LXW_TRUE); + _write_font(self, format, LXW_FALSE, LXW_TRUE); } /***************************************************************************** @@ -136,10 +149,68 @@ _write_num_fmt(lxw_styles *self, uint16_t num_fmt_id, char *format_code) { struct xml_attribute_list attributes; struct xml_attribute *attribute; + char *format_codes[] = { + "General", + "0", + "0.00", + "#,##0", + "#,##0.00", + "($#,##0_);($#,##0)", + "($#,##0_);[Red]($#,##0)", + "($#,##0.00_);($#,##0.00)", + "($#,##0.00_);[Red]($#,##0.00)", + "0%", + "0.00%", + "0.00E+00", + "# ?/?", + "# ?" "?/?" "?", /* Split string to avoid unintentional trigraph. */ + "m/d/yy", + "d-mmm-yy", + "d-mmm", + "mmm-yy", + "h:mm AM/PM", + "h:mm:ss AM/PM", + "h:mm", + "h:mm:ss", + "m/d/yy h:mm", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "General", + "(#,##0_);(#,##0)", + "(#,##0_);[Red](#,##0)", + "(#,##0.00_);(#,##0.00)", + "(#,##0.00_);[Red](#,##0.00)", + "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", + "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)", + "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", + "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)", + "mm:ss", + "[h]:mm:ss", + "mm:ss.0", + "##0.0E+0", + "@" + }; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_INT("numFmtId", num_fmt_id); - LXW_PUSH_ATTRIBUTES_STR("formatCode", format_code); + + if (num_fmt_id < 50) + LXW_PUSH_ATTRIBUTES_STR("formatCode", format_codes[num_fmt_id]); + else if (num_fmt_id < 164) + LXW_PUSH_ATTRIBUTES_STR("formatCode", "General"); + else + LXW_PUSH_ATTRIBUTES_STR("formatCode", format_code); lxw_xml_empty_tag(self->file, "numFmt", &attributes); @@ -346,11 +417,45 @@ _write_font_underline(lxw_styles *self, uint8_t underline) } +/* + * Write the font element. + */ +STATIC void +_write_font_condense(lxw_styles *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("val", "0"); + + lxw_xml_empty_tag(self->file, "condense", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the font element. + */ +STATIC void +_write_font_extend(lxw_styles *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("val", "0"); + + lxw_xml_empty_tag(self->file, "extend", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the font sub-element. */ STATIC void -_write_vert_align(lxw_styles *self, const char *align) +_write_font_vert_align(lxw_styles *self, const char *align) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -367,13 +472,20 @@ _write_vert_align(lxw_styles *self, const char *align) * Write the element. */ STATIC void -_write_font(lxw_styles *self, lxw_format *format, uint8_t is_rich_string) +_write_font(lxw_styles *self, lxw_format *format, uint8_t is_dxf, + uint8_t is_rich_string) { if (is_rich_string) lxw_xml_start_tag(self->file, "rPr", NULL); else lxw_xml_start_tag(self->file, "font", NULL); + if (format->font_condense) + _write_font_condense(self); + + if (format->font_extend) + _write_font_extend(self); + if (format->bold) lxw_xml_empty_tag(self->file, "b", NULL); @@ -393,12 +505,12 @@ _write_font(lxw_styles *self, lxw_format *format, uint8_t is_rich_string) _write_font_underline(self, format->underline); if (format->font_script == LXW_FONT_SUPERSCRIPT) - _write_vert_align(self, "superscript"); + _write_font_vert_align(self, "superscript"); if (format->font_script == LXW_FONT_SUBSCRIPT) - _write_vert_align(self, "subscript"); + _write_font_vert_align(self, "subscript"); - if (format->font_size > 0.0) + if (!is_dxf && format->font_size > 0.0) _write_font_size(self, format->font_size); if (format->theme) @@ -407,18 +519,20 @@ _write_font(lxw_styles *self, lxw_format *format, uint8_t is_rich_string) _write_font_color_indexed(self, format->color_indexed); else if (format->font_color != LXW_COLOR_UNSET) _write_font_color_rgb(self, format->font_color); - else + else if (!is_dxf) _write_font_color_theme(self, LXW_DEFAULT_FONT_THEME); - _write_font_name(self, format->font_name, is_rich_string); - _write_font_family(self, format->font_family); + if (!is_dxf) { + _write_font_name(self, format->font_name, is_rich_string); + _write_font_family(self, format->font_family); - /* Only write the scheme element for the default font type if it - * is a hyperlink. */ - if ((!*format->font_name - || strcmp(LXW_DEFAULT_FONT_NAME, format->font_name) == 0) - && !format->hyperlink) { - _write_font_scheme(self, format->font_scheme); + /* Only write the scheme element for the default font type if it + * is a hyperlink. */ + if ((!*format->font_name + || strcmp(LXW_DEFAULT_FONT_NAME, format->font_name) == 0) + && !format->hyperlink) { + _write_font_scheme(self, format->font_scheme); + } } if (format->hyperlink) { @@ -473,7 +587,7 @@ _write_fonts(lxw_styles *self) STAILQ_FOREACH(format, self->xf_formats, list_pointers) { if (format->has_font) - _write_font(self, format, LXW_FALSE); + _write_font(self, format, LXW_FALSE, LXW_FALSE); } if (self->has_comments) @@ -552,7 +666,7 @@ _write_bg_color(lxw_styles *self, lxw_color_t color) * Write the element. */ STATIC void -_write_fill(lxw_styles *self, lxw_format *format) +_write_fill(lxw_styles *self, lxw_format *format, uint8_t is_dxf) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -583,11 +697,17 @@ _write_fill(lxw_styles *self, lxw_format *format) "gray0625", }; + if (is_dxf) { + bg_color = format->dxf_bg_color; + fg_color = format->dxf_fg_color; + } + LXW_INIT_ATTRIBUTES(); lxw_xml_start_tag(self->file, "fill", NULL); - if (pattern) + /* None/Solid patterns are handled differently for dxf formats. */ + if (pattern && !(is_dxf && pattern <= LXW_PATTERN_SOLID)) LXW_PUSH_ATTRIBUTES_STR("patternType", patterns[pattern]); lxw_xml_start_tag(self->file, "patternFill", &attributes); @@ -624,7 +744,7 @@ _write_fills(lxw_styles *self) STAILQ_FOREACH(format, self->xf_formats, list_pointers) { if (format->has_fill) - _write_fill(self, format); + _write_fill(self, format, LXW_FALSE); } lxw_xml_end_tag(self->file, "fills"); @@ -705,7 +825,7 @@ _write_sub_border(lxw_styles *self, const char *type, uint8_t style, * Write the element. */ STATIC void -_write_border(lxw_styles *self, lxw_format *format) +_write_border(lxw_styles *self, lxw_format *format, uint8_t is_dxf) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -737,8 +857,16 @@ _write_border(lxw_styles *self, lxw_format *format) _write_sub_border(self, "right", format->right, format->right_color); _write_sub_border(self, "top", format->top, format->top_color); _write_sub_border(self, "bottom", format->bottom, format->bottom_color); - _write_sub_border(self, - "diagonal", format->diag_border, format->diag_color); + + if (is_dxf) { + _write_sub_border(self, "vertical", 0, LXW_COLOR_UNSET); + _write_sub_border(self, "horizontal", 0, LXW_COLOR_UNSET); + } + + /* Conditional DXF formats don't allow diagonal borders. */ + if (!is_dxf) + _write_sub_border(self, "diagonal", + format->diag_border, format->diag_color); lxw_xml_end_tag(self->file, "border"); @@ -762,7 +890,7 @@ _write_borders(lxw_styles *self) STAILQ_FOREACH(format, self->xf_formats, list_pointers) { if (format->has_border) - _write_border(self, format); + _write_border(self, format, LXW_FALSE); } lxw_xml_end_tag(self->file, "borders"); @@ -1172,18 +1300,46 @@ _write_cell_styles(lxw_styles *self) /* * Write the element. + * */ STATIC void _write_dxfs(lxw_styles *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; + lxw_format *format; + uint32_t count = self->dxf_count; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("count", "0"); + LXW_PUSH_ATTRIBUTES_INT("count", count); + + if (count) { + lxw_xml_start_tag(self->file, "dxfs", &attributes); + + STAILQ_FOREACH(format, self->dxf_formats, list_pointers) { + lxw_xml_start_tag(self->file, "dxf", NULL); + + if (format->has_dxf_font) + _write_font(self, format, LXW_TRUE, LXW_FALSE); - lxw_xml_empty_tag(self->file, "dxfs", &attributes); + if (format->num_format_index) + _write_num_fmt(self, format->num_format_index, + format->num_format); + if (format->has_dxf_fill) + _write_fill(self, format, LXW_TRUE); + + if (format->has_dxf_border) + _write_border(self, format, LXW_TRUE); + + lxw_xml_end_tag(self->file, "dxf"); + } + + lxw_xml_end_tag(self->file, "dxfs"); + } + else { + lxw_xml_empty_tag(self->file, "dxfs", &attributes); + } LXW_FREE_ATTRIBUTES(); } diff --git a/libxlsxwriter/src/utility.c b/libxlsxwriter/src/utility.c index add47fd..3a9e8c3 100644 --- a/libxlsxwriter/src/utility.c +++ b/libxlsxwriter/src/utility.c @@ -13,6 +13,7 @@ #include #include #include "xlsxwriter.h" +#include "xlsxwriter/common.h" #include "xlsxwriter/third_party/tmpfileplus.h" char *error_strings[LXW_MAX_ERRNO + 1] = { @@ -34,7 +35,6 @@ char *error_strings[LXW_MAX_ERRNO + 1] = { "Worksheet name cannot contain invalid characters: '[ ] : * ? / \\'", "Worksheet name cannot start or end with an apostrophe.", "Worksheet name is already in use.", - "Worksheet name 'History' is reserved by Excel.", "Parameter exceeds Excel's limit of 32 characters.", "Parameter exceeds Excel's limit of 128 characters.", "Parameter exceeds Excel's limit of 255 characters.", @@ -314,10 +314,11 @@ lxw_name_to_col_2(const char *col_str) } /* - * Convert a lxw_datetime struct to an Excel serial date. + * Convert a lxw_datetime struct to an Excel serial date, with a 1900 + * or 1904 epoch. */ double -lxw_datetime_to_excel_date(lxw_datetime *datetime, uint8_t date_1904) +lxw_datetime_to_excel_date_epoch(lxw_datetime *datetime, uint8_t date_1904) { int year = datetime->year; int month = datetime->month; @@ -409,6 +410,15 @@ lxw_datetime_to_excel_date(lxw_datetime *datetime, uint8_t date_1904) return days + seconds; } +/* + * Convert a lxw_datetime struct to an Excel serial date, for the 1900 epoch. + */ +double +lxw_datetime_to_excel_datetime(lxw_datetime *datetime) +{ + return lxw_datetime_to_excel_date_epoch(datetime, LXW_FALSE); +} + /* Simple strdup() implementation since it isn't ANSI C. */ char * lxw_strdup(const char *str) diff --git a/libxlsxwriter/src/vml.c b/libxlsxwriter/src/vml.c index c5647a9..d1fb618 100644 --- a/libxlsxwriter/src/vml.c +++ b/libxlsxwriter/src/vml.c @@ -330,15 +330,17 @@ _vml_write_font(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_imagedata(lxw_vml *self) +_vml_write_imagedata(lxw_vml *self, uint32_t rel_index, char *name) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - char rel_id[] = "rId1"; + char rel_id[LXW_ATTR_32]; + + lxw_snprintf(rel_id, LXW_ATTR_32, "rId%d", rel_index); LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("o:relid", rel_id); - LXW_PUSH_ATTRIBUTES_STR("o:title", "red"); + LXW_PUSH_ATTRIBUTES_STR("o:title", name); lxw_xml_empty_tag(self->file, "v:imagedata", &attributes); @@ -368,18 +370,42 @@ _vml_write_image_path(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_image_shape(lxw_vml *self) +_vml_write_image_shape(lxw_vml *self, uint32_t vml_shape_id, uint32_t z_index, + lxw_vml_obj *image_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - char id[] = "CH"; - char o_spid[] = "_x0000_s1025"; + char width_str[LXW_ATTR_32]; + char height_str[LXW_ATTR_32]; + char style[LXW_MAX_ATTRIBUTE_LENGTH]; + char o_spid[LXW_ATTR_32]; char type[] = "#_x0000_t75"; - char style[] = "position:absolute;margin-left:0;margin-top:0;width:24pt;" - "height:24pt;z-index:1"; + double width; + double height; + + /* Scale the height/width by the resolution, relative to 72dpi. */ + width = image_obj->width * (72.0 / image_obj->x_dpi); + height = image_obj->height * (72.0 / image_obj->y_dpi); + + /* Excel uses a rounding based around 72 and 96 dpi. */ + width = 72.0 / 96.0 * (uint32_t) (width * 96.0 / 72 + 0.25); + height = 72.0 / 96.0 * (uint32_t) (height * 96.0 / 72 + 0.25); + + lxw_sprintf_dbl(width_str, width); + lxw_sprintf_dbl(height_str, height); + + lxw_snprintf(o_spid, LXW_ATTR_32, "_x0000_s%d", vml_shape_id); + + lxw_snprintf(style, + LXW_MAX_ATTRIBUTE_LENGTH, + "position:absolute;" + "margin-left:0;" + "margin-top:0;" + "width:%spt;" + "height:%spt;" "z-index:%d", width_str, height_str, z_index); LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("id", image_obj->image_position); LXW_PUSH_ATTRIBUTES_STR("o:spid", o_spid); LXW_PUSH_ATTRIBUTES_STR("type", type); LXW_PUSH_ATTRIBUTES_STR("style", style); @@ -387,7 +413,7 @@ _vml_write_image_shape(lxw_vml *self) lxw_xml_start_tag(self->file, "v:shape", &attributes); /* Write the v:imagedata element. */ - _vml_write_imagedata(self); + _vml_write_imagedata(self, image_obj->rel_index, image_obj->name); /* Write the o:lock element. */ _vml_write_rotation_lock(self); @@ -398,7 +424,7 @@ _vml_write_image_shape(lxw_vml *self) } /* - * Write the element for images.. + * Write the element for images. */ STATIC void _vml_write_image_shapetype(lxw_vml *self) @@ -960,13 +986,13 @@ lxw_vml_assemble_xml_file(lxw_vml *self) _vml_write_comment_shapetype(self); STAILQ_FOREACH(comment_obj, self->comment_objs, list_pointers) { - self->vml_shape_id += 1; + self->vml_shape_id++; /* Write the element. */ _vml_write_comment_shape(self, self->vml_shape_id, z_index, comment_obj); - z_index += 1; + z_index++; } } @@ -986,8 +1012,13 @@ lxw_vml_assemble_xml_file(lxw_vml *self) _vml_write_image_shapetype(self); STAILQ_FOREACH(image_obj, self->image_objs, list_pointers) { + self->vml_shape_id++; + /* Write the element. */ - _vml_write_image_shape(self); + _vml_write_image_shape(self, self->vml_shape_id, z_index, + image_obj); + + z_index++; } } diff --git a/libxlsxwriter/src/workbook.c b/libxlsxwriter/src/workbook.c index dc7b261..5c6961d 100644 --- a/libxlsxwriter/src/workbook.c +++ b/libxlsxwriter/src/workbook.c @@ -230,7 +230,22 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->image_md5s); } + if (workbook->header_image_md5s) { + for (image_md5 = RB_MIN(lxw_image_md5s, workbook->header_image_md5s); + image_md5; image_md5 = next_image_md5) { + + next_image_md5 = + RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5); + RB_REMOVE(lxw_image_md5s, workbook->header_image_md5s, image_md5); + free(image_md5->md5); + free(image_md5); + } + + free(workbook->header_image_md5s); + } + lxw_hash_free(workbook->used_xf_formats); + lxw_hash_free(workbook->used_dxf_formats); lxw_sst_free(workbook->sst); free(workbook->options.tmpdir); free(workbook->ordered_charts); @@ -290,7 +305,7 @@ _prepare_fonts(lxw_workbook *self) uint16_t *font_index = calloc(1, sizeof(uint16_t)); *font_index = index; format->font_index = index; - format->has_font = 1; + format->has_font = LXW_TRUE; lxw_insert_hash_element(fonts, key, font_index, sizeof(lxw_font)); index++; @@ -300,6 +315,18 @@ _prepare_fonts(lxw_workbook *self) lxw_hash_free(fonts); + /* For DXF formats we only need to check if the properties have changed. */ + LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) { + lxw_format *format = (lxw_format *) used_format_element->value; + + /* The only font properties that can change for a DXF format are: + * color, bold, italic, underline and strikethrough. */ + if (format->font_color || format->bold || format->italic + || format->underline || format->font_strikeout) { + format->has_dxf_font = LXW_TRUE; + } + } + self->font_count = index; } @@ -344,6 +371,15 @@ _prepare_borders(lxw_workbook *self) } } + /* For DXF formats we only need to check if the properties have changed. */ + LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) { + lxw_format *format = (lxw_format *) used_format_element->value; + + if (format->left || format->right || format->top || format->bottom) { + format->has_dxf_border = LXW_TRUE; + } + } + lxw_hash_free(borders); self->border_count = index; @@ -393,6 +429,17 @@ _prepare_fills(lxw_workbook *self) lxw_insert_hash_element(fills, default_fill_2, fill_index2, sizeof(lxw_fill)); + /* For DXF formats we only need to check if the properties have changed. */ + LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) { + lxw_format *format = (lxw_format *) used_format_element->value; + + if (format->pattern || format->bg_color || format->fg_color) { + format->has_dxf_fill = LXW_TRUE; + format->dxf_bg_color = format->bg_color; + format->dxf_fg_color = format->fg_color; + } + } + LXW_FOREACH_ORDERED(used_format_element, self->used_xf_formats) { lxw_format *format = (lxw_format *) used_format_element->value; lxw_fill *key = lxw_format_get_fill_key(format); @@ -514,6 +561,41 @@ _prepare_num_formats(lxw_workbook *self) } } + LXW_FOREACH_ORDERED(used_format_element, self->used_dxf_formats) { + lxw_format *format = (lxw_format *) used_format_element->value; + + /* Format already has a number format index. */ + if (format->num_format_index) + continue; + + /* Check if there is a user defined number format string. */ + if (*format->num_format) { + char num_format[LXW_FORMAT_FIELD_LEN] = { 0 }; + lxw_snprintf(num_format, LXW_FORMAT_FIELD_LEN, "%s", + format->num_format); + + /* Look up the num_format in the hash table. */ + hash_element = lxw_hash_key_exists(num_formats, num_format, + LXW_FORMAT_FIELD_LEN); + + if (hash_element) { + /* Num_Format has already been used. */ + format->num_format_index = *(uint16_t *) hash_element->value; + } + else { + /* This is a new num_format. */ + num_format_index = calloc(1, sizeof(uint16_t)); + *num_format_index = index; + format->num_format_index = index; + lxw_insert_hash_element(num_formats, format->num_format, + num_format_index, + LXW_FORMAT_FIELD_LEN); + index++; + /* Don't update num_format_count for DXF formats. */ + } + } + } + lxw_hash_free(num_formats); self->num_format_count = num_format_count; @@ -879,6 +961,9 @@ _populate_range_dimensions(lxw_workbook *self, lxw_series_range *range) STATIC void _populate_range(lxw_workbook *self, lxw_series_range *range) { + if (!range) + return; + _populate_range_dimensions(self, range); _populate_range_data_cache(self, range); } @@ -892,6 +977,7 @@ _add_chart_cache_data(lxw_workbook *self) { lxw_chart *chart; lxw_chart_series *series; + uint16_t i; STAILQ_FOREACH(chart, self->ordered_charts, ordered_list_pointers) { @@ -906,6 +992,11 @@ _add_chart_cache_data(lxw_workbook *self) _populate_range(self, series->categories); _populate_range(self, series->values); _populate_range(self, series->title.range); + + for (i = 0; i < series->data_label_count; i++) { + lxw_chart_custom_label *data_label = &series->data_labels[i]; + _populate_range(self, data_label->range); + } } } } @@ -927,6 +1018,7 @@ _prepare_drawings(lxw_workbook *self) lxw_image_md5 tmp_image_md5; lxw_image_md5 *new_image_md5 = NULL; lxw_image_md5 *found_duplicate_image = NULL; + uint8_t i; STAILQ_FOREACH(sheet, self->sheets, list_pointers) { if (sheet->is_chartsheet) { @@ -939,11 +1031,14 @@ _prepare_drawings(lxw_workbook *self) } if (STAILQ_EMPTY(worksheet->image_props) - && STAILQ_EMPTY(worksheet->chart_data)) + && STAILQ_EMPTY(worksheet->chart_data) + && !worksheet->has_header_vml) { continue; + } drawing_id++; + /* Prepare worksheet images. */ STAILQ_FOREACH(object_props, worksheet->image_props, list_pointers) { if (object_props->image_type == LXW_IMAGE_PNG) @@ -987,6 +1082,7 @@ _prepare_drawings(lxw_workbook *self) object_props); } + /* Prepare worksheet charts. */ STAILQ_FOREACH(object_props, worksheet->chart_data, list_pointers) { chart_ref_id++; lxw_worksheet_prepare_chart(worksheet, chart_ref_id, drawing_id, @@ -995,6 +1091,55 @@ _prepare_drawings(lxw_workbook *self) STAILQ_INSERT_TAIL(self->ordered_charts, object_props->chart, ordered_list_pointers); } + + /* Prepare worksheet header/footer images. */ + for (i = 0; i < LXW_HEADER_FOOTER_OBJS_MAX; i++) { + + object_props = *worksheet->header_footer_objs[i]; + if (!object_props) + continue; + + if (object_props->image_type == LXW_IMAGE_PNG) + self->has_png = LXW_TRUE; + + if (object_props->image_type == LXW_IMAGE_JPEG) + self->has_jpeg = LXW_TRUE; + + if (object_props->image_type == LXW_IMAGE_BMP) + self->has_bmp = LXW_TRUE; + + /* Check for duplicate images and only store the first instance. */ + if (object_props->md5) { + tmp_image_md5.md5 = object_props->md5; + found_duplicate_image = RB_FIND(lxw_image_md5s, + self->header_image_md5s, + &tmp_image_md5); + } + + if (found_duplicate_image) { + ref_id = found_duplicate_image->id; + object_props->is_duplicate = LXW_TRUE; + } + else { + image_ref_id++; + ref_id = image_ref_id; + +#ifndef USE_NO_MD5 + new_image_md5 = calloc(1, sizeof(lxw_image_md5)); +#endif + if (new_image_md5 && object_props->md5) { + new_image_md5->id = ref_id; + new_image_md5->md5 = lxw_strdup(object_props->md5); + + RB_INSERT(lxw_image_md5s, self->header_image_md5s, + new_image_md5); + } + } + + lxw_worksheet_prepare_header_image(worksheet, ref_id, + object_props); + } + } self->drawing_count = drawing_id; @@ -1012,6 +1157,7 @@ _prepare_vml(lxw_workbook *self) uint32_t comment_id = 0; uint32_t vml_drawing_id = 0; uint32_t vml_data_id = 1; + uint32_t vml_header_id = 0; uint32_t vml_shape_id = 1024; uint32_t comment_count = 0; @@ -1027,12 +1173,12 @@ _prepare_vml(lxw_workbook *self) if (worksheet->has_vml) { self->has_vml = LXW_TRUE; if (worksheet->has_comments) { - self->comment_count += 1; - comment_id += 1; + self->comment_count++; + comment_id++; self->has_comments = LXW_TRUE; } - vml_drawing_id += 1; + vml_drawing_id++; comment_count = lxw_worksheet_prepare_vml_objects(worksheet, vml_data_id, @@ -1043,7 +1189,15 @@ _prepare_vml(lxw_workbook *self) /* Each VML should start with a shape id incremented by 1024. */ vml_data_id += 1 * ((1024 + comment_count) / 1024); vml_shape_id += 1024 * ((1024 + comment_count) / 1024); + } + if (worksheet->has_header_vml) { + self->has_vml = LXW_TRUE; + vml_drawing_id++; + vml_header_id++; + lxw_worksheet_prepare_header_vml_objects(worksheet, + vml_header_id, + vml_drawing_id); } } } @@ -1547,6 +1701,11 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) GOTO_LABEL_ON_MEM_ERROR(workbook->image_md5s, mem_error); RB_INIT(workbook->image_md5s); + /* Add the image MD5 tree. */ + workbook->header_image_md5s = calloc(1, sizeof(struct lxw_image_md5s)); + GOTO_LABEL_ON_MEM_ERROR(workbook->header_image_md5s, mem_error); + RB_INIT(workbook->header_image_md5s); + /* Add the charts list. */ workbook->charts = calloc(1, sizeof(struct lxw_charts)); GOTO_LABEL_ON_MEM_ERROR(workbook->charts, mem_error); @@ -1579,6 +1738,10 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) workbook->used_xf_formats = lxw_hash_new(128, 1, 0); GOTO_LABEL_ON_MEM_ERROR(workbook->used_xf_formats, mem_error); + /* Add a hash table to track format indices. */ + workbook->used_dxf_formats = lxw_hash_new(128, 1, 0); + GOTO_LABEL_ON_MEM_ERROR(workbook->used_dxf_formats, mem_error); + /* Add the worksheets list. */ workbook->custom_properties = calloc(1, sizeof(struct lxw_custom_properties)); @@ -1810,6 +1973,7 @@ workbook_add_format(lxw_workbook *self) RETURN_ON_MEM_ERROR(format, NULL); format->xf_format_indices = self->used_xf_formats; + format->dxf_format_indices = self->used_dxf_formats; format->num_xf_formats = &self->num_xf_formats; STAILQ_INSERT_TAIL(self->formats, format, list_pointers); @@ -2324,10 +2488,6 @@ workbook_validate_sheet_name(lxw_workbook *self, const char *sheetname) if (sheetname[0] == '\'' || sheetname[strlen(sheetname) - 1] == '\'') return LXW_ERROR_SHEETNAME_START_END_APOSTROPHE; - /* Check that the worksheet name isn't the reserved work "History". */ - if (lxw_strcasecmp(sheetname, "history") == 0) - return LXW_ERROR_SHEETNAME_RESERVED; - /* Check if the worksheet name is already in use. */ if (workbook_get_worksheet_by_name(self, sheetname)) return LXW_ERROR_SHEETNAME_ALREADY_USED; diff --git a/libxlsxwriter/src/worksheet.c b/libxlsxwriter/src/worksheet.c index 573715c..8c16659 100644 --- a/libxlsxwriter/src/worksheet.c +++ b/libxlsxwriter/src/worksheet.c @@ -7,6 +7,10 @@ * */ +#ifdef USE_FMEMOPEN +#define _POSIX_C_SOURCE 200809L +#endif + #include "xlsxwriter/xmlwriter.h" #include "xlsxwriter/worksheet.h" #include "xlsxwriter/format.h" @@ -27,12 +31,20 @@ STATIC int _row_cmp(lxw_row *row1, lxw_row *row2); STATIC int _cell_cmp(lxw_cell *cell1, lxw_cell *cell2); STATIC int _drawing_rel_id_cmp(lxw_drawing_rel_id *tuple1, lxw_drawing_rel_id *tuple2); +STATIC int _cond_format_hash_cmp(lxw_cond_format_hash_element *elem_1, + lxw_cond_format_hash_element *elem_2); #ifndef __clang_analyzer__ LXW_RB_GENERATE_ROW(lxw_table_rows, lxw_row, tree_pointers, _row_cmp); LXW_RB_GENERATE_CELL(lxw_table_cells, lxw_cell, tree_pointers, _cell_cmp); LXW_RB_GENERATE_DRAWING_REL_IDS(lxw_drawing_rel_ids, lxw_drawing_rel_id, tree_pointers, _drawing_rel_id_cmp); +LXW_RB_GENERATE_VML_DRAWING_REL_IDS(lxw_vml_drawing_rel_ids, + lxw_drawing_rel_id, tree_pointers, + _drawing_rel_id_cmp); +LXW_RB_GENERATE_COND_FORMAT_HASH(lxw_cond_format_hash, + lxw_cond_format_hash_element, tree_pointers, + _cond_format_hash_cmp); #endif /***************************************************************************** @@ -130,6 +142,10 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) GOTO_LABEL_ON_MEM_ERROR(worksheet->comment_objs, mem_error); STAILQ_INIT(worksheet->comment_objs); + worksheet->header_image_objs = calloc(1, sizeof(struct lxw_comment_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->header_image_objs, mem_error); + STAILQ_INIT(worksheet->header_image_objs); + worksheet->selections = calloc(1, sizeof(struct lxw_selections)); GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error); STAILQ_INIT(worksheet->selections); @@ -152,6 +168,10 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_links, mem_error); STAILQ_INIT(worksheet->drawing_links); + worksheet->vml_drawing_links = calloc(1, sizeof(struct lxw_rel_tuples)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->vml_drawing_links, mem_error); + STAILQ_INIT(worksheet->vml_drawing_links); + if (init_data && init_data->optimize) { FILE *tmpfile; @@ -172,6 +192,16 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_rel_ids, mem_error); RB_INIT(worksheet->drawing_rel_ids); + worksheet->vml_drawing_rel_ids = + calloc(1, sizeof(struct lxw_drawing_rel_ids)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->vml_drawing_rel_ids, mem_error); + RB_INIT(worksheet->vml_drawing_rel_ids); + + worksheet->conditional_formats = + calloc(1, sizeof(struct lxw_cond_format_hash)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->conditional_formats, mem_error); + RB_INIT(worksheet->conditional_formats); + /* Initialize the worksheet dimensions. */ worksheet->dim_rowmax = 0; worksheet->dim_colmax = 0; @@ -215,6 +245,13 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) worksheet->max_url_length = 2079; worksheet->comment_display_default = LXW_COMMENT_DISPLAY_HIDDEN; + worksheet->header_footer_objs[0] = &worksheet->header_left_object_props; + worksheet->header_footer_objs[1] = &worksheet->header_center_object_props; + worksheet->header_footer_objs[2] = &worksheet->header_right_object_props; + worksheet->header_footer_objs[3] = &worksheet->footer_left_object_props; + worksheet->header_footer_objs[4] = &worksheet->footer_center_object_props; + worksheet->header_footer_objs[5] = &worksheet->footer_right_object_props; + if (init_data) { worksheet->name = init_data->name; worksheet->quoted_name = init_data->quoted_name; @@ -237,7 +274,7 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) } /* - * Free a worksheet data_validation. + * Free vml object. */ STATIC void _free_vml_object(lxw_vml_obj *vml_obj) @@ -248,6 +285,8 @@ _free_vml_object(lxw_vml_obj *vml_obj) free(vml_obj->author); free(vml_obj->font_name); free(vml_obj->text); + free(vml_obj->image_position); + free(vml_obj->name); free(vml_obj); } @@ -313,6 +352,7 @@ _free_object_properties(lxw_object_properties *object_property) free(object_property->tip); free(object_property->image_buffer); free(object_property->md5); + free(object_property->image_position); free(object_property); } @@ -336,6 +376,24 @@ _free_data_validation(lxw_data_val_obj *data_validation) free(data_validation); } +/* + * Free a worksheet conditional format obj. + */ +STATIC void +_free_cond_format(lxw_cond_format_obj *cond_format) +{ + if (!cond_format) + return; + + free(cond_format->min_value_string); + free(cond_format->mid_value_string); + free(cond_format->max_value_string); + free(cond_format->type_string); + free(cond_format->guid); + + free(cond_format); +} + /* * Free a relationship structure. */ @@ -363,11 +421,15 @@ lxw_worksheet_free(lxw_worksheet *worksheet) lxw_col_t col; lxw_merged_range *merged_range; lxw_object_properties *object_props; + lxw_vml_obj *header_image_vml; lxw_selection *selection; lxw_data_val_obj *data_validation; lxw_rel_tuple *relationship; + lxw_cond_format_obj *cond_format; struct lxw_drawing_rel_id *drawing_rel_id; struct lxw_drawing_rel_id *next_drawing_rel_id; + struct lxw_cond_format_hash_element *cond_format_elem; + struct lxw_cond_format_hash_element *next_cond_format_elem; if (!worksheet) return; @@ -452,6 +514,16 @@ lxw_worksheet_free(lxw_worksheet *worksheet) /* Just free the list. The list objects are freed from the RB tree. */ free(worksheet->comment_objs); + if (worksheet->header_image_objs) { + while (!STAILQ_EMPTY(worksheet->header_image_objs)) { + header_image_vml = STAILQ_FIRST(worksheet->header_image_objs); + STAILQ_REMOVE_HEAD(worksheet->header_image_objs, list_pointers); + _free_vml_object(header_image_vml); + } + + free(worksheet->header_image_objs); + } + if (worksheet->selections) { while (!STAILQ_EMPTY(worksheet->selections)) { selection = STAILQ_FIRST(worksheet->selections); @@ -493,6 +565,13 @@ lxw_worksheet_free(lxw_worksheet *worksheet) } free(worksheet->drawing_links); + while (!STAILQ_EMPTY(worksheet->vml_drawing_links)) { + relationship = STAILQ_FIRST(worksheet->vml_drawing_links); + STAILQ_REMOVE_HEAD(worksheet->vml_drawing_links, list_pointers); + _free_relationship(relationship); + } + free(worksheet->vml_drawing_links); + if (worksheet->drawing_rel_ids) { for (drawing_rel_id = RB_MIN(lxw_drawing_rel_ids, worksheet->drawing_rel_ids); @@ -510,8 +589,51 @@ lxw_worksheet_free(lxw_worksheet *worksheet) free(worksheet->drawing_rel_ids); } + if (worksheet->vml_drawing_rel_ids) { + for (drawing_rel_id = + RB_MIN(lxw_vml_drawing_rel_ids, worksheet->vml_drawing_rel_ids); + drawing_rel_id; drawing_rel_id = next_drawing_rel_id) { + + next_drawing_rel_id = + RB_NEXT(lxw_vml_drawing_rel_ids, worksheet->drawing_rel_id, + drawing_rel_id); + RB_REMOVE(lxw_vml_drawing_rel_ids, worksheet->vml_drawing_rel_ids, + drawing_rel_id); + free(drawing_rel_id->target); + free(drawing_rel_id); + } + + free(worksheet->vml_drawing_rel_ids); + } + + if (worksheet->conditional_formats) { + for (cond_format_elem = + RB_MIN(lxw_cond_format_hash, worksheet->conditional_formats); + cond_format_elem; cond_format_elem = next_cond_format_elem) { + + next_cond_format_elem = RB_NEXT(lxw_cond_format_hash, + worksheet->conditional_formats, + cond_format_elem); + RB_REMOVE(lxw_cond_format_hash, + worksheet->conditional_formats, cond_format_elem); + + while (!STAILQ_EMPTY(cond_format_elem->cond_formats)) { + cond_format = STAILQ_FIRST(cond_format_elem->cond_formats); + STAILQ_REMOVE_HEAD(cond_format_elem->cond_formats, + list_pointers); + _free_cond_format(cond_format); + } + + free(cond_format_elem->cond_formats); + free(cond_format_elem); + } + + free(worksheet->conditional_formats); + } + _free_relationship(worksheet->external_vml_comment_link); _free_relationship(worksheet->external_comment_link); + _free_relationship(worksheet->external_vml_header_link); if (worksheet->array) { for (col = 0; col < LXW_COL_MAX; col++) { @@ -532,7 +654,17 @@ lxw_worksheet_free(lxw_worksheet *worksheet) free(worksheet->quoted_name); free(worksheet->vba_codename); free(worksheet->vml_data_id_str); + free(worksheet->vml_header_id_str); free(worksheet->comment_author); + free(worksheet->ignore_number_stored_as_text); + free(worksheet->ignore_eval_error); + free(worksheet->ignore_formula_differs); + free(worksheet->ignore_formula_range); + free(worksheet->ignore_formula_unlocked); + free(worksheet->ignore_empty_cell_reference); + free(worksheet->ignore_list_data_validation); + free(worksheet->ignore_calculated_column); + free(worksheet->ignore_two_digit_text_year); free(worksheet); worksheet = NULL; @@ -1015,6 +1147,16 @@ _drawing_rel_id_cmp(lxw_drawing_rel_id *rel_id1, lxw_drawing_rel_id *rel_id2) return strcmp(rel_id1->target, rel_id2->target); } +/* + * Comparator for the conditional format RB hash elements. + */ +STATIC int +_cond_format_hash_cmp(lxw_cond_format_hash_element *elem_1, + lxw_cond_format_hash_element *elem_2) +{ + return strcmp(elem_1->sqref, elem_2->sqref); +} + /* * Get the index used to address a drawing rel link. */ @@ -1077,6 +1219,68 @@ _find_drawing_rel_index(lxw_worksheet *self, char *target) return 0; } +/* + * Get the index used to address a VMLdrawing rel link. + */ +STATIC uint32_t +_get_vml_drawing_rel_index(lxw_worksheet *self, char *target) +{ + lxw_drawing_rel_id tmp_drawing_rel_id; + lxw_drawing_rel_id *found_duplicate_target = NULL; + lxw_drawing_rel_id *new_drawing_rel_id = NULL; + + if (target) { + tmp_drawing_rel_id.target = target; + found_duplicate_target = RB_FIND(lxw_vml_drawing_rel_ids, + self->vml_drawing_rel_ids, + &tmp_drawing_rel_id); + } + + if (found_duplicate_target) { + return found_duplicate_target->id; + } + else { + self->vml_drawing_rel_id++; + + if (target) { + new_drawing_rel_id = calloc(1, sizeof(lxw_drawing_rel_id)); + + if (new_drawing_rel_id) { + new_drawing_rel_id->id = self->vml_drawing_rel_id; + new_drawing_rel_id->target = lxw_strdup(target); + + RB_INSERT(lxw_vml_drawing_rel_ids, self->vml_drawing_rel_ids, + new_drawing_rel_id); + } + } + + return self->vml_drawing_rel_id; + } +} + +/* + * find the index used to address a VML drawing rel link. + */ +STATIC uint32_t +_find_vml_drawing_rel_index(lxw_worksheet *self, char *target) +{ + lxw_drawing_rel_id tmp_drawing_rel_id; + lxw_drawing_rel_id *found_duplicate_target = NULL; + + if (!target) + return 0; + + tmp_drawing_rel_id.target = target; + found_duplicate_target = RB_FIND(lxw_vml_drawing_rel_ids, + self->vml_drawing_rel_ids, + &tmp_drawing_rel_id); + + if (found_duplicate_target) + return found_duplicate_target->id; + else + return 0; +} + /* * Simple replacement for libgen.h basename() for compatibility with MSVC. It * handles forward and back slashes. It doesn't copy exactly the return @@ -1185,11 +1389,21 @@ _worksheet_write_worksheet(lxw_worksheet *self) "spreadsheetml/2006/main"; char xmlns_r[] = "http://schemas.openxmlformats.org/" "officeDocument/2006/relationships"; + char xmlns_mc[] = "http://schemas.openxmlformats.org/" + "markup-compatibility/2006"; + char xmlns_x14ac[] = "http://schemas.microsoft.com/" + "office/spreadsheetml/2009/9/ac"; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r); + if (self->excel_version == 2010) { + LXW_PUSH_ATTRIBUTES_STR("xmlns:mc", xmlns_mc); + LXW_PUSH_ATTRIBUTES_STR("xmlns:x14ac", xmlns_x14ac); + LXW_PUSH_ATTRIBUTES_STR("mc:Ignorable", "x14ac"); + } + lxw_xml_start_tag(self->file, "worksheet", &attributes); LXW_FREE_ATTRIBUTES(); } @@ -1677,6 +1891,9 @@ _worksheet_write_sheet_format_pr(lxw_worksheet *self) if (self->outline_col_level) LXW_PUSH_ATTRIBUTES_INT("outlineLevelCol", self->outline_col_level); + if (self->excel_version == 2010) + LXW_PUSH_ATTRIBUTES_STR("x14ac:dyDescent", "0.25"); + lxw_xml_empty_tag(self->file, "sheetFormatPr", &attributes); LXW_FREE_ATTRIBUTES(); @@ -1920,6 +2137,9 @@ _write_row(lxw_worksheet *self, lxw_row *row, char *spans) if (row->collapsed) LXW_PUSH_ATTRIBUTES_STR("collapsed", "1"); + if (self->excel_version == 2010) + LXW_PUSH_ATTRIBUTES_STR("x14ac:dyDescent", "0.25"); + if (!row->data_changed) lxw_xml_empty_tag(self->file, "row", &attributes); else @@ -1991,14 +2211,15 @@ _worksheet_size_row(lxw_worksheet *self, lxw_row_t row_num, uint8_t anchor) row = lxw_worksheet_find_row(self, row_num); + /* Note, the 0.75 below is due to the difference between 72/96 DPI. */ if (row) { if (row->hidden && anchor != LXW_OBJECT_MOVE_AND_SIZE_AFTER) pixels = 0; else - pixels = (uint32_t) (4.0 / 3.0 * row->height); + pixels = (uint32_t) (row->height / 0.75); } else { - pixels = (uint32_t) (4.0 / 3.0 * self->default_row_height); + pixels = (uint32_t) (self->default_row_height / 0.75); } return pixels; @@ -2510,6 +2731,73 @@ lxw_worksheet_prepare_image(lxw_worksheet *self, } } +/* + * Set up image/drawings for header/footer images. + */ +void +lxw_worksheet_prepare_header_image(lxw_worksheet *self, + uint32_t image_ref_id, + lxw_object_properties *object_props) +{ + lxw_rel_tuple *relationship = NULL; + char filename[LXW_FILENAME_LENGTH]; + lxw_vml_obj *header_image_vml; + char *extension; + + STAILQ_INSERT_TAIL(self->image_props, object_props, list_pointers); + + if (!_find_vml_drawing_rel_index(self, object_props->md5)) { + relationship = calloc(1, sizeof(lxw_rel_tuple)); + RETURN_VOID_ON_MEM_ERROR(relationship); + + relationship->type = lxw_strdup("/image"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../media/image%d.%s", image_ref_id, + object_props->extension); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + STAILQ_INSERT_TAIL(self->vml_drawing_links, relationship, + list_pointers); + } + + header_image_vml = calloc(1, sizeof(lxw_vml_obj)); + GOTO_LABEL_ON_MEM_ERROR(header_image_vml, mem_error); + + header_image_vml->width = object_props->width; + header_image_vml->height = object_props->height; + header_image_vml->x_dpi = object_props->x_dpi; + header_image_vml->y_dpi = object_props->y_dpi; + header_image_vml->rel_index = 1; + + header_image_vml->image_position = + lxw_strdup(object_props->image_position); + header_image_vml->name = lxw_strdup(object_props->description); + + /* Strip the extension from the filename. */ + extension = strchr(header_image_vml->name, '.'); + if (extension) + *extension = '\0'; + + header_image_vml->rel_index = + _get_vml_drawing_rel_index(self, object_props->md5); + + STAILQ_INSERT_TAIL(self->header_image_objs, header_image_vml, + list_pointers); + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } +} + /* * Set up chart/drawings. */ @@ -2721,6 +3009,57 @@ lxw_worksheet_prepare_vml_objects(lxw_worksheet *self, return 0; } +/* + * Set up external linkage for VML header/footer images. + */ +void +lxw_worksheet_prepare_header_vml_objects(lxw_worksheet *self, + uint32_t vml_header_id, + uint32_t vml_drawing_id) +{ + + lxw_rel_tuple *relationship; + char filename[LXW_FILENAME_LENGTH]; + char *vml_data_id_str; + + self->vml_header_id = vml_header_id; + + /* Set up the VML relationship for header images. */ + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/vmlDrawing"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../drawings/vmlDrawing%d.vml", + vml_drawing_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + self->external_vml_header_link = relationship; + + /* If this allocation fails it will be dealt with in packager.c. */ + vml_data_id_str = calloc(1, sizeof("4294967295")); + GOTO_LABEL_ON_MEM_ERROR(vml_data_id_str, mem_error); + + lxw_snprintf(vml_data_id_str, sizeof("4294967295"), "%d", vml_header_id); + + self->vml_header_id_str = vml_data_id_str; + + return; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + + return; +} + /* * Extract width and height information from a PNG file. */ @@ -2817,13 +3156,13 @@ _process_png(lxw_object_properties *object_props) object_props->width = width; object_props->height = height; object_props->x_dpi = x_dpi ? x_dpi : 96; - object_props->y_dpi = y_dpi ? x_dpi : 96; + object_props->y_dpi = y_dpi ? y_dpi : 96; object_props->extension = lxw_strdup("png"); return LXW_NO_ERROR; file_error: - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + LXW_WARN_FORMAT1("worksheet image insertion: " "no size data found in: %s.", object_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; @@ -2943,13 +3282,13 @@ _process_jpeg(lxw_object_properties *image_props) image_props->width = width; image_props->height = height; image_props->x_dpi = x_dpi ? x_dpi : 96; - image_props->y_dpi = y_dpi ? x_dpi : 96; + image_props->y_dpi = y_dpi ? y_dpi : 96; image_props->extension = lxw_strdup("jpeg"); return LXW_NO_ERROR; file_error: - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + LXW_WARN_FORMAT1("worksheet image insertion: " "no size data found in: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; @@ -2998,7 +3337,7 @@ _process_bmp(lxw_object_properties *image_props) return LXW_NO_ERROR; file_error: - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + LXW_WARN_FORMAT1("worksheet image insertion: " "no size data found in: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; @@ -3022,7 +3361,7 @@ _get_image_properties(lxw_object_properties *image_props) /* Read 4 bytes to look for the file header/signature. */ if (fread(signature, 1, 4, image_props->stream) < 4) { - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + LXW_WARN_FORMAT1("worksheet image insertion: " "couldn't read image type for: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; @@ -3041,7 +3380,7 @@ _get_image_properties(lxw_object_properties *image_props) return LXW_ERROR_IMAGE_DIMENSIONS; } else { - LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): " + LXW_WARN_FORMAT1("worksheet image insertion: " "unsupported image format for: %s.", image_props->filename); return LXW_ERROR_IMAGE_DIMENSIONS; @@ -3078,6 +3417,61 @@ _get_image_properties(lxw_object_properties *image_props) return LXW_NO_ERROR; } +/* Conditional formats that refer to the same cell sqref range, like A or + * B1:B9, need to be written as part of one xml structure. Therefore we need + * to store them in a RB hash/tree keyed by sqref. Within the RB hash element + * we then store conditional formats that refer to sqref in a STAILQ list. */ +lxw_error +_store_conditional_format_object(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + lxw_cond_format_hash_element tmp_hash_element; + lxw_cond_format_hash_element *found_hash_element = NULL; + lxw_cond_format_hash_element *new_hash_element = NULL; + + /* Create a temp hash element to do the lookup. */ + LXW_ATTRIBUTE_COPY(tmp_hash_element.sqref, cond_format->sqref); + found_hash_element = RB_FIND(lxw_cond_format_hash, + self->conditional_formats, + &tmp_hash_element); + + if (found_hash_element) { + /* If the RB element exists then add the conditional format to the + * list for the sqref range.*/ + STAILQ_INSERT_TAIL(found_hash_element->cond_formats, cond_format, + list_pointers); + } + else { + /* Create a new RB hash element. */ + new_hash_element = calloc(1, sizeof(lxw_cond_format_hash_element)); + GOTO_LABEL_ON_MEM_ERROR(new_hash_element, mem_error); + + /* Use the sqref as the key. */ + LXW_ATTRIBUTE_COPY(new_hash_element->sqref, cond_format->sqref); + + /* Also create the list where we store the cond format objects. */ + new_hash_element->cond_formats = + calloc(1, sizeof(struct lxw_cond_format_list)); + GOTO_LABEL_ON_MEM_ERROR(new_hash_element->cond_formats, mem_error); + + /* Initialize the list and add the conditional format object. */ + STAILQ_INIT(new_hash_element->cond_formats); + STAILQ_INSERT_TAIL(new_hash_element->cond_formats, cond_format, + list_pointers); + + /* Now insert the RB hash element into the tree. */ + RB_INSERT(lxw_cond_format_hash, self->conditional_formats, + new_hash_element); + + } + + return LXW_NO_ERROR; + +mem_error: + free(new_hash_element); + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + /***************************************************************************** * * XML file assembly functions. @@ -3221,6 +3615,16 @@ _write_formula_num_cell(lxw_worksheet *self, lxw_cell *cell) lxw_xml_data_element(self->file, "v", data, NULL); } +/* + * Write out a formula worksheet cell with a numeric result. + */ +STATIC void +_write_formula_str_cell(lxw_worksheet *self, lxw_cell *cell) +{ + lxw_xml_data_element(self->file, "f", cell->u.string, NULL); + lxw_xml_data_element(self->file, "v", cell->user_data2, NULL); +} + /* * Write out an array formula worksheet cell with a numeric result. */ @@ -3357,8 +3761,17 @@ _write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format) LXW_PUSH_ATTRIBUTES_INT("s", style_index); if (cell->type == FORMULA_CELL) { + /* If user_data2 is set then the formula has a string result. */ + if (cell->user_data2) + LXW_PUSH_ATTRIBUTES_STR("t", "str"); + lxw_xml_start_tag(self->file, "c", &attributes); - _write_formula_num_cell(self, cell); + + if (cell->user_data2) + _write_formula_str_cell(self, cell); + else + _write_formula_num_cell(self, cell); + lxw_xml_end_tag(self->file, "c"); } else if (cell->type == BLANK_CELL) { @@ -3466,33 +3879,95 @@ lxw_worksheet_write_single_row(lxw_worksheet *self) row->row_changed = LXW_FALSE; } -/* - * Write the element. - */ -STATIC void -_worksheet_write_col_info(lxw_worksheet *self, lxw_col_options *options) +/* Process a header/footer image and store it in the correct slot. */ +lxw_error +_worksheet_set_header_footer_image(lxw_worksheet *self, char *filename, + uint8_t image_position) { - struct xml_attribute_list attributes; - struct xml_attribute *attribute; + FILE *image_stream; + char *description; + lxw_object_properties *object_props; + char *image_strings[] = { "LH", "CH", "RH", "LF", "CF", "RF" }; - double width = options->width; - uint8_t has_custom_width = LXW_TRUE; - int32_t xf_index = 0; - double max_digit_width = 7.0; /* For Calabri 11. */ - double padding = 5.0; + /* Not all slots will have image files. */ + if (!filename) + return LXW_NO_ERROR; - /* Get the format index. */ - if (options->format) { - xf_index = lxw_format_get_xf_index(options->format); + /* Check that the image file exists and can be opened. */ + image_stream = lxw_fopen(filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "file doesn't exist or can't be opened: %s.", + filename); + return LXW_ERROR_PARAMETER_VALIDATION; } - /* Check if width is the Excel default. */ - if (width == LXW_DEF_COL_WIDTH) { + /* Use the filename as the default description, like Excel. */ + description = lxw_basename(filename); + if (!description) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "couldn't get basename for file: %s.", filename); + fclose(image_stream); + return LXW_ERROR_PARAMETER_VALIDATION; + } - /* The default col width changes to 0 for hidden columns. */ - if (options->hidden) - width = 0; - else + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + /* Copy other options or set defaults. */ + object_props->filename = lxw_strdup(filename); + object_props->description = lxw_strdup(description); + object_props->stream = image_stream; + + /* Set VML image position string based on the header/footer/position. */ + object_props->image_position = lxw_strdup(image_strings[image_position]); + + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + *self->header_footer_objs[image_position] = object_props; + self->has_header_vml = LXW_TRUE; + fclose(image_stream); + return LXW_NO_ERROR; + } + else { + _free_object_properties(object_props); + fclose(image_stream); + return LXW_ERROR_IMAGE_DIMENSIONS; + } + + return LXW_NO_ERROR; +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_col_info(lxw_worksheet *self, lxw_col_options *options) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + double width = options->width; + uint8_t has_custom_width = LXW_TRUE; + int32_t xf_index = 0; + double max_digit_width = 7.0; /* For Calabri 11. */ + double padding = 5.0; + + /* Get the format index. */ + if (options->format) { + xf_index = lxw_format_get_xf_index(options->format); + } + + /* Check if width is the Excel default. */ + if (width == LXW_DEF_COL_WIDTH) { + + /* The default col width changes to 0 for hidden columns. */ + if (options->hidden) + width = 0; + else has_custom_width = LXW_FALSE; } @@ -4101,6 +4576,31 @@ _worksheet_write_legacy_drawing(lxw_worksheet *self) } +/* + * Write the element. + */ +STATIC void +_worksheet_write_legacy_drawing_hf(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; + + if (!self->has_header_vml) + return; + else + self->rel_count++; + + lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count); + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); + + lxw_xml_empty_tag(self->file, "legacyDrawingHF", &attributes); + + LXW_FREE_ATTRIBUTES(); + +} + /* * Write the element. */ @@ -4319,38 +4819,1648 @@ _worksheet_write_data_validation(lxw_worksheet *self, break; } - if (validation->validate != LXW_VALIDATION_TYPE_ANY) - lxw_xml_end_tag(self->file, "dataValidation"); + if (validation->validate != LXW_VALIDATION_TYPE_ANY) + lxw_xml_end_tag(self->file, "dataValidation"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_data_validations(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_data_val_obj *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(); +} + +/* + * Write the element for strings. + */ +STATIC void +_worksheet_write_formula_str(lxw_worksheet *self, char *data) +{ + lxw_xml_data_element(self->file, "formula", data, NULL); +} + +/* + * Write the element for numbers. + */ +STATIC void +_worksheet_write_formula_num(lxw_worksheet *self, double num) +{ + char data[LXW_ATTR_32]; + + lxw_sprintf_dbl(data, num); + lxw_xml_data_element(self->file, "formula", data, NULL); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_ext(lxw_worksheet *self, char *uri) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns_x_14[] = + "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns:x14", xmlns_x_14); + LXW_PUSH_ATTRIBUTES_STR("uri", uri); + + lxw_xml_start_tag(self->file, "ext", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the dataBar extension element. + */ +STATIC void +_worksheet_write_data_bar_ext(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + /* Create a pseudo GUID for each unique Excel 2010 data bar. */ + cond_format->guid = calloc(1, LXW_GUID_LENGTH); + lxw_snprintf(cond_format->guid, LXW_GUID_LENGTH, + "{DA7ABA51-AAAA-BBBB-%04X-%012X}", + self->index + 1, ++self->data_bar_2010_index); + + lxw_xml_start_tag(self->file, "extLst", NULL); + + _worksheet_write_ext(self, "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"); + + lxw_xml_data_element(self->file, "x14:id", cond_format->guid, NULL); + + lxw_xml_end_tag(self->file, "ext"); + lxw_xml_end_tag(self->file, "extLst"); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_color(lxw_worksheet *self, lxw_color_t color) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char rgb[LXW_ATTR_32]; + + lxw_snprintf(rgb, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("rgb", rgb); + + lxw_xml_empty_tag(self->file, "color", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for strings. + */ +STATIC void +_worksheet_write_cfvo_str(lxw_worksheet *self, uint8_t rule_type, + char *value, uint8_t data_bar_2010) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "min"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) + LXW_PUSH_ATTRIBUTES_STR("type", "num"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) + LXW_PUSH_ATTRIBUTES_STR("type", "percent"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) + LXW_PUSH_ATTRIBUTES_STR("type", "percentile"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) + LXW_PUSH_ATTRIBUTES_STR("type", "formula"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "max"); + + if (!data_bar_2010 || (rule_type != LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && rule_type != LXW_CONDITIONAL_RULE_TYPE_MAXIMUM)) + LXW_PUSH_ATTRIBUTES_STR("val", value); + + lxw_xml_empty_tag(self->file, "cfvo", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for numbers. + */ +STATIC void +_worksheet_write_cfvo_num(lxw_worksheet *self, uint8_t rule_type, + double value, uint8_t data_bar_2010) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "min"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) + LXW_PUSH_ATTRIBUTES_STR("type", "num"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) + LXW_PUSH_ATTRIBUTES_STR("type", "percent"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) + LXW_PUSH_ATTRIBUTES_STR("type", "percentile"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) + LXW_PUSH_ATTRIBUTES_STR("type", "formula"); + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) + LXW_PUSH_ATTRIBUTES_STR("type", "max"); + + if (!data_bar_2010 || (rule_type != LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && rule_type != LXW_CONDITIONAL_RULE_TYPE_MAXIMUM)) + LXW_PUSH_ATTRIBUTES_DBL("val", value); + + lxw_xml_empty_tag(self->file, "cfvo", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_icon_set(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char *icon_set[] = { + "3Arrows", + "3ArrowsGray", + "3Flags", + "3TrafficLights", + "3TrafficLights2", + "3Signs", + "3Symbols", + "3Symbols2", + "4Arrows", + "4ArrowsGray", + "4RedToBlack", + "4Rating", + "4TrafficLights", + "5Arrows", + "5ArrowsGray", + "5Rating", + "5Quarters", + }; + uint8_t percent = LXW_CONDITIONAL_RULE_TYPE_PERCENT; + uint8_t style = cond_format->icon_style; + + LXW_INIT_ATTRIBUTES(); + + if (style != LXW_CONDITIONAL_ICONS_3_TRAFFIC_LIGHTS_UNRIMMED) + LXW_PUSH_ATTRIBUTES_STR("iconSet", icon_set[style]); + + if (cond_format->reverse_icons == LXW_TRUE) + LXW_PUSH_ATTRIBUTES_STR("reverse", "1"); + + if (cond_format->icons_only == LXW_TRUE) + LXW_PUSH_ATTRIBUTES_STR("showValue", "0"); + + lxw_xml_start_tag(self->file, "iconSet", &attributes); + + if (style < LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED) { + _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 33, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 67, LXW_FALSE); + } + + if (style >= LXW_CONDITIONAL_ICONS_4_ARROWS_COLORED + && style < LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED) { + _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 25, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 50, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 75, LXW_FALSE); + } + + if (style >= LXW_CONDITIONAL_ICONS_5_ARROWS_COLORED + && style <= LXW_CONDITIONAL_ICONS_5_QUARTERS) { + _worksheet_write_cfvo_num(self, percent, 0, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 20, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 40, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 60, LXW_FALSE); + _worksheet_write_cfvo_num(self, percent, 80, LXW_FALSE); + } + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for data bar rules. + */ +STATIC void +_worksheet_write_cf_rule_icons(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + _worksheet_write_icon_set(self, cond_format); + + lxw_xml_end_tag(self->file, "iconSet"); + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (cond_format->bar_only) + LXW_PUSH_ATTRIBUTES_STR("showValue", "0"); + + lxw_xml_start_tag(self->file, "dataBar", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for data bar rules. + */ +STATIC void +_worksheet_write_cf_rule_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + _worksheet_write_data_bar(self, cond_format); + + if (cond_format->min_value_string) { + _worksheet_write_cfvo_str(self, cond_format->min_rule_type, + cond_format->min_value_string, + cond_format->data_bar_2010); + } + else { + _worksheet_write_cfvo_num(self, cond_format->min_rule_type, + cond_format->min_value, + cond_format->data_bar_2010); + } + + if (cond_format->max_value_string) { + _worksheet_write_cfvo_str(self, cond_format->max_rule_type, + cond_format->max_value_string, + cond_format->data_bar_2010); + } + else { + _worksheet_write_cfvo_num(self, cond_format->max_rule_type, + cond_format->max_value, + cond_format->data_bar_2010); + } + + _worksheet_write_color(self, cond_format->bar_color); + + lxw_xml_end_tag(self->file, "dataBar"); + + if (cond_format->data_bar_2010) + _worksheet_write_data_bar_ext(self, cond_format); + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for 2 and 3 color scale rules. + */ +STATIC void +_worksheet_write_cf_rule_color_scale(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + lxw_xml_start_tag(self->file, "colorScale", NULL); + + if (cond_format->min_value_string) { + _worksheet_write_cfvo_str(self, cond_format->min_rule_type, + cond_format->min_value_string, LXW_FALSE); + } + else { + _worksheet_write_cfvo_num(self, cond_format->min_rule_type, + cond_format->min_value, LXW_FALSE); + } + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + if (cond_format->mid_value_string) { + _worksheet_write_cfvo_str(self, cond_format->mid_rule_type, + cond_format->mid_value_string, + LXW_FALSE); + } + else { + _worksheet_write_cfvo_num(self, cond_format->mid_rule_type, + cond_format->mid_value, LXW_FALSE); + } + } + + if (cond_format->max_value_string) { + _worksheet_write_cfvo_str(self, cond_format->max_rule_type, + cond_format->max_value_string, LXW_FALSE); + } + else { + _worksheet_write_cfvo_num(self, cond_format->max_rule_type, + cond_format->max_value, LXW_FALSE); + } + + _worksheet_write_color(self, cond_format->min_color); + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) + _worksheet_write_color(self, cond_format->mid_color); + + _worksheet_write_color(self, cond_format->max_color); + + lxw_xml_end_tag(self->file, "colorScale"); + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for formula rules. + */ +STATIC void +_worksheet_write_cf_rule_formula(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + _worksheet_write_formula_str(self, cond_format->min_value_string); + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for top and bottom rules. + */ +STATIC void +_worksheet_write_cf_rule_top(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + if (cond_format->criteria == + LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT) + LXW_PUSH_ATTRIBUTES_INT("percent", 1); + + if (cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) + LXW_PUSH_ATTRIBUTES_INT("bottom", 1); + + /* Rank must be an int in the range 1-1000 . */ + if (cond_format->min_value < 1.0 || cond_format->min_value > 1.0) + LXW_PUSH_ATTRIBUTES_DBL("rank", 10); + else + LXW_PUSH_ATTRIBUTES_DBL("rank", (uint16_t) cond_format->min_value); + + lxw_xml_empty_tag(self->file, "cfRule", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for unique/duplicate rules. + */ +STATIC void +_worksheet_write_cf_rule_duplicate(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + /* Set the attributes common to all rule types. */ + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + lxw_xml_empty_tag(self->file, "cfRule", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for averages rules. + */ +STATIC void +_worksheet_write_cf_rule_average(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint8_t criteria = cond_format->criteria; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("aboveAverage", 0); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE_OR_EQUAL + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_BELOW_OR_EQUAL) + LXW_PUSH_ATTRIBUTES_INT("equalAverage", 1); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_ABOVE + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_1_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("stdDev", 1); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_ABOVE + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_2_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("stdDev", 2); + + if (criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_ABOVE + || criteria == LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) + LXW_PUSH_ATTRIBUTES_INT("stdDev", 3); + + lxw_xml_empty_tag(self->file, "cfRule", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for time_period rules. + */ +STATIC void +_worksheet_write_cf_rule_time_period(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char formula[LXW_MAX_ATTRIBUTE_LENGTH]; + uint8_t pos; + uint8_t criteria = cond_format->criteria; + char *first_cell = cond_format->first_cell; + char *time_periods[] = { + "yesterday", + "today", + "tomorrow", + "last7Days", + "lastWeek", + "thisWeek", + "nextWeek", + "lastMonth", + "thisMonth", + "nextMonth", + }; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + pos = criteria - LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY; + LXW_PUSH_ATTRIBUTES_STR("timePeriod", time_periods[pos]); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "FLOOR(%s,1)=TODAY()-1", first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TODAY) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "FLOOR(%s,1)=TODAY()", first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_TOMORROW) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "FLOOR(%s,1)=TODAY()+1", first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_7_DAYS) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_WEEK) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY()))," + "TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_WEEK) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1," + "ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_WEEK) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY()))," + "ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_LAST_MONTH) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(MONTH(%s)=MONTH(TODAY())-1,OR(YEAR(%s)=YEAR(" + "TODAY()),AND(MONTH(%s)=1,YEAR(A1)=YEAR(TODAY())-1)))", + first_cell, first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_THIS_MONTH) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))", + first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH) { + lxw_snprintf(formula, LXW_MAX_ATTRIBUTE_LENGTH, + "AND(MONTH(%s)=MONTH(TODAY())+1,OR(YEAR(%s)=YEAR(" + "TODAY()),AND(MONTH(%s)=12,YEAR(%s)=YEAR(TODAY())+1)))", + first_cell, first_cell, first_cell, first_cell); + _worksheet_write_formula_str(self, formula); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for blanks/no_blanks, errors/no_errors rules. + */ +STATIC void +_worksheet_write_cf_rule_blanks(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char formula[LXW_ATTR_32]; + uint8_t type = cond_format->type; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (type == LXW_CONDITIONAL_TYPE_BLANKS) { + lxw_snprintf(formula, LXW_ATTR_32, "LEN(TRIM(%s))=0", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (type == LXW_CONDITIONAL_TYPE_NO_BLANKS) { + lxw_snprintf(formula, LXW_ATTR_32, "LEN(TRIM(%s))>0", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (type == LXW_CONDITIONAL_TYPE_ERRORS) { + lxw_snprintf(formula, LXW_ATTR_32, "ISERROR(%s)", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (type == LXW_CONDITIONAL_TYPE_NO_ERRORS) { + lxw_snprintf(formula, LXW_ATTR_32, "NOT(ISERROR(%s))", + cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for text rules. + */ +STATIC void +_worksheet_write_cf_rule_text(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint8_t pos; + char formula[LXW_ATTR_32 * 2]; + char *operators[] = { + "containsText", + "notContains", + "beginsWith", + "endsWith", + }; + uint8_t criteria = cond_format->criteria; + + LXW_INIT_ATTRIBUTES(); + + if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING) + LXW_PUSH_ATTRIBUTES_STR("type", "containsText"); + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING) + LXW_PUSH_ATTRIBUTES_STR("type", "notContainsText"); + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH) + LXW_PUSH_ATTRIBUTES_STR("type", "beginsWith"); + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) + LXW_PUSH_ATTRIBUTES_STR("type", "endsWith"); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + pos = criteria - LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING; + LXW_PUSH_ATTRIBUTES_STR("operator", operators[pos]); + + LXW_PUSH_ATTRIBUTES_STR("text", cond_format->min_value_string); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "NOT(ISERROR(SEARCH(\"%s\",%s)))", + cond_format->min_value_string, cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_NOT_CONTAINING) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "ISERROR(SEARCH(\"%s\",%s))", + cond_format->min_value_string, cond_format->first_cell); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_BEGINS_WITH) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "LEFT(%s,%d)=\"%s\"", + cond_format->first_cell, + (uint16_t) strlen(cond_format->min_value_string), + cond_format->min_value_string); + _worksheet_write_formula_str(self, formula); + } + else if (criteria == LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) { + lxw_snprintf(formula, LXW_ATTR_32 * 2, + "RIGHT(%s,%d)=\"%s\"", + cond_format->first_cell, + (uint16_t) strlen(cond_format->min_value_string), + cond_format->min_value_string); + _worksheet_write_formula_str(self, formula); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for cell rules. + */ +STATIC void +_worksheet_write_cf_rule_cell(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char *operators[] = { + "none", + "equal", + "notEqual", + "greaterThan", + "lessThan", + "greaterThanOrEqual", + "lessThanOrEqual", + "between", + "notBetween", + }; + + LXW_INIT_ATTRIBUTES(); + + LXW_PUSH_ATTRIBUTES_STR("type", cond_format->type_string); + + if (cond_format->dxf_index != LXW_PROPERTY_UNSET) + LXW_PUSH_ATTRIBUTES_INT("dxfId", cond_format->dxf_index); + + LXW_PUSH_ATTRIBUTES_INT("priority", cond_format->dxf_priority); + + if (cond_format->stop_if_true) + LXW_PUSH_ATTRIBUTES_INT("stopIfTrue", 1); + + LXW_PUSH_ATTRIBUTES_STR("operator", operators[cond_format->criteria]); + + lxw_xml_start_tag(self->file, "cfRule", &attributes); + + if (cond_format->min_value_string) + _worksheet_write_formula_str(self, cond_format->min_value_string); + else + _worksheet_write_formula_num(self, cond_format->min_value); + + if (cond_format->has_max) { + if (cond_format->max_value_string) + _worksheet_write_formula_str(self, cond_format->max_value_string); + else + _worksheet_write_formula_num(self, cond_format->max_value); + } + + lxw_xml_end_tag(self->file, "cfRule"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_cf_rule(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + if (cond_format->type == LXW_CONDITIONAL_TYPE_CELL) { + + _worksheet_write_cf_rule_cell(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TEXT) { + + _worksheet_write_cf_rule_text(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TIME_PERIOD) { + + _worksheet_write_cf_rule_time_period(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_DUPLICATE + || cond_format->type == LXW_CONDITIONAL_TYPE_UNIQUE) { + + _worksheet_write_cf_rule_duplicate(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_AVERAGE) { + + _worksheet_write_cf_rule_average(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TOP + || cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) { + + _worksheet_write_cf_rule_top(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_BLANKS + || cond_format->type == LXW_CONDITIONAL_TYPE_NO_BLANKS + || cond_format->type == LXW_CONDITIONAL_TYPE_ERRORS + || cond_format->type == LXW_CONDITIONAL_TYPE_NO_ERRORS) { + + _worksheet_write_cf_rule_blanks(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_FORMULA) { + + _worksheet_write_cf_rule_formula(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_2_COLOR_SCALE + || cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + + _worksheet_write_cf_rule_color_scale(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_DATA_BAR) { + + _worksheet_write_cf_rule_data_bar(self, cond_format); + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_ICON_SETS) { + + _worksheet_write_cf_rule_icons(self, cond_format); + } + +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_conditional_formatting(lxw_worksheet *self, + lxw_cond_format_hash_element *element) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_cond_format_obj *cond_format; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("sqref", element->sqref); + + lxw_xml_start_tag(self->file, "conditionalFormatting", &attributes); + + STAILQ_FOREACH(cond_format, element->cond_formats, list_pointers) { + /* Write the cfRule element. */ + _worksheet_write_cf_rule(self, cond_format); + } + + lxw_xml_end_tag(self->file, "conditionalFormatting"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the conditional formatting> elements. + */ +STATIC void +_worksheet_write_conditional_formats(lxw_worksheet *self) +{ + lxw_cond_format_hash_element *element; + lxw_cond_format_hash_element *next_element; + + for (element = RB_MIN(lxw_cond_format_hash, self->conditional_formats); + element; element = next_element) { + + _worksheet_write_conditional_formatting(self, element); + + next_element = + RB_NEXT(lxw_cond_format_hash, self->conditional_formats, element); + } +} + +/* + * Write the elements for data bar conditional formats. + */ +STATIC void +_worksheet_write_x14_color(lxw_worksheet *self, char *type, lxw_color_t color) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char rgb[LXW_ATTR_32]; + + lxw_snprintf(rgb, LXW_ATTR_32, "FF%06X", color & LXW_COLOR_MASK); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("rgb", rgb); + lxw_xml_empty_tag(self->file, type, &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_x14_cfvo(lxw_worksheet *self, uint8_t rule_type, + double number, char *string) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char data[LXW_ATTR_32]; + uint8_t has_value = LXW_FALSE; + + LXW_INIT_ATTRIBUTES(); + + if (!string) + lxw_sprintf_dbl(data, number); + + if (rule_type == LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN) { + LXW_PUSH_ATTRIBUTES_STR("type", "autoMin"); + has_value = LXW_FALSE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MINIMUM) { + LXW_PUSH_ATTRIBUTES_STR("type", "min"); + has_value = LXW_FALSE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_NUMBER) { + LXW_PUSH_ATTRIBUTES_STR("type", "num"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENT) { + LXW_PUSH_ATTRIBUTES_STR("type", "percent"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_PERCENTILE) { + LXW_PUSH_ATTRIBUTES_STR("type", "percentile"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_FORMULA) { + LXW_PUSH_ATTRIBUTES_STR("type", "formula"); + has_value = LXW_TRUE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + LXW_PUSH_ATTRIBUTES_STR("type", "max"); + has_value = LXW_FALSE; + } + else if (rule_type == LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX) { + LXW_PUSH_ATTRIBUTES_STR("type", "autoMax"); + has_value = LXW_FALSE; + } + + if (has_value) { + lxw_xml_start_tag(self->file, "x14:cfvo", &attributes); + + if (string) + lxw_xml_data_element(self->file, "xm:f", string, NULL); + else + lxw_xml_data_element(self->file, "xm:f", data, NULL); + + lxw_xml_end_tag(self->file, "x14:cfvo"); + } + else { + lxw_xml_empty_tag(self->file, "x14:cfvo", &attributes); + } + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_x14_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char min_length[] = "0"; + char max_length[] = "100"; + char border[] = "1"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("minLength", min_length); + LXW_PUSH_ATTRIBUTES_STR("maxLength", max_length); + + if (!cond_format->bar_no_border) + LXW_PUSH_ATTRIBUTES_STR("border", border); + + if (cond_format->bar_solid) + LXW_PUSH_ATTRIBUTES_STR("gradient", "0"); + + if (cond_format->bar_direction == + LXW_CONDITIONAL_BAR_DIRECTION_RIGHT_TO_LEFT) + LXW_PUSH_ATTRIBUTES_STR("direction", "rightToLeft"); + + if (cond_format->bar_direction == + LXW_CONDITIONAL_BAR_DIRECTION_LEFT_TO_RIGHT) + LXW_PUSH_ATTRIBUTES_STR("direction", "leftToRight"); + + if (cond_format->bar_negative_color_same) + LXW_PUSH_ATTRIBUTES_STR("negativeBarColorSameAsPositive", "1"); + + if (!cond_format->bar_no_border + && !cond_format->bar_negative_border_color_same) + LXW_PUSH_ATTRIBUTES_STR("negativeBarBorderColorSameAsPositive", "0"); + + if (cond_format->bar_axis_position == LXW_CONDITIONAL_BAR_AXIS_MIDPOINT) + LXW_PUSH_ATTRIBUTES_STR("axisPosition", "middle"); + + if (cond_format->bar_axis_position == LXW_CONDITIONAL_BAR_AXIS_NONE) + LXW_PUSH_ATTRIBUTES_STR("axisPosition", "none"); + + lxw_xml_start_tag(self->file, "x14:dataBar", &attributes); + + if (cond_format->auto_min) + cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_AUTO_MIN; + + _worksheet_write_x14_cfvo(self, cond_format->min_rule_type, + cond_format->min_value, + cond_format->min_value_string); + + if (cond_format->auto_max) + cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_AUTO_MAX; + + _worksheet_write_x14_cfvo(self, cond_format->max_rule_type, + cond_format->max_value, + cond_format->max_value_string); + + if (!cond_format->bar_no_border) + _worksheet_write_x14_color(self, "x14:borderColor", + cond_format->bar_border_color); + + if (!cond_format->bar_negative_color_same) + _worksheet_write_x14_color(self, "x14:negativeFillColor", + cond_format->bar_negative_color); + + if (!cond_format->bar_no_border + && !cond_format->bar_negative_border_color_same) + _worksheet_write_x14_color(self, "x14:negativeBorderColor", + cond_format->bar_negative_border_color); + + if (cond_format->bar_axis_position != LXW_CONDITIONAL_BAR_AXIS_NONE) + _worksheet_write_x14_color(self, "x14:axisColor", + cond_format->bar_axis_color); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_x14_cf_rule(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("type", "dataBar"); + LXW_PUSH_ATTRIBUTES_STR("id", cond_format->guid); + + lxw_xml_start_tag(self->file, "x14:cfRule", &attributes); + + /* Write the x14:dataBar element. */ + _worksheet_write_x14_data_bar(self, cond_format); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_xm_sqref(lxw_worksheet *self, + lxw_cond_format_obj *cond_format) +{ + lxw_xml_data_element(self->file, "xm:sqref", cond_format->sqref, NULL); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_conditional_formatting_2010(lxw_worksheet *self, lxw_cond_format_hash_element + *element) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + lxw_cond_format_obj *cond_format; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns:xm", + "http://schemas.microsoft.com/office/excel/2006/main"); + + STAILQ_FOREACH(cond_format, element->cond_formats, list_pointers) { + if (!cond_format->data_bar_2010) + continue; + + lxw_xml_start_tag(self->file, "x14:conditionalFormatting", + &attributes); + + _worksheet_write_x14_cf_rule(self, cond_format); + + lxw_xml_end_tag(self->file, "x14:dataBar"); + lxw_xml_end_tag(self->file, "x14:cfRule"); + _worksheet_write_xm_sqref(self, cond_format); + lxw_xml_end_tag(self->file, "x14:conditionalFormatting"); + } + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for Excel 2010 conditional formatting data bars. + */ +STATIC void +_worksheet_write_ext_list_data_bars(lxw_worksheet *self) +{ + lxw_cond_format_hash_element *element; + lxw_cond_format_hash_element *next_element; + + _worksheet_write_ext(self, "{78C0D931-6437-407d-A8EE-F0AAD7539E65}"); + lxw_xml_start_tag(self->file, "x14:conditionalFormattings", NULL); + + for (element = RB_MIN(lxw_cond_format_hash, self->conditional_formats); + element; element = next_element) { + + _worksheet_write_conditional_formatting_2010(self, element); + + next_element = + RB_NEXT(lxw_cond_format_hash, self->conditional_formats, element); + } + + lxw_xml_end_tag(self->file, "x14:conditionalFormattings"); + lxw_xml_end_tag(self->file, "ext"); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_ext_list(lxw_worksheet *self) +{ + if (self->data_bar_2010_index == 0) + return; + + lxw_xml_start_tag(self->file, "extLst", NULL); + + _worksheet_write_ext_list_data_bars(self); + + lxw_xml_end_tag(self->file, "extLst"); +} + +/* + * Write the element. + */ +STATIC void +_worksheet_write_ignored_error(lxw_worksheet *self, char *ignore_error, + char *range) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("sqref", range); + LXW_PUSH_ATTRIBUTES_STR(ignore_error, "1"); + + lxw_xml_empty_tag(self->file, "ignoredError", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +lxw_error +_validate_conditional_icons(lxw_conditional_format *user) +{ + if (user->icon_style > LXW_CONDITIONAL_ICONS_5_QUARTERS) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_ICON_SETS, " + "invalid icon_style (%d).", user->icon_style); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + else { + return LXW_NO_ERROR; + } +} + +lxw_error +_validate_conditional_data_bar(lxw_worksheet *self, + lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + uint8_t min_rule_type = user_options->min_rule_type; + uint8_t max_rule_type = user_options->max_rule_type; + + if (user_options->data_bar_2010 + || user_options->bar_solid + || user_options->bar_no_border + || user_options->bar_direction + || user_options->bar_axis_position + || user_options->bar_negative_color_same + || user_options->bar_negative_border_color_same + || user_options->bar_negative_color + || user_options->bar_border_color + || user_options->bar_negative_border_color + || user_options->bar_axis_color) { + + cond_format->data_bar_2010 = LXW_TRUE; + self->excel_version = 2010; + } + + if (min_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && min_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->min_rule_type = min_rule_type; + cond_format->min_value = user_options->min_value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->min_value_string); + } + else { + cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_MINIMUM; + cond_format->min_value = 0; + } + + if (max_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && max_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->max_rule_type = max_rule_type; + cond_format->max_value = user_options->max_value; + cond_format->max_value_string = + lxw_strdup_formula(user_options->max_value_string); + } + else { + cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_MAXIMUM; + cond_format->max_value = 0; + } + + if (cond_format->data_bar_2010) { + if (min_rule_type == LXW_CONDITIONAL_RULE_TYPE_NONE) + cond_format->auto_min = LXW_TRUE; + if (max_rule_type == LXW_CONDITIONAL_RULE_TYPE_NONE) + cond_format->auto_max = LXW_TRUE; + } + + cond_format->bar_only = user_options->bar_only; + cond_format->bar_solid = user_options->bar_solid; + cond_format->bar_no_border = user_options->bar_no_border; + cond_format->bar_direction = user_options->bar_direction; + cond_format->bar_axis_position = user_options->bar_axis_position; + cond_format->bar_negative_color_same = + user_options->bar_negative_color_same; + cond_format->bar_negative_border_color_same = + user_options->bar_negative_border_color_same; + + if (user_options->bar_color != LXW_COLOR_UNSET) + cond_format->bar_color = user_options->bar_color; + else + cond_format->bar_color = 0x638EC6; + + if (user_options->bar_negative_color != LXW_COLOR_UNSET) + cond_format->bar_negative_color = user_options->bar_negative_color; + else + cond_format->bar_negative_color = 0xFF0000; + + if (user_options->bar_border_color != LXW_COLOR_UNSET) + cond_format->bar_border_color = user_options->bar_border_color; + else + cond_format->bar_border_color = cond_format->bar_color; + + if (user_options->bar_negative_border_color != LXW_COLOR_UNSET) + cond_format->bar_negative_border_color = + user_options->bar_negative_border_color; + else + cond_format->bar_negative_border_color = 0xFF0000; + + if (user_options->bar_axis_color != LXW_COLOR_UNSET) + cond_format->bar_axis_color = user_options->bar_axis_color; + else + cond_format->bar_axis_color = 0x000000; + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_scale(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + uint8_t min_rule_type = user_options->min_rule_type; + uint8_t mid_rule_type = user_options->mid_rule_type; + uint8_t max_rule_type = user_options->max_rule_type; + + if (min_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && min_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->min_rule_type = min_rule_type; + cond_format->min_value = user_options->min_value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->min_value_string); + } + else { + cond_format->min_rule_type = LXW_CONDITIONAL_RULE_TYPE_MINIMUM; + cond_format->min_value = 0; + } + + if (max_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && max_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->max_rule_type = max_rule_type; + cond_format->max_value = user_options->max_value; + cond_format->max_value_string = + lxw_strdup_formula(user_options->max_value_string); + } + else { + cond_format->max_rule_type = LXW_CONDITIONAL_RULE_TYPE_MAXIMUM; + cond_format->max_value = 0; + } + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + if (mid_rule_type > LXW_CONDITIONAL_RULE_TYPE_MINIMUM + && mid_rule_type < LXW_CONDITIONAL_RULE_TYPE_MAXIMUM) { + cond_format->mid_rule_type = mid_rule_type; + cond_format->mid_value = user_options->mid_value; + cond_format->mid_value_string = + lxw_strdup_formula(user_options->mid_value_string); + } + else { + cond_format->mid_rule_type = LXW_CONDITIONAL_RULE_TYPE_PERCENTILE; + cond_format->mid_value = 50; + } + } + + if (user_options->min_color != LXW_COLOR_UNSET) + cond_format->min_color = user_options->min_color; + else + cond_format->min_color = 0xFF7128; + + if (user_options->max_color != LXW_COLOR_UNSET) + cond_format->max_color = user_options->max_color; + else + cond_format->max_color = 0xFFEF9C; + + if (cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + if (user_options->min_color == LXW_COLOR_UNSET) + cond_format->min_color = 0xF8696B; + + if (user_options->mid_color != LXW_COLOR_UNSET) + cond_format->mid_color = user_options->mid_color; + else + cond_format->mid_color = 0xFFEB84; + + if (user_options->max_color == LXW_COLOR_UNSET) + cond_format->max_color = 0x63BE7B; + } + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_top(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + /* Restrict the range of rank values to Excel's allowed range. */ + if (user_options->criteria == + LXW_CONDITIONAL_CRITERIA_TOP_OR_BOTTOM_PERCENT) { + if (user_options->value < 0.0 || user_options->value > 100.0) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TOP/BOTTOM, " + "top/bottom percent (%g%%) must by in range 0-100", + user_options->value); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + else { + if (user_options->value < 1.0 || user_options->value > 1000.0) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TOP/BOTTOM, " + "top/bottom items (%g) must by in range 1-1000", + user_options->value); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + } + + cond_format->min_value = (uint16_t) user_options->value; + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_average(lxw_conditional_format *user) +{ + if (user->criteria < LXW_CONDITIONAL_CRITERIA_AVERAGE_ABOVE || + user->criteria > LXW_CONDITIONAL_CRITERIA_AVERAGE_3_STD_DEV_BELOW) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_AVERAGE, " + "invalid criteria value (%d).", user->criteria); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + else { + return LXW_NO_ERROR; + } +} + +lxw_error +_validate_conditional_time_period(lxw_conditional_format *user) +{ + if (user->criteria < LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_YESTERDAY || + user->criteria > LXW_CONDITIONAL_CRITERIA_TIME_PERIOD_NEXT_MONTH) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TIME_PERIOD, " + "invalid criteria value (%d).", user->criteria); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + else { + return LXW_NO_ERROR; + } +} + +lxw_error +_validate_conditional_text(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + if (!user_options->value_string) { + + LXW_WARN_FORMAT("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TEXT, " + "value_string can not be NULL. " + "Text must be specified."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (strlen(user_options->value_string) >= LXW_MAX_ATTRIBUTE_LENGTH) { + + LXW_WARN_FORMAT2("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TEXT, " + "value_string length (%d) must be less than %d.", + (uint16_t) strlen(user_options->value_string), + LXW_MAX_ATTRIBUTE_LENGTH); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (user_options->criteria < LXW_CONDITIONAL_CRITERIA_TEXT_CONTAINING || + user_options->criteria > LXW_CONDITIONAL_CRITERIA_TEXT_ENDS_WITH) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_TEXT, " + "invalid criteria value (%d).", + user_options->criteria); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + cond_format->min_value_string = + lxw_strdup_formula(user_options->value_string); - LXW_FREE_ATTRIBUTES(); + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_formula(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + if (!user_options->value_string) { + + LXW_WARN_FORMAT("worksheet_conditional_format_cell()/_range(): " + "For type = LXW_CONDITIONAL_TYPE_FORMULA, " + "value_string can not be NULL. " + "Formula must be specified."); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + cond_format->min_value_string = + lxw_strdup_formula(user_options->value_string); + + return LXW_NO_ERROR; +} + +lxw_error +_validate_conditional_cell(lxw_cond_format_obj *cond_format, + lxw_conditional_format *user_options) +{ + cond_format->min_value = user_options->value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->value_string); + + if (cond_format->criteria == LXW_CONDITIONAL_CRITERIA_BETWEEN + || cond_format->criteria == LXW_CONDITIONAL_CRITERIA_NOT_BETWEEN) { + cond_format->has_max = LXW_TRUE; + cond_format->min_value = user_options->min_value; + cond_format->max_value = user_options->max_value; + cond_format->min_value_string = + lxw_strdup_formula(user_options->min_value_string); + cond_format->max_value_string = + lxw_strdup_formula(user_options->max_value_string); + } + + return LXW_NO_ERROR; } /* - * Write the element. + * Write the element. */ STATIC void -_worksheet_write_data_validations(lxw_worksheet *self) +_worksheet_write_ignored_errors(lxw_worksheet *self) { - struct xml_attribute_list attributes; - struct xml_attribute *attribute; - lxw_data_val_obj *data_validation; - - if (self->num_validations == 0) + if (!self->has_ignore_errors) return; - LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_INT("count", self->num_validations); + lxw_xml_start_tag(self->file, "ignoredErrors", NULL); - lxw_xml_start_tag(self->file, "dataValidations", &attributes); + if (self->ignore_number_stored_as_text) { + _worksheet_write_ignored_error(self, "numberStoredAsText", + self->ignore_number_stored_as_text); + } - STAILQ_FOREACH(data_validation, self->data_validations, list_pointers) { - /* Write the dataValidation element. */ - _worksheet_write_data_validation(self, data_validation); + if (self->ignore_eval_error) { + _worksheet_write_ignored_error(self, "evalError", + self->ignore_eval_error); } - lxw_xml_end_tag(self->file, "dataValidations"); + if (self->ignore_formula_differs) { + _worksheet_write_ignored_error(self, "formula", + self->ignore_formula_differs); + } - LXW_FREE_ATTRIBUTES(); + if (self->ignore_formula_range) { + _worksheet_write_ignored_error(self, "formulaRange", + self->ignore_formula_range); + } + + if (self->ignore_formula_unlocked) { + _worksheet_write_ignored_error(self, "unlockedFormula", + self->ignore_formula_unlocked); + } + + if (self->ignore_empty_cell_reference) { + _worksheet_write_ignored_error(self, "emptyCellReference", + self->ignore_empty_cell_reference); + } + + if (self->ignore_list_data_validation) { + _worksheet_write_ignored_error(self, "listDataValidation", + self->ignore_list_data_validation); + } + + if (self->ignore_calculated_column) { + _worksheet_write_ignored_error(self, "calculatedColumn", + self->ignore_calculated_column); + } + + if (self->ignore_two_digit_text_year) { + _worksheet_write_ignored_error(self, "twoDigitTextYear", + self->ignore_two_digit_text_year); + } + + lxw_xml_end_tag(self->file, "ignoredErrors"); } /* @@ -4441,6 +6551,9 @@ lxw_worksheet_assemble_xml_file(lxw_worksheet *self) /* Write the mergeCells element. */ _worksheet_write_merge_cells(self); + /* Write the conditionalFormatting elements. */ + _worksheet_write_conditional_formats(self); + /* Write the dataValidations element. */ _worksheet_write_data_validations(self); @@ -4465,12 +6578,21 @@ lxw_worksheet_assemble_xml_file(lxw_worksheet *self) /* Write the colBreaks element. */ _worksheet_write_col_breaks(self); + /* Write the ignoredErrors element. */ + _worksheet_write_ignored_errors(self); + /* Write the drawing element. */ _worksheet_write_drawings(self); /* Write the legacyDrawing element. */ _worksheet_write_legacy_drawing(self); + /* Write the legacyDrawingHF element. */ + _worksheet_write_legacy_drawing_hf(self); + + /* Write the extLst element. */ + _worksheet_write_ext_list(self); + /* Close the worksheet tag. */ lxw_xml_end_tag(self->file, "worksheet"); } @@ -4547,9 +6669,7 @@ worksheet_write_string(lxw_worksheet *self, } else { /* Look for and escape control chars in the string. */ - if (strpbrk(string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C" - "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16" - "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) { + if (lxw_has_control_characters(string)) { string_copy = lxw_escape_control_characters(string); } else { @@ -4598,6 +6718,41 @@ worksheet_write_formula_num(lxw_worksheet *self, return LXW_NO_ERROR; } +/* + * Write a formula with a string result to a cell in Excel. + */ +lxw_error +worksheet_write_formula_str(lxw_worksheet *self, + lxw_row_t row_num, + lxw_col_t col_num, + const char *formula, + lxw_format *format, const char *result) +{ + lxw_cell *cell; + char *formula_copy; + lxw_error err; + + if (!formula) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + /* Strip leading "=" from formula. */ + if (formula[0] == '=') + formula_copy = lxw_strdup(formula + 1); + else + formula_copy = lxw_strdup(formula); + + cell = _new_formula_cell(row_num, col_num, formula_copy, format); + cell->user_data2 = lxw_strdup(result); + + _insert_cell(self, row_num, col_num, cell); + + return LXW_NO_ERROR; +} + /* * Write a formula with a default result to a cell in Excel . */ @@ -4645,7 +6800,11 @@ worksheet_write_array_formula_num(lxw_worksheet *self, if (!formula) return LXW_ERROR_NULL_PARAMETER_IGNORED; - /* Check that column number is valid and store the max value */ + /* Check that row and col are valid and store max and min values. */ + err = _check_dimensions(self, first_row, first_col, LXW_FALSE, LXW_FALSE); + if (err) + return err; + err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE); if (err) return err; @@ -4776,7 +6935,7 @@ worksheet_write_datetime(lxw_worksheet *self, if (err) return err; - excel_date = lxw_datetime_to_excel_date(datetime, LXW_EPOCH_1900); + excel_date = lxw_datetime_to_excel_date_epoch(datetime, LXW_EPOCH_1900); cell = _new_number_cell(row_num, col_num, excel_date, format); @@ -5116,9 +7275,7 @@ worksheet_write_rich_string(lxw_worksheet *self, } else { /* Look for and escape control chars in the string. */ - if (strpbrk(rich_string, "\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C" - "\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16" - "\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) { + if (lxw_has_control_characters(rich_string)) { string_copy = lxw_escape_control_characters(rich_string); free(rich_string); } @@ -5771,10 +7928,11 @@ lxw_error worksheet_set_header_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { - if (options) { - if (options->margin >= 0.0) - self->margin_header = options->margin; - } + lxw_error err; + char *found_string; + char *offset_string; + uint8_t placeholder_count = 0; + uint8_t image_count = 0; if (!string) return LXW_ERROR_NULL_PARAMETER_IGNORED; @@ -5783,6 +7941,89 @@ worksheet_set_header_opt(lxw_worksheet *self, const char *string, return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; lxw_strcpy(self->header, string); + + /* Replace &[Picture] with &G which is used internally by Excel. */ + while ((found_string = strstr(self->header, "&[Picture]"))) { + found_string++; + *found_string = 'G'; + + do { + found_string++; + offset_string = found_string + sizeof("Picture"); + *found_string = *offset_string; + } while (*offset_string); + } + + /* Count &G placeholders and ensure there are sufficient images. */ + found_string = self->header; + while (*found_string) { + if (*found_string == '&' && *(found_string + 1) == 'G') + placeholder_count++; + found_string++; + } + + if (placeholder_count > 0 && !options) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + /* Reset the header string. */ + self->header[0] = '\0'; + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (options) { + /* Ensure there are enough images to match the placeholders. There is + * a potential bug where there are sufficient images but in the wrong + * positions but we don't currently try to deal with that.*/ + if (options->image_left) + image_count++; + if (options->image_center) + image_count++; + if (options->image_right) + image_count++; + + if (placeholder_count != image_count) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + /* Reset the header string. */ + self->header[0] = '\0'; + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any existing header image objects. */ + _free_object_properties(self->header_left_object_props); + _free_object_properties(self->header_center_object_props); + _free_object_properties(self->header_right_object_props); + + if (options->margin > 0.0) + self->margin_header = options->margin; + + err = _worksheet_set_header_footer_image(self, + options->image_left, + HEADER_LEFT); + if (err) + return err; + + err = _worksheet_set_header_footer_image(self, + options->image_center, + HEADER_CENTER); + if (err) + return err; + + err = _worksheet_set_header_footer_image(self, + options->image_right, + HEADER_RIGHT); + if (err) + return err; + } + self->header_footer_changed = 1; return LXW_NO_ERROR; @@ -5795,10 +8036,11 @@ lxw_error worksheet_set_footer_opt(lxw_worksheet *self, const char *string, lxw_header_footer_options *options) { - if (options) { - if (options->margin >= 0.0) - self->margin_footer = options->margin; - } + lxw_error err; + char *found_string; + char *offset_string; + uint8_t placeholder_count = 0; + uint8_t image_count = 0; if (!string) return LXW_ERROR_NULL_PARAMETER_IGNORED; @@ -5807,6 +8049,91 @@ worksheet_set_footer_opt(lxw_worksheet *self, const char *string, return LXW_ERROR_255_STRING_LENGTH_EXCEEDED; lxw_strcpy(self->footer, string); + + /* Replace &[Picture] with &G which is used internally by Excel. */ + while ((found_string = strstr(self->footer, "&[Picture]"))) { + found_string++; + *found_string = 'G'; + + do { + found_string++; + offset_string = found_string + sizeof("Picture"); + *found_string = *offset_string; + } while (*offset_string); + } + + /* Count &G placeholders and ensure there are sufficient images. */ + found_string = self->footer; + while (*found_string) { + if (*found_string == '&' && *(found_string + 1) == 'G') + placeholder_count++; + found_string++; + } + + if (placeholder_count > 0 && !options) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + /* Reset the footer string. */ + self->footer[0] = '\0'; + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + if (options) { + /* Ensure there are enough images to match the placeholders. There is + * a potential bug where there are sufficient images but in the wrong + * positions but we don't currently try to deal with that.*/ + if (options->image_left) + image_count++; + if (options->image_center) + image_count++; + if (options->image_right) + image_count++; + + if (placeholder_count != image_count) { + LXW_WARN_FORMAT1("worksheet_set_header_opt/footer_opt(): " + "the number of &G/&[Picture] placeholders in option " + "string \"%s\" does not match the number of supplied " + "images.", string); + + /* Reset the header string. */ + self->footer[0] = '\0'; + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Free any existing footer image objects. */ + _free_object_properties(self->footer_left_object_props); + _free_object_properties(self->footer_center_object_props); + _free_object_properties(self->footer_right_object_props); + + if (options->margin > 0.0) + self->margin_footer = options->margin; + + err = _worksheet_set_header_footer_image(self, + options->image_left, + FOOTER_LEFT); + if (err) + return err; + + err = _worksheet_set_header_footer_image(self, + options->image_center, + FOOTER_CENTER); + if (err) + return err; + + if (options->image_right) { + err = _worksheet_set_header_footer_image(self, + options->image_right, + FOOTER_RIGHT); + if (err) + return err; + } + } + self->header_footer_changed = 1; return LXW_NO_ERROR; @@ -6300,9 +8627,14 @@ worksheet_insert_image_buffer_opt(lxw_worksheet *self, return LXW_ERROR_NULL_PARAMETER_IGNORED; } - /* Write the image buffer to a temporary file so we can read the - * dimensions like an ordinary file. */ + /* Write the image buffer to a file (preferably in memory) so we can read + * the dimensions like an ordinary file. */ +#ifdef USE_FMEMOPEN + image_stream = fmemopen(NULL, image_size, "w+b"); +#else image_stream = lxw_tmpfile(self->tmpdir); +#endif + if (!image_stream) return LXW_ERROR_CREATING_TMPFILE; @@ -6338,6 +8670,8 @@ worksheet_insert_image_buffer_opt(lxw_worksheet *self, object_props->y_offset = user_options->y_offset; object_props->x_scale = user_options->x_scale; object_props->y_scale = user_options->y_scale; + object_props->url = lxw_strdup(user_options->url); + object_props->tip = lxw_strdup(user_options->tip); object_props->object_position = user_options->object_position; object_props->description = lxw_strdup(user_options->description); } @@ -6708,16 +9042,18 @@ worksheet_data_validation_range(lxw_worksheet *self, lxw_row_t first_row, || validation->validate == LXW_VALIDATION_TYPE_TIME) { if (is_between) { copy->value_number = - lxw_datetime_to_excel_date(&validation->minimum_datetime, - LXW_EPOCH_1900); + lxw_datetime_to_excel_date_epoch(&validation-> + minimum_datetime, + LXW_EPOCH_1900); copy->maximum_number = - lxw_datetime_to_excel_date(&validation->maximum_datetime, - LXW_EPOCH_1900); + lxw_datetime_to_excel_date_epoch(&validation-> + maximum_datetime, + LXW_EPOCH_1900); } else { copy->value_number = - lxw_datetime_to_excel_date(&validation->value_datetime, - LXW_EPOCH_1900); + lxw_datetime_to_excel_date_epoch(&validation->value_datetime, + LXW_EPOCH_1900); } } @@ -6748,6 +9084,202 @@ worksheet_data_validation_cell(lxw_worksheet *self, lxw_row_t row, row, col, validation); } +/* + * Add a conditional format to a worksheet, for a range. + */ +lxw_error +worksheet_conditional_format_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_conditional_format *user_options) +{ + lxw_cond_format_obj *cond_format; + lxw_row_t tmp_row; + lxw_col_t tmp_col; + lxw_error err = LXW_NO_ERROR; + char *type_strings[] = { + "none", + "cellIs", + "containsText", + "timePeriod", + "aboveAverage", + "duplicateValues", + "uniqueValues", + "top10", + "top10", + "containsBlanks", + "notContainsBlanks", + "containsErrors", + "notContainsErrors", + "expression", + "colorScale", + "colorScale", + "dataBar", + "iconSet", + }; + + /* 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; + + /* Check the validation type is in correct enum range. */ + if (user_options->type <= LXW_CONDITIONAL_TYPE_NONE || + user_options->type >= LXW_CONDITIONAL_TYPE_LAST) { + + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "invalid type value (%d).", user_options->type); + + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Create a copy of the parameters from the user data validation. */ + cond_format = calloc(1, sizeof(lxw_cond_format_obj)); + GOTO_LABEL_ON_MEM_ERROR(cond_format, error); + + /* Create the data validation range. */ + if (first_row == last_row && first_col == last_col) + lxw_rowcol_to_cell(cond_format->sqref, first_row, last_col); + else + lxw_rowcol_to_range(cond_format->sqref, first_row, first_col, + last_row, last_col); + + /* Store the first cell string for text and date rules. */ + lxw_rowcol_to_cell(cond_format->first_cell, first_row, last_col); + + /* Overwrite the sqref range with a user supplied set of ranges. */ + if (user_options->multi_range) { + + if (strlen(user_options->multi_range) >= LXW_MAX_ATTRIBUTE_LENGTH) { + LXW_WARN_FORMAT1("worksheet_conditional_format_cell()/_range(): " + "multi_range >= limit = %d.", + LXW_MAX_ATTRIBUTE_LENGTH); + err = LXW_ERROR_PARAMETER_VALIDATION; + goto error; + } + + LXW_ATTRIBUTE_COPY(cond_format->sqref, user_options->multi_range); + } + + /* Get the conditional format dxf format index. */ + if (user_options->format) + cond_format->dxf_index = + lxw_format_get_dxf_index(user_options->format); + else + cond_format->dxf_index = LXW_PROPERTY_UNSET; + + /* Set some common option for all validation types. */ + cond_format->type = user_options->type; + cond_format->criteria = user_options->criteria; + cond_format->stop_if_true = user_options->stop_if_true; + cond_format->type_string = lxw_strdup(type_strings[cond_format->type]); + + /* Validate the user input for various types of rules. */ + if (user_options->type == LXW_CONDITIONAL_TYPE_CELL + || cond_format->type == LXW_CONDITIONAL_TYPE_DUPLICATE + || cond_format->type == LXW_CONDITIONAL_TYPE_UNIQUE) { + + err = _validate_conditional_cell(cond_format, user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_TEXT) { + + err = _validate_conditional_text(cond_format, user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_TIME_PERIOD) { + + err = _validate_conditional_time_period(user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_AVERAGE) { + + err = _validate_conditional_average(user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_TOP + || cond_format->type == LXW_CONDITIONAL_TYPE_BOTTOM) { + + err = _validate_conditional_top(cond_format, user_options); + if (err) + goto error; + } + else if (user_options->type == LXW_CONDITIONAL_TYPE_FORMULA) { + + err = _validate_conditional_formula(cond_format, user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_2_COLOR_SCALE + || cond_format->type == LXW_CONDITIONAL_3_COLOR_SCALE) { + + err = _validate_conditional_scale(cond_format, user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_DATA_BAR) { + + err = _validate_conditional_data_bar(self, cond_format, user_options); + if (err) + goto error; + } + else if (cond_format->type == LXW_CONDITIONAL_TYPE_ICON_SETS) { + + err = _validate_conditional_icons(user_options); + if (err) + goto error; + + cond_format->icon_style = user_options->icon_style; + cond_format->reverse_icons = user_options->reverse_icons; + cond_format->icons_only = user_options->icons_only; + } + + /* Set the priority based on the order of adding. */ + cond_format->dxf_priority = ++self->dxf_priority; + + /* Store the conditional format object. */ + err = _store_conditional_format_object(self, cond_format); + + if (err) + goto error; + else + return LXW_NO_ERROR; + +error: + _free_cond_format(cond_format); + return err; +} + +/* + * Add a conditional format to a worksheet, for a cell. + */ +lxw_error +worksheet_conditional_format_cell(lxw_worksheet *self, + lxw_row_t row, + lxw_col_t col, + lxw_conditional_format *options) +{ + return worksheet_conditional_format_range(self, row, col, + row, col, options); +} + /* * Set the VBA name for the worksheet. */ @@ -6781,3 +9313,62 @@ worksheet_show_comments(lxw_worksheet *self) { self->comment_display_default = LXW_COMMENT_DISPLAY_VISIBLE; } + +/* + * Ignore various Excel errors/warnings in a worksheet for user defined ranges. + */ +lxw_error +worksheet_ignore_errors(lxw_worksheet *self, uint8_t type, const char *range) +{ + if (!range) { + LXW_WARN("worksheet_ignore_errors(): " "'range' must be specified."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + if (type <= 0 || type >= LXW_IGNORE_LAST_OPTION) { + LXW_WARN("worksheet_ignore_errors(): " "unknown option in 'type'."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Set the ranges to be ignored. */ + if (type == LXW_IGNORE_NUMBER_STORED_AS_TEXT) { + free(self->ignore_number_stored_as_text); + self->ignore_number_stored_as_text = lxw_strdup(range); + } + else if (type == LXW_IGNORE_EVAL_ERROR) { + free(self->ignore_eval_error); + self->ignore_eval_error = lxw_strdup(range); + } + else if (type == LXW_IGNORE_FORMULA_DIFFERS) { + free(self->ignore_formula_differs); + self->ignore_formula_differs = lxw_strdup(range); + } + else if (type == LXW_IGNORE_FORMULA_RANGE) { + free(self->ignore_formula_range); + self->ignore_formula_range = lxw_strdup(range); + } + else if (type == LXW_IGNORE_FORMULA_UNLOCKED) { + free(self->ignore_formula_unlocked); + self->ignore_formula_unlocked = lxw_strdup(range); + } + else if (type == LXW_IGNORE_EMPTY_CELL_REFERENCE) { + free(self->ignore_empty_cell_reference); + self->ignore_empty_cell_reference = lxw_strdup(range); + } + else if (type == LXW_IGNORE_LIST_DATA_VALIDATION) { + free(self->ignore_list_data_validation); + self->ignore_list_data_validation = lxw_strdup(range); + } + else if (type == LXW_IGNORE_CALCULATED_COLUMN) { + free(self->ignore_calculated_column); + self->ignore_calculated_column = lxw_strdup(range); + } + else if (type == LXW_IGNORE_TWO_DIGIT_TEXT_YEAR) { + free(self->ignore_two_digit_text_year); + self->ignore_two_digit_text_year = lxw_strdup(range); + } + + self->has_ignore_errors = LXW_TRUE; + + return LXW_NO_ERROR; +} diff --git a/libxlsxwriter/src/xmlwriter.c b/libxlsxwriter/src/xmlwriter.c index fa6946c..5133681 100644 --- a/libxlsxwriter/src/xmlwriter.c +++ b/libxlsxwriter/src/xmlwriter.c @@ -227,6 +227,23 @@ lxw_escape_data(const char *data) return encoded; } +/* + * Check for control characters in strings. + */ +uint8_t +lxw_has_control_characters(const char *string) +{ + while (string) { + /* 0xE0 == 0b11100000 masks values > 0x19 == 0b00011111. */ + if (!(*string & 0xE0) && *string != 0x0A && *string != 0x09) + return LXW_TRUE; + + string++; + } + + return LXW_FALSE; +} + /* * Escape control characters in strings with _xHHHH_. */