diff --git a/.indent.pro b/.indent.pro index f473baeb..676d57b3 100644 --- a/.indent.pro +++ b/.indent.pro @@ -132,6 +132,10 @@ -T lxw_repeat_cols -T lxw_repeat_rows -T lxw_rich_string_tuple +-T lxw_rich_value +-T lxw_rich_value_rel +-T lxw_rich_value_structure +-T lxw_rich_value_types -T lxw_row -T lxw_row_col_options -T lxw_row_t diff --git a/examples/embed_images.c b/examples/embed_images.c new file mode 100644 index 00000000..eb240d7f --- /dev/null +++ b/examples/embed_images.c @@ -0,0 +1,37 @@ +/* + * An example of embedding images into a worksheet using the libxlsxwriter + * library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + /* Create a new workbook and add a worksheet. */ + lxw_workbook *workbook = workbook_new("embed_images.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + /* Change some of the column widths for clarity. */ + worksheet_set_column(worksheet, COLS("A:B"), 30, NULL); + + /* Embed an image. */ + worksheet_write_string(worksheet, CELL("A2"), "Embed an image in a cell:", NULL); + worksheet_embed_image(worksheet, CELL("B2"), "logo.png"); + + /* Make a row bigger and embed the image. */ + worksheet_set_row(worksheet, 3, 72, NULL); + worksheet_write_string(worksheet, CELL("A4"), "Embed an image in a cell:", NULL); + worksheet_embed_image(worksheet, CELL("B4"), "logo.png"); + + /* Make a row bigger and embed the image. */ + worksheet_set_row(worksheet, 5, 150, NULL); + worksheet_write_string(worksheet, CELL("A6"), "Embed an image in a cell:", NULL); + worksheet_embed_image(worksheet, CELL("B6"), "logo.png"); + + workbook_close(workbook); + + return 0; +} diff --git a/include/xlsxwriter/content_types.h b/include/xlsxwriter/content_types.h index 8abeb5c3..e337ca1f 100644 --- a/include/xlsxwriter/content_types.h +++ b/include/xlsxwriter/content_types.h @@ -64,6 +64,7 @@ void lxw_ct_add_shared_strings(lxw_content_types *content_types); void lxw_ct_add_calc_chain(lxw_content_types *content_types); void lxw_ct_add_custom_properties(lxw_content_types *content_types); void lxw_ct_add_metadata(lxw_content_types *content_types); +void lxw_ct_add_rich_value(lxw_content_types *content_types); /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/include/xlsxwriter/metadata.h b/include/xlsxwriter/metadata.h index 7a515ab4..85fbb7ab 100644 --- a/include/xlsxwriter/metadata.h +++ b/include/xlsxwriter/metadata.h @@ -20,6 +20,9 @@ typedef struct lxw_metadata { FILE *file; + uint8_t has_dynamic_functions; + uint8_t has_embedded_images; + uint32_t num_embedded_images; } lxw_metadata; diff --git a/include/xlsxwriter/packager.h b/include/xlsxwriter/packager.h index f9876d94..7016573e 100644 --- a/include/xlsxwriter/packager.h +++ b/include/xlsxwriter/packager.h @@ -37,6 +37,10 @@ #include "vml.h" #include "comment.h" #include "metadata.h" +#include "rich_value.h" +#include "rich_value_rel.h" +#include "rich_value_types.h" +#include "rich_value_structure.h" #define LXW_ZIP_BUFFER_SIZE (16384) diff --git a/include/xlsxwriter/relationships.h b/include/xlsxwriter/relationships.h index d33d2625..882d9ff0 100644 --- a/include/xlsxwriter/relationships.h +++ b/include/xlsxwriter/relationships.h @@ -61,6 +61,7 @@ void lxw_add_ms_package_relationship(lxw_relationships *self, void lxw_add_worksheet_relationship(lxw_relationships *self, const char *type, const char *target, const char *target_mode); +void lxw_add_rich_value_relationship(lxw_relationships *self); /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/include/xlsxwriter/rich_value.h b/include/xlsxwriter/rich_value.h new file mode 100644 index 00000000..1ea43888 --- /dev/null +++ b/include/xlsxwriter/rich_value.h @@ -0,0 +1,51 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * rich_value - A libxlsxwriter library for creating Excel XLSX rich_value files. + * + */ +#ifndef __LXW_RICH_VALUE_H__ +#define __LXW_RICH_VALUE_H__ + +#include + +#include "common.h" +#include "workbook.h" + +/* + * Struct to represent a rich_value object. + */ +typedef struct lxw_rich_value { + + FILE *file; + lxw_workbook *workbook; + +} lxw_rich_value; + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +lxw_rich_value *lxw_rich_value_new(void); +void lxw_rich_value_free(lxw_rich_value *rich_value); +void lxw_rich_value_assemble_xml_file(lxw_rich_value *self); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +STATIC void _rich_value_xml_declaration(lxw_rich_value *self); + +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_RICH_VALUE_H__ */ diff --git a/include/xlsxwriter/rich_value_rel.h b/include/xlsxwriter/rich_value_rel.h new file mode 100644 index 00000000..2d35ef0d --- /dev/null +++ b/include/xlsxwriter/rich_value_rel.h @@ -0,0 +1,50 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * rich_value_rel - A libxlsxwriter library for creating Excel XLSX rich_value_rel files. + * + */ +#ifndef __LXW_RICH_VALUE_REL_H__ +#define __LXW_RICH_VALUE_REL_H__ + +#include + +#include "common.h" + +/* + * Struct to represent a rich_value_rel object. + */ +typedef struct lxw_rich_value_rel { + + FILE *file; + uint32_t num_embedded_images; + +} lxw_rich_value_rel; + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +lxw_rich_value_rel *lxw_rich_value_rel_new(void); +void lxw_rich_value_rel_free(lxw_rich_value_rel *rich_value_rel); +void lxw_rich_value_rel_assemble_xml_file(lxw_rich_value_rel *self); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +STATIC void _rich_value_rel_xml_declaration(lxw_rich_value_rel *self); + +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_RICH_VALUE_REL_H__ */ diff --git a/include/xlsxwriter/rich_value_structure.h b/include/xlsxwriter/rich_value_structure.h new file mode 100644 index 00000000..750adc14 --- /dev/null +++ b/include/xlsxwriter/rich_value_structure.h @@ -0,0 +1,53 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * rich_value_structure - A libxlsxwriter library for creating Excel XLSX rich_value_structure files. + * + */ +#ifndef __LXW_RICH_VALUE_STRUCTURE_H__ +#define __LXW_RICH_VALUE_STRUCTURE_H__ + +#include + +#include "common.h" + +/* + * Struct to represent a rich_value_structure object. + */ +typedef struct lxw_rich_value_structure { + + FILE *file; + uint8_t has_embedded_image_descriptions; + +} lxw_rich_value_structure; + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +lxw_rich_value_structure *lxw_rich_value_structure_new(void); +void lxw_rich_value_structure_free(lxw_rich_value_structure + *rich_value_structure); +void lxw_rich_value_structure_assemble_xml_file(lxw_rich_value_structure + *self); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +STATIC void _rich_value_structure_xml_declaration(lxw_rich_value_structure + *self); + +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_RICH_VALUE_STRUCTURE_H__ */ diff --git a/include/xlsxwriter/rich_value_types.h b/include/xlsxwriter/rich_value_types.h new file mode 100644 index 00000000..24bb9aa0 --- /dev/null +++ b/include/xlsxwriter/rich_value_types.h @@ -0,0 +1,49 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * rich_value_types - A libxlsxwriter library for creating Excel XLSX rich_value_types files. + * + */ +#ifndef __LXW_RICH_VALUE_TYPES_H__ +#define __LXW_RICH_VALUE_TYPES_H__ + +#include + +#include "common.h" + +/* + * Struct to represent a rich_value_types object. + */ +typedef struct lxw_rich_value_types { + + FILE *file; + +} lxw_rich_value_types; + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +lxw_rich_value_types *lxw_rich_value_types_new(void); +void lxw_rich_value_types_free(lxw_rich_value_types *rich_value_types); +void lxw_rich_value_types_assemble_xml_file(lxw_rich_value_types *self); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +STATIC void _rich_value_types_xml_declaration(lxw_rich_value_types *self); + +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_RICH_VALUE_TYPES_H__ */ diff --git a/include/xlsxwriter/workbook.h b/include/xlsxwriter/workbook.h index 4d90f342..f32ad0cf 100644 --- a/include/xlsxwriter/workbook.h +++ b/include/xlsxwriter/workbook.h @@ -299,6 +299,7 @@ typedef struct lxw_workbook { struct lxw_worksheet_names *worksheet_names; struct lxw_chartsheet_names *chartsheet_names; struct lxw_image_md5s *image_md5s; + struct lxw_image_md5s *embedded_image_md5s; struct lxw_image_md5s *header_image_md5s; struct lxw_image_md5s *background_md5s; struct lxw_charts *charts; @@ -337,6 +338,10 @@ typedef struct lxw_workbook { uint8_t has_vml; uint8_t has_comments; uint8_t has_metadata; + uint8_t has_embedded_images; + uint8_t has_dynamic_functions; + + uint32_t num_embedded_images; lxw_hash_table *used_xf_formats; lxw_hash_table *used_dxf_formats; diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 01aa3f45..9a28b397 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -721,6 +721,7 @@ enum cell_types { DYNAMIC_ARRAY_FORMULA_CELL, BLANK_CELL, BOOLEAN_CELL, + ERROR_CELL, COMMENT, HYPERLINK_URL, HYPERLINK_INTERNAL, @@ -818,6 +819,7 @@ STAILQ_HEAD(lxw_selections, lxw_selection); STAILQ_HEAD(lxw_data_validations, lxw_data_val_obj); STAILQ_HEAD(lxw_cond_format_list, lxw_cond_format_obj); STAILQ_HEAD(lxw_image_props, lxw_object_properties); +STAILQ_HEAD(lxw_embedded_image_props, lxw_object_properties); STAILQ_HEAD(lxw_chart_props, lxw_object_properties); STAILQ_HEAD(lxw_comment_objs, lxw_vml_obj); STAILQ_HEAD(lxw_table_objs, lxw_table_obj); @@ -2120,6 +2122,7 @@ typedef struct lxw_worksheet { struct lxw_data_validations *data_validations; struct lxw_cond_format_hash *conditional_formats; struct lxw_image_props *image_props; + struct lxw_image_props *embedded_image_props; struct lxw_chart_props *chart_data; struct lxw_drawing_rel_ids *drawing_rel_ids; struct lxw_vml_drawing_rel_ids *vml_drawing_rel_ids; @@ -2193,7 +2196,7 @@ typedef struct lxw_worksheet { uint8_t zoom_scale_normal; uint8_t black_white; uint8_t num_validations; - uint8_t has_dynamic_arrays; + uint8_t has_dynamic_functions; char *vba_codename; uint16_t num_buttons; @@ -3783,6 +3786,34 @@ lxw_error worksheet_insert_image_buffer_opt(lxw_worksheet *worksheet, size_t image_size, lxw_image_options *options); +/** + * @brief TODO + * + * @param worksheet + * @param row + * @param col + * @param filename + * @return lxw_error + */ +lxw_error worksheet_embed_image(lxw_worksheet *worksheet, + lxw_row_t row, lxw_col_t col, + const char *filename); + +/** + * @brief TODO + * + * @param worksheet + * @param row + * @param col + * @param filename + * @param options + * @return lxw_error + */ +lxw_error worksheet_embed_image_opt(lxw_worksheet *worksheet, + lxw_row_t row, lxw_col_t col, + const char *filename, + lxw_image_options *options); + /** * @brief Set the background image for a worksheet. * @@ -5812,6 +5843,10 @@ void lxw_worksheet_write_sheet_pr(lxw_worksheet *worksheet); void lxw_worksheet_write_page_setup(lxw_worksheet *worksheet); void lxw_worksheet_write_header_footer(lxw_worksheet *worksheet); +void worksheet_set_error_cell(lxw_worksheet *worksheet, + lxw_object_properties *object_props, + uint32_t ref_id); + /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/src/content_types.c b/src/content_types.c index bda0b826..ecb669a1 100644 --- a/src/content_types.c +++ b/src/content_types.c @@ -391,3 +391,23 @@ lxw_ct_add_metadata(lxw_content_types *self) lxw_ct_add_override(self, "/xl/metadata.xml", LXW_APP_DOCUMENT "spreadsheetml.sheetMetadata+xml"); } + +/* + * Add the richValue files to the ContentTypes overrides. + */ +void +lxw_ct_add_rich_value(lxw_content_types *self) +{ + lxw_ct_add_override(self, "/xl/richData/rdRichValueTypes.xml", + LXW_APP_MSEXCEL "rdrichvaluetypes+xml"); + + lxw_ct_add_override(self, "/xl/richData/rdrichvalue.xml", + LXW_APP_MSEXCEL "rdrichvalue+xml"); + + lxw_ct_add_override(self, "/xl/richData/rdrichvaluestructure.xml", + LXW_APP_MSEXCEL "rdrichvaluestructure+xml"); + + lxw_ct_add_override(self, "/xl/richData/richValueRel.xml", + LXW_APP_MSEXCEL "richvaluerel+xml"); + +} diff --git a/src/metadata.c b/src/metadata.c index 197cec42..36b19f1f 100644 --- a/src/metadata.c +++ b/src/metadata.c @@ -77,10 +77,17 @@ _metadata_write_metadata(lxw_metadata *self) "spreadsheetml/2006/main"; char xmlns_xda[] = "http://schemas.microsoft.com/office/" "spreadsheetml/2017/dynamicarray"; + char xmlns_xlrd[] = "http://schemas.microsoft.com/office/" + "spreadsheetml/2017/richdata"; LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); - LXW_PUSH_ATTRIBUTES_STR("xmlns:xda", xmlns_xda); + + if (self->has_embedded_images) + LXW_PUSH_ATTRIBUTES_STR("xmlns:xlrd", xmlns_xlrd); + + if (self->has_dynamic_functions) + LXW_PUSH_ATTRIBUTES_STR("xmlns:xda", xmlns_xda); lxw_xml_start_tag(self->file, "metadata", &attributes); @@ -88,10 +95,10 @@ _metadata_write_metadata(lxw_metadata *self) } /* - * Write the element. + * Write the element for dynamic functions. */ STATIC void -_metadata_write_metadata_type(lxw_metadata *self) +_metadata_write_cell_metadata_type(lxw_metadata *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -116,6 +123,34 @@ _metadata_write_metadata_type(lxw_metadata *self) LXW_FREE_ATTRIBUTES(); } +/* + * Write the element for embedded images. + */ +STATIC void +_metadata_write_value_metadata_type(lxw_metadata *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("name", "XLRICHVALUE"); + LXW_PUSH_ATTRIBUTES_INT("minSupportedVersion", 120000); + LXW_PUSH_ATTRIBUTES_INT("copy", 1); + LXW_PUSH_ATTRIBUTES_INT("pasteAll", 1); + LXW_PUSH_ATTRIBUTES_INT("pasteValues", 1); + LXW_PUSH_ATTRIBUTES_INT("merge", 1); + LXW_PUSH_ATTRIBUTES_INT("splitFirst", 1); + LXW_PUSH_ATTRIBUTES_INT("rowColShift", 1); + LXW_PUSH_ATTRIBUTES_INT("clearFormats", 1); + LXW_PUSH_ATTRIBUTES_INT("clearComments", 1); + LXW_PUSH_ATTRIBUTES_INT("assign", 1); + LXW_PUSH_ATTRIBUTES_INT("coerce", 1); + + lxw_xml_empty_tag(self->file, "metadataType", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the element. */ @@ -124,14 +159,24 @@ _metadata_write_metadata_types(lxw_metadata *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; + uint8_t count = 0; + + if (self->has_dynamic_functions) + count++; + + if (self->has_embedded_images) + count++; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_INT("count", 1); + LXW_PUSH_ATTRIBUTES_INT("count", count); lxw_xml_start_tag(self->file, "metadataTypes", &attributes); /* Write the metadataType element. */ - _metadata_write_metadata_type(self); + if (self->has_dynamic_functions) + _metadata_write_cell_metadata_type(self); + if (self->has_embedded_images) + _metadata_write_value_metadata_type(self); lxw_xml_end_tag(self->file, "metadataTypes"); @@ -157,10 +202,10 @@ _metadata_write_xda_dynamic_array_properties(lxw_metadata *self) } /* - * Write the element. + * Write the element for dynamic functions. */ STATIC void -_metadata_write_ext(lxw_metadata *self) +_metadata_write_cell_ext(lxw_metadata *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -179,10 +224,49 @@ _metadata_write_ext(lxw_metadata *self) } /* - * Write the element. + * Write the element. */ STATIC void -_metadata_write_future_metadata(lxw_metadata *self) +_metadata_write_xlrd_rvb(lxw_metadata *self, uint32_t index) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("i", index); + + lxw_xml_empty_tag(self->file, "xlrd:rvb", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for embedded images. + */ +STATIC void +_metadata_write_value_ext(lxw_metadata *self, uint32_t index) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("uri", "{3e2802c4-a4d2-4d8b-9148-e3be6c30e623}"); + + lxw_xml_start_tag(self->file, "ext", &attributes); + + /* Write the xlrd:rvb element. */ + _metadata_write_xlrd_rvb(self, index); + + lxw_xml_end_tag(self->file, "ext"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for dynamic functions. + */ +STATIC void +_metadata_write_cell_future_metadata(lxw_metadata *self) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -194,14 +278,12 @@ _metadata_write_future_metadata(lxw_metadata *self) lxw_xml_start_tag(self->file, "futureMetadata", &attributes); lxw_xml_start_tag(self->file, "bk", NULL); - lxw_xml_start_tag(self->file, "extLst", NULL); /* Write the ext element. */ - _metadata_write_ext(self); + _metadata_write_cell_ext(self); lxw_xml_end_tag(self->file, "extLst"); - lxw_xml_end_tag(self->file, "bk"); lxw_xml_end_tag(self->file, "futureMetadata"); @@ -209,18 +291,52 @@ _metadata_write_future_metadata(lxw_metadata *self) LXW_FREE_ATTRIBUTES(); } +/* + * Write the element for embedded images. + */ +STATIC void +_metadata_write_value_future_metadata(lxw_metadata *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint32_t i; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("name", "XLRICHVALUE"); + LXW_PUSH_ATTRIBUTES_INT("count", self->num_embedded_images); + + lxw_xml_start_tag(self->file, "futureMetadata", &attributes); + + for (i = 0; i < self->num_embedded_images; i++) { + lxw_xml_start_tag(self->file, "bk", NULL); + + lxw_xml_start_tag(self->file, "extLst", NULL); + + /* Write the ext element. */ + _metadata_write_value_ext(self, i); + + lxw_xml_end_tag(self->file, "extLst"); + lxw_xml_end_tag(self->file, "bk"); + + } + + lxw_xml_end_tag(self->file, "futureMetadata"); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the element. */ STATIC void -_metadata_write_rc(lxw_metadata *self) +_metadata_write_rc(lxw_metadata *self, uint8_t type, uint32_t index) { struct xml_attribute_list attributes; struct xml_attribute *attribute; LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("t", "1"); - LXW_PUSH_ATTRIBUTES_STR("v", "0"); + LXW_PUSH_ATTRIBUTES_INT("t", type); + LXW_PUSH_ATTRIBUTES_INT("v", index); lxw_xml_empty_tag(self->file, "rc", &attributes); @@ -228,7 +344,7 @@ _metadata_write_rc(lxw_metadata *self) } /* - * Write the element. + * Write the element for dynamic functions. */ STATIC void _metadata_write_cell_metadata(lxw_metadata *self) @@ -244,7 +360,7 @@ _metadata_write_cell_metadata(lxw_metadata *self) lxw_xml_start_tag(self->file, "bk", NULL); /* Write the rc element. */ - _metadata_write_rc(self); + _metadata_write_rc(self, 1, 0); lxw_xml_end_tag(self->file, "bk"); @@ -253,6 +369,39 @@ _metadata_write_cell_metadata(lxw_metadata *self) LXW_FREE_ATTRIBUTES(); } +/* + * Write the element for embedded images. + */ +STATIC void +_metadata_write_value_metadata(lxw_metadata *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + uint8_t type = 1; + uint32_t i; + + if (self->has_dynamic_functions) + type = 2; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("count", self->num_embedded_images); + + lxw_xml_start_tag(self->file, "valueMetadata", &attributes); + + for (i = 0; i < self->num_embedded_images; i++) { + lxw_xml_start_tag(self->file, "bk", NULL); + + /* Write the rc element. */ + _metadata_write_rc(self, type, i); + + lxw_xml_end_tag(self->file, "bk"); + } + + lxw_xml_end_tag(self->file, "valueMetadata"); + + LXW_FREE_ATTRIBUTES(); +} + /***************************************************************************** * * XML file assembly functions. @@ -275,10 +424,16 @@ lxw_metadata_assemble_xml_file(lxw_metadata *self) _metadata_write_metadata_types(self); /* Write the futureMetadata element. */ - _metadata_write_future_metadata(self); + if (self->has_dynamic_functions) + _metadata_write_cell_future_metadata(self); + if (self->has_embedded_images) + _metadata_write_value_future_metadata(self); /* Write the cellMetadata element. */ - _metadata_write_cell_metadata(self); + if (self->has_dynamic_functions) + _metadata_write_cell_metadata(self); + if (self->has_embedded_images) + _metadata_write_value_metadata(self); lxw_xml_end_tag(self->file, "metadata"); } diff --git a/src/packager.c b/src/packager.c index bf156d38..c455f653 100644 --- a/src/packager.c +++ b/src/packager.c @@ -383,9 +383,43 @@ _write_image_files(lxw_packager *self) else worksheet = sheet->u.worksheet; - if (STAILQ_EMPTY(worksheet->image_props)) + if (STAILQ_EMPTY(worksheet->image_props) + && STAILQ_EMPTY(worksheet->embedded_image_props)) continue; + STAILQ_FOREACH(object_props, worksheet->embedded_image_props, + list_pointers) { + + if (object_props->is_duplicate) + continue; + + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "xl/media/image%d.%s", index++, + object_props->extension); + + if (!object_props->is_image_buffer) { + /* Check that the image file exists and can be opened. */ + image_stream = lxw_fopen(object_props->filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("Error adding image to xlsx file: file " + "doesn't exist or can't be opened: %s.", + object_props->filename); + return LXW_ERROR_CREATING_TMPFILE; + } + + err = _add_file_to_zip(self, image_stream, filename); + fclose(image_stream); + } + else { + err = _add_buffer_to_zip(self, + object_props->image_buffer, + object_props->image_buffer_size, + filename); + } + + RETURN_ON_ERROR(err); + } + STAILQ_FOREACH(object_props, worksheet->image_props, list_pointers) { if (object_props->is_duplicate) @@ -1053,6 +1087,10 @@ _write_metadata_file(lxw_packager *self) goto mem_error; } + metadata->has_embedded_images = self->workbook->has_embedded_images; + metadata->num_embedded_images = self->workbook->num_embedded_images; + metadata->has_dynamic_functions = self->workbook->has_dynamic_functions; + lxw_metadata_assemble_xml_file(metadata); err = _add_to_zip(self, metadata->file, &buffer, &buffer_size, @@ -1067,6 +1105,176 @@ _write_metadata_file(lxw_packager *self) return err; } +/* + * Write the rdrichvalue.xml file. + */ +STATIC lxw_error +_write_rich_value_file(lxw_packager *self) +{ + lxw_error err = LXW_NO_ERROR; + lxw_rich_value *rich_value; + char *buffer = NULL; + size_t buffer_size = 0; + + if (!self->workbook->has_embedded_images) + return LXW_NO_ERROR; + + rich_value = lxw_rich_value_new(); + rich_value->workbook = self->workbook; + + if (!rich_value) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto mem_error; + } + + rich_value->file = + lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir); + if (!rich_value->file) { + err = LXW_ERROR_CREATING_TMPFILE; + goto mem_error; + } + + lxw_rich_value_assemble_xml_file(rich_value); + + err = _add_to_zip(self, rich_value->file, &buffer, &buffer_size, + "xl/richData/rdrichvalue.xml"); + + fclose(rich_value->file); + free(buffer); + +mem_error: + lxw_rich_value_free(rich_value); + + return err; +} + +/* + * Write the richValueRel.xml file. + */ +STATIC lxw_error +_write_rich_value_rel_file(lxw_packager *self) +{ + lxw_error err = LXW_NO_ERROR; + lxw_rich_value_rel *rich_value_rel; + char *buffer = NULL; + size_t buffer_size = 0; + + if (!self->workbook->has_embedded_images) + return LXW_NO_ERROR; + + rich_value_rel = lxw_rich_value_rel_new(); + rich_value_rel->num_embedded_images = self->workbook->num_embedded_images; + + if (!rich_value_rel) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto mem_error; + } + + rich_value_rel->file = + lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir); + if (!rich_value_rel->file) { + err = LXW_ERROR_CREATING_TMPFILE; + goto mem_error; + } + + lxw_rich_value_rel_assemble_xml_file(rich_value_rel); + + err = _add_to_zip(self, rich_value_rel->file, &buffer, &buffer_size, + "xl/richData/richValueRel.xml"); + + fclose(rich_value_rel->file); + free(buffer); + +mem_error: + lxw_rich_value_rel_free(rich_value_rel); + + return err; +} + +/* + * Write the rdRichValueTypes.xml file. + */ +STATIC lxw_error +_write_rich_value_types_file(lxw_packager *self) +{ + lxw_error err = LXW_NO_ERROR; + lxw_rich_value_types *rich_value_types; + char *buffer = NULL; + size_t buffer_size = 0; + + if (!self->workbook->has_embedded_images) + return LXW_NO_ERROR; + + rich_value_types = lxw_rich_value_types_new(); + + if (!rich_value_types) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto mem_error; + } + + rich_value_types->file = + lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir); + if (!rich_value_types->file) { + err = LXW_ERROR_CREATING_TMPFILE; + goto mem_error; + } + + lxw_rich_value_types_assemble_xml_file(rich_value_types); + + err = _add_to_zip(self, rich_value_types->file, &buffer, &buffer_size, + "xl/richData/rdRichValueTypes.xml"); + + fclose(rich_value_types->file); + free(buffer); + +mem_error: + lxw_rich_value_types_free(rich_value_types); + + return err; +} + +/* + * Write the rdrichvaluestructure.xml file. + */ +STATIC lxw_error +_write_rich_value_structure_file(lxw_packager *self) +{ + lxw_error err = LXW_NO_ERROR; + lxw_rich_value_structure *rich_value_structure; + char *buffer = NULL; + size_t buffer_size = 0; + + if (!self->workbook->has_embedded_images) + return LXW_NO_ERROR; + + rich_value_structure = lxw_rich_value_structure_new(); + + if (!rich_value_structure) { + err = LXW_ERROR_MEMORY_MALLOC_FAILED; + goto mem_error; + } + + rich_value_structure->file = + lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir); + if (!rich_value_structure->file) { + err = LXW_ERROR_CREATING_TMPFILE; + goto mem_error; + } + + lxw_rich_value_structure_assemble_xml_file(rich_value_structure); + + err = _add_to_zip(self, rich_value_structure->file, &buffer, &buffer_size, + "xl/richData/rdrichvaluestructure.xml"); + + fclose(rich_value_structure->file); + free(buffer); + +mem_error: + lxw_rich_value_structure_free(rich_value_structure); + + return err; +} + /* * Write the custom.xml file. */ @@ -1327,6 +1535,9 @@ _write_content_types_file(lxw_packager *self) if (workbook->has_metadata) lxw_ct_add_metadata(content_types); + if (workbook->has_embedded_images) + lxw_ct_add_rich_value(content_types); + lxw_content_types_assemble_xml_file(content_types); err = _add_to_zip(self, content_types->file, &buffer, &buffer_size, @@ -1397,6 +1608,9 @@ _write_workbook_rels_file(lxw_packager *self) if (workbook->has_metadata) lxw_add_document_relationship(rels, "/sheetMetadata", "metadata.xml"); + if (workbook->has_embedded_images) + lxw_add_rich_value_relationship(rels); + lxw_relationships_assemble_xml_file(rels); err = _add_to_zip(self, rels->file, &buffer, &buffer_size, @@ -1715,6 +1929,75 @@ _write_vba_project_rels_file(lxw_packager *self) return err; } +/* + * Write the richValueRel.xml.rels files for embedded images. + */ +STATIC lxw_error +_write_rich_value_rels_file(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + lxw_object_properties *object_props; + + lxw_relationships *rels; + char *buffer = NULL; + size_t buffer_size = 0; + char sheetname[LXW_FILENAME_LENGTH] = { 0 }; + char target[LXW_FILENAME_LENGTH] = { 0 }; + lxw_error err = LXW_NO_ERROR; + uint32_t index = 1; + + if (!workbook->has_embedded_images) + return LXW_NO_ERROR; + + rels = lxw_relationships_new(); + + rels->file = lxw_get_filehandle(&buffer, &buffer_size, self->tmpdir); + if (!rels->file) { + lxw_free_relationships(rels); + return LXW_ERROR_CREATING_TMPFILE; + } + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + + if (STAILQ_EMPTY(worksheet->embedded_image_props)) + continue; + + STAILQ_FOREACH(object_props, worksheet->embedded_image_props, + list_pointers) { + + if (object_props->is_duplicate) + continue; + + lxw_snprintf(target, LXW_FILENAME_LENGTH, + "../media/image%d.%s", index++, + object_props->extension); + + lxw_add_document_relationship(rels, "/image", target); + + } + + } + + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, + "xl/richData/_rels/richValueRel.xml.rels"); + + lxw_relationships_assemble_xml_file(rels); + + err = _add_to_zip(self, rels->file, &buffer, &buffer_size, sheetname); + + fclose(rels->file); + free(buffer); + lxw_free_relationships(rels); + + return err; +} + /* * Write the _rels/.rels xml file. */ @@ -1954,6 +2237,21 @@ lxw_create_package(lxw_packager *self) error = _write_metadata_file(self); RETURN_AND_ZIPCLOSE_ON_ERROR(error); + error = _write_rich_value_file(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + + error = _write_rich_value_rel_file(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + + error = _write_rich_value_types_file(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + + error = _write_rich_value_structure_file(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + + error = _write_rich_value_rels_file(self); + RETURN_AND_ZIPCLOSE_ON_ERROR(error); + error = _write_app_file(self); RETURN_AND_ZIPCLOSE_ON_ERROR(error); diff --git a/src/relationships.c b/src/relationships.c index a3940bd2..116dc3c8 100644 --- a/src/relationships.c +++ b/src/relationships.c @@ -244,3 +244,26 @@ lxw_add_worksheet_relationship(lxw_relationships *self, const char *type, { _add_relationship(self, LXW_SCHEMA_DOCUMENT, type, target, target_mode); } + +/* + * Add a richValue relationship to sheet .rels xml files. + */ +void +lxw_add_rich_value_relationship(lxw_relationships *self) +{ + _add_relationship(self, + "http://schemas.microsoft.com/office/2022/10/relationships/", + "richValueRel", "richData/richValueRel.xml", NULL); + _add_relationship(self, + "http://schemas.microsoft.com/office/2017/06/relationships/", + "rdRichValue", "richData/rdrichvalue.xml", NULL); + _add_relationship(self, + "http://schemas.microsoft.com/office/2017/06/relationships/", + "rdRichValueStructure", + "richData/rdrichvaluestructure.xml", NULL); + _add_relationship(self, + "http://schemas.microsoft.com/office/2017/06/relationships/", + "rdRichValueTypes", "richData/rdRichValueTypes.xml", + NULL); + +} diff --git a/src/rich_value.c b/src/rich_value.c new file mode 100644 index 00000000..f5476bad --- /dev/null +++ b/src/rich_value.c @@ -0,0 +1,190 @@ +/***************************************************************************** + * rich_value - A library for creating Excel XLSX rich_value files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/rich_value.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Create a new rich_value object. + */ +lxw_rich_value * +lxw_rich_value_new(void) +{ + lxw_rich_value *rich_value = calloc(1, sizeof(lxw_rich_value)); + GOTO_LABEL_ON_MEM_ERROR(rich_value, mem_error); + + return rich_value; + +mem_error: + lxw_rich_value_free(rich_value); + return NULL; +} + +/* + * Free a rich_value object. + */ +void +lxw_rich_value_free(lxw_rich_value *rich_value) +{ + if (!rich_value) + return; + + free(rich_value); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ + +/* + * Write the XML declaration. + */ +STATIC void +_rich_value_xml_declaration(lxw_rich_value *self) +{ + lxw_xml_declaration(self->file); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Write the element. + */ +STATIC void +_rich_value_write_v(lxw_rich_value *self, char *value) +{ + lxw_xml_data_element(self->file, "v", value, NULL); + +} + +/* + * Write the element. + */ +STATIC void +_rich_value_write_rv(lxw_rich_value *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("s", "0"); + + lxw_xml_start_tag(self->file, "rv", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the metadata for each embedded image. + */ +void +lxw_rich_value_write_images(lxw_rich_value *self) +{ + + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + lxw_object_properties *object_props; + char value[LXW_UINT32_T_LENGTH]; + uint32_t index = 0; + uint8_t type = 5; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + + STAILQ_FOREACH(object_props, worksheet->embedded_image_props, + list_pointers) { + + if (object_props->is_duplicate) + continue; + + if (object_props->decorative) + type = 6; + + /* Write the rv element. */ + _rich_value_write_rv(self); + + /* Write the v element. */ + lxw_snprintf(value, LXW_UINT32_T_LENGTH, "%u", index); + _rich_value_write_v(self, value); + + lxw_snprintf(value, LXW_UINT32_T_LENGTH, "%u", type); + _rich_value_write_v(self, value); + + lxw_xml_end_tag(self->file, "rv"); + + index++; + + } + } + +} + +/* + * Write the element. + */ +STATIC void +_rich_value_write_rv_data(lxw_rich_value *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = + "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + LXW_PUSH_ATTRIBUTES_INT("count", self->workbook->num_embedded_images); + + lxw_xml_start_tag(self->file, "rvData", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Assemble and write the XML file. + */ +void +lxw_rich_value_assemble_xml_file(lxw_rich_value *self) +{ + /* Write the XML declaration. */ + _rich_value_xml_declaration(self); + + /* Write the rvData element. */ + _rich_value_write_rv_data(self); + + lxw_rich_value_write_images(self); + + lxw_xml_end_tag(self->file, "rvData"); +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ diff --git a/src/rich_value_rel.c b/src/rich_value_rel.c new file mode 100644 index 00000000..2bda9096 --- /dev/null +++ b/src/rich_value_rel.c @@ -0,0 +1,142 @@ +/***************************************************************************** + * rich_value_rel - A library for creating Excel XLSX rich_value_rel files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/rich_value_rel.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Create a new rich_value_rel object. + */ +lxw_rich_value_rel * +lxw_rich_value_rel_new(void) +{ + lxw_rich_value_rel *rich_value_rel = + calloc(1, sizeof(lxw_rich_value_rel)); + GOTO_LABEL_ON_MEM_ERROR(rich_value_rel, mem_error); + + return rich_value_rel; + +mem_error: + lxw_rich_value_rel_free(rich_value_rel); + return NULL; +} + +/* + * Free a rich_value_rel object. + */ +void +lxw_rich_value_rel_free(lxw_rich_value_rel *rich_value_rel) +{ + if (!rich_value_rel) + return; + + free(rich_value_rel); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ + +/* + * Write the XML declaration. + */ +STATIC void +_rich_value_rel_xml_declaration(lxw_rich_value_rel *self) +{ + lxw_xml_declaration(self->file); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Write the element. + */ +STATIC void +_rich_value_rel_write_rel(lxw_rich_value_rel *self, uint32_t rel_index) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; + + lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", rel_index); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); + + lxw_xml_empty_tag(self->file, "rel", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_rel_write_rich_value_rels(lxw_rich_value_rel *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = + "http://schemas.microsoft.com/office/spreadsheetml/2022/richvaluerel"; + char xmlns_r[] = + "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + LXW_PUSH_ATTRIBUTES_STR("xmlns:r", xmlns_r); + + lxw_xml_start_tag(self->file, "richValueRels", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Assemble and write the XML file. + */ +void +lxw_rich_value_rel_assemble_xml_file(lxw_rich_value_rel *self) +{ + uint32_t i; + + /* Write the XML declaration. */ + _rich_value_rel_xml_declaration(self); + + /* Write the richValueRels element. */ + _rich_value_rel_write_rich_value_rels(self); + + for (i = 1; i <= self->num_embedded_images; i++) { + /* Write the rel element. */ + _rich_value_rel_write_rel(self, i); + } + + lxw_xml_end_tag(self->file, "richValueRels"); +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ diff --git a/src/rich_value_structure.c b/src/rich_value_structure.c new file mode 100644 index 00000000..43bd7b58 --- /dev/null +++ b/src/rich_value_structure.c @@ -0,0 +1,176 @@ +/***************************************************************************** + * rich_value_structure - A library for creating Excel XLSX rich_value_structure files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/rich_value_structure.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Create a new rich_value_structure object. + */ +lxw_rich_value_structure * +lxw_rich_value_structure_new(void) +{ + lxw_rich_value_structure *rich_value_structure = + calloc(1, sizeof(lxw_rich_value_structure)); + GOTO_LABEL_ON_MEM_ERROR(rich_value_structure, mem_error); + + return rich_value_structure; + +mem_error: + lxw_rich_value_structure_free(rich_value_structure); + return NULL; +} + +/* + * Free a rich_value_structure object. + */ +void +lxw_rich_value_structure_free(lxw_rich_value_structure *rich_value_structure) +{ + if (!rich_value_structure) + return; + + free(rich_value_structure); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ + +/* + * Write the XML declaration. + */ +STATIC void +_rich_value_structure_xml_declaration(lxw_rich_value_structure *self) +{ + lxw_xml_declaration(self->file); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Write the element. + */ +STATIC void +_rich_value_structure_write_k2(lxw_rich_value_structure *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("n", "CalcOrigin"); + LXW_PUSH_ATTRIBUTES_STR("t", "i"); + + lxw_xml_empty_tag(self->file, "k", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_structure_write_k1(lxw_rich_value_structure *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("n", "_rvRel:LocalImageIdentifier"); + LXW_PUSH_ATTRIBUTES_STR("t", "i"); + + lxw_xml_empty_tag(self->file, "k", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_structure_write_s(lxw_rich_value_structure *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("t", "_localImage"); + + lxw_xml_start_tag(self->file, "s", &attributes); + + /* Write the k element. */ + _rich_value_structure_write_k1(self); + _rich_value_structure_write_k2(self); + + lxw_xml_end_tag(self->file, "s"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_structure_write_rv_structures(lxw_rich_value_structure *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = + "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + LXW_PUSH_ATTRIBUTES_STR("count", "1"); + + lxw_xml_start_tag(self->file, "rvStructures", &attributes); + + /* Write the s element. */ + _rich_value_structure_write_s(self); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Assemble and write the XML file. + */ +void +lxw_rich_value_structure_assemble_xml_file(lxw_rich_value_structure *self) +{ + /* Write the XML declaration. */ + _rich_value_structure_xml_declaration(self); + + /* Write the rvStructures element. */ + _rich_value_structure_write_rv_structures(self); + + lxw_xml_end_tag(self->file, "rvStructures"); + +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ diff --git a/src/rich_value_types.c b/src/rich_value_types.c new file mode 100644 index 00000000..cb3e8134 --- /dev/null +++ b/src/rich_value_types.c @@ -0,0 +1,199 @@ +/***************************************************************************** + * rich_value_types - A library for creating Excel XLSX rich_value_types files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/rich_value_types.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Create a new rich_value_types object. + */ +lxw_rich_value_types * +lxw_rich_value_types_new(void) +{ + lxw_rich_value_types *rich_value_types = + calloc(1, sizeof(lxw_rich_value_types)); + GOTO_LABEL_ON_MEM_ERROR(rich_value_types, mem_error); + + return rich_value_types; + +mem_error: + lxw_rich_value_types_free(rich_value_types); + return NULL; +} + +/* + * Free a rich_value_types object. + */ +void +lxw_rich_value_types_free(lxw_rich_value_types *rich_value_types) +{ + if (!rich_value_types) + return; + + free(rich_value_types); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ + +/* + * Write the XML declaration. + */ +STATIC void +_rich_value_types_xml_declaration(lxw_rich_value_types *self) +{ + lxw_xml_declaration(self->file); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Write the element. + */ +STATIC void +_rich_value_types_write_rv_types_info(lxw_rich_value_types *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = + "http://schemas.microsoft.com/office/spreadsheetml/2017/richdata2"; + char xmlns_mc[] = + "http://schemas.openxmlformats.org/markup-compatibility/2006"; + char xmlns_x[] = + "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; + char mc_ignorable[] = "x"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + LXW_PUSH_ATTRIBUTES_STR("xmlns:mc", xmlns_mc); + LXW_PUSH_ATTRIBUTES_STR("mc:Ignorable", mc_ignorable); + LXW_PUSH_ATTRIBUTES_STR("xmlns:x", xmlns_x); + + lxw_xml_start_tag(self->file, "rvTypesInfo", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_types_write_flag(lxw_rich_value_types *self, char *name) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("name", name); + LXW_PUSH_ATTRIBUTES_STR("value", "1"); + + lxw_xml_empty_tag(self->file, "flag", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_types_write_key(lxw_rich_value_types *self, char *name) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("name", name); + + lxw_xml_start_tag(self->file, "key", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_rich_value_types_write_key_flags(lxw_rich_value_types *self) +{ + int i; + char *key_flags[10][3] = { + {"_Self", "ExcludeFromFile", "ExcludeFromCalcComparison"}, + {"_DisplayString", "ExcludeFromCalcComparison", ""}, + {"_Flags", "ExcludeFromCalcComparison", ""}, + {"_Format", "ExcludeFromCalcComparison", ""}, + {"_SubLabel", "ExcludeFromCalcComparison", ""}, + {"_Attribution", "ExcludeFromCalcComparison", ""}, + {"_Icon", "ExcludeFromCalcComparison", ""}, + {"_Display", "ExcludeFromCalcComparison", ""}, + {"_CanonicalPropertyNames", "ExcludeFromCalcComparison", ""}, + {"_ClassificationId", "ExcludeFromCalcComparison", ""}, + }; + + lxw_xml_start_tag(self->file, "global", NULL); + lxw_xml_start_tag(self->file, "keyFlags", NULL); + + for (i = 0; i < 10; i++) { + char **flags = key_flags[i]; + + _rich_value_types_write_key(self, flags[0]); + _rich_value_types_write_flag(self, flags[1]); + + if (*flags[2]) { + _rich_value_types_write_flag(self, flags[2]); + + } + + lxw_xml_end_tag(self->file, "key"); + } + + lxw_xml_end_tag(self->file, "keyFlags"); + lxw_xml_end_tag(self->file, "global"); +} + +/* + * Assemble and write the XML file. + */ +void +lxw_rich_value_types_assemble_xml_file(lxw_rich_value_types *self) +{ + /* Write the XML declaration. */ + _rich_value_types_xml_declaration(self); + + /* Write the rvTypesInfo element. */ + _rich_value_types_write_rv_types_info(self); + + /* Write the keyFlags element. */ + _rich_value_types_write_key_flags(self); + + lxw_xml_end_tag(self->file, "rvTypesInfo"); +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ diff --git a/src/workbook.c b/src/workbook.c index 0c8b22b3..dbd2f28c 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -217,6 +217,7 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->chartsheet_names); } + /* TODO add macro for these RB image frees. */ if (workbook->image_md5s) { for (image_md5 = RB_MIN(lxw_image_md5s, workbook->image_md5s); image_md5; image_md5 = next_image_md5) { @@ -231,12 +232,30 @@ lxw_workbook_free(lxw_workbook *workbook) free(workbook->image_md5s); } + if (workbook->embedded_image_md5s) { + for (image_md5 = + RB_MIN(lxw_image_md5s, workbook->embedded_image_md5s); image_md5; + image_md5 = next_image_md5) { + + next_image_md5 = + RB_NEXT(lxw_image_md5s, workbook->embedded_image_md5s, + image_md5); + RB_REMOVE(lxw_image_md5s, workbook->embedded_image_md5s, + image_md5); + free(image_md5->md5); + free(image_md5); + } + + free(workbook->embedded_image_md5s); + } + if (workbook->header_image_md5s) { for (image_md5 = RB_MIN(lxw_image_md5s, workbook->header_image_md5s); image_md5; image_md5 = next_image_md5) { next_image_md5 = - RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5); + RB_NEXT(lxw_image_md5s, workbook->header_image_md5s, + image_md5); RB_REMOVE(lxw_image_md5s, workbook->header_image_md5s, image_md5); free(image_md5->md5); free(image_md5); @@ -250,7 +269,7 @@ lxw_workbook_free(lxw_workbook *workbook) image_md5; image_md5 = next_image_md5) { next_image_md5 = - RB_NEXT(lxw_image_md5s, workbook->image_md5, image_md5); + RB_NEXT(lxw_image_md5s, workbook->background_md5s, image_md5); RB_REMOVE(lxw_image_md5s, workbook->background_md5s, image_md5); free(image_md5->md5); free(image_md5); @@ -1082,6 +1101,7 @@ _prepare_drawings(lxw_workbook *self) } if (STAILQ_EMPTY(worksheet->image_props) + && STAILQ_EMPTY(worksheet->embedded_image_props) && STAILQ_EMPTY(worksheet->chart_data) && !worksheet->has_header_vml && !worksheet->has_background_image) { continue; @@ -1089,6 +1109,44 @@ _prepare_drawings(lxw_workbook *self) drawing_id++; + /* Prepare embedded worksheet images. */ + STAILQ_FOREACH(object_props, worksheet->embedded_image_props, + list_pointers) { + + _store_image_type(self, object_props->image_type); + + /* Check for duplicate images and only store the first instance. */ + if (object_props->md5) { + tmp_image_md5.md5 = object_props->md5; + found_duplicate_image = RB_FIND(lxw_image_md5s, + self->embedded_image_md5s, + &tmp_image_md5); + } + + if (found_duplicate_image) { + ref_id = found_duplicate_image->id; + object_props->is_duplicate = LXW_TRUE; + } + else { + image_ref_id++; + ref_id = image_ref_id; + self->num_embedded_images++; + +#ifndef USE_NO_MD5 + new_image_md5 = calloc(1, sizeof(lxw_image_md5)); +#endif + if (new_image_md5 && object_props->md5) { + new_image_md5->id = ref_id; + new_image_md5->md5 = lxw_strdup(object_props->md5); + + RB_INSERT(lxw_image_md5s, self->embedded_image_md5s, + new_image_md5); + } + } + + worksheet_set_error_cell(worksheet, object_props, ref_id); + } + /* Prepare background images. */ if (worksheet->has_background_image) { @@ -1830,6 +1888,11 @@ workbook_new_opt(const char *filename, lxw_workbook_options *options) GOTO_LABEL_ON_MEM_ERROR(workbook->image_md5s, mem_error); RB_INIT(workbook->image_md5s); + /* Add the embedded image MD5 tree. */ + workbook->embedded_image_md5s = calloc(1, sizeof(struct lxw_image_md5s)); + GOTO_LABEL_ON_MEM_ERROR(workbook->embedded_image_md5s, mem_error); + RB_INIT(workbook->embedded_image_md5s); + /* Add the header image MD5 tree. */ workbook->header_image_md5s = calloc(1, sizeof(struct lxw_image_md5s)); GOTO_LABEL_ON_MEM_ERROR(workbook->header_image_md5s, mem_error); @@ -2153,8 +2216,16 @@ workbook_close(lxw_workbook *self) if (worksheet->index == self->active_sheet) worksheet->active = LXW_TRUE; - if (worksheet->has_dynamic_arrays) + if (worksheet->has_dynamic_functions) { self->has_metadata = LXW_TRUE; + self->has_dynamic_functions = LXW_TRUE; + + } + + if (!STAILQ_EMPTY(worksheet->embedded_image_props)) { + self->has_metadata = LXW_TRUE; + self->has_embedded_images = LXW_TRUE; + } } /* Set workbook and worksheet VBA codenames if a macro has been added. */ diff --git a/src/worksheet.c b/src/worksheet.c index df13a073..44c2b28f 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -142,6 +142,11 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) GOTO_LABEL_ON_MEM_ERROR(worksheet->image_props, mem_error); STAILQ_INIT(worksheet->image_props); + worksheet->embedded_image_props = + calloc(1, sizeof(struct lxw_embedded_image_props)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->embedded_image_props, mem_error); + STAILQ_INIT(worksheet->embedded_image_props); + worksheet->chart_data = calloc(1, sizeof(struct lxw_chart_props)); GOTO_LABEL_ON_MEM_ERROR(worksheet->chart_data, mem_error); STAILQ_INIT(worksheet->chart_data); @@ -368,7 +373,8 @@ _free_cell(lxw_cell *cell) return; if (cell->type != NUMBER_CELL && cell->type != STRING_CELL - && cell->type != BLANK_CELL && cell->type != BOOLEAN_CELL) { + && cell->type != BLANK_CELL && cell->type != BOOLEAN_CELL + && cell->type != ERROR_CELL) { free((void *) cell->u.string); } @@ -607,6 +613,17 @@ lxw_worksheet_free(lxw_worksheet *worksheet) free(worksheet->image_props); } + if (worksheet->embedded_image_props) { + while (!STAILQ_EMPTY(worksheet->embedded_image_props)) { + object_props = STAILQ_FIRST(worksheet->embedded_image_props); + STAILQ_REMOVE_HEAD(worksheet->embedded_image_props, + list_pointers); + _free_object_properties(object_props); + } + + free(worksheet->embedded_image_props); + } + if (worksheet->chart_data) { while (!STAILQ_EMPTY(worksheet->chart_data)) { object_props = STAILQ_FIRST(worksheet->chart_data); @@ -991,6 +1008,25 @@ _new_boolean_cell(lxw_row_t row_num, lxw_col_t col_num, int value, return cell; } +/* + * Create a new worksheet error cell object. + */ +STATIC lxw_cell * +_new_error_cell(lxw_row_t row_num, lxw_col_t col_num, uint32_t 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 = ERROR_CELL; + cell->format = format; + cell->u.number = value; + + return cell; +} + /* * Create a new comment cell object. */ @@ -4522,6 +4558,15 @@ _write_boolean_cell(lxw_worksheet *self, lxw_cell *cell) lxw_xml_data_element(self->file, "v", data, NULL); } +/* + * Write out a error worksheet cell. + */ +STATIC void +_write_error_cell(lxw_worksheet *self) +{ + lxw_xml_data_element(self->file, "v", "#VALUE!", NULL); +} + /* * Calculate the "spans" attribute of the tag. This is an XLSX * optimization and isn't strictly required. However, it makes comparing @@ -4652,6 +4697,13 @@ _write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format) _write_array_formula_num_cell(self, cell); lxw_xml_end_tag(self->file, "c"); } + else if (cell->type == ERROR_CELL) { + LXW_PUSH_ATTRIBUTES_STR("t", "e"); + LXW_PUSH_ATTRIBUTES_DBL("vm", cell->u.number); + lxw_xml_start_tag(self->file, "c", &attributes); + _write_error_cell(self); + lxw_xml_end_tag(self->file, "c"); + } LXW_FREE_ATTRIBUTES(); } @@ -8093,7 +8145,7 @@ _store_array_formula(lxw_worksheet *self, _insert_cell(self, first_row, first_col, cell); if (is_dynamic) - self->has_dynamic_arrays = LXW_TRUE; + self->has_dynamic_functions = LXW_TRUE; /* Pad out the rest of the area with formatted zeroes. */ if (!self->optimize) { @@ -10591,6 +10643,108 @@ worksheet_insert_image_buffer(lxw_worksheet *self, image_buffer, image_size, NULL); } +/* + * Embed an image with options into the worksheet. + */ +lxw_error +worksheet_embed_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; + const char *description; + lxw_object_properties *object_props; + lxw_error err; + + if (!filename) { + LXW_WARN("worksheet_embed_image()/_opt(): " + "filename must be specified."); + return LXW_ERROR_NULL_PARAMETER_IGNORED; + } + + /* Check that the image file exists and can be opened. */ + image_stream = lxw_fopen(filename, "rb"); + if (!image_stream) { + LXW_WARN_FORMAT1("worksheet_embed_image()/_opt(): " + "file doesn't exist or can't be opened: %s.", + filename); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + /* Use the filename as the default description, like Excel. */ + description = lxw_basename(filename); + if (!description) { + LXW_WARN_FORMAT1("worksheet_embed_image()/_opt(): " + "couldn't get basename for file: %s.", filename); + fclose(image_stream); + return LXW_ERROR_PARAMETER_VALIDATION; + } + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + /* Create a new object to hold the image properties. */ + object_props = calloc(1, sizeof(lxw_object_properties)); + if (!object_props) { + fclose(image_stream); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + if (user_options) { + object_props->x_offset = user_options->x_offset; + object_props->y_offset = user_options->y_offset; + object_props->x_scale = user_options->x_scale; + object_props->y_scale = user_options->y_scale; + object_props->object_position = user_options->object_position; + object_props->url = lxw_strdup(user_options->url); + object_props->tip = lxw_strdup(user_options->tip); + object_props->decorative = user_options->decorative; + + if (user_options->description) + description = user_options->description; + } + + /* Copy other options or set defaults. */ + object_props->filename = lxw_strdup(filename); + object_props->description = lxw_strdup(description); + object_props->stream = image_stream; + object_props->row = row_num; + object_props->col = col_num; + + if (object_props->x_scale == 0.0) + object_props->x_scale = 1; + + if (object_props->y_scale == 0.0) + object_props->y_scale = 1; + + if (_get_image_properties(object_props) == LXW_NO_ERROR) { + STAILQ_INSERT_TAIL(self->embedded_image_props, object_props, + list_pointers); + fclose(image_stream); + + return LXW_NO_ERROR; + } + else { + _free_object_properties(object_props); + fclose(image_stream); + return LXW_ERROR_IMAGE_DIMENSIONS; + } + +} + +/* + * Embed an image into the worksheet. + */ +lxw_error +worksheet_embed_image(lxw_worksheet *self, + lxw_row_t row_num, lxw_col_t col_num, + const char *filename) +{ + return worksheet_embed_image_opt(self, row_num, col_num, filename, NULL); +} + /* * Set an image as a worksheet background. */ @@ -11421,3 +11575,18 @@ worksheet_ignore_errors(lxw_worksheet *self, uint8_t type, const char *range) return LXW_NO_ERROR; } + +/* + * Write an error cell for versions of Excel that don't support embedded images. + */ +void +worksheet_set_error_cell(lxw_worksheet *self, + lxw_object_properties *object_props, uint32_t ref_id) +{ + lxw_row_t row_num = object_props->row; + lxw_col_t col_num = object_props->col; + + lxw_cell *cell = _new_error_cell(row_num, col_num, ref_id, NULL); + _insert_cell(self, row_num, col_num, cell); + +} diff --git a/test/functional/src/test_embed_image01.c b/test/functional/src/test_embed_image01.c new file mode 100644 index 00000000..e46b7113 --- /dev/null +++ b/test/functional/src/test_embed_image01.c @@ -0,0 +1,21 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet, CELL("A1"), "images/red.png"); + + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image02.c b/test/functional/src/test_embed_image02.c new file mode 100644 index 00000000..f891975e --- /dev/null +++ b/test/functional/src/test_embed_image02.c @@ -0,0 +1,21 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image02.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet, 0, 0, "images/red.png"); + worksheet_embed_image(worksheet, 8, 4, "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image03.c b/test/functional/src/test_embed_image03.c new file mode 100644 index 00000000..820a361d --- /dev/null +++ b/test/functional/src/test_embed_image03.c @@ -0,0 +1,21 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet, 0, 0, "images/red.png"); + worksheet_embed_image(worksheet, 8, 4, "images/blue.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image04.c b/test/functional/src/test_embed_image04.c new file mode 100644 index 00000000..8550a7a0 --- /dev/null +++ b/test/functional/src/test_embed_image04.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image04.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet1, 0, 0, "images/red.png"); + worksheet_embed_image(worksheet2, 8, 4, "images/blue.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image05.c b/test/functional/src/test_embed_image05.c new file mode 100644 index 00000000..0ad6d987 --- /dev/null +++ b/test/functional/src/test_embed_image05.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image05.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_dynamic_array_formula(worksheet, 0, 0, 2, 0, "=LEN(B1:B3)", NULL); + + worksheet_embed_image(worksheet, 8, 4, "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image06.c b/test/functional/src/test_embed_image06.c new file mode 100644 index 00000000..ec5ec8b2 --- /dev/null +++ b/test/functional/src/test_embed_image06.c @@ -0,0 +1,21 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image06.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet, 0, 0, "images/red.png"); + worksheet_insert_image(worksheet, 8, 4, "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image07.c b/test/functional/src/test_embed_image07.c new file mode 100644 index 00000000..ef5300d8 --- /dev/null +++ b/test/functional/src/test_embed_image07.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image07.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet, 0, 0, "images/red.png"); + worksheet_embed_image(worksheet, 2, 0, "images/blue.png"); + worksheet_insert_image(worksheet, 8, 4, "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image11.c b/test/functional/src/test_embed_image11.c new file mode 100644 index 00000000..4744f6c8 --- /dev/null +++ b/test/functional/src/test_embed_image11.c @@ -0,0 +1,23 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image11.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_set_column(worksheet, 0, 0, 11, NULL); + worksheet_set_row(worksheet, 0, 72, NULL); + + worksheet_embed_image(worksheet, 0, 0, "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_embed_image13.c b/test/functional/src/test_embed_image13.c new file mode 100644 index 00000000..11f527f3 --- /dev/null +++ b/test/functional/src/test_embed_image13.c @@ -0,0 +1,34 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_embed_image13.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet1, 0, 0, "images/red.png"); + worksheet_embed_image(worksheet1, 2, 0, "images/blue.png"); + worksheet_embed_image(worksheet1, 4, 0, "images/yellow.png"); + + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet2, 0, 0, "images/yellow.png"); + worksheet_embed_image(worksheet2, 2, 0, "images/red.png"); + worksheet_embed_image(worksheet2, 4, 0, "images/blue.png"); + + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + + worksheet_embed_image(worksheet3, 0, 0, "images/blue.png"); + worksheet_embed_image(worksheet3, 2, 0, "images/yellow.png"); + worksheet_embed_image(worksheet3, 4, 0, "images/red.png"); + + return workbook_close(workbook); +} diff --git a/test/functional/test_embed_image.py b/test/functional/test_embed_image.py new file mode 100644 index 00000000..05d49933 --- /dev/null +++ b/test/functional/test_embed_image.py @@ -0,0 +1,47 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# SPDX-License-Identifier: BSD-2-Clause +# Copyright 2014-2024, John McNamara, jmcnamara@cpan.org. +# + +import os +import pytest +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_embed_image01(self): + self.run_exe_test('test_embed_image01') + + # Some of the following tests require MD5 hash support to remove duplicate images. + @pytest.mark.skipif(os.environ.get('USE_NO_MD5'), reason="compiled without MD5 support") + def test_embed_image02(self): + self.run_exe_test('test_embed_image02') + + def test_embed_image03(self): + self.run_exe_test('test_embed_image03') + + def test_embed_image04(self): + self.run_exe_test('test_embed_image04') + + def test_embed_image05(self): + self.run_exe_test('test_embed_image05') + + def test_embed_image06(self): + self.run_exe_test('test_embed_image06') + + def test_embed_image07(self): + self.run_exe_test('test_embed_image07') + + def test_embed_image11(self): + self.run_exe_test('test_embed_image11') + + @pytest.mark.skipif(os.environ.get('USE_NO_MD5'), reason="compiled without MD5 support") + def test_embed_image13(self): + self.run_exe_test('test_embed_image13') diff --git a/test/functional/xlsx_files/embed_image01.xlsx b/test/functional/xlsx_files/embed_image01.xlsx new file mode 100644 index 00000000..cd3818c6 Binary files /dev/null and b/test/functional/xlsx_files/embed_image01.xlsx differ diff --git a/test/functional/xlsx_files/embed_image02.xlsx b/test/functional/xlsx_files/embed_image02.xlsx new file mode 100644 index 00000000..a4870d86 Binary files /dev/null and b/test/functional/xlsx_files/embed_image02.xlsx differ diff --git a/test/functional/xlsx_files/embed_image03.xlsx b/test/functional/xlsx_files/embed_image03.xlsx new file mode 100644 index 00000000..cfb4cf6b Binary files /dev/null and b/test/functional/xlsx_files/embed_image03.xlsx differ diff --git a/test/functional/xlsx_files/embed_image04.xlsx b/test/functional/xlsx_files/embed_image04.xlsx new file mode 100644 index 00000000..b626b771 Binary files /dev/null and b/test/functional/xlsx_files/embed_image04.xlsx differ diff --git a/test/functional/xlsx_files/embed_image05.xlsx b/test/functional/xlsx_files/embed_image05.xlsx new file mode 100644 index 00000000..5430cdc4 Binary files /dev/null and b/test/functional/xlsx_files/embed_image05.xlsx differ diff --git a/test/functional/xlsx_files/embed_image06.xlsx b/test/functional/xlsx_files/embed_image06.xlsx new file mode 100644 index 00000000..3b9f0e35 Binary files /dev/null and b/test/functional/xlsx_files/embed_image06.xlsx differ diff --git a/test/functional/xlsx_files/embed_image07.xlsx b/test/functional/xlsx_files/embed_image07.xlsx new file mode 100644 index 00000000..af4fba33 Binary files /dev/null and b/test/functional/xlsx_files/embed_image07.xlsx differ diff --git a/test/functional/xlsx_files/embed_image11.xlsx b/test/functional/xlsx_files/embed_image11.xlsx new file mode 100644 index 00000000..989ea03b Binary files /dev/null and b/test/functional/xlsx_files/embed_image11.xlsx differ diff --git a/test/functional/xlsx_files/embed_image13.xlsx b/test/functional/xlsx_files/embed_image13.xlsx new file mode 100644 index 00000000..038f9747 Binary files /dev/null and b/test/functional/xlsx_files/embed_image13.xlsx differ diff --git a/test/unit/Makefile b/test/unit/Makefile index b1a58124..d4e1fe8b 100644 --- a/test/unit/Makefile +++ b/test/unit/Makefile @@ -43,6 +43,10 @@ SRCS += $(wildcard vml/test*.c) SRCS += $(wildcard comment/test*.c) SRCS += $(wildcard metadata/test*.c) SRCS += $(wildcard table/test*.c) +SRCS += $(wildcard rich_value/test*.c) +SRCS += $(wildcard rich_value_rel/test*.c) +SRCS += $(wildcard rich_value_types/test*.c) +SRCS += $(wildcard rich_value_structure/test*.c) # End of SRCS OBJS = $(patsubst %.c,%.o,$(SRCS)) @@ -80,6 +84,10 @@ all : $(Q)$(MAKE) -C comment $(Q)$(MAKE) -C metadata $(Q)$(MAKE) -C table + $(Q)$(MAKE) -C rich_value + $(Q)$(MAKE) -C rich_value_rel + $(Q)$(MAKE) -C rich_value_types + $(Q)$(MAKE) -C rich_value_structure # END make all clean : @@ -102,6 +110,10 @@ clean : $(Q)$(MAKE) clean -C comment $(Q)$(MAKE) clean -C metadata $(Q)$(MAKE) clean -C table + $(Q)$(MAKE) clean -C rich_value + $(Q)$(MAKE) clean -C rich_value_rel + $(Q)$(MAKE) clean -C rich_value_types + $(Q)$(MAKE) clean -C rich_value_structure # END make clean diff --git a/test/unit/rich_value/Makefile b/test/unit/rich_value/Makefile new file mode 100644 index 00000000..2c05b8e2 --- /dev/null +++ b/test/unit/rich_value/Makefile @@ -0,0 +1,8 @@ +############################################################################### +# +# Makefile for libxlsxwriter library. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +include ../Makefile.unit diff --git a/test/unit/rich_value/main.c b/test/unit/rich_value/main.c new file mode 100644 index 00000000..91ddf170 --- /dev/null +++ b/test/unit/rich_value/main.c @@ -0,0 +1,15 @@ +/* + * Test runner for xmlwriter using ctest. + * + * Copyright 2014-2024 John McNamara, jmcnamara@cpan.org + * + */ +#define CTEST_MAIN + +#include "../ctest.h" + +int main(int argc, const char *argv[]) +{ + return ctest_main(argc, argv); +} + diff --git a/test/unit/rich_value/test_rich_value_xml_declaration.c b/test/unit/rich_value/test_rich_value_xml_declaration.c new file mode 100644 index 00000000..faf66d69 --- /dev/null +++ b/test/unit/rich_value/test_rich_value_xml_declaration.c @@ -0,0 +1,28 @@ +/* + * Tests for the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "xlsxwriter/rich_value.h" + +// Test _xml_declaration(). +CTEST(rich_value, xml_declaration) { + + char* got; + char exp[] = "\n"; + FILE* testfile = tmpfile(); + + lxw_rich_value *rich_value = lxw_rich_value_new(); + rich_value->file = testfile; + + _rich_value_xml_declaration(rich_value); + + RUN_XLSX_STREQ(exp, got); + + lxw_rich_value_free(rich_value); +} diff --git a/test/unit/rich_value_rel/Makefile b/test/unit/rich_value_rel/Makefile new file mode 100644 index 00000000..2c05b8e2 --- /dev/null +++ b/test/unit/rich_value_rel/Makefile @@ -0,0 +1,8 @@ +############################################################################### +# +# Makefile for libxlsxwriter library. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +include ../Makefile.unit diff --git a/test/unit/rich_value_rel/main.c b/test/unit/rich_value_rel/main.c new file mode 100644 index 00000000..91ddf170 --- /dev/null +++ b/test/unit/rich_value_rel/main.c @@ -0,0 +1,15 @@ +/* + * Test runner for xmlwriter using ctest. + * + * Copyright 2014-2024 John McNamara, jmcnamara@cpan.org + * + */ +#define CTEST_MAIN + +#include "../ctest.h" + +int main(int argc, const char *argv[]) +{ + return ctest_main(argc, argv); +} + diff --git a/test/unit/rich_value_rel/test_rich_value_rel_xml_declaration.c b/test/unit/rich_value_rel/test_rich_value_rel_xml_declaration.c new file mode 100644 index 00000000..c7067928 --- /dev/null +++ b/test/unit/rich_value_rel/test_rich_value_rel_xml_declaration.c @@ -0,0 +1,28 @@ +/* + * Tests for the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "xlsxwriter/rich_value_rel.h" + +// Test _xml_declaration(). +CTEST(rich_value_rel, xml_declaration) { + + char* got; + char exp[] = "\n"; + FILE* testfile = tmpfile(); + + lxw_rich_value_rel *rich_value_rel = lxw_rich_value_rel_new(); + rich_value_rel->file = testfile; + + _rich_value_rel_xml_declaration(rich_value_rel); + + RUN_XLSX_STREQ(exp, got); + + lxw_rich_value_rel_free(rich_value_rel); +} diff --git a/test/unit/rich_value_structure/Makefile b/test/unit/rich_value_structure/Makefile new file mode 100644 index 00000000..2c05b8e2 --- /dev/null +++ b/test/unit/rich_value_structure/Makefile @@ -0,0 +1,8 @@ +############################################################################### +# +# Makefile for libxlsxwriter library. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +include ../Makefile.unit diff --git a/test/unit/rich_value_structure/main.c b/test/unit/rich_value_structure/main.c new file mode 100644 index 00000000..91ddf170 --- /dev/null +++ b/test/unit/rich_value_structure/main.c @@ -0,0 +1,15 @@ +/* + * Test runner for xmlwriter using ctest. + * + * Copyright 2014-2024 John McNamara, jmcnamara@cpan.org + * + */ +#define CTEST_MAIN + +#include "../ctest.h" + +int main(int argc, const char *argv[]) +{ + return ctest_main(argc, argv); +} + diff --git a/test/unit/rich_value_structure/test_rich_value_structure_xml_declaration.c b/test/unit/rich_value_structure/test_rich_value_structure_xml_declaration.c new file mode 100644 index 00000000..58250e34 --- /dev/null +++ b/test/unit/rich_value_structure/test_rich_value_structure_xml_declaration.c @@ -0,0 +1,28 @@ +/* + * Tests for the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "xlsxwriter/rich_value_structure.h" + +// Test _xml_declaration(). +CTEST(rich_value_structure, xml_declaration) { + + char* got; + char exp[] = "\n"; + FILE* testfile = tmpfile(); + + lxw_rich_value_structure *rich_value_structure = lxw_rich_value_structure_new(); + rich_value_structure->file = testfile; + + _rich_value_structure_xml_declaration(rich_value_structure); + + RUN_XLSX_STREQ(exp, got); + + lxw_rich_value_structure_free(rich_value_structure); +} diff --git a/test/unit/rich_value_types/Makefile b/test/unit/rich_value_types/Makefile new file mode 100644 index 00000000..2c05b8e2 --- /dev/null +++ b/test/unit/rich_value_types/Makefile @@ -0,0 +1,8 @@ +############################################################################### +# +# Makefile for libxlsxwriter library. +# +# Copyright 2014-2015, John McNamara, jmcnamara@cpan.org +# + +include ../Makefile.unit diff --git a/test/unit/rich_value_types/main.c b/test/unit/rich_value_types/main.c new file mode 100644 index 00000000..91ddf170 --- /dev/null +++ b/test/unit/rich_value_types/main.c @@ -0,0 +1,15 @@ +/* + * Test runner for xmlwriter using ctest. + * + * Copyright 2014-2024 John McNamara, jmcnamara@cpan.org + * + */ +#define CTEST_MAIN + +#include "../ctest.h" + +int main(int argc, const char *argv[]) +{ + return ctest_main(argc, argv); +} + diff --git a/test/unit/rich_value_types/test_rich_value_types_xml_declaration.c b/test/unit/rich_value_types/test_rich_value_types_xml_declaration.c new file mode 100644 index 00000000..2a035242 --- /dev/null +++ b/test/unit/rich_value_types/test_rich_value_types_xml_declaration.c @@ -0,0 +1,28 @@ +/* + * Tests for the libxlsxwriter library. + * + * Copyright 2014-2024, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "xlsxwriter/rich_value_types.h" + +// Test _xml_declaration(). +CTEST(rich_value_types, xml_declaration) { + + char* got; + char exp[] = "\n"; + FILE* testfile = tmpfile(); + + lxw_rich_value_types *rich_value_types = lxw_rich_value_types_new(); + rich_value_types->file = testfile; + + _rich_value_types_xml_declaration(rich_value_types); + + RUN_XLSX_STREQ(exp, got); + + lxw_rich_value_types_free(rich_value_types); +}