libxlsxwriter/src/worksheet.c
2015-12-30 00:40:12 +00:00

4198 lines
111 KiB
C

/*****************************************************************************
* worksheet - A library for creating Excel XLSX worksheet files.
*
* Used in conjunction with the libxlsxwriter library.
*
* Copyright 2014-2015, John McNamara, jmcnamara@cpan.org. See LICENSE.txt.
*
*/
#include <ctype.h>
#include "xlsxwriter/xmlwriter.h"
#include "xlsxwriter/worksheet.h"
#include "xlsxwriter/format.h"
#include "xlsxwriter/utility.h"
#include "xlsxwriter/relationships.h"
#define LXW_STR_MAX 32767
#define BUFFER_SIZE 4096
#define LXW_PORTRAIT 1
#define LXW_LANDSCAPE 0
#define LXW_PRINT_ACROSS 1
/*
* Forward declarations.
*/
STATIC void _worksheet_write_rows(lxw_worksheet *self);
STATIC int _row_cmp(lxw_row *row1, lxw_row *row2);
STATIC int _cell_cmp(lxw_cell *cell1, lxw_cell *cell2);
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);
/*****************************************************************************
*
* Private functions.
*
****************************************************************************/
/*
* Create a new worksheet object.
*/
lxw_worksheet *
_new_worksheet(lxw_worksheet_init_data *init_data)
{
lxw_worksheet *worksheet = calloc(1, sizeof(lxw_worksheet));
GOTO_LABEL_ON_MEM_ERROR(worksheet, mem_error);
worksheet->table = calloc(1, sizeof(struct lxw_table_rows));
GOTO_LABEL_ON_MEM_ERROR(worksheet->table, mem_error);
worksheet->hyperlinks = calloc(1, sizeof(struct lxw_table_rows));
GOTO_LABEL_ON_MEM_ERROR(worksheet->hyperlinks, mem_error);
/* Initialize the cached rows. */
worksheet->table->cached_row_num = LXW_ROW_MAX + 1;
worksheet->hyperlinks->cached_row_num = LXW_ROW_MAX + 1;
if (init_data && init_data->optimize) {
worksheet->array = calloc(LXW_COL_MAX, sizeof(struct lxw_cell *));
GOTO_LABEL_ON_MEM_ERROR(worksheet->array, mem_error);
}
worksheet->col_options =
calloc(LXW_COL_META_MAX, sizeof(lxw_col_options *));
worksheet->col_options_max = LXW_COL_META_MAX;
GOTO_LABEL_ON_MEM_ERROR(worksheet->col_options, mem_error);
worksheet->col_formats = calloc(LXW_COL_META_MAX, sizeof(lxw_format *));
worksheet->col_formats_max = LXW_COL_META_MAX;
GOTO_LABEL_ON_MEM_ERROR(worksheet->col_formats, mem_error);
worksheet->optimize_row = calloc(1, sizeof(struct lxw_row));
GOTO_LABEL_ON_MEM_ERROR(worksheet->optimize_row, mem_error);
worksheet->optimize_row->height = LXW_DEF_ROW_HEIGHT;
worksheet->merged_ranges = calloc(1, sizeof(struct lxw_merged_ranges));
GOTO_LABEL_ON_MEM_ERROR(worksheet->merged_ranges, mem_error);
worksheet->images = calloc(1, sizeof(struct lxw_images));
GOTO_LABEL_ON_MEM_ERROR(worksheet->images, mem_error);
worksheet->selections = calloc(1, sizeof(struct lxw_selections));
GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error);
worksheet->external_hyperlinks = calloc(1, sizeof(struct lxw_rel_tuples));
GOTO_LABEL_ON_MEM_ERROR(worksheet->external_hyperlinks, mem_error);
worksheet->external_drawing_links =
calloc(1, sizeof(struct lxw_rel_tuples));
GOTO_LABEL_ON_MEM_ERROR(worksheet->external_drawing_links, mem_error);
worksheet->drawing_links = calloc(1, sizeof(struct lxw_rel_tuples));
GOTO_LABEL_ON_MEM_ERROR(worksheet->drawing_links, mem_error);
RB_INIT(worksheet->table);
RB_INIT(worksheet->hyperlinks);
STAILQ_INIT(worksheet->merged_ranges);
STAILQ_INIT(worksheet->images);
STAILQ_INIT(worksheet->selections);
STAILQ_INIT(worksheet->external_hyperlinks);
STAILQ_INIT(worksheet->external_drawing_links);
STAILQ_INIT(worksheet->drawing_links);
if (init_data && init_data->optimize) {
worksheet->optimize_tmpfile = lxw_tmpfile();
worksheet->file = worksheet->optimize_tmpfile;
}
/* Initialize the worksheet dimensions. */
worksheet->dim_rowmax = 0;
worksheet->dim_colmax = 0;
worksheet->dim_rowmin = LXW_ROW_MAX;
worksheet->dim_colmin = LXW_COL_MAX;
worksheet->default_row_height = LXW_DEF_ROW_HEIGHT;
worksheet->default_row_pixels = 20;
worksheet->default_col_pixels = 64;
/* Initialize the page setup properties. */
worksheet->fit_height = 0;
worksheet->fit_width = 0;
worksheet->page_start = 0;
worksheet->print_scale = 100;
worksheet->fit_page = 0;
worksheet->orientation = LXW_TRUE;
worksheet->page_order = 0;
worksheet->page_setup_changed = LXW_FALSE;
worksheet->page_view = LXW_FALSE;
worksheet->paper_size = 0;
worksheet->vertical_dpi = 0;
worksheet->horizontal_dpi = 0;
worksheet->margin_left = 0.7;
worksheet->margin_right = 0.7;
worksheet->margin_top = 0.75;
worksheet->margin_bottom = 0.75;
worksheet->margin_header = 0.3;
worksheet->margin_footer = 0.3;
worksheet->print_gridlines = 0;
worksheet->screen_gridlines = 1;
worksheet->print_options_changed = 0;
worksheet->zoom = 100;
worksheet->zoom_scale_normal = LXW_TRUE;
worksheet->show_zeros = LXW_TRUE;
worksheet->outline_on = LXW_TRUE;
worksheet->tab_color = LXW_COLOR_UNSET;
if (init_data) {
worksheet->name = init_data->name;
worksheet->quoted_name = init_data->quoted_name;
worksheet->index = init_data->index;
worksheet->hidden = init_data->hidden;
worksheet->sst = init_data->sst;
worksheet->optimize = init_data->optimize;
worksheet->active_sheet = init_data->active_sheet;
worksheet->first_sheet = init_data->first_sheet;
}
return worksheet;
mem_error:
_free_worksheet(worksheet);
return NULL;
}
/*
* Free a worksheet cell.
*/
void
_free_cell(lxw_cell *cell)
{
if (!cell)
return;
if (cell->type != NUMBER_CELL && cell->type != STRING_CELL
&& cell->type != BLANK_CELL) {
free(cell->u.string);
}
free(cell->user_data1);
free(cell->user_data2);
free(cell);
}
/*
* Free a worksheet row.
*/
void
_free_row(lxw_row *row)
{
lxw_cell *cell;
lxw_cell *next_cell;
if (!row)
return;
for (cell = RB_MIN(lxw_table_cells, row->cells); cell; cell = next_cell) {
next_cell = RB_NEXT(lxw_table_cells, row->cells, cell);
RB_REMOVE(lxw_table_cells, row->cells, cell);
_free_cell(cell);
}
free(row->cells);
free(row);
}
/*
* Free a worksheet image_options.
*/
void
_free_image_options(lxw_image_options *image)
{
if (!image)
return;
free(image->filename);
free(image->short_name);
free(image->url);
free(image->tip);
free(image);
}
/*
* Free a worksheet object.
*/
void
_free_worksheet(lxw_worksheet *worksheet)
{
lxw_row *row;
lxw_row *next_row;
lxw_col_t col;
lxw_merged_range *merged_range;
lxw_image_options *image;
lxw_selection *selection;
lxw_rel_tuple *relationship;
if (!worksheet)
return;
if (worksheet->col_options) {
for (col = 0; col < worksheet->col_options_max; col++) {
if (worksheet->col_options[col])
free(worksheet->col_options[col]);
}
}
free(worksheet->col_options);
free(worksheet->col_sizes);
free(worksheet->col_formats);
if (worksheet->table) {
for (row = RB_MIN(lxw_table_rows, worksheet->table); row;
row = next_row) {
next_row = RB_NEXT(lxw_table_rows, worksheet->table, row);
RB_REMOVE(lxw_table_rows, worksheet->table, row);
_free_row(row);
}
free(worksheet->table);
}
if (worksheet->hyperlinks) {
for (row = RB_MIN(lxw_table_rows, worksheet->hyperlinks); row;
row = next_row) {
next_row = RB_NEXT(lxw_table_rows, worksheet->hyperlinks, row);
RB_REMOVE(lxw_table_rows, worksheet->hyperlinks, row);
_free_row(row);
}
free(worksheet->hyperlinks);
}
if (worksheet->merged_ranges) {
while (!STAILQ_EMPTY(worksheet->merged_ranges)) {
merged_range = STAILQ_FIRST(worksheet->merged_ranges);
STAILQ_REMOVE_HEAD(worksheet->merged_ranges, list_pointers);
free(merged_range);
}
free(worksheet->merged_ranges);
}
if (worksheet->images) {
while (!STAILQ_EMPTY(worksheet->images)) {
image = STAILQ_FIRST(worksheet->images);
STAILQ_REMOVE_HEAD(worksheet->images, list_pointers);
_free_image_options(image);
}
free(worksheet->images);
}
if (worksheet->selections) {
while (!STAILQ_EMPTY(worksheet->selections)) {
selection = STAILQ_FIRST(worksheet->selections);
STAILQ_REMOVE_HEAD(worksheet->selections, list_pointers);
free(selection);
}
free(worksheet->selections);
}
/* TODO. Add function for freeing the relationship lists. */
while (!STAILQ_EMPTY(worksheet->external_hyperlinks)) {
relationship = STAILQ_FIRST(worksheet->external_hyperlinks);
STAILQ_REMOVE_HEAD(worksheet->external_hyperlinks, list_pointers);
free(relationship->type);
free(relationship->target);
free(relationship->target_mode);
free(relationship);
}
free(worksheet->external_hyperlinks);
while (!STAILQ_EMPTY(worksheet->external_drawing_links)) {
relationship = STAILQ_FIRST(worksheet->external_drawing_links);
STAILQ_REMOVE_HEAD(worksheet->external_drawing_links, list_pointers);
free(relationship->type);
free(relationship->target);
free(relationship->target_mode);
free(relationship);
}
free(worksheet->external_drawing_links);
while (!STAILQ_EMPTY(worksheet->drawing_links)) {
relationship = STAILQ_FIRST(worksheet->drawing_links);
STAILQ_REMOVE_HEAD(worksheet->drawing_links, list_pointers);
free(relationship->type);
free(relationship->target);
free(relationship->target_mode);
free(relationship);
}
free(worksheet->drawing_links);
if (worksheet->array) {
for (col = 0; col < LXW_COL_MAX; col++) {
_free_cell(worksheet->array[col]);
}
free(worksheet->array);
}
if (worksheet->optimize_row)
free(worksheet->optimize_row);
if (worksheet->drawing)
_free_drawing(worksheet->drawing);
free(worksheet->name);
free(worksheet->quoted_name);
free(worksheet);
worksheet = NULL;
}
/*
* Create a new worksheet row object.
*/
STATIC lxw_row *
_new_row(lxw_row_t row_num)
{
lxw_row *row = calloc(1, sizeof(lxw_row));
if (row) {
row->row_num = row_num;
row->cells = calloc(1, sizeof(struct lxw_table_cells));
row->height = LXW_DEF_ROW_HEIGHT;
if (row->cells)
RB_INIT(row->cells);
else
LXW_MEM_ERROR();
}
else {
LXW_MEM_ERROR();
}
return row;
}
/*
* Create a new worksheet number cell object.
*/
STATIC lxw_cell *
_new_number_cell(lxw_row_t row_num,
lxw_col_t col_num, double value, lxw_format *format)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = NUMBER_CELL;
cell->format = format;
cell->u.number = value;
return cell;
}
/*
* Create a new worksheet string cell object.
*/
STATIC lxw_cell *
_new_string_cell(lxw_row_t row_num,
lxw_col_t col_num, int32_t string_id, lxw_format *format)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = STRING_CELL;
cell->format = format;
cell->u.string_id = string_id;
return cell;
}
/*
* Create a new worksheet inline_string cell object.
*/
STATIC lxw_cell *
_new_inline_string_cell(lxw_row_t row_num,
lxw_col_t col_num, char *string, lxw_format *format)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = INLINE_STRING_CELL;
cell->format = format;
cell->u.string = string;
return cell;
}
/*
* Create a new worksheet formula cell object.
*/
STATIC lxw_cell *
_new_formula_cell(lxw_row_t row_num,
lxw_col_t col_num, char *formula, lxw_format *format)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = FORMULA_CELL;
cell->format = format;
cell->u.string = formula;
return cell;
}
/*
* Create a new worksheet array formula cell object.
*/
STATIC lxw_cell *
_new_array_formula_cell(lxw_row_t row_num, lxw_col_t col_num, char *formula,
char *range, lxw_format *format)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = ARRAY_FORMULA_CELL;
cell->format = format;
cell->u.string = formula;
cell->user_data1 = range;
return cell;
}
/*
* Create a new worksheet blank cell object.
*/
STATIC lxw_cell *
_new_blank_cell(lxw_row_t row_num, lxw_col_t col_num, lxw_format *format)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = BLANK_CELL;
cell->format = format;
return cell;
}
/*
* Create a new worksheet hyperlink cell object.
*/
STATIC lxw_cell *
_new_hyperlink_cell(lxw_row_t row_num, lxw_col_t col_num,
enum cell_types link_type, char *url, char *string,
char *tooltip)
{
lxw_cell *cell = calloc(1, sizeof(lxw_cell));
RETURN_ON_MEM_ERROR(cell, cell);
cell->row_num = row_num;
cell->col_num = col_num;
cell->type = link_type;
cell->u.string = url;
cell->user_data1 = string;
cell->user_data2 = tooltip;
return cell;
}
/*
* Get or create the row object for a given row number.
*/
STATIC lxw_row *
_get_row_list(struct lxw_table_rows *table, lxw_row_t row_num)
{
lxw_row *row;
lxw_row *existing_row;
if (table->cached_row_num == row_num)
return table->cached_row;
/* Create a new row and try and insert it. */
row = _new_row(row_num);
existing_row = RB_INSERT(lxw_table_rows, table, row);
/* If existing_row is not NULL, then it already existed. Free new row */
/* and return existing_row. */
if (existing_row) {
_free_row(row);
row = existing_row;
}
table->cached_row = row;
table->cached_row_num = row_num;
return row;
}
/*
* Get or create the row object for a given row number.
*/
STATIC lxw_row *
_get_row(lxw_worksheet *self, lxw_row_t row_num)
{
lxw_row *row;
if (!self->optimize) {
row = _get_row_list(self->table, row_num);
return row;
}
else {
if (row_num < self->optimize_row->row_num) {
return NULL;
}
else if (row_num == self->optimize_row->row_num) {
return self->optimize_row;
}
else {
/* Flush row. */
_worksheet_write_single_row(self);
row = self->optimize_row;
row->row_num = row_num;
return row;
}
}
}
/*
* Insert a cell object in the cell list of a row object.
*/
STATIC void
_insert_cell_list(struct lxw_table_cells *cell_list,
lxw_cell *cell, lxw_col_t col_num)
{
lxw_cell *existing_cell;
cell->col_num = col_num;
existing_cell = RB_INSERT(lxw_table_cells, cell_list, cell);
/* If existing_cell is not NULL, then that cell already existed. */
/* Remove existing_cell and add new one in again. */
if (existing_cell) {
RB_REMOVE(lxw_table_cells, cell_list, existing_cell);
/* Add it in again. */
RB_INSERT(lxw_table_cells, cell_list, cell);
_free_cell(existing_cell);
}
return;
}
/*
* Insert a cell object into the cell list or array.
*/
STATIC void
_insert_cell(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
lxw_cell *cell)
{
lxw_row *row = _get_row(self, row_num);
if (!self->optimize) {
row->data_changed = LXW_TRUE;
_insert_cell_list(row->cells, cell, col_num);
}
else {
if (row) {
row->data_changed = LXW_TRUE;
/* Overwrite an existing cell if necessary. */
if (self->array[col_num])
_free_cell(self->array[col_num]);
self->array[col_num] = cell;
}
}
}
/*
* Insert a hyperlink object into the hyperlink list.
*/
STATIC void
_insert_hyperlink(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num,
lxw_cell *link)
{
lxw_row *row = _get_row_list(self->hyperlinks, row_num);
_insert_cell_list(row->cells, link, col_num);
}
/*
* Next power of two for column reallocs. Taken from bithacks in the public
* domain.
*/
STATIC lxw_col_t
_next_power_of_two(uint16_t col)
{
col--;
col |= col >> 1;
col |= col >> 2;
col |= col >> 4;
col |= col >> 8;
col++;
return col;
}
/*
* Check that row and col are within the allowed Excel range and store max
* and min values for use in other methods/elements.
*
* The ignore_row/ignore_col flags are used to indicate that we wish to
* perform the dimension check without storing the value.
*/
STATIC int8_t
_check_dimensions(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, int8_t ignore_row, int8_t ignore_col)
{
if (row_num >= LXW_ROW_MAX)
return -LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
if (col_num >= LXW_COL_MAX)
return -LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
/* In optimization mode we don't change dimensions for rows that are */
/* already written. */
if (!ignore_row && !ignore_col && self->optimize) {
if (row_num < self->optimize_row->row_num)
return -LXW_ERROR_WORKSHEET_INDEX_OUT_OF_RANGE;
}
if (!ignore_row) {
if (row_num < self->dim_rowmin)
self->dim_rowmin = row_num;
if (row_num > self->dim_rowmax)
self->dim_rowmax = row_num;
}
if (!ignore_col) {
if (col_num < self->dim_colmin)
self->dim_colmin = col_num;
if (col_num > self->dim_colmax)
self->dim_colmax = col_num;
}
return 0;
}
/*
* Comparator for the row structure red/black tree.
*/
STATIC int
_row_cmp(lxw_row *row1, lxw_row *row2)
{
if (row1->row_num > row2->row_num)
return 1;
if (row1->row_num < row2->row_num)
return -1;
return 0;
}
/*
* Comparator for the cell structure red/black tree.
*/
STATIC int
_cell_cmp(lxw_cell *cell1, lxw_cell *cell2)
{
if (cell1->col_num > cell2->col_num)
return 1;
if (cell1->col_num < cell2->col_num)
return -1;
return 0;
}
/*
* Hash a worksheet password. Based on the algorithm provided by Daniel Rentz
* of OpenOffice.
*/
uint16_t
_hash_password(char *password)
{
uint8_t count;
uint8_t i;
uint16_t hash = 0x0000;
count = strlen(password);
for (i = 0; i < count; i++) {
uint32_t low_15;
uint32_t high_15;
uint32_t letter = password[i] << (i + 1);
low_15 = letter & 0x7fff;
high_15 = letter & (0x7fff << 15);
high_15 = high_15 >> 15;
letter = low_15 | high_15;
hash ^= letter;
}
hash ^= count;
hash ^= 0xCE4B;
return hash;
}
/*****************************************************************************
*
* XML functions.
*
****************************************************************************/
/*
* Write the XML declaration.
*/
STATIC void
_worksheet_xml_declaration(lxw_worksheet *self)
{
_xml_declaration(self->file);
}
/*
* Write the <worksheet> element.
*/
STATIC void
_worksheet_write_worksheet(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char xmlns[] = "http://schemas.openxmlformats.org/"
"spreadsheetml/2006/main";
char xmlns_r[] = "http://schemas.openxmlformats.org/"
"officeDocument/2006/relationships";
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("xmlns", xmlns);
_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r);
_xml_start_tag(self->file, "worksheet", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <dimension> element.
*/
STATIC void
_worksheet_write_dimension(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char ref[MAX_CELL_RANGE_LENGTH];
lxw_row_t dim_rowmin = self->dim_rowmin;
lxw_row_t dim_rowmax = self->dim_rowmax;
lxw_col_t dim_colmin = self->dim_colmin;
lxw_col_t dim_colmax = self->dim_colmax;
if (dim_rowmin == LXW_ROW_MAX && dim_colmin == LXW_COL_MAX) {
/* If the rows and cols are still the defaults then no dimensions have
* been set and we use the default range "A1". */
lxw_range(ref, 0, 0, 0, 0);
}
else if (dim_rowmin == LXW_ROW_MAX && dim_colmin != LXW_COL_MAX) {
/* If the rows aren't set but the columns are then the dimensions have
* been changed via set_column(). */
lxw_range(ref, 0, dim_colmin, 0, dim_colmax);
}
else {
lxw_range(ref, dim_rowmin, dim_colmin, dim_rowmax, dim_colmax);
}
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("ref", ref);
_xml_empty_tag(self->file, "dimension", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <pane> element for freeze panes.
*/
STATIC void
_worksheet_write_freeze_panes(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
lxw_selection *selection;
lxw_selection *user_selection;
lxw_row_t row = self->panes.first_row;
lxw_col_t col = self->panes.first_col;
lxw_row_t top_row = self->panes.top_row;
lxw_col_t left_col = self->panes.left_col;
char row_cell[MAX_CELL_NAME_LENGTH];
char col_cell[MAX_CELL_NAME_LENGTH];
char top_left_cell[MAX_CELL_NAME_LENGTH];
char active_pane[LXW_PANE_NAME_LENGTH];
/* If there is a user selection we remove it from the list and use it. */
if (!STAILQ_EMPTY(self->selections)) {
user_selection = STAILQ_FIRST(self->selections);
STAILQ_REMOVE_HEAD(self->selections, list_pointers);
}
else {
/* or else create a new blank selection. */
user_selection = calloc(1, sizeof(lxw_selection));
RETURN_VOID_ON_MEM_ERROR(user_selection);
}
_INIT_ATTRIBUTES();
lxw_rowcol_to_cell(top_left_cell, top_row, left_col);
/* Set the active pane. */
if (row && col) {
strcpy(active_pane, "bottomRight");
lxw_rowcol_to_cell(row_cell, row, 0);
lxw_rowcol_to_cell(col_cell, 0, col);
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "topRight");
strcpy(selection->active_cell, col_cell);
strcpy(selection->sqref, col_cell);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "bottomLeft");
strcpy(selection->active_cell, row_cell);
strcpy(selection->sqref, row_cell);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "bottomRight");
strcpy(selection->active_cell, user_selection->active_cell);
strcpy(selection->sqref, user_selection->sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
}
else if (col) {
strcpy(active_pane, "topRight");
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "topRight");
strcpy(selection->active_cell, user_selection->active_cell);
strcpy(selection->sqref, user_selection->sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
}
else {
strcpy(active_pane, "bottomLeft");
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "bottomLeft");
strcpy(selection->active_cell, user_selection->active_cell);
strcpy(selection->sqref, user_selection->sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
}
if (col)
_PUSH_ATTRIBUTES_INT("xSplit", col);
if (row)
_PUSH_ATTRIBUTES_INT("ySplit", row);
_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell);
_PUSH_ATTRIBUTES_STR("activePane", active_pane);
if (self->panes.type == FREEZE_PANES)
_PUSH_ATTRIBUTES_STR("state", "frozen");
else if (self->panes.type == FREEZE_SPLIT_PANES)
_PUSH_ATTRIBUTES_STR("state", "frozenSplit");
_xml_empty_tag(self->file, "pane", &attributes);
free(user_selection);
_FREE_ATTRIBUTES();
}
/*
* Convert column width from user units to pane split width.
*/
STATIC uint32_t
_worksheet_calculate_x_split_width(double x_split)
{
uint32_t width;
uint32_t pixels;
uint32_t points;
uint32_t twips;
double max_digit_width = 7.0; /* For Calabri 11. */
double padding = 5.0;
/* Convert to pixels. */
if (x_split < 1.0) {
pixels = (uint32_t) (x_split * (max_digit_width + padding) + 0.5);
}
else {
pixels = (uint32_t) (x_split * max_digit_width + 0.5) + 5;
}
/* Convert to points. */
points = (pixels * 3) / 4;
/* Convert to twips (twentieths of a point). */
twips = points * 20;
/* Add offset/padding. */
width = twips + 390;
return width;
}
/*
* Write the <pane> element for split panes.
*/
STATIC void
_worksheet_write_split_panes(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
lxw_selection *selection;
lxw_selection *user_selection;
lxw_row_t row = self->panes.first_row;
lxw_col_t col = self->panes.first_col;
lxw_row_t top_row = self->panes.top_row;
lxw_col_t left_col = self->panes.left_col;
double x_split = self->panes.x_split;
double y_split = self->panes.y_split;
uint8_t has_selection = LXW_FALSE;
char row_cell[MAX_CELL_NAME_LENGTH];
char col_cell[MAX_CELL_NAME_LENGTH];
char top_left_cell[MAX_CELL_NAME_LENGTH];
char active_pane[LXW_PANE_NAME_LENGTH];
/* If there is a user selection we remove it from the list and use it. */
if (!STAILQ_EMPTY(self->selections)) {
user_selection = STAILQ_FIRST(self->selections);
STAILQ_REMOVE_HEAD(self->selections, list_pointers);
has_selection = LXW_TRUE;
}
else {
/* or else create a new blank selection. */
user_selection = calloc(1, sizeof(lxw_selection));
RETURN_VOID_ON_MEM_ERROR(user_selection);
}
_INIT_ATTRIBUTES();
/* Convert the row and col to 1/20 twip units with padding. */
if (y_split > 0.0)
y_split = (uint32_t) (20 * y_split + 300);
if (x_split > 0.0)
x_split = _worksheet_calculate_x_split_width(x_split);
/* For non-explicit topLeft definitions, estimate the cell offset based on
* the pixels dimensions. This is only a workaround and doesn't take
* adjusted cell dimensions into account.
*/
if (top_row == row && left_col == col) {
top_row = (lxw_row_t) (0.5 + (y_split - 300.0) / 20.0 / 15.0);
left_col = (lxw_col_t) (0.5 + (x_split - 390.0) / 20.0 / 3.0 / 16.0);
}
lxw_rowcol_to_cell(top_left_cell, top_row, left_col);
/* If there is no selection set the active cell to the top left cell. */
if (!has_selection) {
strcpy(user_selection->active_cell, top_left_cell);
strcpy(user_selection->sqref, top_left_cell);
}
/* Set the active pane. */
if (y_split > 0.0 && x_split > 0.0) {
strcpy(active_pane, "bottomRight");
lxw_rowcol_to_cell(row_cell, top_row, 0);
lxw_rowcol_to_cell(col_cell, 0, left_col);
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "topRight");
strcpy(selection->active_cell, col_cell);
strcpy(selection->sqref, col_cell);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "bottomLeft");
strcpy(selection->active_cell, row_cell);
strcpy(selection->sqref, row_cell);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "bottomRight");
strcpy(selection->active_cell, user_selection->active_cell);
strcpy(selection->sqref, user_selection->sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
}
else if (x_split > 0.0) {
strcpy(active_pane, "topRight");
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "topRight");
strcpy(selection->active_cell, user_selection->active_cell);
strcpy(selection->sqref, user_selection->sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
}
else {
strcpy(active_pane, "bottomLeft");
selection = calloc(1, sizeof(lxw_selection));
if (selection) {
strcpy(selection->pane, "bottomLeft");
strcpy(selection->active_cell, user_selection->active_cell);
strcpy(selection->sqref, user_selection->sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
}
if (x_split > 0.0)
_PUSH_ATTRIBUTES_DBL("xSplit", x_split);
if (y_split > 0.0)
_PUSH_ATTRIBUTES_DBL("ySplit", y_split);
_PUSH_ATTRIBUTES_STR("topLeftCell", top_left_cell);
if (has_selection)
_PUSH_ATTRIBUTES_STR("activePane", active_pane);
_xml_empty_tag(self->file, "pane", &attributes);
free(user_selection);
_FREE_ATTRIBUTES();
}
/*
* Write the <selection> element.
*/
STATIC void
_worksheet_write_selection(lxw_worksheet *self, lxw_selection *selection)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
_INIT_ATTRIBUTES();
if (*selection->pane)
_PUSH_ATTRIBUTES_STR("pane", selection->pane);
if (*selection->active_cell)
_PUSH_ATTRIBUTES_STR("activeCell", selection->active_cell);
if (*selection->sqref)
_PUSH_ATTRIBUTES_STR("sqref", selection->sqref);
_xml_empty_tag(self->file, "selection", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <selection> elements.
*/
STATIC void
_worksheet_write_selections(lxw_worksheet *self)
{
lxw_selection *selection;
STAILQ_FOREACH(selection, self->selections, list_pointers) {
_worksheet_write_selection(self, selection);
}
}
/*
* Write the frozen or split <pane> elements.
*/
STATIC void
_worksheet_write_panes(lxw_worksheet *self)
{
if (self->panes.type == NO_PANES)
return;
else if (self->panes.type == FREEZE_PANES)
_worksheet_write_freeze_panes(self);
else if (self->panes.type == FREEZE_SPLIT_PANES)
_worksheet_write_freeze_panes(self);
else if (self->panes.type == SPLIT_PANES)
_worksheet_write_split_panes(self);
}
/*
* Write the <sheetView> element.
*/
STATIC void
_worksheet_write_sheet_view(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
_INIT_ATTRIBUTES();
/* Hide screen gridlines if required */
if (!self->screen_gridlines)
_PUSH_ATTRIBUTES_STR("showGridLines", "0");
/* Hide zeroes in cells. */
if (!self->show_zeros) {
_PUSH_ATTRIBUTES_STR("showZeros", "0");
}
/* Display worksheet right to left for Hebrew, Arabic and others. */
if (self->right_to_left) {
_PUSH_ATTRIBUTES_STR("rightToLeft", "1");
}
/* Show that the sheet tab is selected. */
if (self->selected)
_PUSH_ATTRIBUTES_STR("tabSelected", "1");
/* Turn outlines off. Also required in the outlinePr element. */
if (!self->outline_on) {
_PUSH_ATTRIBUTES_STR("showOutlineSymbols", "0");
}
/* Set the page view/layout mode if required. */
if (self->page_view)
_PUSH_ATTRIBUTES_STR("view", "pageLayout");
/* Set the zoom level. */
if (self->zoom != 100) {
if (!self->page_view) {
_PUSH_ATTRIBUTES_INT("zoomScale", self->zoom);
if (self->zoom_scale_normal)
_PUSH_ATTRIBUTES_INT("zoomScaleNormal", self->zoom);
}
}
_PUSH_ATTRIBUTES_STR("workbookViewId", "0");
if (self->panes.type != NO_PANES || !STAILQ_EMPTY(self->selections)) {
_xml_start_tag(self->file, "sheetView", &attributes);
_worksheet_write_panes(self);
_worksheet_write_selections(self);
_xml_end_tag(self->file, "sheetView");
}
else {
_xml_empty_tag(self->file, "sheetView", &attributes);
}
_FREE_ATTRIBUTES();
}
/*
* Write the <sheetViews> element.
*/
STATIC void
_worksheet_write_sheet_views(lxw_worksheet *self)
{
_xml_start_tag(self->file, "sheetViews", NULL);
/* Write the sheetView element. */
_worksheet_write_sheet_view(self);
_xml_end_tag(self->file, "sheetViews");
}
/*
* Write the <sheetFormatPr> element.
*/
STATIC void
_worksheet_write_sheet_format_pr(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_DBL("defaultRowHeight", self->default_row_height);
if (self->default_row_height != LXW_DEF_ROW_HEIGHT)
_PUSH_ATTRIBUTES_STR("customHeight", "1");
if (self->default_row_zeroed)
_PUSH_ATTRIBUTES_STR("zeroHeight", "1");
_xml_empty_tag(self->file, "sheetFormatPr", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <sheetData> element.
*/
STATIC void
_worksheet_write_sheet_data(lxw_worksheet *self)
{
if (RB_EMPTY(self->table)) {
_xml_empty_tag(self->file, "sheetData", NULL);
}
else {
_xml_start_tag(self->file, "sheetData", NULL);
_worksheet_write_rows(self);
_xml_end_tag(self->file, "sheetData");
}
}
/*
* Write the <sheetData> element when the memory optimization is on. In which
* case we read the data stored in the temp file and rewrite it to the XML
* sheet file.
*/
STATIC void
_worksheet_write_optimized_sheet_data(lxw_worksheet *self)
{
uint16_t read_size = 1;
char buffer[BUFFER_SIZE];
if (self->dim_rowmin == LXW_ROW_MAX) {
/* If the dimensions aren't defined then there is no data to write. */
_xml_empty_tag(self->file, "sheetData", NULL);
}
else {
_xml_start_tag(self->file, "sheetData", NULL);
/* Flush and rewind the temp file. */
fflush(self->optimize_tmpfile);
rewind(self->optimize_tmpfile);
while (read_size) {
read_size = fread(buffer, 1, BUFFER_SIZE, self->optimize_tmpfile);
fwrite(buffer, 1, read_size, self->file);
}
fclose(self->optimize_tmpfile);
_xml_end_tag(self->file, "sheetData");
}
}
/*
* Write the <pageMargins> element.
*/
STATIC void
_worksheet_write_page_margins(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
double left = self->margin_left;
double right = self->margin_right;
double top = self->margin_top;
double bottom = self->margin_bottom;
double header = self->margin_header;
double footer = self->margin_footer;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_DBL("left", left);
_PUSH_ATTRIBUTES_DBL("right", right);
_PUSH_ATTRIBUTES_DBL("top", top);
_PUSH_ATTRIBUTES_DBL("bottom", bottom);
_PUSH_ATTRIBUTES_DBL("header", header);
_PUSH_ATTRIBUTES_DBL("footer", footer);
_xml_empty_tag(self->file, "pageMargins", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <pageSetup> element.
* The following is an example taken from Excel.
* <pageSetup
* paperSize="9"
* scale="110"
* fitToWidth="2"
* fitToHeight="2"
* pageOrder="overThenDown"
* orientation="portrait"
* blackAndWhite="1"
* draft="1"
* horizontalDpi="200"
* verticalDpi="200"
* r:id="rId1"
* />
*/
STATIC void
_worksheet_write_page_setup(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
_INIT_ATTRIBUTES();
if (!self->page_setup_changed)
return;
/* Set paper size. */
if (self->paper_size)
_PUSH_ATTRIBUTES_INT("paperSize", self->paper_size);
/* Set the print_scale. */
if (self->print_scale != 100)
_PUSH_ATTRIBUTES_INT("scale", self->print_scale);
/* Set the "Fit to page" properties. */
if (self->fit_page && self->fit_width != 1)
_PUSH_ATTRIBUTES_INT("fitToWidth", self->fit_width);
if (self->fit_page && self->fit_height != 1)
_PUSH_ATTRIBUTES_INT("fitToHeight", self->fit_height);
/* Set the page print direction. */
if (self->page_order)
_PUSH_ATTRIBUTES_STR("pageOrder", "overThenDown");
/* Set start page. */
if (self->page_start > 1)
_PUSH_ATTRIBUTES_INT("firstPageNumber", self->page_start);
/* Set page orientation. */
if (self->orientation)
_PUSH_ATTRIBUTES_STR("orientation", "portrait");
else
_PUSH_ATTRIBUTES_STR("orientation", "landscape");
/* Set start page active flag. */
if (self->page_start)
_PUSH_ATTRIBUTES_INT("useFirstPageNumber", 1);
/* Set the DPI. Mainly only for testing. */
if (self->horizontal_dpi)
_PUSH_ATTRIBUTES_INT("horizontalDpi", self->horizontal_dpi);
if (self->vertical_dpi)
_PUSH_ATTRIBUTES_INT("verticalDpi", self->vertical_dpi);
_xml_empty_tag(self->file, "pageSetup", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <printOptions> element.
*/
STATIC void
_worksheet_write_print_options(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
if (!self->print_options_changed)
return;
_INIT_ATTRIBUTES();
/* Set horizontal centering. */
if (self->hcenter) {
_PUSH_ATTRIBUTES_STR("horizontalCentered", "1");
}
/* Set vertical centering. */
if (self->vcenter) {
_PUSH_ATTRIBUTES_STR("verticalCentered", "1");
}
/* Enable row and column headers. */
if (self->print_headers) {
_PUSH_ATTRIBUTES_STR("headings", "1");
}
/* Set printed gridlines. */
if (self->print_gridlines) {
_PUSH_ATTRIBUTES_STR("gridLines", "1");
}
_xml_empty_tag(self->file, "printOptions", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <row> element.
*/
STATIC void
_write_row(lxw_worksheet *self, lxw_row *row, char *spans)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
int32_t xf_index = 0;
double height;
if (row->format) {
xf_index = _get_xf_index(row->format);
}
if (row->height_changed)
height = row->height;
else
height = self->default_row_height;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_INT("r", row->row_num + 1);
if (spans)
_PUSH_ATTRIBUTES_STR("spans", spans);
if (xf_index)
_PUSH_ATTRIBUTES_INT("s", xf_index);
if (row->format)
_PUSH_ATTRIBUTES_STR("customFormat", "1");
if (height != LXW_DEF_ROW_HEIGHT)
_PUSH_ATTRIBUTES_INT("ht", height);
if (row->hidden)
_PUSH_ATTRIBUTES_STR("hidden", "1");
if (height != LXW_DEF_ROW_HEIGHT)
_PUSH_ATTRIBUTES_STR("customHeight", "1");
if (row->collapsed)
_PUSH_ATTRIBUTES_STR("collapsed", "1");
if (!row->data_changed)
_xml_empty_tag(self->file, "row", &attributes);
else
_xml_start_tag(self->file, "row", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Convert the width of a cell from user's units to pixels. Excel rounds the
* column width to the nearest pixel. If the width hasn't been set by the user
* we use the default value. If the column is hidden it has a value of zero.
*/
STATIC int32_t
_worksheet_size_col(lxw_worksheet *self, uint32_t col)
{
col++;
return self->default_col_pixels;
}
/*
* Convert the height of a cell from user's units to pixels. If the height
* hasn't been set by the user we use the default value. If the row is hidden
* it has a value of zero.
*/
STATIC int32_t
_worksheet_size_row(lxw_worksheet *self, uint32_t row)
{
uint32_t pixels;
row++;
pixels = (uint32_t) (4.0 / 3.0 * self->default_row_height);
return pixels;
}
/*
* Calculate the vertices that define the position of a graphical object
* within the worksheet in pixels.
* +------------+------------+
* | A | B |
* +-----+------------+------------+
* | |(x1,y1) | |
* | 1 |(A1)._______|______ |
* | | | | |
* | | | | |
* +-----+----| BITMAP |-----+
* | | | | |
* | 2 | |______________. |
* | | | (B2)|
* | | | (x2,y2)|
* +---- +------------+------------+
*
* Example of an object that covers some of the area from cell A1 to cell B2.
* Based on the width and height of the object we need to calculate 8 vars:
*
* col_start, row_start, col_end, row_end, x1, y1, x2, y2.
*
* We also calculate the absolute x and y position of the top left vertex of
* the object. This is required for images:
*
* x_abs, y_abs
*
* The width and height of the cells that the object occupies can be variable
* and have to be taken into account.
*
* The values of col_start and row_start are passed in from the calling
* function. The values of col_end and row_end are calculated by subtracting
* the width and height of the object from the width and height of the
* underlying cells.
*/
STATIC void
_worksheet_position_object_pixels(lxw_worksheet *self,
lxw_image_options *image,
lxw_drawing_object *drawing_object)
{
uint32_t col_start; /* Column containing upper left corner. */
int32_t x1; /* Distance to left side of object. */
uint32_t row_start; /* Row containing top left corner. */
int32_t y1; /* Distance to top of object. */
uint32_t col_end; /* Column containing lower right corner. */
uint32_t x2; /* Distance to right side of object. */
uint32_t row_end; /* Row containing bottom right corner. */
uint32_t y2; /* Distance to bottom of object. */
int32_t width; /* Width of object frame. */
int32_t height; /* Height of object frame. */
uint32_t x_abs = 0; /* Abs. distance to left side of object. */
uint32_t y_abs = 0; /* Abs. distance to top side of object. */
uint32_t i;
col_start = image->col;
row_start = image->row;
x1 = image->x_offset;
y1 = image->y_offset;
width = image->width;
height = image->height;
/* Adjust start column for negative offsets. */
while (x1 < 0 && col_start > 0) {
x1 += _worksheet_size_col(self, col_start - 1);
col_start--;
}
/* Adjust start row for negative offsets. */
while (y1 < 0 && row_start > 0) {
y1 += _worksheet_size_row(self, row_start - 1);
row_start--;
}
/* Ensure that the image isn't shifted off the page at top left. */
if (x1 < 0)
x1 = 0;
if (y1 < 0)
y1 = 0;
/* Calculate the absolute x offset of the top-left vertex. */
if (self->col_size_changed) {
for (i = 0; i < col_start; i++)
x_abs += _worksheet_size_col(self, i);
}
else {
/* Optimization for when the column widths haven't changed. */
x_abs += self->default_col_pixels * col_start;
}
x_abs += x1;
/* Calculate the absolute y offset of the top-left vertex. */
/* Store the column change to allow optimizations. */
if (self->row_size_changed) {
for (i = 0; i < row_start; i++)
y_abs += _worksheet_size_row(self, i);
}
else {
/* Optimization for when the row heights haven"t changed. */
y_abs += self->default_row_pixels * row_start;
}
y_abs += y1;
/* Adjust start col for offsets that are greater than the col width. */
while (x1 >= _worksheet_size_col(self, col_start)) {
x1 -= _worksheet_size_col(self, col_start);
col_start++;
}
/* Adjust start row for offsets that are greater than the row height. */
while (y1 >= _worksheet_size_row(self, row_start)) {
y1 -= _worksheet_size_row(self, row_start);
row_start++;
}
/* Initialize end cell to the same as the start cell. */
col_end = col_start;
row_end = row_start;
width = width + x1;
height = height + y1;
/* Subtract the underlying cell widths to find the end cell. */
while (width >= _worksheet_size_col(self, col_end)) {
width -= _worksheet_size_col(self, col_end);
col_end++;
}
/* Subtract the underlying cell heights to find the end cell. */
while (height >= _worksheet_size_row(self, row_end)) {
height -= _worksheet_size_row(self, row_end);
row_end++;
}
/* The end vertices are whatever is left from the width and height. */
x2 = width;
y2 = height;
/* Add the dimensions to the drawing object. */
drawing_object->from.col = col_start;
drawing_object->from.row = row_start;
drawing_object->from.col_offset = x1;
drawing_object->from.row_offset = y1;
drawing_object->to.col = col_end;
drawing_object->to.row = row_end;
drawing_object->to.col_offset = x2;
drawing_object->to.row_offset = y2;
drawing_object->col_absolute = x_abs;
drawing_object->row_absolute = y_abs;
}
/*
* Calculate the vertices that define the position of a graphical object
* within the worksheet in EMUs. The vertices are expressed as English
* Metric Units (EMUs). There are 12,700 EMUs per point.
* Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel.
*/
STATIC void
_worksheet_position_object_emus(lxw_worksheet *self,
lxw_image_options *image,
lxw_drawing_object *drawing_object)
{
_worksheet_position_object_pixels(self, image, drawing_object);
/* Convert the pixel values to EMUs. See above. */
drawing_object->from.col_offset *= 9525;
drawing_object->from.row_offset *= 9525;
drawing_object->to.col_offset *= 9525;
drawing_object->to.row_offset *= 9525;
drawing_object->col_absolute *= 9525;
drawing_object->row_absolute *= 9525;
}
/*
* Set up image/drawings.
*/
void
_worksheet_prepare_image(lxw_worksheet *self,
uint16_t image_ref_id, uint16_t drawing_id,
lxw_image_options *image)
{
lxw_drawing_object *drawing_object;
lxw_rel_tuple *relationship;
double width;
double height;
char filename[FILENAME_LEN];
if (!self->drawing) {
self->drawing = _new_drawing();
self->drawing->embedded = LXW_TRUE;
RETURN_VOID_ON_MEM_ERROR(self->drawing);
relationship = calloc(1, sizeof(lxw_rel_tuple));
GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
relationship->type = lxw_strdup("/drawing");
GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
lxw_snprintf(filename, FILENAME_LEN, "../drawings/drawing%d.xml",
drawing_id);
relationship->target = lxw_strdup(filename);
GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
STAILQ_INSERT_TAIL(self->external_drawing_links, relationship,
list_pointers);
}
drawing_object = calloc(1, sizeof(lxw_drawing_object));
RETURN_VOID_ON_MEM_ERROR(drawing_object);
drawing_object->anchor_type = LXW_ANCHOR_TYPE_IMAGE;
drawing_object->edit_as = LXW_ANCHOR_EDIT_AS_ONE_CELL;
drawing_object->description = lxw_strdup(image->short_name);
/* Scale to user scale. */
width = image->width * image->x_scale;
height = image->height * image->y_scale;
/* Scale by non 96dpi resolutions. */
width *= 96.0 / image->x_dpi;
height *= 96.0 / image->y_dpi;
/* Convert to the nearest pixel. */
image->width = (uint32_t) (0.5 + width);
image->height = (uint32_t) (0.5 + height);
_worksheet_position_object_emus(self, image, drawing_object);
/* Convert from pixels to emus. */
drawing_object->width = image->width * 9525;
drawing_object->height = image->height * 9525;
_add_drawing_object(self->drawing, drawing_object);
relationship = calloc(1, sizeof(lxw_rel_tuple));
GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
relationship->type = lxw_strdup("/image");
GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
lxw_snprintf(filename, 32, "../media/image%d.png", image_ref_id);
relationship->target = lxw_strdup(filename);
GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
STAILQ_INSERT_TAIL(self->drawing_links, relationship, list_pointers);
return;
mem_error:
if (relationship) {
free(relationship->type);
free(relationship->target);
free(relationship->target_mode);
free(relationship);
}
}
/*****************************************************************************
*
* XML file assembly functions.
*
****************************************************************************/
/*
* Write out a number worksheet cell.
*/
STATIC void
_write_number_cell(lxw_worksheet *self, lxw_cell *cell)
{
char data[ATTR_32];
lxw_snprintf(data, ATTR_32, "%.16g", cell->u.number);
_xml_data_element(self->file, "v", data, NULL);
}
/*
* Write out a string worksheet cell.
*/
STATIC void
_write_string_cell(lxw_worksheet *self, lxw_cell *cell)
{
char data[ATTR_32];
lxw_snprintf(data, ATTR_32, "%d", cell->u.string_id);
_xml_data_element(self->file, "v", data, NULL);
}
/*
* Write out a formula worksheet cell with a numeric result.
*/
STATIC void
_write_formula_num_cell(lxw_worksheet *self, lxw_cell *cell)
{
char data[ATTR_32];
lxw_snprintf(data, ATTR_32, "%.16g", cell->formula_result);
_xml_data_element(self->file, "f", cell->u.string, NULL);
_xml_data_element(self->file, "v", data, NULL);
}
/*
* Write out an array formula worksheet cell with a numeric result.
*/
STATIC void
_write_array_formula_num_cell(lxw_worksheet *self, lxw_cell *cell)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char data[ATTR_32];
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("t", "array");
_PUSH_ATTRIBUTES_STR("ref", cell->user_data1);
lxw_snprintf(data, ATTR_32, "%.16g", cell->formula_result);
_xml_data_element(self->file, "f", cell->u.string, &attributes);
_xml_data_element(self->file, "v", data, NULL);
_FREE_ATTRIBUTES();
}
/*
* Write out an inline string.
*/
STATIC void
_write_inline_string_cell(lxw_worksheet *self, lxw_cell *cell)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char *string = cell->u.string;
_INIT_ATTRIBUTES();
/* Add attribute to preserve leading or trailing whitespace. */
if (isspace((unsigned char) string[0])
|| isspace((unsigned char) string[strlen(string) - 1]))
_PUSH_ATTRIBUTES_STR("xml:space", "preserve");
_xml_start_tag(self->file, "is", NULL);
_xml_data_element(self->file, "t", string, &attributes);
_xml_end_tag(self->file, "is");
_FREE_ATTRIBUTES();
}
/*
* Calculate the "spans" attribute of the <row> tag. This is an XLSX
* optimization and isn't strictly required. However, it makes comparing
* files easier.
*
* The span is the same for each block of 16 rows.
*/
STATIC void
_calculate_spans(struct lxw_row *row, char *span, int32_t *block_num)
{
lxw_col_t span_col_min = RB_MIN(lxw_table_cells, row->cells)->col_num;
lxw_col_t span_col_max = RB_MAX(lxw_table_cells, row->cells)->col_num;
lxw_col_t col_min;
lxw_col_t col_max;
*block_num = row->row_num / 16;
row = RB_NEXT(lxw_table_rows, root, row);
while (row && (int32_t) (row->row_num / 16) == *block_num) {
if (!RB_EMPTY(row->cells)) {
col_min = RB_MIN(lxw_table_cells, row->cells)->col_num;
col_max = RB_MAX(lxw_table_cells, row->cells)->col_num;
if (col_min < span_col_min)
span_col_min = col_min;
if (col_max > span_col_max)
span_col_max = col_max;
}
row = RB_NEXT(lxw_table_rows, root, row);
}
lxw_snprintf(span, MAX_CELL_RANGE_LENGTH,
"%d:%d", span_col_min + 1, span_col_max + 1);
}
/*
* Write out a generic worksheet cell.
*/
STATIC void
_write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char range[MAX_CELL_NAME_LENGTH] = { 0 };
lxw_row_t row_num = cell->row_num;
lxw_col_t col_num = cell->col_num;
int32_t index = 0;
lxw_rowcol_to_cell(range, row_num, col_num);
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("r", range);
if (cell->format) {
index = _get_xf_index(cell->format);
}
else if (row_format) {
index = _get_xf_index(row_format);
}
else if (col_num < self->col_formats_max && self->col_formats[col_num]) {
index = _get_xf_index(self->col_formats[col_num]);
}
if (index)
_PUSH_ATTRIBUTES_INT("s", index);
if (cell->type == NUMBER_CELL) {
_xml_start_tag(self->file, "c", &attributes);
_write_number_cell(self, cell);
_xml_end_tag(self->file, "c");
}
else if (cell->type == STRING_CELL) {
_PUSH_ATTRIBUTES_STR("t", "s");
_xml_start_tag(self->file, "c", &attributes);
_write_string_cell(self, cell);
_xml_end_tag(self->file, "c");
}
else if (cell->type == INLINE_STRING_CELL) {
_PUSH_ATTRIBUTES_STR("t", "inlineStr");
_xml_start_tag(self->file, "c", &attributes);
_write_inline_string_cell(self, cell);
_xml_end_tag(self->file, "c");
}
else if (cell->type == FORMULA_CELL) {
_xml_start_tag(self->file, "c", &attributes);
_write_formula_num_cell(self, cell);
_xml_end_tag(self->file, "c");
}
else if (cell->type == BLANK_CELL) {
_xml_empty_tag(self->file, "c", &attributes);
}
else if (cell->type == ARRAY_FORMULA_CELL) {
_xml_start_tag(self->file, "c", &attributes);
_write_array_formula_num_cell(self, cell);
_xml_end_tag(self->file, "c");
}
_FREE_ATTRIBUTES();
}
/*
* Write out the worksheet data as a series of rows and cells.
*/
STATIC void
_worksheet_write_rows(lxw_worksheet *self)
{
lxw_row *row;
lxw_cell *cell;
int32_t block_num = -1;
char spans[MAX_CELL_RANGE_LENGTH] = { 0 };
RB_FOREACH(row, lxw_table_rows, self->table) {
if (RB_EMPTY(row->cells)) {
/* Row contains no cells but has height, format or other data. */
/* Write a default span for default rows. */
if (self->default_row_set)
_write_row(self, row, "1:1");
else
_write_row(self, row, NULL);
}
else {
/* Row and cell data. */
if ((int32_t) row->row_num / 16 > block_num)
_calculate_spans(row, spans, &block_num);
_write_row(self, row, spans);
RB_FOREACH(cell, lxw_table_cells, row->cells) {
_write_cell(self, cell, row->format);
}
_xml_end_tag(self->file, "row");
}
}
}
/*
* Write out the worksheet data as a single row with cells. This method is
* used when memory optimization is on. A single row is written and the data
* array is reset. That way only one row of data is kept in memory at any one
* time. We don't write span data in the optimized case since it is optional.
*/
void
_worksheet_write_single_row(lxw_worksheet *self)
{
lxw_row *row = self->optimize_row;
lxw_col_t col;
/* skip row if it doesn't contain row formatting, cell data or a comment. */
if (!(row->row_changed || row->data_changed))
return;
/* Write the cells if the row contains data. */
if (!row->data_changed) {
/* Row data only. No cells. */
_write_row(self, row, NULL);
}
else {
/* Row and cell data. */
_write_row(self, row, NULL);
for (col = self->dim_colmin; col <= self->dim_colmax; col++) {
if (self->array[col]) {
_write_cell(self, self->array[col], row->format);
_free_cell(self->array[col]);
self->array[col] = NULL;
}
}
_xml_end_tag(self->file, "row");
}
/* Reset the row. */
row->height = LXW_DEF_ROW_HEIGHT;
row->format = NULL;
row->hidden = LXW_FALSE;
row->level = 0;
row->collapsed = LXW_FALSE;
row->data_changed = LXW_FALSE;
row->row_changed = LXW_FALSE;
}
/*
* Write the <col> 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 = _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;
}
/* Convert column width from user units to character width. */
if (width > 0) {
if (width < 1) {
width = (uint16_t) (((uint16_t)
(width * (max_digit_width + padding) + 0.5))
/ max_digit_width * 256.0) / 256.0;
}
else {
width = (uint16_t) (((uint16_t)
(width * max_digit_width + 0.5) + padding)
/ max_digit_width * 256.0) / 256.0;
}
}
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_INT("min", 1 + options->firstcol);
_PUSH_ATTRIBUTES_INT("max", 1 + options->lastcol);
_PUSH_ATTRIBUTES_DBL("width", width);
if (xf_index)
_PUSH_ATTRIBUTES_INT("style", xf_index);
if (options->hidden)
_PUSH_ATTRIBUTES_STR("hidden", "1");
if (has_custom_width)
_PUSH_ATTRIBUTES_STR("customWidth", "1");
if (options->level)
_PUSH_ATTRIBUTES_INT("outlineLevel", options->level);
if (options->collapsed)
_PUSH_ATTRIBUTES_STR("collapsed", "1");
_xml_empty_tag(self->file, "col", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <cols> element and <col> sub elements.
*/
STATIC void
_worksheet_write_cols(lxw_worksheet *self)
{
lxw_col_t col;
if (!self->col_size_changed)
return;
_xml_start_tag(self->file, "cols", NULL);
for (col = 0; col < self->col_options_max; col++) {
if (self->col_options[col])
_worksheet_write_col_info(self, self->col_options[col]);
}
_xml_end_tag(self->file, "cols");
}
/*
* Write the <mergeCell> element.
*/
STATIC void
_worksheet_write_merge_cell(lxw_worksheet *self,
lxw_merged_range *merged_range)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char ref[MAX_CELL_RANGE_LENGTH];
_INIT_ATTRIBUTES();
/* Convert the merge dimensions to a cell range. */
lxw_range(ref, merged_range->first_row, merged_range->first_col,
merged_range->last_row, merged_range->last_col);
_PUSH_ATTRIBUTES_STR("ref", ref);
_xml_empty_tag(self->file, "mergeCell", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <mergeCells> element.
*/
STATIC void
_worksheet_write_merge_cells(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
lxw_merged_range *merged_range;
if (self->merged_range_count) {
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_INT("count", self->merged_range_count);
_xml_start_tag(self->file, "mergeCells", &attributes);
STAILQ_FOREACH(merged_range, self->merged_ranges, list_pointers) {
_worksheet_write_merge_cell(self, merged_range);
}
_xml_end_tag(self->file, "mergeCells");
_FREE_ATTRIBUTES();
}
}
/*
* Write the <oddHeader> element.
*/
STATIC void
_worksheet_write_odd_header(lxw_worksheet *self)
{
_xml_data_element(self->file, "oddHeader", self->header, NULL);
}
/*
* Write the <oddFooter> element.
*/
STATIC void
_worksheet_write_odd_footer(lxw_worksheet *self)
{
_xml_data_element(self->file, "oddFooter", self->footer, NULL);
}
/*
* Write the <headerFooter> element.
*/
STATIC void
_worksheet_write_header_footer(lxw_worksheet *self)
{
if (!self->header_footer_changed)
return;
_xml_start_tag(self->file, "headerFooter", NULL);
if (self->header[0] != '\0')
_worksheet_write_odd_header(self);
if (self->footer[0] != '\0')
_worksheet_write_odd_footer(self);
_xml_end_tag(self->file, "headerFooter");
}
/*
* Write the <pageSetUpPr> element.
*/
STATIC void
_worksheet_write_page_set_up_pr(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
if (!self->fit_page)
return;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("fitToPage", "1");
_xml_empty_tag(self->file, "pageSetUpPr", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <tabColor> element.
*/
STATIC void
_worksheet_write_tab_color(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char rgb_str[ATTR_32];
if (self->tab_color == LXW_COLOR_UNSET)
return;
lxw_snprintf(rgb_str, ATTR_32, "FF%06X",
self->tab_color & LXW_COLOR_MASK);
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("rgb", rgb_str);
_xml_empty_tag(self->file, "tabColor", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <sheetPr> element for Sheet level properties.
*/
STATIC void
_worksheet_write_sheet_pr(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
if (!self->fit_page
&& !self->filter_on
&& self->tab_color == LXW_COLOR_UNSET
&& !self->outline_changed && !self->vba_codename) {
return;
}
_INIT_ATTRIBUTES();
if (self->vba_codename)
_PUSH_ATTRIBUTES_INT("codeName", self->vba_codename);
if (self->filter_on)
_PUSH_ATTRIBUTES_STR("filterMode", "1");
if (self->fit_page || self->tab_color != LXW_COLOR_UNSET
|| self->outline_changed) {
_xml_start_tag(self->file, "sheetPr", &attributes);
_worksheet_write_tab_color(self);
/* _worksheet_write_outline_pr(self); */
_worksheet_write_page_set_up_pr(self);
_xml_end_tag(self->file, "sheetPr");
}
else {
_xml_empty_tag(self->file, "sheetPr", &attributes);
}
_FREE_ATTRIBUTES();
}
/*
* Write the <brk> element.
*/
STATIC void
_worksheet_write_brk(lxw_worksheet *self, uint32_t id, uint32_t max)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_INT("id", id);
_PUSH_ATTRIBUTES_INT("max", max);
_PUSH_ATTRIBUTES_STR("man", "1");
_xml_empty_tag(self->file, "brk", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <rowBreaks> element.
*/
STATIC void
_worksheet_write_row_breaks(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
uint16_t count = 0;
uint16_t i;
if (self->hbreaks == NULL)
return;
while (self->hbreaks[count])
count++;
if (count > LXW_BREAKS_MAX)
count = LXW_BREAKS_MAX;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_INT("count", count);
_PUSH_ATTRIBUTES_INT("manualBreakCount", count);
_xml_start_tag(self->file, "rowBreaks", &attributes);
for (i = 0; i < count; i++)
_worksheet_write_brk(self, self->hbreaks[i], LXW_COL_MAX - 1);
_xml_end_tag(self->file, "rowBreaks");
_FREE_ATTRIBUTES();
}
/*
* Write the <colBreaks> element.
*/
STATIC void
_worksheet_write_col_breaks(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
uint16_t count = 0;
uint16_t i;
if (self->vbreaks == NULL)
return;
while (self->vbreaks[count])
count++;
if (count > LXW_BREAKS_MAX)
count = LXW_BREAKS_MAX;
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_INT("count", count);
_PUSH_ATTRIBUTES_INT("manualBreakCount", count);
_xml_start_tag(self->file, "colBreaks", &attributes);
for (i = 0; i < count; i++)
_worksheet_write_brk(self, self->vbreaks[i], LXW_ROW_MAX - 1);
_xml_end_tag(self->file, "colBreaks");
_FREE_ATTRIBUTES();
}
/*
* Write the <autoFilter> element.
*/
STATIC void
_worksheet_write_auto_filter(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char range[MAX_CELL_RANGE_LENGTH];
if (!self->autofilter.in_use)
return;
lxw_range(range,
self->autofilter.first_row,
self->autofilter.first_col,
self->autofilter.last_row, self->autofilter.last_col);
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("ref", range);
_xml_empty_tag(self->file, "autoFilter", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <hyperlink> element for external links.
*/
STATIC void
_worksheet_write_hyperlink_external(lxw_worksheet *self, lxw_row_t row_num,
lxw_col_t col_num, const char *location,
const char *tooltip, uint16_t id)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char ref[MAX_CELL_NAME_LENGTH];
char r_id[MAX_ATTRIBUTE_LENGTH];
lxw_rowcol_to_cell(ref, row_num, col_num);
lxw_snprintf(r_id, ATTR_32, "rId%d", id);
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("ref", ref);
_PUSH_ATTRIBUTES_STR("r:id", r_id);
if (location)
_PUSH_ATTRIBUTES_STR("location", location);
if (tooltip)
_PUSH_ATTRIBUTES_STR("tooltip", tooltip);
_xml_empty_tag(self->file, "hyperlink", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Write the <hyperlink> element for internal links.
*/
STATIC void
_worksheet_write_hyperlink_internal(lxw_worksheet *self, lxw_row_t row_num,
lxw_col_t col_num, const char *location,
const char *display, const char *tooltip)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
char ref[MAX_CELL_NAME_LENGTH];
lxw_rowcol_to_cell(ref, row_num, col_num);
_INIT_ATTRIBUTES();
_PUSH_ATTRIBUTES_STR("ref", ref);
if (location)
_PUSH_ATTRIBUTES_STR("location", location);
if (tooltip)
_PUSH_ATTRIBUTES_STR("tooltip", tooltip);
if (display)
_PUSH_ATTRIBUTES_STR("display", display);
_xml_empty_tag(self->file, "hyperlink", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Process any stored hyperlinks in row/col order and write the <hyperlinks>
* element. The attributes are different for internal and external links.
*/
STATIC void
_worksheet_write_hyperlinks(lxw_worksheet *self)
{
lxw_row *row;
lxw_cell *link;
lxw_rel_tuple *relationship;
if (RB_EMPTY(self->hyperlinks))
return;
/* Write the hyperlink elements. */
_xml_start_tag(self->file, "hyperlinks", NULL);
RB_FOREACH(row, lxw_table_rows, self->hyperlinks) {
RB_FOREACH(link, lxw_table_cells, row->cells) {
if (link->type == HYPERLINK_URL
|| link->type == HYPERLINK_EXTERNAL) {
self->rel_count++;
relationship = calloc(1, sizeof(lxw_rel_tuple));
GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error);
relationship->type = lxw_strdup("/hyperlink");
GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error);
relationship->target = lxw_strdup(link->u.string);
GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error);
relationship->target_mode = lxw_strdup("External");
GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error);
STAILQ_INSERT_TAIL(self->external_hyperlinks, relationship,
list_pointers);
_worksheet_write_hyperlink_external(self, link->row_num,
link->col_num,
link->user_data1,
link->user_data2,
self->rel_count);
}
if (link->type == HYPERLINK_INTERNAL) {
_worksheet_write_hyperlink_internal(self, link->row_num,
link->col_num,
link->u.string,
link->user_data1,
link->user_data2);
}
}
}
_xml_end_tag(self->file, "hyperlinks");
return;
mem_error:
if (relationship) {
free(relationship->type);
free(relationship->target);
free(relationship->target_mode);
free(relationship);
}
_xml_end_tag(self->file, "hyperlinks");
}
/*
* Write the <sheetProtection> element.
*/
STATIC void
_worksheet_write_sheet_protection(lxw_worksheet *self)
{
struct xml_attribute_list attributes;
struct xml_attribute *attribute;
struct lxw_protection *protect = &self->protection;
if (!protect->is_configured)
return;
_INIT_ATTRIBUTES();
if (*protect->hash)
_PUSH_ATTRIBUTES_STR("password", protect->hash);
if (!protect->no_sheet)
_PUSH_ATTRIBUTES_INT("sheet", 1);
if (protect->content)
_PUSH_ATTRIBUTES_INT("content", 1);
if (!protect->objects)
_PUSH_ATTRIBUTES_INT("objects", 1);
if (!protect->scenarios)
_PUSH_ATTRIBUTES_INT("scenarios", 1);
if (protect->format_cells)
_PUSH_ATTRIBUTES_INT("formatCells", 0);
if (protect->format_columns)
_PUSH_ATTRIBUTES_INT("formatColumns", 0);
if (protect->format_rows)
_PUSH_ATTRIBUTES_INT("formatRows", 0);
if (protect->insert_columns)
_PUSH_ATTRIBUTES_INT("insertColumns", 0);
if (protect->insert_rows)
_PUSH_ATTRIBUTES_INT("insertRows", 0);
if (protect->insert_hyperlinks)
_PUSH_ATTRIBUTES_INT("insertHyperlinks", 0);
if (protect->delete_columns)
_PUSH_ATTRIBUTES_INT("deleteColumns", 0);
if (protect->delete_rows)
_PUSH_ATTRIBUTES_INT("deleteRows", 0);
if (protect->no_select_locked_cells)
_PUSH_ATTRIBUTES_INT("selectLockedCells", 1);
if (protect->sort)
_PUSH_ATTRIBUTES_INT("sort", 0);
if (protect->autofilter)
_PUSH_ATTRIBUTES_INT("autoFilter", 0);
if (protect->pivot_tables)
_PUSH_ATTRIBUTES_INT("pivotTables", 0);
if (protect->no_select_unlocked_cells)
_PUSH_ATTRIBUTES_INT("selectUnlockedCells", 1);
_xml_empty_tag(self->file, "sheetProtection", &attributes);
_FREE_ATTRIBUTES();
}
/*
* Assemble and write the XML file.
*/
void
_worksheet_assemble_xml_file(lxw_worksheet *self)
{
/* Write the XML declaration. */
_worksheet_xml_declaration(self);
/* Write the worksheet element. */
_worksheet_write_worksheet(self);
/* Write the worksheet properties. */
_worksheet_write_sheet_pr(self);
/* Write the worksheet dimensions. */
_worksheet_write_dimension(self);
/* Write the sheet view properties. */
_worksheet_write_sheet_views(self);
/* Write the sheet format properties. */
_worksheet_write_sheet_format_pr(self);
/* Write the sheet column info. */
_worksheet_write_cols(self);
/* Write the sheetData element. */
if (!self->optimize)
_worksheet_write_sheet_data(self);
else
_worksheet_write_optimized_sheet_data(self);
/* Write the sheetProtection element. */
_worksheet_write_sheet_protection(self);
/* Write the autoFilter element. */
_worksheet_write_auto_filter(self);
/* Write the mergeCells element. */
_worksheet_write_merge_cells(self);
/* Write the hyperlink element. */
_worksheet_write_hyperlinks(self);
/* Write the printOptions element. */
_worksheet_write_print_options(self);
/* Write the worksheet page_margins. */
_worksheet_write_page_margins(self);
/* Write the worksheet page setup. */
_worksheet_write_page_setup(self);
/* Write the headerFooter element. */
_worksheet_write_header_footer(self);
/* Write the rowBreaks element. */
_worksheet_write_row_breaks(self);
/* Write the colBreaks element. */
_worksheet_write_col_breaks(self);
/* Close the worksheet tag. */
_xml_end_tag(self->file, "worksheet");
}
/*****************************************************************************
*
* Public functions.
*
****************************************************************************/
/*
* Write a number to a cell in Excel.
*/
int8_t
worksheet_write_number(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, double value, lxw_format *format)
{
lxw_cell *cell;
int8_t err;
err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
if (err)
return err;
cell = _new_number_cell(row_num, col_num, value, format);
_insert_cell(self, row_num, col_num, cell);
return 0;
}
/*
* Write a string to an Excel file.
*/
int8_t
worksheet_write_string(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, const char *string,
lxw_format *format)
{
lxw_cell *cell;
int32_t string_id;
char *string_copy;
int8_t err;
if (!string || !*string) {
/* Treat a NULL or empty string with formatting as a blank cell. */
/* Null strings without formats should be ignored. */
if (format)
return worksheet_write_blank(self, row_num, col_num, format);
else
return -LXW_ERROR_WORKSHEET_NULL_STRING_IGNORED;
}
err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
if (err)
return err;
if (strlen(string) > LXW_STR_MAX)
return -LXW_ERROR_WORKSHEET_MAX_STRING_LENGTH_EXCEEDED;
if (!self->optimize) {
/* Get the SST string ID for the string. */
string_id = _get_sst_index(self->sst, string);
if (string_id < 0)
return -LXW_ERROR_WORKSHEET_STRING_HASH_NOT_FOUND;
cell = _new_string_cell(row_num, col_num, string_id, format);
}
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")) {
string_copy = _escape_control_characters(string);
}
else {
string_copy = lxw_strdup(string);
}
cell = _new_inline_string_cell(row_num, col_num, string_copy, format);
}
_insert_cell(self, row_num, col_num, cell);
return 0;
}
/*
* Write a formula with a numerical result to a cell in Excel.
*/
int8_t
worksheet_write_formula_num(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num,
const char *formula,
lxw_format *format, double result)
{
lxw_cell *cell;
char *formula_copy;
int8_t err;
if (!formula)
return -LXW_ERROR_WORKSHEET_NULL_STRING_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->formula_result = result;
_insert_cell(self, row_num, col_num, cell);
return 0;
}
/*
* Write a formula with a default result to a cell in Excel .
*/
int8_t
worksheet_write_formula(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, const char *formula,
lxw_format *format)
{
return worksheet_write_formula_num(self, row_num, col_num, formula,
format, 0);
}
/*
* Write a formula with a numerical result to a cell in Excel.
*/
int8_t
worksheet_write_array_formula_num(lxw_worksheet *self,
lxw_row_t first_row,
lxw_col_t first_col,
lxw_row_t last_row,
lxw_col_t last_col,
const char *formula,
lxw_format *format, double result)
{
lxw_cell *cell;
lxw_row_t tmp_row;
lxw_col_t tmp_col;
char *formula_copy;
char *range;
int8_t err;
/* 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;
}
if (!formula)
return -LXW_ERROR_WORKSHEET_NULL_STRING_IGNORED;
/* Check that column number is valid and store the max value */
err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
if (err)
return err;
/* Define the array range. */
range = calloc(1, MAX_CELL_RANGE_LENGTH);
RETURN_ON_MEM_ERROR(range, -1);
if (first_row == last_row && first_col == last_col)
lxw_rowcol_to_cell(range, first_row, last_col);
else
lxw_range(range, first_row, first_col, last_row, last_col);
/* Copy and trip leading "{=" from formula. */
if (formula[0] == '{')
if (formula[1] == '=')
formula_copy = lxw_strdup(formula + 2);
else
formula_copy = lxw_strdup(formula + 1);
else
formula_copy = lxw_strdup(formula);
/* Strip trailing "}" from formula. */
if (formula_copy[strlen(formula_copy) - 1] == '}')
formula_copy[strlen(formula_copy) - 1] = '\0';
/* Create a new array formula cell object. */
cell = _new_array_formula_cell(first_row, first_col,
formula_copy, range, format);
cell->formula_result = result;
_insert_cell(self, first_row, first_col, cell);
/* Pad out the rest of the area with formatted zeroes. */
if (!self->optimize) {
for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) {
for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) {
if (tmp_row == first_row && tmp_col == first_col)
continue;
worksheet_write_number(self, tmp_row, tmp_col, 0, format);
}
}
}
return 0;
}
/*
* Write an array formula with a default result to a cell in Excel .
*/
int8_t
worksheet_write_array_formula(lxw_worksheet *self,
lxw_row_t first_row,
lxw_col_t first_col,
lxw_row_t last_row,
lxw_col_t last_col,
const char *formula, lxw_format *format)
{
return worksheet_write_array_formula_num(self, first_row, first_col,
last_row, last_col, formula,
format, 0);
}
/*
* Write a blank cell with a format to a cell in Excel.
*/
int8_t
worksheet_write_blank(lxw_worksheet *self,
lxw_row_t row_num, lxw_col_t col_num,
lxw_format *format)
{
lxw_cell *cell;
int8_t err;
/* Blank cells without formatting are ignored by Excel. */
if (!format)
return 0;
err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
if (err)
return err;
cell = _new_blank_cell(row_num, col_num, format);
_insert_cell(self, row_num, col_num, cell);
return 0;
}
/*
* Write a date and or time to a cell in Excel.
*/
int8_t
worksheet_write_datetime(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, lxw_datetime *datetime,
lxw_format *format)
{
lxw_cell *cell;
double excel_date;
int8_t err;
err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
if (err)
return err;
excel_date = _datetime_to_excel_date(datetime, EPOCH_1900);
cell = _new_number_cell(row_num, col_num, excel_date, format);
_insert_cell(self, row_num, col_num, cell);
return 0;
}
/*
* Write a hyperlink/url to an Excel file.
*/
int8_t
worksheet_write_url_opt(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, const char *url,
lxw_format *format, const char *string,
const char *tooltip)
{
lxw_cell *link;
char *string_copy = NULL;
char *url_copy = NULL;
char *url_external = NULL;
char *url_string = NULL;
char *tooltip_copy = NULL;
char *found_string;
int8_t err;
size_t string_size;
size_t i;
enum cell_types link_type = HYPERLINK_URL;
if (!url || !*url)
return -LXW_ERROR_WORKSHEET_NULL_STRING_IGNORED;
/* Check the Excel limit of URLS per worksheet. */
if (self->hlink_count > LXW_MAX_NUMBER_URLS)
return -LXW_ERROR_WORKSHEET_MAX_NUMBER_URLS_EXCEEDED;
err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE);
if (err)
return err;
/* Set the URI scheme from internal links. */
found_string = strstr(url, "internal:");
if (found_string)
link_type = HYPERLINK_INTERNAL;
/* Set the URI scheme from external links. */
found_string = strstr(url, "external:");
if (found_string)
link_type = HYPERLINK_EXTERNAL;
if (string) {
string_copy = lxw_strdup(string);
GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error);
}
else {
if (link_type == HYPERLINK_URL) {
/* Strip the mailto header. */
found_string = strstr(url, "mailto:");
if (found_string)
string_copy = lxw_strdup(url + sizeof("mailto"));
else
string_copy = lxw_strdup(url);
}
else {
string_copy = lxw_strdup(url + sizeof("__ternal"));
}
GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error);
}
if (url) {
if (link_type == HYPERLINK_URL)
url_copy = lxw_strdup(url);
else
url_copy = lxw_strdup(url + sizeof("__ternal"));
GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
}
if (tooltip) {
tooltip_copy = lxw_strdup(tooltip);
GOTO_LABEL_ON_MEM_ERROR(tooltip_copy, mem_error);
}
if (link_type == HYPERLINK_INTERNAL) {
url_string = lxw_strdup(string_copy);
GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error);
}
/* Escape the URL. */
if (link_type == HYPERLINK_URL && strlen(url_copy) >= 3) {
uint8_t not_escaped = 1;
/* First check if the URL is already escaped by the user. */
for (i = 0; i <= strlen(url_copy) - 3; i++) {
if (url_copy[i] == '%' && isxdigit(url_copy[i + 1])
&& isxdigit(url_copy[i + 2])) {
not_escaped = 0;
break;
}
}
if (not_escaped) {
url_external = calloc(1, strlen(url_copy) * 3 + 1);
GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error);
for (i = 0; i <= strlen(url_copy); i++) {
switch (url_copy[i]) {
case (' '):
case ('"'):
case ('%'):
case ('<'):
case ('>'):
case ('['):
case (']'):
case ('`'):
case ('^'):
case ('{'):
case ('}'):
sprintf(url_external + strlen(url_external), "%%%2x",
url_copy[i]);
break;
default:
url_external[strlen(url_external)] = url_copy[i];
}
}
free(url_copy);
url_copy = lxw_strdup(url_external);
GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
free(url_external);
url_external = NULL;
}
}
if (link_type == HYPERLINK_EXTERNAL) {
/* External Workbook links need to be modified into the right format.
* The URL will look something like "c:\temp\file.xlsx#Sheet!A1".
* We need the part to the left of the # as the URL and the part to
* the right as the "location" string (if it exists).
*/
/* For external links change the dir separator from Unix to DOS. */
for (i = 0; i <= strlen(url_copy); i++)
if (url_copy[i] == '/')
url_copy[i] = '\\';
for (i = 0; i <= strlen(string_copy); i++)
if (string_copy[i] == '/')
string_copy[i] = '\\';
found_string = strchr(url_copy, '#');
if (found_string) {
url_string = lxw_strdup(found_string + 1);
GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error);
*found_string = '\0';
}
/* Look for Windows style "C:/" link or Windows share "\\" link. */
found_string = strchr(url_copy, ':');
if (!found_string)
found_string = strstr(url_copy, "\\\\");
if (found_string) {
/* Add the file:/// URI to the url if non-local. */
string_size = sizeof("file:///") + strlen(url_copy);
url_external = calloc(1, string_size);
GOTO_LABEL_ON_MEM_ERROR(url_external, mem_error);
lxw_snprintf(url_external, string_size, "file:///%s", url_copy);
}
/* Convert a ./dir/file.xlsx link to dir/file.xlsx. */
found_string = strstr(url_copy, ".\\");
if (found_string == url_copy)
memmove(url_copy, url_copy + 2, strlen(url_copy) - 1);
if (url_external) {
free(url_copy);
url_copy = lxw_strdup(url_external);
GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error);
free(url_external);
url_external = NULL;
}
}
/* Excel limits escaped URL to 255 characters. */
if (strlen(url_copy) > 255)
goto mem_error;
err = worksheet_write_string(self, row_num, col_num, string_copy, format);
if (err)
goto mem_error;
link = _new_hyperlink_cell(row_num, col_num, link_type, url_copy,
url_string, tooltip_copy);
GOTO_LABEL_ON_MEM_ERROR(link, mem_error);
_insert_hyperlink(self, row_num, col_num, link);
free(string_copy);
self->hlink_count++;
return 0;
mem_error:
free(string_copy);
free(url_copy);
free(url_external);
free(url_string);
free(tooltip_copy);
return -LXW_ERROR_WORKSHEET_MEMORY_ERROR;
}
/*
* Write a hyperlink/url to an Excel file.
*/
int8_t
worksheet_write_url(lxw_worksheet *self,
lxw_row_t row_num,
lxw_col_t col_num, const char *url, lxw_format *format)
{
return worksheet_write_url_opt(self, row_num, col_num, url, format, NULL,
NULL);
}
/*
* Set the properties of a single column or a range of columns.
*/
int8_t
worksheet_set_column(lxw_worksheet *self,
lxw_col_t firstcol,
lxw_col_t lastcol,
double width,
lxw_format *format, lxw_row_col_options *user_options)
{
lxw_col_options *copied_options;
uint8_t ignore_row = LXW_TRUE;
uint8_t ignore_col = LXW_TRUE;
uint8_t hidden = LXW_FALSE;
uint8_t level = 0;
uint8_t collapsed = LXW_FALSE;
lxw_col_t col;
int8_t err;
if (user_options) {
hidden = user_options->hidden;
level = user_options->level;
collapsed = user_options->collapsed;
}
/* Ensure second col is larger than first. */
if (firstcol > lastcol) {
lxw_col_t tmp = firstcol;
firstcol = lastcol;
lastcol = tmp;
}
/* Ensure that the cols are valid and store max and min values.
* NOTE: The check shouldn't modify the row dimensions and should only
* modify the column dimensions in certain cases. */
if (format != NULL || (width != LXW_DEF_COL_WIDTH && hidden))
ignore_col = LXW_FALSE;
err = _check_dimensions(self, 0, firstcol, ignore_row, ignore_col);
if (!err)
err = _check_dimensions(self, 0, lastcol, ignore_row, ignore_col);
if (err)
return err;
/* Resize the col_options array if required. */
if (firstcol >= self->col_options_max) {
lxw_col_t col;
lxw_col_t old_size = self->col_options_max;
lxw_col_t new_size = _next_power_of_two(firstcol + 1);
lxw_col_options **new_ptr = realloc(self->col_options,
new_size *
sizeof(lxw_col_options *));
if (new_ptr) {
for (col = old_size; col < new_size; col++)
new_ptr[col] = NULL;
self->col_options = new_ptr;
self->col_options_max = new_size;
}
else {
return -LXW_ERROR_WORKSHEET_MEMORY_ERROR;
}
}
/* Resize the col_formats array if required. */
if (lastcol >= self->col_formats_max) {
lxw_col_t col;
lxw_col_t old_size = self->col_formats_max;
lxw_col_t new_size = _next_power_of_two(lastcol + 1);
lxw_format **new_ptr = realloc(self->col_formats,
new_size * sizeof(lxw_format *));
if (new_ptr) {
for (col = old_size; col < new_size; col++)
new_ptr[col] = NULL;
self->col_formats = new_ptr;
self->col_formats_max = new_size;
}
else {
return -LXW_ERROR_WORKSHEET_MEMORY_ERROR;
}
}
copied_options = calloc(1, sizeof(lxw_col_options));
RETURN_ON_MEM_ERROR(copied_options, -LXW_ERROR_WORKSHEET_MEMORY_ERROR);
/* Store the column option based on the first column. */
copied_options->firstcol = firstcol;
copied_options->lastcol = lastcol;
copied_options->width = width;
copied_options->format = format;
copied_options->hidden = hidden;
copied_options->level = level;
copied_options->collapsed = collapsed;
self->col_options[firstcol] = copied_options;
/* Store the column formats for use when writing cell data. */
for (col = firstcol; col <= lastcol; col++) {
self->col_formats[col] = format;
}
/* Store the column change to allow optimizations. */
self->col_size_changed = LXW_TRUE;
return 0;
}
/*
* Set the properties of a row.
*/
int8_t
worksheet_set_row(lxw_worksheet *self,
lxw_row_t row_num,
double height,
lxw_format *format, lxw_row_col_options *user_options)
{
lxw_col_t min_col;
uint8_t hidden = LXW_FALSE;
uint8_t level = 0;
uint8_t collapsed = LXW_FALSE;
lxw_row *row;
int8_t err;
if (user_options) {
hidden = user_options->hidden;
level = user_options->level;
collapsed = user_options->collapsed;
}
/* Use minimum col in _check_dimensions(). */
if (self->dim_colmin != LXW_COL_MAX)
min_col = self->dim_colmin;
else
min_col = 0;
err = _check_dimensions(self, row_num, min_col, LXW_FALSE, LXW_FALSE);
if (err)
return err;
/* If the height is 0 the row is hidden and the height is the default. */
if (height == 0) {
hidden = LXW_TRUE;
height = self->default_row_height;
}
row = _get_row(self, row_num);
row->height = height;
row->format = format;
row->hidden = hidden;
row->level = level;
row->collapsed = collapsed;
row->row_changed = LXW_TRUE;
if (height != self->default_row_height)
row->height_changed = LXW_TRUE;
return 0;
}
/*
* Merge a range of cells. The first cell should contain the data and the others
* should be blank. All cells should contain the same format.
*/
uint8_t
worksheet_merge_range(lxw_worksheet *self, lxw_row_t first_row,
lxw_col_t first_col, lxw_row_t last_row,
lxw_col_t last_col, const char *string,
lxw_format *format)
{
lxw_merged_range *merged_range;
lxw_row_t tmp_row;
lxw_col_t tmp_col;
int8_t err;
/* Excel doesn't allow a single cell to be merged */
if (first_row == last_row && first_col == last_col)
return 1;
/* 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 column number is valid and store the max value */
err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
if (err)
return err;
/* Store the merge range. */
merged_range = calloc(1, sizeof(lxw_merged_range));
RETURN_ON_MEM_ERROR(merged_range, 2);
merged_range->first_row = first_row;
merged_range->first_col = first_col;
merged_range->last_row = last_row;
merged_range->last_col = last_col;
STAILQ_INSERT_TAIL(self->merged_ranges, merged_range, list_pointers);
self->merged_range_count++;
/* Write the first cell */
worksheet_write_string(self, first_row, first_col, string, format);
/* Pad out the rest of the area with formatted blank cells. */
for (tmp_row = first_row; tmp_row <= last_row; tmp_row++) {
for (tmp_col = first_col; tmp_col <= last_col; tmp_col++) {
if (tmp_row == first_row && tmp_col == first_col)
continue;
worksheet_write_blank(self, tmp_row, tmp_col, format);
}
}
return 0;
}
/*
* Set the autofilter area in the worksheet.
*/
uint8_t
worksheet_autofilter(lxw_worksheet *self, lxw_row_t first_row,
lxw_col_t first_col, lxw_row_t last_row,
lxw_col_t last_col)
{
lxw_row_t tmp_row;
lxw_col_t tmp_col;
int8_t err;
/* Excel doesn't allow a single cell to be merged */
if (first_row == last_row && first_col == last_col)
return 1;
/* 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 column number is valid and store the max value */
err = _check_dimensions(self, last_row, last_col, LXW_FALSE, LXW_FALSE);
if (err)
return err;
self->autofilter.in_use = LXW_TRUE;
self->autofilter.first_row = first_row;
self->autofilter.first_col = first_col;
self->autofilter.last_row = last_row;
self->autofilter.last_col = last_col;
return 0;
}
/*
* Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
* highlighted.
*/
void
worksheet_select(lxw_worksheet *self)
{
self->selected = LXW_TRUE;
/* Selected worksheet can't be hidden. */
self->hidden = LXW_FALSE;
}
/*
* Set this worksheet as the active worksheet, i.e. the worksheet that is
* displayed when the workbook is opened. Also set it as selected.
*/
void
worksheet_activate(lxw_worksheet *self)
{
self->selected = LXW_TRUE;
self->active = LXW_TRUE;
/* Active worksheet can't be hidden. */
self->hidden = LXW_FALSE;
*self->active_sheet = self->index;
}
/*
* Set this worksheet as the first visible sheet. This is necessary
* when there are a large number of worksheets and the activated
* worksheet is not visible on the screen.
*/
void
worksheet_set_first_sheet(lxw_worksheet *self)
{
/* Active worksheet can't be hidden. */
self->hidden = LXW_FALSE;
*self->first_sheet = self->index;
}
/*
* Hide this worksheet.
*/
void
worksheet_hide(lxw_worksheet *self)
{
self->hidden = LXW_TRUE;
/* A hidden worksheet shouldn't be active or selected. */
self->selected = LXW_FALSE;
/* If this is active_sheet or first_sheet reset the workbook value. */
if (*self->first_sheet == self->index)
*self->first_sheet = 0;
if (*self->active_sheet == self->index)
*self->active_sheet = 0;
}
/*
* Set which cell or cells are selected in a worksheet.
*/
void
worksheet_set_selection(lxw_worksheet *self,
lxw_row_t first_row, lxw_col_t first_col,
lxw_row_t last_row, lxw_col_t last_col)
{
lxw_selection *selection;
lxw_row_t tmp_row;
lxw_col_t tmp_col;
char active_cell[MAX_CELL_RANGE_LENGTH];
char sqref[MAX_CELL_RANGE_LENGTH];
/* Only allow selection to be set once to avoid freeing/re-creating it. */
if (!STAILQ_EMPTY(self->selections))
return;
/* Excel doesn't set a selection for cell A1 since it is the default. */
if (first_row == 0 && first_col == 0 && last_row == 0 && last_col == 0)
return;
selection = calloc(1, sizeof(lxw_selection));
RETURN_VOID_ON_MEM_ERROR(selection);
/* Set the cell range selection. Do this before swapping max/min to */
/* allow the selection direction to be reversed. */
lxw_rowcol_to_cell(active_cell, first_row, first_col);
/* Swap last row/col for first row/col if necessary. */
if (first_row > last_row) {
tmp_row = first_row;
first_row = last_row;
last_row = tmp_row;
}
if (first_col > last_col) {
tmp_col = first_col;
first_col = last_col;
last_col = tmp_col;
}
/* If the first and last cell are the same write a single cell. */
if ((first_row == last_row) && (first_col == last_col))
lxw_rowcol_to_cell(sqref, first_row, first_col);
else
lxw_range(sqref, first_row, first_col, last_row, last_col);
strcpy(selection->pane, "");
strcpy(selection->active_cell, active_cell);
strcpy(selection->sqref, sqref);
STAILQ_INSERT_TAIL(self->selections, selection, list_pointers);
}
/*
* Set panes and mark them as frozen. With extra options.
*/
void
worksheet_freeze_panes_opt(lxw_worksheet *self,
lxw_row_t first_row, lxw_col_t first_col,
lxw_row_t top_row, lxw_col_t left_col,
uint8_t type)
{
self->panes.first_row = first_row;
self->panes.first_col = first_col;
self->panes.top_row = top_row;
self->panes.left_col = left_col;
self->panes.x_split = 0.0;
self->panes.y_split = 0.0;
if (type)
self->panes.type = FREEZE_SPLIT_PANES;
else
self->panes.type = FREEZE_PANES;
}
/*
* Set panes and mark them as frozen.
*/
void
worksheet_freeze_panes(lxw_worksheet *self,
lxw_row_t first_row, lxw_col_t first_col)
{
worksheet_freeze_panes_opt(self, first_row, first_col,
first_row, first_col, 0);
}
/*
* Set panes and mark them as split.With extra options.
*/
void
worksheet_split_panes_opt(lxw_worksheet *self,
double y_split, double x_split,
lxw_row_t top_row, lxw_col_t left_col)
{
self->panes.first_row = 0;
self->panes.first_col = 0;
self->panes.top_row = top_row;
self->panes.left_col = left_col;
self->panes.x_split = x_split;
self->panes.y_split = y_split;
self->panes.type = SPLIT_PANES;
}
/*
* Set panes and mark them as split.
*/
void
worksheet_split_panes(lxw_worksheet *self, double y_split, double x_split)
{
worksheet_split_panes_opt(self, y_split, x_split, 0, 0);
}
/*
* Set the page orientation as portrait.
*/
void
worksheet_set_portrait(lxw_worksheet *self)
{
self->orientation = LXW_PORTRAIT;
self->page_setup_changed = LXW_TRUE;
}
/*
* Set the page orientation as landscape.
*/
void
worksheet_set_landscape(lxw_worksheet *self)
{
self->orientation = LXW_LANDSCAPE;
self->page_setup_changed = LXW_TRUE;
}
/*
* Set the page view mode for Mac Excel.
*/
void
worksheet_set_page_view(lxw_worksheet *self)
{
self->page_view = LXW_TRUE;
}
/*
* Set the paper type. Example. 1 = US Letter, 9 = A4
*/
void
worksheet_set_paper(lxw_worksheet *self, uint8_t paper_size)
{
self->paper_size = paper_size;
self->page_setup_changed = LXW_TRUE;
}
/*
* Set the order in which pages are printed.
*/
void
worksheet_print_across(lxw_worksheet *self)
{
self->page_order = LXW_PRINT_ACROSS;
self->page_setup_changed = LXW_TRUE;
}
/*
* Set all the page margins in inches.
*/
void
worksheet_set_margins(lxw_worksheet *self, double left, double right,
double top, double bottom)
{
if (left >= 0)
self->margin_left = left;
if (right >= 0)
self->margin_right = right;
if (top >= 0)
self->margin_top = top;
if (bottom >= 0)
self->margin_bottom = bottom;
}
/*
* Set the page header caption and options.
*/
uint8_t
worksheet_set_header_opt(lxw_worksheet *self, char *string,
lxw_header_footer_options *options)
{
if (options) {
if (options->margin > 0)
self->margin_header = options->margin;
}
if (!string)
return 1;
if (strlen(string) >= LXW_HEADER_FOOTER_MAX)
return 1;
strcpy(self->header, string);
self->header_footer_changed = 1;
return 0;
}
/*
* Set the page footer caption and options.
*/
uint8_t
worksheet_set_footer_opt(lxw_worksheet *self, char *string,
lxw_header_footer_options *options)
{
if (options) {
if (options->margin > 0)
self->margin_footer = options->margin;
}
if (!string)
return 1;
if (strlen(string) >= LXW_HEADER_FOOTER_MAX)
return 1;
strcpy(self->footer, string);
self->header_footer_changed = 1;
return 0;
}
/*
* Set the page header caption.
*/
uint8_t
worksheet_set_header(lxw_worksheet *self, char *string)
{
return worksheet_set_header_opt(self, string, NULL);
}
/*
* Set the page footer caption.
*/
uint8_t
worksheet_set_footer(lxw_worksheet *self, char *string)
{
return worksheet_set_footer_opt(self, string, NULL);
}
/*
* Set the option to show/hide gridlines on the screen and the printed page.
*/
void
worksheet_gridlines(lxw_worksheet *self, uint8_t option)
{
if (option == LXW_HIDE_ALL_GRIDLINES) {
self->print_gridlines = 0;
self->screen_gridlines = 0;
}
if (option & LXW_SHOW_SCREEN_GRIDLINES) {
self->screen_gridlines = 1;
}
if (option & LXW_SHOW_PRINT_GRIDLINES) {
self->print_gridlines = 1;
self->print_options_changed = 1;
}
}
/*
* Center the page horizontally.
*/
void
worksheet_center_horizontally(lxw_worksheet *self)
{
self->print_options_changed = 1;
self->hcenter = 1;
}
/*
* Center the page horizontally.
*/
void
worksheet_center_vertically(lxw_worksheet *self)
{
self->print_options_changed = 1;
self->vcenter = 1;
}
/*
* Set the option to print the row and column headers on the printed page.
*/
void
worksheet_print_row_col_headers(lxw_worksheet *self)
{
self->print_headers = 1;
self->print_options_changed = 1;
}
/*
* Set the rows to repeat at the top of each printed page.
*/
uint8_t
worksheet_repeat_rows(lxw_worksheet *self, lxw_row_t first_row,
lxw_row_t last_row)
{
lxw_row_t tmp_row;
if (first_row > last_row) {
tmp_row = last_row;
last_row = first_row;
first_row = tmp_row;
}
if (_check_dimensions(self, last_row, 0, LXW_IGNORE, LXW_IGNORE))
return 1;
self->repeat_rows.in_use = LXW_TRUE;
self->repeat_rows.first_row = first_row;
self->repeat_rows.last_row = last_row;
return 0;
}
/*
* Set the columns to repeat at the left hand side of each printed page.
*/
uint8_t
worksheet_repeat_columns(lxw_worksheet *self, lxw_col_t first_col,
lxw_col_t last_col)
{
lxw_col_t tmp_col;
if (first_col > last_col) {
tmp_col = last_col;
last_col = first_col;
first_col = tmp_col;
}
if (_check_dimensions(self, last_col, 0, LXW_IGNORE, LXW_IGNORE))
return 1;
self->repeat_cols.in_use = LXW_TRUE;
self->repeat_cols.first_col = first_col;
self->repeat_cols.last_col = last_col;
return 0;
}
/*
* Set the print area in the current worksheet.
*/
uint8_t
worksheet_print_area(lxw_worksheet *self, lxw_row_t first_row,
lxw_col_t first_col, lxw_row_t last_row,
lxw_col_t last_col)
{
lxw_row_t tmp_row;
lxw_col_t tmp_col;
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;
}
if (_check_dimensions(self, last_row, last_col, LXW_IGNORE, LXW_IGNORE))
return 1;
/* Ignore max area since it is the same as no print area in Excel. */
if (first_row == 0 && first_col == 0 && last_row == LXW_ROW_MAX - 1
&& last_col == LXW_COL_MAX - 1) {
return 0;
}
self->print_area.in_use = LXW_TRUE;
self->print_area.first_row = first_row;
self->print_area.last_row = last_row;
self->print_area.first_col = first_col;
self->print_area.last_col = last_col;
return 0;
}
/* Store the vertical and horizontal number of pages that will define the
* maximum area printed.
*/
void
worksheet_fit_to_pages(lxw_worksheet *self, uint16_t width, uint16_t height)
{
self->fit_page = 1;
self->fit_width = width;
self->fit_height = height;
self->page_setup_changed = 1;
}
/*
* Set the start page number.
*/
void
worksheet_set_start_page(lxw_worksheet *self, uint16_t start_page)
{
self->page_start = start_page;
}
/*
* Set the scale factor for the printed page.
*/
void
worksheet_set_print_scale(lxw_worksheet *self, uint16_t scale)
{
/* Confine the scale to Excel"s range */
if (scale < 10 || scale > 400)
return;
/* Turn off "fit to page" option. */
self->fit_page = LXW_FALSE;
self->print_scale = scale;
self->page_setup_changed = LXW_TRUE;
}
/*
* Store the horizontal page breaks on a worksheet.
*/
void
worksheet_set_h_pagebreaks(lxw_worksheet *self, lxw_row_t hbreaks[])
{
self->hbreaks = hbreaks;
}
/*
* Store the vertical page breaks on a worksheet.
*/
void
worksheet_set_v_pagebreaks(lxw_worksheet *self, lxw_col_t vbreaks[])
{
self->vbreaks = vbreaks;
}
/*
* Set the worksheet zoom factor.
*/
void
worksheet_set_zoom(lxw_worksheet *self, uint16_t scale)
{
/* Confine the scale to Excel"s range */
if (scale < 10 || scale > 400) {
LXW_WARN("worksheet_set_zoom(): "
"Zoom factor scale outside range: 10 <= zoom <= 400");
return;
}
self->zoom = scale;
}
/*
* Hide cell zero values.
*/
void
worksheet_hide_zero(lxw_worksheet *self)
{
self->show_zeros = LXW_FALSE;
}
/*
* Display the worksheet right to left for some eastern versions of Excel.
*/
void
worksheet_right_to_left(lxw_worksheet *self)
{
self->right_to_left = LXW_TRUE;
}
/*
* Set the color of the worksheet tab.
*/
void
worksheet_set_tab_color(lxw_worksheet *self, lxw_color_t color)
{
self->tab_color = color;
}
/*
* Set the worksheet protection flags to prevent modification of worksheet
* objects.
*/
void
worksheet_protect(lxw_worksheet *self, char *password,
lxw_protection *options)
{
struct lxw_protection *protect = &self->protection;
/* Copy any user parameters to the internal structure. */
if (options)
memcpy(protect, options, sizeof(lxw_protection));
/* Zero the hash storage in case of copied initialization data. */
protect->hash[0] = '\0';
if (password) {
uint16_t hash = _hash_password(password);
lxw_snprintf(protect->hash, 5, "%X", hash);
}
protect->is_configured = LXW_TRUE;
}
/*
* Set the default row properties
*/
void
worksheet_set_default_row(lxw_worksheet *self, double height,
uint8_t hide_unused_rows)
{
if (height < 0)
height = self->default_row_height;
if (height != self->default_row_height) {
self->default_row_height = height;
self->row_size_changed = LXW_TRUE;
}
if (hide_unused_rows)
self->default_row_zeroed = LXW_TRUE;
self->default_row_set = LXW_TRUE;
}
/*
* Insert an image into the worksheet.
*/
int
worksheet_insert_image_opt(lxw_worksheet *self,
lxw_row_t row_num, lxw_col_t col_num,
const char *filename,
lxw_image_options *user_options)
{
FILE *image_stream;
lxw_image_options *options;
if (!filename) {
LXW_WARN("worksheet_insert_image()/_opts(): "
"filename must be specified");
return -1;
}
/* Check that the image file exists and can be opened. */
image_stream = fopen(filename, "rb");
if (!image_stream) {
LXW_WARN_FORMAT("worksheet_insert_image()/_opts(): "
"file doesn't exist or can't be opened: %s",
filename);
return -1;
}
/* Create a new object to hold the image options. */
options = calloc(1, sizeof(lxw_image_options));
RETURN_ON_MEM_ERROR(options, -1);
if (user_options) {
memcpy(options, user_options, sizeof(lxw_image_options));
options->url = lxw_strdup(user_options->url);
options->tip = lxw_strdup(user_options->tip);
}
/* Copy other options or set defaults. */
options->filename = lxw_strdup(filename);
options->stream = image_stream;
if (!options->x_scale)
options->x_scale = 1;
if (!options->y_scale)
options->y_scale = 1;
options->row = row_num;
options->col = col_num;
STAILQ_INSERT_TAIL(self->images, options, list_pointers);
return 0;
}
/*
* Insert an image into the worksheet.
*/
int
worksheet_insert_image(lxw_worksheet *self,
lxw_row_t row_num, lxw_col_t col_num,
const char *filename)
{
return worksheet_insert_image_opt(self, row_num, col_num, filename, NULL);
}