From 85dfccd73462e83eb7eca824fca35806ae558ce3 Mon Sep 17 00:00:00 2001 From: John McNamara Date: Fri, 27 Dec 2019 20:10:11 +0000 Subject: [PATCH] Added support for cell comments and options. Issue #38 --- .indent.pro | 5 + examples/comments1.c | 20 + include/xlsxwriter/comment.h | 76 ++ include/xlsxwriter/common.h | 3 + include/xlsxwriter/content_types.h | 4 + include/xlsxwriter/format.h | 2 + include/xlsxwriter/packager.h | 2 + include/xlsxwriter/vml.h | 55 + include/xlsxwriter/workbook.h | 2 + include/xlsxwriter/worksheet.h | 167 ++- src/comment.c | 443 ++++++++ src/content_types.c | 19 + src/format.c | 30 +- src/packager.c | 143 ++- src/styles.c | 36 +- src/utility.c | 1 + src/vml.c | 1001 +++++++++++++++++ src/workbook.c | 66 +- src/worksheet.c | 518 ++++++++- test/functional/src/test_comment01.c | 23 + test/functional/src/test_comment02.c | 24 + test/functional/src/test_comment03.c | 24 + test/functional/src/test_comment04.c | 31 + test/functional/src/test_comment05.c | 33 + test/functional/src/test_comment06.c | 31 + test/functional/src/test_comment07.c | 28 + test/functional/src/test_comment08.c | 31 + test/functional/src/test_comment09.c | 28 + test/functional/src/test_comment10.c | 25 + test/functional/src/test_comment11.c | 26 + test/functional/src/test_comment12.c | 26 + test/functional/src/test_comment13.c | 26 + test/functional/src/test_comment14.c | 25 + test/functional/test_comment.py | 57 + test/functional/xlsx_files/comment01.xlsx | Bin 0 -> 8828 bytes test/functional/xlsx_files/comment02.xlsx | Bin 0 -> 8916 bytes test/functional/xlsx_files/comment03.xlsx | Bin 0 -> 8955 bytes test/functional/xlsx_files/comment04.xlsx | Bin 0 -> 11207 bytes test/functional/xlsx_files/comment05.xlsx | Bin 0 -> 72801 bytes test/functional/xlsx_files/comment06.xlsx | Bin 0 -> 8693 bytes test/functional/xlsx_files/comment07.xlsx | Bin 0 -> 8663 bytes test/functional/xlsx_files/comment08.xlsx | Bin 0 -> 8693 bytes test/functional/xlsx_files/comment09.xlsx | Bin 0 -> 8602 bytes test/functional/xlsx_files/comment10.xlsx | Bin 0 -> 7379 bytes test/functional/xlsx_files/comment11.xlsx | Bin 0 -> 9258 bytes test/functional/xlsx_files/comment12.xlsx | Bin 0 -> 8877 bytes test/functional/xlsx_files/comment13.xlsx | Bin 0 -> 8835 bytes test/functional/xlsx_files/comment14.xlsx | Bin 0 -> 8886 bytes test/unit/Makefile | 6 + test/unit/comment/Makefile | 8 + test/unit/comment/main.c | 15 + .../comment/test_comment_xml_declaration.c | 28 + test/unit/vml/Makefile | 8 + test/unit/vml/main.c | 15 + 54 files changed, 3046 insertions(+), 65 deletions(-) create mode 100644 examples/comments1.c create mode 100644 include/xlsxwriter/comment.h create mode 100644 include/xlsxwriter/vml.h create mode 100644 src/comment.c create mode 100644 src/vml.c create mode 100644 test/functional/src/test_comment01.c create mode 100644 test/functional/src/test_comment02.c create mode 100644 test/functional/src/test_comment03.c create mode 100644 test/functional/src/test_comment04.c create mode 100644 test/functional/src/test_comment05.c create mode 100644 test/functional/src/test_comment06.c create mode 100644 test/functional/src/test_comment07.c create mode 100644 test/functional/src/test_comment08.c create mode 100644 test/functional/src/test_comment09.c create mode 100644 test/functional/src/test_comment10.c create mode 100644 test/functional/src/test_comment11.c create mode 100644 test/functional/src/test_comment12.c create mode 100644 test/functional/src/test_comment13.c create mode 100644 test/functional/src/test_comment14.c create mode 100644 test/functional/test_comment.py create mode 100644 test/functional/xlsx_files/comment01.xlsx create mode 100644 test/functional/xlsx_files/comment02.xlsx create mode 100644 test/functional/xlsx_files/comment03.xlsx create mode 100644 test/functional/xlsx_files/comment04.xlsx create mode 100644 test/functional/xlsx_files/comment05.xlsx create mode 100644 test/functional/xlsx_files/comment06.xlsx create mode 100644 test/functional/xlsx_files/comment07.xlsx create mode 100644 test/functional/xlsx_files/comment08.xlsx create mode 100644 test/functional/xlsx_files/comment09.xlsx create mode 100644 test/functional/xlsx_files/comment10.xlsx create mode 100644 test/functional/xlsx_files/comment11.xlsx create mode 100644 test/functional/xlsx_files/comment12.xlsx create mode 100644 test/functional/xlsx_files/comment13.xlsx create mode 100644 test/functional/xlsx_files/comment14.xlsx create mode 100644 test/unit/comment/Makefile create mode 100644 test/unit/comment/main.c create mode 100644 test/unit/comment/test_comment_xml_declaration.c create mode 100644 test/unit/vml/Makefile create mode 100644 test/unit/vml/main.c diff --git a/.indent.pro b/.indent.pro index dbe34c45..4fc5b915 100644 --- a/.indent.pro +++ b/.indent.pro @@ -44,6 +44,7 @@ /* libxlsxwriter typedefs. */ -T lxw_app +-T lxw_author_id -T lxw_autofilter -T lxw_border -T lxw_cell @@ -84,6 +85,8 @@ -T lxw_col_options -T lxw_col_t -T lxw_color_t +-T lxw_comment +-T lxw_comment_options -T lxw_content_types -T lxw_core -T lxw_custom @@ -132,6 +135,8 @@ -T lxw_styles -T lxw_theme -T lxw_tuple +-T lxw_vml +-T lxw_vml_obj -T lxw_workbook -T lxw_workbook_options -T lxw_worksheet diff --git a/examples/comments1.c b/examples/comments1.c new file mode 100644 index 00000000..578e4ba6 --- /dev/null +++ b/examples/comments1.c @@ -0,0 +1,20 @@ +/* + * An example of writing cell comments to a worksheet using libxlsxwriter. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("comments1.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_string( worksheet, 0, 0, "Hello" , NULL); + + worksheet_write_comment(worksheet, 0, 0, "This is a comment"); + + return workbook_close(workbook); +} diff --git a/include/xlsxwriter/comment.h b/include/xlsxwriter/comment.h new file mode 100644 index 00000000..45fde010 --- /dev/null +++ b/include/xlsxwriter/comment.h @@ -0,0 +1,76 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * comment - A libxlsxwriter library for creating Excel XLSX comment files. + * + */ +#ifndef __LXW_COMMENT_H__ +#define __LXW_COMMENT_H__ + +#include + +#include "common.h" +#include "worksheet.h" + +/* Define the tree.h RB structs for the red-black head types. */ +RB_HEAD(lxw_author_ids, lxw_author_id); + +/* + * Struct to represent a comment object. + */ +typedef struct lxw_comment { + + FILE *file; + struct lxw_comment_objs *comment_objs; + struct lxw_author_ids *author_ids; + char *comment_author; + uint32_t author_id; + +} lxw_comment; + +/* Struct to an author id */ +typedef struct lxw_author_id { + uint32_t id; + char *author; + + RB_ENTRY (lxw_author_id) tree_pointers; +} lxw_author_id; + +#define LXW_RB_GENERATE_AUTHOR_IDS(name, type, field, cmp) \ + RB_GENERATE_INSERT_COLOR(name, type, field, static) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ + RB_GENERATE_INSERT(name, type, field, cmp, static) \ + RB_GENERATE_REMOVE(name, type, field, static) \ + RB_GENERATE_FIND(name, type, field, cmp, static) \ + RB_GENERATE_NEXT(name, type, field, static) \ + RB_GENERATE_MINMAX(name, type, field, static) \ + /* Add unused struct to allow adding a semicolon */ \ + struct lxw_rb_generate_author_ids{int unused;} + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +lxw_comment *lxw_comment_new(); +void lxw_comment_free(lxw_comment *comment); +void lxw_comment_assemble_xml_file(lxw_comment *self); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +STATIC void _comment_xml_declaration(lxw_comment *self); + +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_COMMENT_H__ */ diff --git a/include/xlsxwriter/common.h b/include/xlsxwriter/common.h index e75ee594..d7a38fc3 100644 --- a/include/xlsxwriter/common.h +++ b/include/xlsxwriter/common.h @@ -94,6 +94,9 @@ typedef enum lxw_error { /** Unknown zip error when closing xlsx file. */ LXW_ERROR_ZIP_CLOSE, + /** Feature is not currently supported in this configuration. */ + LXW_ERROR_FEATURE_NOT_SUPPORTED, + /** NULL function parameter ignored. */ LXW_ERROR_NULL_PARAMETER_IGNORED, diff --git a/include/xlsxwriter/content_types.h b/include/xlsxwriter/content_types.h index 5881c7e5..5c77f8a0 100644 --- a/include/xlsxwriter/content_types.h +++ b/include/xlsxwriter/content_types.h @@ -53,6 +53,10 @@ void lxw_ct_add_chart_name(lxw_content_types *content_types, const char *name); void lxw_ct_add_drawing_name(lxw_content_types *content_types, const char *name); +void lxw_ct_add_comment_name(lxw_content_types *content_types, + const char *name); +void lxw_ct_add_vml_name(lxw_content_types *content_types); + 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); diff --git a/include/xlsxwriter/format.h b/include/xlsxwriter/format.h index bf3c034a..4dd39b4b 100644 --- a/include/xlsxwriter/format.h +++ b/include/xlsxwriter/format.h @@ -1206,6 +1206,8 @@ void format_set_font_extend(lxw_format *format); void format_set_reading_order(lxw_format *format, uint8_t value); void format_set_theme(lxw_format *format, uint8_t value); void format_set_hyperlink(lxw_format *format); +void format_set_color_indexed(lxw_format *format, uint8_t value); +void format_set_font_only(lxw_format *format); /* Declarations required for unit testing. */ #ifdef TESTING diff --git a/include/xlsxwriter/packager.h b/include/xlsxwriter/packager.h index a563570c..b1303d34 100644 --- a/include/xlsxwriter/packager.h +++ b/include/xlsxwriter/packager.h @@ -29,6 +29,8 @@ #include "format.h" #include "content_types.h" #include "relationships.h" +#include "vml.h" +#include "comment.h" #define LXW_ZIP_BUFFER_SIZE (16384) diff --git a/include/xlsxwriter/vml.h b/include/xlsxwriter/vml.h new file mode 100644 index 00000000..401fe1b3 --- /dev/null +++ b/include/xlsxwriter/vml.h @@ -0,0 +1,55 @@ +/* + * libxlsxwriter + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + * vml - A libxlsxwriter library for creating Excel XLSX vml files. + * + */ +#ifndef __LXW_VML_H__ +#define __LXW_VML_H__ + +#include + +#include "common.h" +#include "worksheet.h" + +/* + * Struct to represent a vml object. + */ +typedef struct lxw_vml { + + FILE *file; + uint8_t type; + struct lxw_comment_objs *button_objs; + struct lxw_comment_objs *comment_objs; + struct lxw_comment_objs *image_objs; + char *vml_data_id_str; + uint32_t vml_shape_id; + uint8_t comment_display_default; + +} lxw_vml; + + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +lxw_vml *lxw_vml_new(); +void lxw_vml_free(lxw_vml *vml); +void lxw_vml_assemble_xml_file(lxw_vml *self); + +/* Declarations required for unit testing. */ +#ifdef TESTING + +#endif /* TESTING */ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* __LXW_VML_H__ */ diff --git a/include/xlsxwriter/workbook.h b/include/xlsxwriter/workbook.h index 76bd96ec..32add9a0 100644 --- a/include/xlsxwriter/workbook.h +++ b/include/xlsxwriter/workbook.h @@ -298,6 +298,7 @@ typedef struct lxw_workbook { uint16_t num_xf_formats; uint16_t num_format_count; uint16_t drawing_count; + uint16_t comment_count; uint16_t font_count; uint16_t border_count; @@ -308,6 +309,7 @@ typedef struct lxw_workbook { uint8_t has_png; uint8_t has_jpeg; uint8_t has_bmp; + uint8_t has_vml; lxw_hash_table *used_xf_formats; diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 307a2ea5..2264f27d 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -55,6 +55,7 @@ #include "format.h" #include "styles.h" #include "utility.h" +#include "relationships.h" #define LXW_ROW_MAX 1048576 #define LXW_COL_MAX 16384 @@ -203,6 +204,20 @@ enum lxw_validation_error_types { LXW_VALIDATION_ERROR_TYPE_INFORMATION }; +/** Set the display type for a cell comment. This is hidden by default but + * can be set to visible with the `worksheet_show_comments()` function. */ +enum lxw_comment_display_types { + /** Default to the worksheet default which can be hidden or visible.*/ + LXW_COMMENT_DISPLAY_DEFAULT, + + /** Hide the cell comment. Usually the default. */ + LXW_COMMENT_DISPLAY_HIDDEN, + + /** Show the cell comment. Can also be set for the worksheet with the + * `worksheet_show_comments()` function.*/ + LXW_COMMENT_DISPLAY_VISIBLE +}; + enum cell_types { NUMBER_CELL = 1, STRING_CELL, @@ -260,14 +275,14 @@ struct lxw_table_rows { struct lxw_rb_generate_cell{int unused;} #define LXW_RB_GENERATE_DRAWING_REL_IDS(name, type, field, cmp) \ - RB_GENERATE_INSERT_COLOR(name, type, field, static) \ - RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ - RB_GENERATE_INSERT(name, type, field, cmp, static) \ - RB_GENERATE_REMOVE(name, type, field, static) \ - RB_GENERATE_FIND(name, type, field, cmp, static) \ - RB_GENERATE_NEXT(name, type, field, static) \ - RB_GENERATE_MINMAX(name, type, field, static) \ - /* Add unused struct to allow adding a semicolon */ \ + RB_GENERATE_INSERT_COLOR(name, type, field, static) \ + RB_GENERATE_REMOVE_COLOR(name, type, field, static) \ + RB_GENERATE_INSERT(name, type, field, cmp, static) \ + RB_GENERATE_REMOVE(name, type, field, static) \ + RB_GENERATE_FIND(name, type, field, cmp, static) \ + RB_GENERATE_NEXT(name, type, field, static) \ + RB_GENERATE_MINMAX(name, type, field, static) \ + /* Add unused struct to allow adding a semicolon */ \ struct lxw_rb_generate_drawing_rel_ids{int unused;} STAILQ_HEAD(lxw_merged_ranges, lxw_merged_range); @@ -275,6 +290,7 @@ STAILQ_HEAD(lxw_selections, lxw_selection); STAILQ_HEAD(lxw_data_validations, lxw_data_val_obj); STAILQ_HEAD(lxw_image_props, lxw_object_properties); STAILQ_HEAD(lxw_chart_props, lxw_object_properties); +STAILQ_HEAD(lxw_comment_objs, lxw_vml_obj); /** * @brief Options for rows and columns. @@ -658,6 +674,105 @@ typedef struct lxw_object_properties { STAILQ_ENTRY (lxw_object_properties) list_pointers; } lxw_object_properties; +/** + * @brief Options for inserted comments. + * + * Options for modifying comments inserted via `worksheet_write_comment_opt()`. + * + */ +typedef struct lxw_comment_options { + + /** This option is used to make a cell comment visible when the worksheet + * is opened. The default behavior in Excel is that comments are + * initially hidden. However, it is also possible in Excel to make + * individual comments or all comments visible. You can make all + * comments in the worksheet visible using the + * `worksheet_show_comments()` function. Defaults to LXW_FALSE.*/ + uint8_t visible; + + /** This option is used to set the row in which the comment will + * appear. By default Excel displays comments one cell to the right and + * one cell above the cell to which the comment relates. The `start_row` + * and `start_col` options should both be set to 0 if not used.*/ + lxw_row_t start_row; + + /** This option is used to set the column in which the comment will + * appear. See the `start_row` option for more information. */ + lxw_col_t start_col; + + /** This option is used to set the width of the cell comment box + * explicitly in pixels. The default width is 128 pixels. */ + uint16_t width; + + /** This option is used to set the height of the cell comment box + * explicitly in pixels. The default height is 74 pixels. */ + uint16_t height; + + /** X scale of the comment as a decimal. */ + double x_scale; + + /** Y scale of the comment as a decimal. */ + double y_scale; + + /** Offset from the left of the cell in pixels. */ + int32_t x_offset; + + /** Offset from the top of the cell in pixels. */ + int32_t y_offset; + + /** This option is used to set the background color of cell comment + * box. The color should be an RGB integer value, see @ref + * working_with_colors. */ + lxw_color_t color; + + /** This option is used to set the font for the comment. The default font + * is 'Tahoma'. */ + char *font_name; + + /** This option is used to set the font size for the comment. The default + * is 8. */ + double font_size; + + /** This option is used to set the font family number for the comment. + * Not required very often. Set to 0. */ + uint8_t font_family; + + /** This option is used to indicate the author of the cell comment. Excel + * displays the author in the status bar at the bottom of the + * worksheet. The default author for all cell comments in a worksheet can + * be set using the `worksheet_set_comments_author()` function. Set to + * NULL if not required.*/ + char *author; + +} lxw_comment_options; + +/* Internal structure for VML object options. */ +typedef struct lxw_vml_obj { + + lxw_row_t row; + lxw_col_t col; + lxw_row_t start_row; + lxw_col_t start_col; + int32_t x_offset; + int32_t y_offset; + uint32_t col_absolute; + uint32_t row_absolute; + uint32_t width; + uint32_t height; + lxw_color_t color; + uint8_t font_family; + uint8_t visible; + uint32_t author_id; + double font_size; + struct lxw_drawing_coords from; + struct lxw_drawing_coords to; + char *author; + char *font_name; + char *text; + STAILQ_ENTRY (lxw_vml_obj) list_pointers; + +} lxw_vml_obj; + /** * @brief Header and footer options. * @@ -790,6 +905,7 @@ typedef struct lxw_worksheet { struct lxw_image_props *image_props; struct lxw_chart_props *chart_data; struct lxw_drawing_rel_ids *drawing_rel_ids; + struct lxw_comment_objs *comment_objs; lxw_row_t dim_rowmin; lxw_row_t dim_rowmax; @@ -902,6 +1018,16 @@ typedef struct lxw_worksheet { lxw_drawing *drawing; lxw_format *default_url_format; + uint8_t has_vml; + uint8_t has_comments; + uint8_t has_header_vml; + lxw_rel_tuple *external_vml_comment_link; + lxw_rel_tuple *external_comment_link; + char *comment_author; + char *vml_data_id_str; + uint32_t vml_shape_id; + uint8_t comment_display_default; + STAILQ_ENTRY (lxw_worksheet) list_pointers; } lxw_worksheet; @@ -935,6 +1061,8 @@ typedef struct lxw_row { uint8_t row_changed; uint8_t data_changed; uint8_t height_changed; + uint8_t has_comments; + struct lxw_table_cells *cells; /* tree management pointers for tree.h. */ @@ -947,6 +1075,7 @@ typedef struct lxw_cell { lxw_col_t col_num; enum cell_types type; lxw_format *format; + lxw_vml_obj *comment; union { double number; @@ -1547,6 +1676,14 @@ lxw_error worksheet_write_rich_string(lxw_worksheet *worksheet, lxw_rich_string_tuple *rich_string[], lxw_format *format); +lxw_error worksheet_write_comment_opt(lxw_worksheet *worksheet, + lxw_row_t row_num, lxw_col_t col_num, + char *string, + lxw_comment_options *options); + +lxw_error worksheet_write_comment(lxw_worksheet *worksheet, + lxw_row_t row, lxw_col_t col, char *string); + /** * @brief Set the properties for a row of cells. * @@ -3417,6 +3554,11 @@ void worksheet_set_default_row(lxw_worksheet *worksheet, double height, */ lxw_error worksheet_set_vba_name(lxw_worksheet *worksheet, const char *name); +void worksheet_set_comments_author(lxw_worksheet *worksheet, + const char *author); + +void worksheet_show_comments(lxw_worksheet *worksheet); + lxw_worksheet *lxw_worksheet_new(lxw_worksheet_init_data *init_data); void lxw_worksheet_free(lxw_worksheet *worksheet); void lxw_worksheet_assemble_xml_file(lxw_worksheet *worksheet); @@ -3431,9 +3573,14 @@ void lxw_worksheet_prepare_chart(lxw_worksheet *worksheet, lxw_object_properties *object_props, uint8_t is_chartsheet); -lxw_row *lxw_worksheet_find_row(lxw_worksheet *worksheet, lxw_row_t row_num); -lxw_cell *lxw_worksheet_find_cell(lxw_row *row, lxw_col_t col_num); +uint32_t lxw_worksheet_prepare_vml_objects(lxw_worksheet *worksheet, + uint32_t vml_data_id, + uint32_t vml_shape_id, + uint32_t vml_drawing_id, + uint32_t comment_id); +lxw_row *lxw_worksheet_find_row(lxw_worksheet *worksheet, lxw_row_t row_num); +lxw_cell *lxw_worksheet_find_cell_in_row(lxw_row *row, lxw_col_t col_num); /* * External functions to call intern XML methods shared with chartsheet. */ diff --git a/src/comment.c b/src/comment.c new file mode 100644 index 00000000..12c31add --- /dev/null +++ b/src/comment.c @@ -0,0 +1,443 @@ +/***************************************************************************** + * comment - A library for creating Excel XLSX comment files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/comment.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +STATIC int _author_id_cmp(lxw_author_id *tuple1, lxw_author_id *tuple2); + +#ifndef __clang_analyzer__ +LXW_RB_GENERATE_AUTHOR_IDS(lxw_author_ids, lxw_author_id, + tree_pointers, _author_id_cmp); +#endif + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Comparator for the author ids. + */ +STATIC int +_author_id_cmp(lxw_author_id *author_id1, lxw_author_id *author_id2) +{ + return strcmp(author_id1->author, author_id2->author); +} + +/* + * Check if an author already existing in the author/id table. + */ +STATIC uint8_t +_check_author(lxw_comment *self, char *author) +{ + lxw_author_id tmp_author_id; + lxw_author_id *existing_author = NULL; + + if (!author) + return LXW_TRUE; + + tmp_author_id.author = author; + existing_author = RB_FIND(lxw_author_ids, + self->author_ids, &tmp_author_id); + + if (existing_author) + return LXW_TRUE; + else + return LXW_FALSE; +} + +/* + * Get the index used for an author name. + */ +STATIC uint32_t +_get_author_index(lxw_comment *self, char *author) +{ + lxw_author_id tmp_author_id; + lxw_author_id *existing_author = NULL; + lxw_author_id *new_author_id = NULL; + + if (!author) + return 0; + + tmp_author_id.author = author; + existing_author = RB_FIND(lxw_author_ids, + self->author_ids, &tmp_author_id); + + if (existing_author) { + return existing_author->id; + } + else { + new_author_id = calloc(1, sizeof(lxw_author_id)); + + if (new_author_id) { + new_author_id->id = self->author_id; + new_author_id->author = lxw_strdup(author); + self->author_id++; + + RB_INSERT(lxw_author_ids, self->author_ids, new_author_id); + + return new_author_id->id; + } + else { + return 0; + } + } +} + +/* + * Create a new comment object. + */ +lxw_comment * +lxw_comment_new() +{ + lxw_comment *comment = calloc(1, sizeof(lxw_comment)); + GOTO_LABEL_ON_MEM_ERROR(comment, mem_error); + + comment->author_ids = calloc(1, sizeof(struct lxw_author_ids)); + GOTO_LABEL_ON_MEM_ERROR(comment->author_ids, mem_error); + RB_INIT(comment->author_ids); + + return comment; + +mem_error: + lxw_comment_free(comment); + return NULL; +} + +/* + * Free a comment object. + */ +void +lxw_comment_free(lxw_comment *comment) +{ + struct lxw_author_id *author_id; + struct lxw_author_id *next_author_id; + + if (!comment) + return; + + if (comment->author_ids) { + for (author_id = + RB_MIN(lxw_author_ids, comment->author_ids); + author_id; author_id = next_author_id) { + + next_author_id = + RB_NEXT(lxw_author_ids, worksheet->author_id, author_id); + RB_REMOVE(lxw_author_ids, comment->author_ids, author_id); + free(author_id->author); + free(author_id); + } + + free(comment->author_ids); + } + + free(comment); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ + +/* + * Write the XML declaration. + */ +STATIC void +_comment_xml_declaration(lxw_comment *self) +{ + lxw_xml_declaration(self->file); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Write the element. + */ +STATIC void +_comment_write_text_t(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + lxw_xml_data_element(self->file, "t", comment_obj->text, NULL); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_family(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("val", comment_obj->font_family); + + lxw_xml_empty_tag(self->file, "family", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_r_font(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char font_name[LXW_ATTR_32]; + + if (comment_obj->font_name) + lxw_snprintf(font_name, LXW_ATTR_32, "%s", comment_obj->font_name); + else + lxw_snprintf(font_name, LXW_MAX_ATTRIBUTE_LENGTH, "Tahoma"); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("val", font_name); + + lxw_xml_empty_tag(self->file, "rFont", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_color(lxw_comment *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char indexed[] = "81"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("indexed", indexed); + + lxw_xml_empty_tag(self->file, "color", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_sz(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_DBL("val", comment_obj->font_size); + + lxw_xml_empty_tag(self->file, "sz", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_r_pr(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + lxw_xml_start_tag(self->file, "rPr", NULL); + + /* Write the sz element. */ + _comment_write_sz(self, comment_obj); + + /* Write the color element. */ + _comment_write_color(self); + + /* Write the rFont element. */ + _comment_write_r_font(self, comment_obj); + + /* Write the family element. */ + _comment_write_family(self, comment_obj); + + lxw_xml_end_tag(self->file, "rPr"); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_r(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + lxw_xml_start_tag(self->file, "r", NULL); + + /* Write the rPr element. */ + _comment_write_r_pr(self, comment_obj); + + /* Write the t element. */ + _comment_write_text_t(self, comment_obj); + + lxw_xml_end_tag(self->file, "r"); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_text(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + lxw_xml_start_tag(self->file, "text", NULL); + + /* Write the r element. */ + _comment_write_r(self, comment_obj); + + lxw_xml_end_tag(self->file, "text"); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_comment(lxw_comment *self, lxw_vml_obj *comment_obj) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char ref[LXW_MAX_CELL_NAME_LENGTH]; + + lxw_rowcol_to_cell(ref, comment_obj->row, comment_obj->col); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("ref", ref); + LXW_PUSH_ATTRIBUTES_INT("authorId", comment_obj->author_id); + + lxw_xml_start_tag(self->file, "comment", &attributes); + + /* Write the text element. */ + _comment_write_text(self, comment_obj); + + lxw_xml_end_tag(self->file, "comment"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_comment_list(lxw_comment *self) +{ + lxw_vml_obj *comment_obj; + + lxw_xml_start_tag(self->file, "commentList", NULL); + + STAILQ_FOREACH(comment_obj, self->comment_objs, list_pointers) { + /* Write the comment element. */ + _comment_write_comment(self, comment_obj); + } + + lxw_xml_end_tag(self->file, "commentList"); + +} + +/* + * Write the element. + */ +STATIC void +_comment_write_author(lxw_comment *self, char *author) +{ + lxw_xml_data_element(self->file, "author", author, NULL); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_authors(lxw_comment *self) +{ + lxw_vml_obj *comment_obj; + char *author; + + lxw_xml_start_tag(self->file, "authors", NULL); + + /* Set the default author (from worksheet_set_comments_author()). */ + if (self->comment_author) { + _get_author_index(self, self->comment_author); + _comment_write_author(self, self->comment_author); + } + else { + _get_author_index(self, ""); + _comment_write_author(self, ""); + } + + STAILQ_FOREACH(comment_obj, self->comment_objs, list_pointers) { + author = comment_obj->author; + + if (author) { + + if (!_check_author(self, author)) + _comment_write_author(self, author); + + comment_obj->author_id = _get_author_index(self, author); + } + } + + lxw_xml_end_tag(self->file, "authors"); +} + +/* + * Write the element. + */ +STATIC void +_comment_write_comments(lxw_comment *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns[] = + "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns", xmlns); + + lxw_xml_start_tag(self->file, "comments", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Assemble and write the XML file. + */ +void +lxw_comment_assemble_xml_file(lxw_comment *self) +{ + /* Write the XML declaration. */ + _comment_xml_declaration(self); + + /* Write the comments element. */ + _comment_write_comments(self); + + /* Write the authors element. */ + _comment_write_authors(self); + + /* Write the commentList element. */ + _comment_write_comment_list(self); + + lxw_xml_end_tag(self->file, "comments"); +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ diff --git a/src/content_types.c b/src/content_types.c index fcbdf529..3bf3b238 100644 --- a/src/content_types.c +++ b/src/content_types.c @@ -322,6 +322,25 @@ lxw_ct_add_drawing_name(lxw_content_types *self, const char *name) lxw_ct_add_override(self, name, LXW_APP_DOCUMENT "drawing+xml"); } +/* + * Add the name of a VML drawing to the ContentTypes overrides. + */ +void +lxw_ct_add_vml_name(lxw_content_types *self) +{ + lxw_ct_add_default(self, "vml", LXW_APP_DOCUMENT "vmlDrawing"); +} + +/* + * Add the name of a comment to the ContentTypes overrides. + */ +void +lxw_ct_add_comment_name(lxw_content_types *self, const char *name) +{ + lxw_ct_add_override(self, name, + LXW_APP_DOCUMENT "spreadsheetml.comments+xml"); +} + /* * Add the sharedStrings link to the ContentTypes overrides. */ diff --git a/src/format.c b/src/format.c index a52a23c2..fc905442 100644 --- a/src/format.c +++ b/src/format.c @@ -117,18 +117,6 @@ lxw_format_free(lxw_format *format) format = NULL; } -/* - * Check a user input color. - */ -lxw_color_t -lxw_format_check_colorz(lxw_color_t color) -{ - if (color == LXW_COLOR_UNSET) - return color; - else - return color & LXW_COLOR_MASK; -} - /* * Check a user input border. */ @@ -728,6 +716,24 @@ format_set_theme(lxw_format *self, uint8_t value) self->theme = value; } +/* + * Set the color_indexed property. + */ +void +format_set_color_indexed(lxw_format *self, uint8_t value) +{ + self->color_indexed = value; +} + +/* + * Set the font_only property. + */ +void +format_set_font_only(lxw_format *self) +{ + self->font_only = LXW_TRUE; +} + /* * Set the theme property. */ diff --git a/src/packager.c b/src/packager.c index 6bfcaf1f..c80f14e0 100644 --- a/src/packager.c +++ b/src/packager.c @@ -445,6 +445,120 @@ _get_drawing_count(lxw_packager *self) return drawing_count; } +/* + * Write the comment/header VML files. + */ +STATIC lxw_error +_write_vml_files(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + lxw_vml *vml; + char filename[LXW_FILENAME_LENGTH] = { 0 }; + uint32_t index = 1; + lxw_error err; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + + if (!worksheet->has_vml) + continue; + + vml = lxw_vml_new(); + if (!vml) + return LXW_ERROR_MEMORY_MALLOC_FAILED; + + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "xl/drawings/vmlDrawing%d.vml", index++); + + vml->file = lxw_tmpfile(self->tmpdir); + if (!vml->file) { + lxw_vml_free(vml); + return LXW_ERROR_CREATING_TMPFILE; + } + + vml->comment_objs = worksheet->comment_objs; + vml->vml_shape_id = worksheet->vml_shape_id; + vml->comment_display_default = worksheet->comment_display_default; + + if (worksheet->vml_data_id_str) { + vml->vml_data_id_str = worksheet->vml_data_id_str; + } + else { + fclose(vml->file); + lxw_vml_free(vml); + return LXW_ERROR_MEMORY_MALLOC_FAILED; + } + + lxw_vml_assemble_xml_file(vml); + + err = _add_file_to_zip(self, vml->file, filename); + + fclose(vml->file); + lxw_vml_free(vml); + + RETURN_ON_ERROR(err); + } + + return LXW_NO_ERROR; +} + +/* + * Write the comment files. + */ +STATIC lxw_error +_write_comment_files(lxw_packager *self) +{ + lxw_workbook *workbook = self->workbook; + lxw_sheet *sheet; + lxw_worksheet *worksheet; + lxw_comment *comment; + char filename[LXW_FILENAME_LENGTH] = { 0 }; + uint32_t index = 1; + lxw_error err; + + STAILQ_FOREACH(sheet, workbook->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + + if (!worksheet->has_comments) + continue; + + comment = lxw_comment_new(); + if (!comment) + return LXW_ERROR_MEMORY_MALLOC_FAILED; + + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "xl/comments%d.xml", index++); + + comment->file = lxw_tmpfile(self->tmpdir); + if (!comment->file) { + lxw_comment_free(comment); + return LXW_ERROR_CREATING_TMPFILE; + } + + comment->comment_objs = worksheet->comment_objs; + comment->comment_author = worksheet->comment_author; + + lxw_comment_assemble_xml_file(comment); + + err = _add_file_to_zip(self, comment->file, filename); + + fclose(comment->file); + lxw_comment_free(comment); + + RETURN_ON_ERROR(err); + } + + return LXW_NO_ERROR; +} + /* * Write the sharedStrings.xml file. */ @@ -792,6 +906,15 @@ _write_content_types_file(lxw_packager *self) lxw_ct_add_drawing_name(content_types, filename); } + if (workbook->has_vml) + lxw_ct_add_vml_name(content_types); + + for (index = 1; index <= workbook->comment_count; index++) { + lxw_snprintf(filename, LXW_FILENAME_LENGTH, + "/xl/comments%d.xml", index); + lxw_ct_add_comment_name(content_types, filename); + } + if (workbook->sst->string_count) lxw_ct_add_shared_strings(content_types); @@ -898,7 +1021,9 @@ _write_worksheet_rels_file(lxw_packager *self) index++; if (STAILQ_EMPTY(worksheet->external_hyperlinks) && - STAILQ_EMPTY(worksheet->external_drawing_links)) + STAILQ_EMPTY(worksheet->external_drawing_links) && + !worksheet->external_vml_comment_link && + !worksheet->external_comment_link) continue; rels = lxw_relationships_new(); @@ -919,6 +1044,16 @@ _write_worksheet_rels_file(lxw_packager *self) rel->target_mode); } + rel = worksheet->external_vml_comment_link; + if (rel) + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + + rel = worksheet->external_comment_link; + if (rel) + lxw_add_worksheet_relationship(rels, rel->type, rel->target, + rel->target_mode); + lxw_snprintf(sheetname, LXW_FILENAME_LENGTH, "xl/worksheets/_rels/sheet%d.xml.rels", index); @@ -1228,6 +1363,12 @@ lxw_create_package(lxw_packager *self) error = _write_drawing_files(self); RETURN_ON_ERROR(error); + error = _write_vml_files(self); + RETURN_ON_ERROR(error); + + error = _write_comment_files(self); + RETURN_ON_ERROR(error); + error = _write_shared_strings_file(self); RETURN_ON_ERROR(error); diff --git a/src/styles.c b/src/styles.c index 84efc867..61095b48 100644 --- a/src/styles.c +++ b/src/styles.c @@ -240,6 +240,23 @@ _write_font_color_rgb(lxw_styles *self, int32_t rgb) LXW_FREE_ATTRIBUTES(); } +/* + * Write the element for indexed colors. + */ +STATIC void +_write_font_color_indexed(lxw_styles *self, uint8_t index) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_INT("indexed", index); + + lxw_xml_empty_tag(self->file, "color", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + /* * Write the element. */ @@ -386,6 +403,8 @@ _write_font(lxw_styles *self, lxw_format *format, uint8_t is_rich_string) if (format->theme) _write_font_color_theme(self, format->theme); + else if (format->color_indexed) + _write_font_color_indexed(self, format->color_indexed); else if (format->font_color != LXW_COLOR_UNSET) _write_font_color_rgb(self, format->font_color); else @@ -1051,14 +1070,27 @@ _write_cell_xfs(lxw_styles *self) struct xml_attribute_list attributes; struct xml_attribute *attribute; lxw_format *format; + uint32_t count = self->xf_count; + uint32_t i = 0; + + /* If the last format is "font_only" it is for the comment font and + * shouldn't be counted. This is a workaround to get the last object + * in the list since STAILQ_LAST() requires __containerof and isn't + * ANSI compatible. */ + STAILQ_FOREACH(format, self->xf_formats, list_pointers) { + i++; + if (i == self->xf_count && format->font_only) + count--; + } LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_INT("count", self->xf_count); + LXW_PUSH_ATTRIBUTES_INT("count", count); lxw_xml_start_tag(self->file, "cellXfs", &attributes); STAILQ_FOREACH(format, self->xf_formats, list_pointers) { - _write_xf(self, format); + if (!format->font_only) + _write_xf(self, format); } lxw_xml_end_tag(self->file, "cellXfs"); diff --git a/src/utility.c b/src/utility.c index 564acc11..e2ce5c1e 100644 --- a/src/utility.c +++ b/src/utility.c @@ -27,6 +27,7 @@ char *error_strings[LXW_MAX_ERRNO + 1] = { "Zip error ZIP_INTERNALERROR while creating the xlsx file.", "File error or unknown zip error when adding sub file to xlsx file.", "Unknown zip error when closing xlsx file.", + "Feature is not currently supported in this configuration.", "NULL function parameter ignored.", "Function parameter validation error.", "Worksheet name exceeds Excel's limit of 31 characters.", diff --git a/src/vml.c b/src/vml.c new file mode 100644 index 00000000..78b95b24 --- /dev/null +++ b/src/vml.c @@ -0,0 +1,1001 @@ +/***************************************************************************** + * vml - A library for creating Excel XLSX vml files. + * + * Used in conjunction with the libxlsxwriter library. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org. See LICENSE.txt. + * + */ + +#include "xlsxwriter/xmlwriter.h" +#include "xlsxwriter/vml.h" +#include "xlsxwriter/utility.h" + +/* + * Forward declarations. + */ + +/***************************************************************************** + * + * Private functions. + * + ****************************************************************************/ + +/* + * Create a new vml object. + */ +lxw_vml * +lxw_vml_new() +{ + lxw_vml *vml = calloc(1, sizeof(lxw_vml)); + GOTO_LABEL_ON_MEM_ERROR(vml, mem_error); + + return vml; + +mem_error: + lxw_vml_free(vml); + return NULL; +} + +/* + * Free a vml object. + */ +void +lxw_vml_free(lxw_vml *vml) +{ + if (!vml) + return; + + free(vml); +} + +/***************************************************************************** + * + * XML functions. + * + ****************************************************************************/ +/* + * Write the element. + */ +STATIC void +_vml_write_visible(lxw_vml *self) +{ + lxw_xml_empty_tag(self->file, "x:Visible", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_formula(lxw_vml *self, char *equation) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("eqn", equation); + + lxw_xml_empty_tag(self->file, "v:f", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_formulas(lxw_vml *self) +{ + lxw_xml_start_tag(self->file, "v:formulas", NULL); + + _vml_write_formula(self, "if lineDrawn pixelLineWidth 0"); + _vml_write_formula(self, "sum @0 1 0"); + _vml_write_formula(self, "sum 0 0 @1"); + _vml_write_formula(self, "prod @2 1 2"); + _vml_write_formula(self, "prod @3 21600 pixelWidth"); + _vml_write_formula(self, "prod @3 21600 pixelHeight"); + _vml_write_formula(self, "sum @0 0 1"); + _vml_write_formula(self, "prod @6 1 2"); + _vml_write_formula(self, "prod @7 21600 pixelWidth"); + _vml_write_formula(self, "sum @8 21600 0"); + _vml_write_formula(self, "prod @7 21600 pixelHeight"); + _vml_write_formula(self, "sum @10 21600 0"); + + lxw_xml_end_tag(self->file, "v:formulas"); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_text_halign(lxw_vml *self) +{ + + lxw_xml_data_element(self->file, "x:TextHAlign", "Center", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_text_valign(lxw_vml *self) +{ + lxw_xml_data_element(self->file, "x:TextVAlign", "Center", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_fmla_macro(lxw_vml *self) +{ + lxw_xml_data_element(self->file, "x:FmlaMacro", "[0]!Button1_Click", + NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_print_object(lxw_vml *self) +{ + lxw_xml_data_element(self->file, "x:PrintObject", "False", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_aspect_ratio_lock(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("v:ext", "edit"); + LXW_PUSH_ATTRIBUTES_STR("aspectratio", "t"); + + lxw_xml_empty_tag(self->file, "o:lock", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_rotation_lock(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("v:ext", "edit"); + LXW_PUSH_ATTRIBUTES_STR("rotation", "t"); + + lxw_xml_empty_tag(self->file, "o:lock", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_column(lxw_vml *self, lxw_vml_obj *comment_obj) +{ + char data[LXW_ATTR_32]; + + lxw_snprintf(data, LXW_ATTR_32, "%d", comment_obj->col); + + lxw_xml_data_element(self->file, "x:Column", data, NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_row(lxw_vml *self, lxw_vml_obj *comment_obj) +{ + char data[LXW_ATTR_32]; + + lxw_snprintf(data, LXW_ATTR_32, "%d", comment_obj->row); + + lxw_xml_data_element(self->file, "x:Row", data, NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_auto_fill(lxw_vml *self) +{ + lxw_xml_data_element(self->file, "x:AutoFill", "False", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_anchor(lxw_vml *self, lxw_vml_obj *comment_obj) +{ + char anchor_data[LXW_MAX_ATTRIBUTE_LENGTH]; + + lxw_snprintf(anchor_data, + LXW_MAX_ATTRIBUTE_LENGTH, + "%d, %d, %d, %d, %d, %d, %d, %d", + comment_obj->from.col, + (uint32_t) comment_obj->from.col_offset, + comment_obj->from.row, + (uint32_t) comment_obj->from.row_offset, + comment_obj->to.col, + (uint32_t) comment_obj->to.col_offset, + comment_obj->to.row, (uint32_t) comment_obj->to.row_offset); + + lxw_xml_data_element(self->file, "x:Anchor", anchor_data, NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_size_with_cells(lxw_vml *self) +{ + lxw_xml_empty_tag(self->file, "x:SizeWithCells", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_move_with_cells(lxw_vml *self) +{ + lxw_xml_empty_tag(self->file, "x:MoveWithCells", NULL); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_shadow(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("on", "t"); + LXW_PUSH_ATTRIBUTES_STR("color", "black"); + LXW_PUSH_ATTRIBUTES_STR("obscured", "t"); + + lxw_xml_empty_tag(self->file, "v:shadow", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_stroke(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("joinstyle", "miter"); + + lxw_xml_empty_tag(self->file, "v:stroke", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_shapetype_lock(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("v:ext", "edit"); + LXW_PUSH_ATTRIBUTES_STR("shapetype", "t"); + + lxw_xml_empty_tag(self->file, "o:lock", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_font(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("face", "Calibri"); + LXW_PUSH_ATTRIBUTES_STR("size", "220"); + LXW_PUSH_ATTRIBUTES_STR("color", "#000000"); + + lxw_xml_data_element(self->file, "font", "Button 1", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_imagedata(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char rel_id[] = "rId1"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("o:relid", rel_id); + LXW_PUSH_ATTRIBUTES_STR("o:title", "red"); + + lxw_xml_empty_tag(self->file, "v:imagedata", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_image_path(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("o:extrusionok", "f"); + LXW_PUSH_ATTRIBUTES_STR("gradientshapeok", "t"); + LXW_PUSH_ATTRIBUTES_STR("o:connecttype", "rect"); + + lxw_xml_empty_tag(self->file, "v:path", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_image_shape(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char id[] = "CH"; + char o_spid[] = "_x0000_s1025"; + char type[] = "#_x0000_t75"; + char style[] = "position:absolute;margin-left:0;margin-top:0;width:24pt;" + "height:24pt;z-index:1"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("o:spid", o_spid); + LXW_PUSH_ATTRIBUTES_STR("type", type); + LXW_PUSH_ATTRIBUTES_STR("style", style); + + lxw_xml_start_tag(self->file, "v:shape", &attributes); + + /* Write the v:imagedata element. */ + _vml_write_imagedata(self); + + /* Write the o:lock element. */ + _vml_write_rotation_lock(self); + + lxw_xml_end_tag(self->file, "v:shape"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for images.. + */ +STATIC void +_vml_write_image_shapetype(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char id[] = "_x0000_t75"; + char coordsize[] = "21600,21600"; + char o_spt[] = "75"; + char o_preferrelative[] = "t"; + char path[] = "m@4@5l@4@11@9@11@9@5xe"; + char filled[] = "f"; + char stroked[] = "f"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("coordsize", coordsize); + LXW_PUSH_ATTRIBUTES_STR("o:spt", o_spt); + LXW_PUSH_ATTRIBUTES_STR("o:preferrelative", o_preferrelative); + LXW_PUSH_ATTRIBUTES_STR("path", path); + LXW_PUSH_ATTRIBUTES_STR("filled", filled); + LXW_PUSH_ATTRIBUTES_STR("stroked", stroked); + + lxw_xml_start_tag(self->file, "v:shapetype", &attributes); + + /* Write the v:stroke element. */ + _vml_write_stroke(self); + + /* Write the v:formulas element. */ + _vml_write_formulas(self); + + /* Write the v:path element. */ + _vml_write_image_path(self); + + /* Write the o:lock element. */ + _vml_write_aspect_ratio_lock(self); + + lxw_xml_end_tag(self->file, "v:shapetype"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_button_client_data(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("ObjectType", "Button"); + + lxw_xml_start_tag(self->file, "x:ClientData", &attributes); + + /* Write the element. */ + _vml_write_anchor(self, NULL); + + /* Write the x:PrintObject element. */ + _vml_write_print_object(self); + + /* Write the x:AutoFill element. */ + _vml_write_auto_fill(self); + + /* Write the x:FmlaMacro element. */ + _vml_write_fmla_macro(self); + + /* Write the x:TextHAlign element. */ + _vml_write_text_halign(self); + + /* Write the x:TextVAlign element. */ + _vml_write_text_valign(self); + + lxw_xml_end_tag(self->file, "x:ClientData"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the
element. + */ +STATIC void +_vml_write_button_div(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("style", "text-align:center"); + + lxw_xml_start_tag(self->file, "div", &attributes); + + /* Write the font element. */ + _vml_write_font(self); + + lxw_xml_end_tag(self->file, "div"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_button_textbox(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("style", "mso-direction-alt:auto"); + LXW_PUSH_ATTRIBUTES_STR("o:singleclick", "f"); + + lxw_xml_start_tag(self->file, "v:textbox", &attributes); + + /* Write the div element. */ + _vml_write_button_div(self); + + lxw_xml_end_tag(self->file, "v:textbox"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_button_fill(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("color2", "buttonFace [67]"); + LXW_PUSH_ATTRIBUTES_STR("o:detectmouseclick", "t"); + + lxw_xml_empty_tag(self->file, "v:fill", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for buttons. + */ +STATIC void +_vml_write_button_path(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("shadowok", "f"); + LXW_PUSH_ATTRIBUTES_STR("o:extrusionok", "f"); + LXW_PUSH_ATTRIBUTES_STR("strokeok", "f"); + LXW_PUSH_ATTRIBUTES_STR("fillok", "f"); + LXW_PUSH_ATTRIBUTES_STR("o:connecttype", "rect"); + + lxw_xml_empty_tag(self->file, "v:path", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for buttons. + */ +STATIC void +_vml_write_button_shape(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char id[] = "_x0000_s1025"; + char type[] = "#_x0000_t201"; + char style[] = "position:absolute;margin-left:96pt;margin-top:15pt;" + "width:48pt;height:15pt;z-index:1;mso-wrap-style:tight"; + char o_button[] = "t"; + char fillcolor[] = "buttonFace [67]"; + char strokecolor[] = "windowText [64]"; + char o_insetmode[] = "auto"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("type", type); + LXW_PUSH_ATTRIBUTES_STR("style", style); + LXW_PUSH_ATTRIBUTES_STR("o:button", o_button); + LXW_PUSH_ATTRIBUTES_STR("fillcolor", fillcolor); + LXW_PUSH_ATTRIBUTES_STR("strokecolor", strokecolor); + LXW_PUSH_ATTRIBUTES_STR("o:insetmode", o_insetmode); + + lxw_xml_start_tag(self->file, "v:shape", &attributes); + + /* Write the v:fill element. */ + _vml_write_button_fill(self); + + /* Write the o:lock element. */ + _vml_write_rotation_lock(self); + + /* Write the v:textbox element. */ + _vml_write_button_textbox(self); + + /* Write the x:ClientData element. */ + _vml_write_button_client_data(self); + + lxw_xml_end_tag(self->file, "v:shape"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for buttons. + */ +STATIC void +_vml_write_button_shapetype(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char id[] = "_x0000_t201"; + char coordsize[] = "21600,21600"; + char o_spt[] = "201"; + char path[] = "m,l,21600r21600,l21600,xe"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("coordsize", coordsize); + LXW_PUSH_ATTRIBUTES_STR("o:spt", o_spt); + LXW_PUSH_ATTRIBUTES_STR("path", path); + + lxw_xml_start_tag(self->file, "v:shapetype", &attributes); + + /* Write the v:stroke element. */ + _vml_write_stroke(self); + + /* Write the v:path element. */ + _vml_write_button_path(self); + + /* Write the o:lock element. */ + _vml_write_shapetype_lock(self); + + lxw_xml_end_tag(self->file, "v:shapetype"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_comment_client_data(lxw_vml *self, lxw_vml_obj *comment_obj) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("ObjectType", "Note"); + + lxw_xml_start_tag(self->file, "x:ClientData", &attributes); + + /* Write the element. */ + _vml_write_move_with_cells(self); + + /* Write the element. */ + _vml_write_size_with_cells(self); + + /* Write the element. */ + _vml_write_anchor(self, comment_obj); + + /* Write the element. */ + _vml_write_auto_fill(self); + + /* Write the element. */ + _vml_write_row(self, comment_obj); + + /* Write the element. */ + _vml_write_column(self, comment_obj); + + /* Write the x:Visible element. */ + if (comment_obj->visible == LXW_COMMENT_DISPLAY_VISIBLE) + _vml_write_visible(self); + + lxw_xml_end_tag(self->file, "x:ClientData"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the
element. + */ +STATIC void +_vml_write_comment_div(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("style", "text-align:left"); + + lxw_xml_start_tag(self->file, "div", &attributes); + + lxw_xml_end_tag(self->file, "div"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_comment_textbox(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("style", "mso-direction-alt:auto"); + + lxw_xml_start_tag(self->file, "v:textbox", &attributes); + + /* Write the div element. */ + _vml_write_comment_div(self); + + lxw_xml_end_tag(self->file, "v:textbox"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_comment_fill(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("color2", "#ffffe1"); + + lxw_xml_empty_tag(self->file, "v:fill", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_comment_path(lxw_vml *self, uint8_t has_gradient, char *type) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + + if (has_gradient) + LXW_PUSH_ATTRIBUTES_STR("gradientshapeok", "t"); + + LXW_PUSH_ATTRIBUTES_STR("o:connecttype", type); + + lxw_xml_empty_tag(self->file, "v:path", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for comments. + */ +STATIC void +_vml_write_comment_shape(lxw_vml *self, uint32_t vml_shape_id, + uint32_t z_index, lxw_vml_obj *comment_obj) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char id[LXW_ATTR_32]; + char margin_left[LXW_ATTR_32]; + char margin_top[LXW_ATTR_32]; + char width[LXW_ATTR_32]; + char height[LXW_ATTR_32]; + char visible[LXW_ATTR_32]; + char fillcolor[LXW_ATTR_32]; + char style[LXW_MAX_ATTRIBUTE_LENGTH]; + char type[] = "#_x0000_t202"; + char o_insetmode[] = "auto"; + + lxw_sprintf_dbl(margin_left, comment_obj->col_absolute * 0.75); + lxw_sprintf_dbl(margin_top, comment_obj->row_absolute * 0.75); + lxw_sprintf_dbl(width, comment_obj->width * 0.75); + lxw_sprintf_dbl(height, comment_obj->height * 0.75); + + lxw_snprintf(id, LXW_ATTR_32, "_x0000_s%d", vml_shape_id); + + if (comment_obj->visible == LXW_COMMENT_DISPLAY_DEFAULT) + comment_obj->visible = self->comment_display_default; + + if (comment_obj->visible == LXW_COMMENT_DISPLAY_VISIBLE) + lxw_snprintf(visible, LXW_ATTR_32, "visible"); + else + lxw_snprintf(visible, LXW_ATTR_32, "hidden"); + + if (comment_obj->color) + lxw_snprintf(fillcolor, LXW_ATTR_32, "#%06x", + comment_obj->color & LXW_COLOR_MASK); + else + lxw_snprintf(fillcolor, LXW_ATTR_32, "#%06x", 0xffffe1); + + lxw_snprintf(style, + LXW_MAX_ATTRIBUTE_LENGTH, + "position:absolute;" + "margin-left:%spt;" + "margin-top:%spt;" + "width:%spt;" + "height:%spt;" + "z-index:%d;" + "visibility:%s", + margin_left, margin_top, width, height, z_index, visible); + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("type", type); + LXW_PUSH_ATTRIBUTES_STR("style", style); + LXW_PUSH_ATTRIBUTES_STR("fillcolor", fillcolor); + LXW_PUSH_ATTRIBUTES_STR("o:insetmode", o_insetmode); + + lxw_xml_start_tag(self->file, "v:shape", &attributes); + + /* Write the v:fill element. */ + _vml_write_comment_fill(self); + + /* Write the v:shadow element. */ + _vml_write_shadow(self); + + /* Write the v:path element. */ + _vml_write_comment_path(self, LXW_FALSE, "none"); + + /* Write the v:textbox element. */ + _vml_write_comment_textbox(self); + + /* Write the x:ClientData element. */ + _vml_write_comment_client_data(self, comment_obj); + + lxw_xml_end_tag(self->file, "v:shape"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element for comments. + */ +STATIC void +_vml_write_comment_shapetype(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char id[] = "_x0000_t202"; + char coordsize[] = "21600,21600"; + char o_spt[] = "202"; + char path[] = "m,l,21600r21600,l21600,xe"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("id", id); + LXW_PUSH_ATTRIBUTES_STR("coordsize", coordsize); + LXW_PUSH_ATTRIBUTES_STR("o:spt", o_spt); + LXW_PUSH_ATTRIBUTES_STR("path", path); + + lxw_xml_start_tag(self->file, "v:shapetype", &attributes); + + /* Write the v:stroke element. */ + _vml_write_stroke(self); + + /* Write the v:path element. */ + _vml_write_comment_path(self, LXW_TRUE, "rect"); + + lxw_xml_end_tag(self->file, "v:shapetype"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_idmap(lxw_vml *self) +{ + /* Since the vml_data_id_str may exceed the LXW_MAX_ATTRIBUTE_LENGTH we + * write it directly without the xml helper functions. */ + fprintf(self->file, "", + self->vml_data_id_str); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_shapelayout(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("v:ext", "edit"); + + lxw_xml_start_tag(self->file, "o:shapelayout", &attributes); + + /* Write the o:idmap element. */ + _vml_write_idmap(self); + + lxw_xml_end_tag(self->file, "o:shapelayout"); + + LXW_FREE_ATTRIBUTES(); +} + +/* + * Write the element. + */ +STATIC void +_vml_write_xml_namespace(lxw_vml *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char xmlns_v[] = "urn:schemas-microsoft-com:vml"; + char xmlns_o[] = "urn:schemas-microsoft-com:office:office"; + char xmlns_x[] = "urn:schemas-microsoft-com:office:excel"; + + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("xmlns:v", xmlns_v); + LXW_PUSH_ATTRIBUTES_STR("xmlns:o", xmlns_o); + LXW_PUSH_ATTRIBUTES_STR("xmlns:x", xmlns_x); + + lxw_xml_start_tag(self->file, "xml", &attributes); + + LXW_FREE_ATTRIBUTES(); +} + +/***************************************************************************** + * + * XML file assembly functions. + * + ****************************************************************************/ + +/* + * Assemble and write the XML file. + */ +void +lxw_vml_assemble_xml_file(lxw_vml *self) +{ + lxw_vml_obj *comment_obj; + lxw_vml_obj *button_obj; + lxw_vml_obj *image_obj; + uint32_t z_index = 1; + + /* Write the xml namespace element. Note, the VML files have no + * XML declaration.*/ + _vml_write_xml_namespace(self); + + /* Write the o:shapelayout element. */ + _vml_write_shapelayout(self); + + if (self->comment_objs) { + /* Write the element. */ + _vml_write_comment_shapetype(self); + + STAILQ_FOREACH(comment_obj, self->comment_objs, list_pointers) { + self->vml_shape_id += 1; + + /* Write the element. */ + _vml_write_comment_shape(self, self->vml_shape_id, z_index, + comment_obj); + + z_index += 1; + } + } + + if (self->button_objs) { + /* Write the element. */ + _vml_write_button_shapetype(self); + + STAILQ_FOREACH(button_obj, self->button_objs, list_pointers) { + /* Write the element. */ + _vml_write_button_shape(self); + + } + } + + if (self->image_objs) { + /* Write the element. */ + _vml_write_image_shapetype(self); + + STAILQ_FOREACH(image_obj, self->image_objs, list_pointers) { + /* Write the element. */ + _vml_write_image_shape(self); + } + } + + lxw_xml_end_tag(self->file, "xml"); +} + +/***************************************************************************** + * + * Public functions. + * + ****************************************************************************/ diff --git a/src/workbook.c b/src/workbook.c index d49d759d..60ede164 100644 --- a/src/workbook.c +++ b/src/workbook.c @@ -767,7 +767,7 @@ _populate_range_data_cache(lxw_workbook *self, lxw_series_range *range) return; } - cell_obj = lxw_worksheet_find_cell(row_obj, col_num); + cell_obj = lxw_worksheet_find_cell_in_row(row_obj, col_num); if (cell_obj) { if (cell_obj->type == NUMBER_CELL) { @@ -1000,6 +1000,65 @@ _prepare_drawings(lxw_workbook *self) self->drawing_count = drawing_id; } +/* + * Iterate through the worksheets and set up the VML objects. + */ + +STATIC void +_prepare_vml(lxw_workbook *self) +{ + lxw_worksheet *worksheet; + lxw_sheet *sheet; + uint32_t comment_id = 0; + uint32_t vml_drawing_id = 0; + uint32_t vml_data_id = 1; + uint32_t vml_shape_id = 1024; + lxw_format *format; + uint32_t comment_count = 0; + + STAILQ_FOREACH(sheet, self->sheets, list_pointers) { + if (sheet->is_chartsheet) + continue; + else + worksheet = sheet->u.worksheet; + + if (!worksheet->has_vml && !worksheet->has_header_vml) + continue; + + if (worksheet->has_vml) { + self->has_vml = LXW_TRUE; + if (worksheet->has_comments) { + self->comment_count += 1; + comment_id += 1; + } + + vml_drawing_id += 1; + + comment_count = lxw_worksheet_prepare_vml_objects(worksheet, + vml_data_id, + vml_shape_id, + vml_drawing_id, + comment_id); + + /* Each VML should start with a shape id incremented by 1024. */ + vml_data_id += 1 * ((1024 + comment_count) / 1024); + vml_shape_id += 1024 * ((1024 + comment_count) / 1024); + + } + } + + if (self->comment_count) { + /* Add a font format for cell comments. */ + format = workbook_add_format(self); + format_set_font_name(format, "Tahoma"); + format_set_font_size(format, 8); + format_set_color_indexed(format, 81); + format_set_font_only(format); + /* Initialize its index. */ + lxw_format_get_xf_index(format); + } +} + /* * Iterate through the worksheets and store any defined names used for print * ranges or repeat rows/columns. @@ -1821,6 +1880,9 @@ workbook_close(lxw_workbook *self) } } + /* Prepare the worksheet VML elements such as comments. */ + _prepare_vml(self); + /* Set the defined names for the worksheets such as Print Titles. */ _prepare_defined_names(self); @@ -2250,8 +2312,6 @@ workbook_unset_default_url_format(lxw_workbook *self) self->default_url_format->theme = 0; } -lxw_format *default_url_format; - /* * Validate the worksheet name based on Excel's rules. */ diff --git a/src/worksheet.c b/src/worksheet.c index 04f6bc18..982a82e9 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -11,7 +11,6 @@ #include "xlsxwriter/worksheet.h" #include "xlsxwriter/format.h" #include "xlsxwriter/utility.h" -#include "xlsxwriter/relationships.h" #include "xlsxwriter/third_party/md5.h" #define LXW_STR_MAX 32767 @@ -48,27 +47,27 @@ LXW_RB_GENERATE_DRAWING_REL_IDS(lxw_drawing_rel_ids, lxw_drawing_rel_id, lxw_row * lxw_worksheet_find_row(lxw_worksheet *self, lxw_row_t row_num) { - lxw_row row; + lxw_row tmp_row; - row.row_num = row_num; + tmp_row.row_num = row_num; - return RB_FIND(lxw_table_rows, self->table, &row); + return RB_FIND(lxw_table_rows, self->table, &tmp_row); } /* * Find but don't create a cell object for a given row object and col number. */ lxw_cell * -lxw_worksheet_find_cell(lxw_row *row, lxw_col_t col_num) +lxw_worksheet_find_cell_in_row(lxw_row *row, lxw_col_t col_num) { - lxw_cell cell; + lxw_cell tmp_cell; if (!row) return NULL; - cell.col_num = col_num; + tmp_cell.col_num = col_num; - return RB_FIND(lxw_table_cells, row->cells, &cell); + return RB_FIND(lxw_table_cells, row->cells, &tmp_cell); } /* @@ -122,6 +121,10 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) GOTO_LABEL_ON_MEM_ERROR(worksheet->chart_data, mem_error); STAILQ_INIT(worksheet->chart_data); + worksheet->comment_objs = calloc(1, sizeof(struct lxw_comment_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->comment_objs, mem_error); + STAILQ_INIT(worksheet->comment_objs); + worksheet->selections = calloc(1, sizeof(struct lxw_selections)); GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error); STAILQ_INIT(worksheet->selections); @@ -205,6 +208,7 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) worksheet->outline_right = LXW_FALSE; worksheet->tab_color = LXW_COLOR_UNSET; worksheet->max_url_length = 2079; + worksheet->comment_display_default = LXW_COMMENT_DISPLAY_HIDDEN; if (init_data) { worksheet->name = init_data->name; @@ -227,6 +231,22 @@ mem_error: return NULL; } +/* + * Free a worksheet data_validation. + */ +STATIC void +_free_vml_object(lxw_vml_obj *vml_obj) +{ + if (!vml_obj) + return; + + free(vml_obj->author); + free(vml_obj->font_name); + free(vml_obj->text); + + free(vml_obj); +} + /* * Free a worksheet cell. */ @@ -245,6 +265,8 @@ _free_cell(lxw_cell *cell) free(cell->user_data1); free(cell->user_data2); + _free_vml_object(cell->comment); + free(cell); } @@ -309,6 +331,22 @@ _free_data_validation(lxw_data_val_obj *data_validation) free(data_validation); } +/* + * Free a relationship structure. + */ +STATIC void +_free_relationship(lxw_rel_tuple *relationship) +{ + if (!relationship) + return; + + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + + free(relationship); +} + /* * Free a worksheet object. */ @@ -394,6 +432,9 @@ lxw_worksheet_free(lxw_worksheet *worksheet) free(worksheet->chart_data); } + /* Just free the list. The list objects are free elsewhere. */ + free(worksheet->comment_objs); + if (worksheet->selections) { while (!STAILQ_EMPTY(worksheet->selections)) { selection = STAILQ_FIRST(worksheet->selections); @@ -417,30 +458,21 @@ lxw_worksheet_free(lxw_worksheet *worksheet) 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_relationship(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_relationship(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_relationship(relationship); } free(worksheet->drawing_links); @@ -461,6 +493,9 @@ lxw_worksheet_free(lxw_worksheet *worksheet) free(worksheet->drawing_rel_ids); } + _free_relationship(worksheet->external_vml_comment_link); + _free_relationship(worksheet->external_comment_link); + if (worksheet->array) { for (col = 0; col < LXW_COL_MAX; col++) { _free_cell(worksheet->array[col]); @@ -479,6 +514,8 @@ lxw_worksheet_free(lxw_worksheet *worksheet) free(worksheet->name); free(worksheet->quoted_name); free(worksheet->vba_codename); + free(worksheet->vml_data_id_str); + free(worksheet->comment_author); free(worksheet); worksheet = NULL; @@ -758,6 +795,7 @@ _insert_cell_list(struct lxw_table_cells *cell_list, /* If existing_cell is not NULL, then that cell already existed. */ /* Remove existing_cell and add new one in again. */ if (existing_cell) { + existing_cell->comment = NULL; RB_REMOVE(lxw_table_cells, cell_list, existing_cell); /* Add it in again. */ @@ -776,6 +814,7 @@ _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); + lxw_vml_obj *existing_comment = NULL; if (!self->optimize) { row->data_changed = LXW_TRUE; @@ -786,10 +825,14 @@ _insert_cell(lxw_worksheet *self, lxw_row_t row_num, lxw_col_t col_num, row->data_changed = LXW_TRUE; /* Overwrite an existing cell if necessary. */ - if (self->array[col_num]) + if (self->array[col_num]) { + existing_comment = self->array[col_num]->comment; + self->array[col_num]->comment = NULL; _free_cell(self->array[col_num]); + } self->array[col_num] = cell; + self->array[col_num]->comment = existing_comment; } } } @@ -891,6 +934,9 @@ _cell_cmp(lxw_cell *cell1, lxw_cell *cell2) return 0; } +/* + * Comparator for the image/hyperlink relationship ids. + */ STATIC int _drawing_rel_id_cmp(lxw_drawing_rel_id *rel_id1, lxw_drawing_rel_id *rel_id2) { @@ -2087,6 +2133,154 @@ _worksheet_position_object_emus(lxw_worksheet *self, drawing_object->row_absolute *= 9525; } +/* + * This function handles the additional optional parameters to + * worksheet_write_comment_opt() as well as calculating the comment object + * position and vertices. + */ +void +_get_comment_params(lxw_vml_obj *comment, lxw_comment_options *options) +{ + + lxw_row_t start_row; + lxw_col_t start_col; + int32_t x_offset; + int32_t y_offset; + uint32_t height = 74; + uint32_t width = 128; + double x_scale = 1.0; + double y_scale = 1.0; + lxw_row_t row = comment->row; + lxw_col_t col = comment->col;; + + /* Set the default start cell and offsets for the comment. These are + * generally fixed in relation to the parent cell. However there are some + * edge cases for cells at the, er, edges. */ + if (row == 0) + y_offset = 2; + else if (row == LXW_ROW_MAX - 3) + y_offset = 16; + else if (row == LXW_ROW_MAX - 2) + y_offset = 16; + else if (row == LXW_ROW_MAX - 1) + y_offset = 14; + else + y_offset = 10; + + if (col == LXW_COL_MAX - 3) + x_offset = 49; + else if (col == LXW_COL_MAX - 2) + x_offset = 49; + else if (col == LXW_COL_MAX - 1) + x_offset = 49; + else + x_offset = 15; + + if (row == 0) + start_row = 0; + else if (row == LXW_ROW_MAX - 3) + start_row = LXW_ROW_MAX - 7; + else if (row == LXW_ROW_MAX - 2) + start_row = LXW_ROW_MAX - 6; + else if (row == LXW_ROW_MAX - 1) + start_row = LXW_ROW_MAX - 5; + else + start_row = row - 1; + + if (col == LXW_COL_MAX - 3) + start_col = LXW_COL_MAX - 6; + else if (col == LXW_COL_MAX - 2) + start_col = LXW_COL_MAX - 5; + else if (col == LXW_COL_MAX - 1) + start_col = LXW_COL_MAX - 4; + else + start_col = col + 1; + + /* Set the default font properties. */ + comment->font_size = 8; + comment->font_family = 2; + + /* Set any user defined options. */ + if (options) { + + if (options->width > 0.0) + width = options->width; + + if (options->height > 0.0) + height = options->height; + + if (options->x_scale > 0.0) + x_scale = options->x_scale; + + if (options->y_scale > 0.0) + y_scale = options->y_scale; + + if (options->x_offset != 0) + x_offset = options->x_offset; + + if (options->y_offset != 0) + y_offset = options->y_offset; + + if (options->start_row > 0 || options->start_col > 0) { + start_row = options->start_row; + start_col = options->start_col; + } + + if (options->font_size > 0.0) + comment->font_size = options->font_size; + + if (options->font_family > 0) + comment->font_family = options->font_family; + + comment->visible = options->visible; + comment->color = options->color; + comment->author = lxw_strdup(options->author); + comment->font_name = lxw_strdup(options->font_name); + } + + /* Scale the width/height to the default/user scale and round to the + * nearest pixel. */ + width = (uint32_t) (0.5 + x_scale * width); + height = (uint32_t) (0.5 + y_scale * height); + + comment->width = width; + comment->height = height; + comment->start_col = start_col; + comment->start_row = start_row; + comment->x_offset = x_offset; + comment->y_offset = y_offset; +} + +/* + * Calculate the comment object position and vertices. + */ +void +_worksheet_position_vml_object(lxw_worksheet *self, lxw_vml_obj *comment) +{ + lxw_object_properties object_props; + lxw_drawing_object drawing_object; + + object_props.col = comment->start_col; + object_props.row = comment->start_row; + object_props.x_offset = comment->x_offset; + object_props.y_offset = comment->y_offset; + object_props.width = comment->width; + object_props.height = comment->height; + + _worksheet_position_object_pixels(self, &object_props, &drawing_object); + + comment->from.col = drawing_object.from.col; + comment->from.row = drawing_object.from.row; + comment->from.col_offset = drawing_object.from.col_offset; + comment->from.row_offset = drawing_object.from.row_offset; + comment->to.col = drawing_object.to.col; + comment->to.row = drawing_object.to.row; + comment->to.col_offset = drawing_object.to.col_offset; + comment->to.row_offset = drawing_object.to.row_offset; + comment->col_absolute = drawing_object.col_absolute; + comment->row_absolute = drawing_object.row_absolute; +} + /* * Set up image/drawings. */ @@ -2142,7 +2336,6 @@ lxw_worksheet_prepare_image(lxw_worksheet *self, width *= 96.0 / object_props->x_dpi; height *= 96.0 / object_props->y_dpi; - /* Convert to the nearest pixel. */ object_props->width = width; object_props->height = height; @@ -2187,7 +2380,7 @@ lxw_worksheet_prepare_image(lxw_worksheet *self, relationship->target = lxw_escape_url_characters(url + 1, LXW_TRUE); GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); - strncpy(relationship->target, "file:///", sizeof("file:///") - 1); + memcpy(relationship->target, "file:///", sizeof("file:///") - 1); } else { relationship->target_mode = lxw_strdup("External"); @@ -2345,6 +2538,124 @@ mem_error: } } +/* + * Set up VML objects, such as comments, in the worksheet. + */ +uint32_t +lxw_worksheet_prepare_vml_objects(lxw_worksheet *self, + uint32_t vml_data_id, + uint32_t vml_shape_id, + uint32_t vml_drawing_id, + uint32_t comment_id) +{ + lxw_row *row; + lxw_cell *cell; + lxw_rel_tuple *relationship; + char filename[LXW_FILENAME_LENGTH]; + uint32_t comment_count = 0; + uint32_t i; + uint32_t tmp_data_id; + size_t data_str_len = 0; + size_t used = 0; + char *vml_data_id_str; + + RB_FOREACH(row, lxw_table_rows, self->table) { + + if (row->has_comments) { + RB_FOREACH(cell, lxw_table_cells, row->cells) { + if (cell->comment) { + /* Calculate the worksheet position of the comment. */ + _worksheet_position_vml_object(self, cell->comment); + + /* Store comment in a simple list for use by packager. */ + STAILQ_INSERT_TAIL(self->comment_objs, cell->comment, + list_pointers); + comment_count++; + } + } + } + } + + /* Set up the VML relationship for comments/buttons/header images. */ + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/vmlDrawing"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../drawings/vmlDrawing%d.vml", + vml_drawing_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + self->external_vml_comment_link = relationship; + + if (self->has_comments) { + /* Only need this relationship object for comment VMLs. */ + + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + + relationship->type = lxw_strdup("/comments"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + + lxw_snprintf(filename, 32, "../comments%d.xml", comment_id); + + relationship->target = lxw_strdup(filename); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + + self->external_comment_link = relationship; + } + + /* The vml.c element data id contains a comma separated range + * when there is more than one 1024 block of comments, like this: + * data="1,2,3". Since this could potentially (but unlikely) exceed + * LXW_MAX_ATTRIBUTE_LENGTH we need to allocate space dynamically. */ + + /* Calculate the total space required for the ID for each 1024 block. */ + for (i = 0; i <= comment_count / 1024; i++) { + tmp_data_id = vml_data_id + i; + + /* Calculate the space required for the digits in the id. */ + while (tmp_data_id) { + data_str_len++; + tmp_data_id /= 10; + } + + /* Add an extra char for comma separator or '\O'. */ + data_str_len++; + }; + + /* If this allocation fails it will be dealt with in packager.c. */ + vml_data_id_str = calloc(1, data_str_len); + GOTO_LABEL_ON_MEM_ERROR(vml_data_id_str, mem_error); + + /* Create the CSV list in the allocated space. */ + for (i = 0; i <= comment_count / 1024; i++) { + tmp_data_id = vml_data_id + i; + lxw_snprintf(vml_data_id_str + used, data_str_len - used, "%d,", + tmp_data_id); + + used = strlen(vml_data_id_str); + }; + + self->vml_shape_id = vml_shape_id; + self->vml_data_id_str = vml_data_id_str; + + return comment_count; + +mem_error: + if (relationship) { + free(relationship->type); + free(relationship->target); + free(relationship->target_mode); + free(relationship); + } + + return 0; +} + /* * Extract width and height information from a PNG file. */ @@ -2986,7 +3297,8 @@ _write_cell(lxw_worksheet *self, lxw_cell *cell, lxw_format *row_format) lxw_xml_end_tag(self->file, "c"); } else if (cell->type == BLANK_CELL) { - lxw_xml_empty_tag(self->file, "c", &attributes); + if (cell->format) + lxw_xml_empty_tag(self->file, "c", &attributes); } else if (cell->type == BOOLEAN_CELL) { LXW_PUSH_ATTRIBUTES_STR("t", "b"); @@ -3032,10 +3344,13 @@ _worksheet_write_rows(lxw_worksheet *self) _write_row(self, row, spans); - RB_FOREACH(cell, lxw_table_cells, row->cells) { - _write_cell(self, cell, row->format); + if (row->data_changed) { + RB_FOREACH(cell, lxw_table_cells, row->cells) { + _write_cell(self, cell, row->format); + } + + lxw_xml_end_tag(self->file, "row"); } - lxw_xml_end_tag(self->file, "row"); } } } @@ -3257,10 +3572,10 @@ _worksheet_write_header_footer(lxw_worksheet *self) lxw_xml_start_tag(self->file, "headerFooter", NULL); - if (self->header[0] != '\0') + if (*self->header) _worksheet_write_odd_header(self); - if (self->footer[0] != '\0') + if (*self->footer) _worksheet_write_odd_footer(self); lxw_xml_end_tag(self->file, "headerFooter"); @@ -3696,6 +4011,31 @@ _worksheet_write_sheet_protection(lxw_worksheet *self, LXW_FREE_ATTRIBUTES(); } +/* + * Write the element. + */ +STATIC void +_worksheet_write_legacy_drawing(lxw_worksheet *self) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; + + if (!self->has_vml) + return; + else + self->rel_count++; + + lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", self->rel_count); + LXW_INIT_ATTRIBUTES(); + LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); + + lxw_xml_empty_tag(self->file, "legacyDrawing", &attributes); + + LXW_FREE_ATTRIBUTES(); + +} + /* * Write the element. */ @@ -3707,9 +4047,7 @@ _worksheet_write_drawing(lxw_worksheet *self, uint16_t id) char r_id[LXW_MAX_ATTRIBUTE_LENGTH]; lxw_snprintf(r_id, LXW_ATTR_32, "rId%d", id); - LXW_INIT_ATTRIBUTES(); - LXW_PUSH_ATTRIBUTES_STR("r:id", r_id); lxw_xml_empty_tag(self->file, "drawing", &attributes); @@ -4065,6 +4403,9 @@ lxw_worksheet_assemble_xml_file(lxw_worksheet *self) /* Write the drawing element. */ _worksheet_write_drawings(self); + /* Write the legacyDrawing element. */ + _worksheet_write_legacy_drawing(self); + /* Close the worksheet tag. */ lxw_xml_end_tag(self->file, "worksheet"); } @@ -4316,10 +4657,6 @@ worksheet_write_blank(lxw_worksheet *self, lxw_cell *cell; lxw_error err; - /* Blank cells without formatting are ignored by Excel. */ - if (!format) - return LXW_NO_ERROR; - err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); if (err) return err; @@ -4343,7 +4680,6 @@ worksheet_write_boolean(lxw_worksheet *self, lxw_error err; err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); - if (err) return err; @@ -4473,6 +4809,7 @@ worksheet_write_url_opt(lxw_worksheet *self, found_string = strchr(url_copy, '#'); if (found_string) { + free(url_string); url_string = lxw_strdup(found_string + 1); GOTO_LABEL_ON_MEM_ERROR(url_string, mem_error); @@ -4735,6 +5072,96 @@ mem_error: return LXW_ERROR_MEMORY_MALLOC_FAILED; } +/* + * Write a comment to a worksheet cell in Excel. + */ +lxw_error +worksheet_write_comment_opt(lxw_worksheet *self, + lxw_row_t row_num, lxw_col_t col_num, + char *text, lxw_comment_options *options) +{ + lxw_row *row; + lxw_cell *cell; + lxw_error err; + lxw_vml_obj *comment; + uint8_t data_changed = LXW_FALSE; + + if (self->optimize) { + LXW_WARN("worksheet_write_comment/opt(): " + "Not supported in 'constant_memory' mode."); + + return LXW_ERROR_FEATURE_NOT_SUPPORTED; + } + + err = _check_dimensions(self, row_num, col_num, LXW_FALSE, LXW_FALSE); + if (err) + return err; + + if (!text) + return LXW_ERROR_NULL_PARAMETER_IGNORED; + + if (lxw_utf8_strlen(text) > LXW_STR_MAX) + return LXW_ERROR_MAX_STRING_LENGTH_EXCEEDED; + + comment = calloc(1, sizeof(lxw_vml_obj)); + GOTO_LABEL_ON_MEM_ERROR(comment, mem_error); + + comment->text = lxw_strdup(text); + GOTO_LABEL_ON_MEM_ERROR(comment->text, mem_error); + + row = lxw_worksheet_find_row(self, row_num); + if (row) + data_changed = row->data_changed; + + cell = lxw_worksheet_find_cell_in_row(row, col_num); + if (cell) { + free(cell->comment); + } + else { + /* If there isn't an existing cell we use a new blank cell. */ + cell = _new_blank_cell(row_num, col_num, NULL); + _insert_cell(self, row_num, col_num, cell); + } + + comment->row = row_num; + comment->col = col_num; + + /* Set user and default parameters for the comment. */ + _get_comment_params(comment, options); + + cell->comment = comment; + + if (!row) { + row = lxw_worksheet_find_row(self, row_num); + row->data_changed = LXW_FALSE; + } + else { + row->data_changed = data_changed; + } + + row->has_comments = LXW_TRUE; + self->has_vml = LXW_TRUE; + self->has_comments = LXW_TRUE; + + return LXW_NO_ERROR; + +mem_error: + if (comment) + _free_vml_object(comment); + + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + +/* + * Write a comment to a worksheet cell in Excel. + */ +lxw_error +worksheet_write_comment(lxw_worksheet *self, + lxw_row_t row_num, lxw_col_t col_num, char *string) +{ + return worksheet_write_comment_opt(self, row_num, col_num, string, NULL); +} + /* * Set the properties of a single column or a range of columns with options. */ @@ -5960,7 +6387,6 @@ worksheet_insert_chart_opt(lxw_worksheet *self, object_props->row = row_num; object_props->col = col_num; - /* TODO. Read defaults from chart. */ object_props->width = 480; object_props->height = 288; @@ -6289,3 +6715,21 @@ worksheet_set_vba_name(lxw_worksheet *self, const char *name) return LXW_NO_ERROR; } + +/* + * Set the default author of the cell comments. + */ +void +worksheet_set_comments_author(lxw_worksheet *self, const char *author) +{ + self->comment_author = lxw_strdup(author); +} + +/* + * Make any comments in the worksheet visible, unless explicitly hidden. + */ +void +worksheet_show_comments(lxw_worksheet *self) +{ + self->comment_display_default = LXW_COMMENT_DISPLAY_VISIBLE; +} diff --git a/test/functional/src/test_comment01.c b/test/functional/src/test_comment01.c new file mode 100644 index 00000000..a595a767 --- /dev/null +++ b/test/functional/src/test_comment01.c @@ -0,0 +1,23 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet, CELL("B2"), "Some text"); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment02.c b/test/functional/src/test_comment02.c new file mode 100644 index 00000000..d40aae61 --- /dev/null +++ b/test/functional/src/test_comment02.c @@ -0,0 +1,24 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment02.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet, CELL("B2"), "Some text"); + worksheet_write_comment(worksheet, CELL("D17"), "More text"); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment03.c b/test/functional/src/test_comment03.c new file mode 100644 index 00000000..d099e5b0 --- /dev/null +++ b/test/functional/src/test_comment03.c @@ -0,0 +1,24 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet, CELL("A1"), "Some text"); + worksheet_write_comment(worksheet, CELL("XFD1048576"), "Some text"); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment04.c b/test/functional/src/test_comment04.c new file mode 100644 index 00000000..b31d7308 --- /dev/null +++ b/test/functional/src/test_comment04.c @@ -0,0 +1,31 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment04.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + + (void)worksheet2; + + worksheet_write_string(worksheet1, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet1, CELL("B2"), "Some text"); + + worksheet_write_string(worksheet3, CELL("A1"), "Bar", NULL); + worksheet_write_comment(worksheet3, CELL("C7"), "More text"); + + worksheet_set_comments_author(worksheet1, "John"); + worksheet_set_comments_author(worksheet3, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment05.c b/test/functional/src/test_comment05.c new file mode 100644 index 00000000..bad20dae --- /dev/null +++ b/test/functional/src/test_comment05.c @@ -0,0 +1,33 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment05.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + uint32_t row; + uint16_t col; + + (void)worksheet2; + + for (row = 0; row <= 127; row++) + for (col = 0; col <= 15; col++) + worksheet_write_comment(worksheet1, row, col, "Some text"); + + worksheet_write_comment(worksheet3, CELL("A1"), "More text"); + + worksheet_set_comments_author(worksheet1, "John"); + worksheet_set_comments_author(worksheet3, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment06.c b/test/functional/src/test_comment06.c new file mode 100644 index 00000000..79616654 --- /dev/null +++ b/test/functional/src/test_comment06.c @@ -0,0 +1,31 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment06.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_comment_options options = {.visible = LXW_COMMENT_DISPLAY_VISIBLE}; + + + worksheet_write_comment(worksheet, CELL("A1"), "Some text"); + worksheet_write_comment(worksheet, CELL("A2"), "Some text"); + + worksheet_write_comment_opt(worksheet, CELL("A3"), "Some text", &options); + + worksheet_write_comment(worksheet, CELL("A4"), "Some text"); + worksheet_write_comment(worksheet, CELL("A5"), "Some text"); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment07.c b/test/functional/src/test_comment07.c new file mode 100644 index 00000000..63665c83 --- /dev/null +++ b/test/functional/src/test_comment07.c @@ -0,0 +1,28 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment07.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_comment(worksheet, CELL("A1"), "Some text"); + worksheet_write_comment(worksheet, CELL("A2"), "Some text"); + worksheet_write_comment(worksheet, CELL("A3"), "Some text"); + worksheet_write_comment(worksheet, CELL("A4"), "Some text"); + worksheet_write_comment(worksheet, CELL("A5"), "Some text"); + + worksheet_show_comments(worksheet); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment08.c b/test/functional/src/test_comment08.c new file mode 100644 index 00000000..0e5a1ab2 --- /dev/null +++ b/test/functional/src/test_comment08.c @@ -0,0 +1,31 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment08.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_comment_options options1 = {.visible = LXW_COMMENT_DISPLAY_HIDDEN}; + lxw_comment_options options2 = {.visible = LXW_COMMENT_DISPLAY_VISIBLE}; + + worksheet_write_comment(worksheet, CELL("A1"), "Some text"); + worksheet_write_comment(worksheet, CELL("A2"), "Some text"); + worksheet_write_comment_opt(worksheet, CELL("A3"), "Some text", &options1); + worksheet_write_comment_opt(worksheet, CELL("A4"), "Some text", &options2); + worksheet_write_comment(worksheet, CELL("A5"), "Some text"); + + worksheet_show_comments(worksheet); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment09.c b/test/functional/src/test_comment09.c new file mode 100644 index 00000000..8ce999a3 --- /dev/null +++ b/test/functional/src/test_comment09.c @@ -0,0 +1,28 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment09.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_comment_options options1 = {.author = "John"}; + lxw_comment_options options2 = {.author = "Perl"}; + + + worksheet_write_comment_opt(worksheet, CELL("A1"), "Some text", &options1); + worksheet_write_comment_opt(worksheet, CELL("A2"), "Some text", &options2); + worksheet_write_comment(worksheet, CELL("A3"), "Some text"); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment10.c b/test/functional/src/test_comment10.c new file mode 100644 index 00000000..a1ed1893 --- /dev/null +++ b/test/functional/src/test_comment10.c @@ -0,0 +1,25 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment10.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_comment_options options = {.color = 0x98FE97}; + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + worksheet_write_comment_opt(worksheet, CELL("B2"), "Some text", &options); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment11.c b/test/functional/src/test_comment11.c new file mode 100644 index 00000000..291051b2 --- /dev/null +++ b/test/functional/src/test_comment11.c @@ -0,0 +1,26 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment11.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + + (void)worksheet1; + + worksheet_write_string(worksheet2, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet2, CELL("B2"), "Some text"); + + worksheet_set_comments_author(worksheet2, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment12.c b/test/functional/src/test_comment12.c new file mode 100644 index 00000000..20afab69 --- /dev/null +++ b/test/functional/src/test_comment12.c @@ -0,0 +1,26 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment12.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_set_row(worksheet, 0, 21, NULL); + worksheet_set_column(worksheet, 1, 1, 10, NULL); + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet, CELL("A1"), "Some text"); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment13.c b/test/functional/src/test_comment13.c new file mode 100644 index 00000000..fe82aa17 --- /dev/null +++ b/test/functional/src/test_comment13.c @@ -0,0 +1,26 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment13.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_comment_options options = {.font_name = "Courier", .font_size = 10, .font_family = 3}; + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + + worksheet_write_comment_opt(worksheet, CELL("B2"), "Some text", &options); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_comment14.c b/test/functional/src/test_comment14.c new file mode 100644 index 00000000..840c4de1 --- /dev/null +++ b/test/functional/src/test_comment14.c @@ -0,0 +1,25 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_comment14.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_write_string(worksheet, CELL("A1"), "Foo", NULL); + worksheet_write_comment(worksheet, CELL("B2"), "Some text"); + + worksheet_set_column(worksheet, 2, 2, 13, NULL); + + worksheet_set_comments_author(worksheet, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/test_comment.py b/test/functional/test_comment.py new file mode 100644 index 00000000..20b78c71 --- /dev/null +++ b/test/functional/test_comment.py @@ -0,0 +1,57 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2019, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_comment01(self): + self.run_exe_test('test_comment01') + + def test_comment02(self): + self.run_exe_test('test_comment02') + + def test_comment03(self): + self.run_exe_test('test_comment03') + + def test_comment04(self): + self.run_exe_test('test_comment04') + + def test_comment05(self): + self.run_exe_test('test_comment05') + + def test_comment06(self): + self.run_exe_test('test_comment06') + + def test_comment07(self): + self.run_exe_test('test_comment07') + + def test_comment08(self): + self.run_exe_test('test_comment08') + + def test_comment09(self): + self.run_exe_test('test_comment09') + + def test_comment10(self): + self.run_exe_test('test_comment10') + + def test_comment11(self): + self.run_exe_test('test_comment11') + + def test_comment12(self): + self.run_exe_test('test_comment12') + + def test_comment13(self): + self.ignore_files = ['xl/styles.xml'] + self.run_exe_test('test_comment13') + + def test_comment14(self): + self.run_exe_test('test_comment14') diff --git a/test/functional/xlsx_files/comment01.xlsx b/test/functional/xlsx_files/comment01.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..69a9c2086e19cb026a4f315157c194688601c58a GIT binary patch literal 8828 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 literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/comment02.xlsx b/test/functional/xlsx_files/comment02.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d4614713787e9b7f85d281672073b39c17cfeb3a GIT binary patch literal 8916 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

wfzx`g*Xk625`KU-$Op1$^EXM(XG$|#`VmGqSw^ zRgg3fvbz+pgSs*8mrAfJ(sIy5sy>lKeBAZE=3_}D@Kc7tQ+JPX6~xDsObbJr=4)L; znLVu%%yW*Lqdw-HZZ32?%wn7wQ;0+9ZH-f;D*}9)Mk;+rbrw?<5Z*?oO__61PZ95^ z1;2wcy#D{?eRlSa}C^wQ70IQuPLSncjp8gL0XwQg9F1k!*><> zwNCo99BU+SsTNJYE~hsDt@FUnpI-8Q#U^jOlofuur}h2}=TBLzVYzS~49{ZfaD0>e z#P)Cf$gfT$M7j6XcZ!zy3|H)U%68wBqei^NXGM|OefXVzsU^OKIX#2bRkL}XG)B__ zb;iti-#}5!{STV`IJ&7~(chTyQXu!rRC5(h&!^9Cw8e%^y?FZ`dktivb=DSo6a4KUs$~q z6<^567PaOj`Aoa*7fdD-h(LM;EqUTS7#FM9-E_o0DeP59_LRO-wpROfBEucm$83S0 z9c&Xc2%luvl0>og5Eu`6c1aLW=4*%sb3C69_s4VtX~->_PL_UAD1M~T7F|`%8D^Fr zpn-&&@+rDvZ?l}Nl53!5O{iuyKjZnWD0(k+6QWFERBixv_5HB-u|k)4#s-qn<;k3+ z=P$1$j^I1VKew8Wg%=5!a3_($orL$(Nd{m`OZbGu?ni6XrzkJm&Oz9SdF8*mZ8ar1 zQNgW&bdQ-5>G>|{)Py`W6$3k4!_{k*%ENhfJTYf3Feg{`OlpeJlMq8MV#QJx-L-QL z5>LF{<{c|i-Iy4v7;H-9lt^8s($Xmq&>w`~i;{-47L)s6b;KA;Ng8^7t?x7Hq`uCKBy!5Igb>g`G`;@CxN=_m+l zM{>?RnF)jD&1=CFu|z|F^p{h3iF=4?9Pg4}Ga3-Pv1B%lKnoJdW4VsBClBRzscPxx zyu^8*R0-zornJaXf3Uw8-N|w-dN;kY?Xkb}nk}@qlr3P1?~j@L;P4Dx7;ZQXxZ&{O z44)y`K+zU#ZO3i^wl(@WR{Afo2e%v`BFaLerh53=)B6)NXA8a+UxsONRXgGp9+8 zE2!feE-|Yzq(P*8tlE~z5D9K-WU9!OQ5c1YH5QMQ;_L#q^j`RcckK3{pF$$gXgQp5 zle;~&0(s}-u)FoRsYMc_nXyhtgz!i3*UnX|@jB(LV&Gd&;uKrVK65S5T2s$n_7G*M z@jz)?YAsQ~^x`~!DXIc-gFhrd3~#Cvbg#6s;k+Ls_OA43!u>B6&E05Cnu?sB;LfWJ zu=gP{Kh`zhUeeJ0RJ^BeI6FV+Je*J!eWGYEPm?_r?v!q~`J_NuO5?@JO&F^+X~0%Cy(L0Xr4-cu_H)X|9d zCuwyVG&8H>2rMyEp~$_ESjLqByu4FtgHa&n@o=*vo>WQCw@M#6(fo{NX|SKZt@iQ3 zhg^p$L@LnE(?Cddy==FDdBDqp4+6<_9ezRnS;(bJ+NyH3i04TZsrCg+f@%Whf)wSmF8A`EaI_88bQp~l<~iWT>9Y-8u9O!`?DbM(0b|$X9U1Wquq{ms~5v z76@puOMV4Fzx`R4P0;%CWDC?`QI`W!W*u7Rw1nWvtrfja)!xg$*Tu1?0BnGO>+J^k zK#Abjt4)DAoD1!(S_l2U#BMU@&S12G#YVP59dpjHMo6Ux{5{ePkG#8cc@_YN?^nTk z>R0;*ZHKni%%>p&!V@u#7e<%xdiE!J5pF$zGpm0d$N$S)|GNHRIIk%CcLjeRBK}M8 z`!yII62FZUZwmf>BJ;bT6Fk!X_xa3CJvUkUUz*;*|8a1Gv%e{PlWzVcOoj17_#fo+ zO$|3m-d`HN{?zbWiS^SOzY47%^QM~$Z_=Z`6yC!6@k%$z(whoyHmkoBR1^GM)4yBS zo1!;)eQsfG!%Kvi8UqtvXd)(A=vljZLh5GIfEq|+wZc6{X xAowKzqPuRO0PiwsBYcXj|9pbiIqDAo6;{{b(9TlxS1 literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/comment04.xlsx b/test/functional/xlsx_files/comment04.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..2bc1bcddafb20713ac02d53d33281cafb985bf75 GIT binary patch literal 11207 zcmeHtbyVEhvUTI`?(XjHgy0&2h2ZYqSO^l_-GXa?;1Vop2<{L(XmBUN^);EfH#1D` zTi@UByUl7_zpmX))joBqb{$m(C}?Z|EC3z=08jw9w#GygApn4TSO5SEFb|<8 zX76h7%+t}#MV}RDXG@+p4MCp+fCPX3zqkKe9{8-Oq|hmj-iCA+Ql%x=P(erwC-qxf(VE;w1$!R<>K7+1}nBtbVRN>pJFe+&%(UQldijpp2uJPqmz=xMH6i%zTJ z9Vs{}$ULmO>LN_1?xUusYJ_omf-|EO)^SfQ?KcSFt&!_Sj$H(3@69KyGkPgb+3*1^ z3w2H0t~rpH&1QdKFqE=HWwcV>26rjbX~vRC7@b`e@}L_BqRKa zWVc0zXwg09T;7t!A$^KOcZOX_BJkyc!!;1)fFPR#2lY#aM}hMZ_f7Od;WyW-asDQs zrHoMKGCkBRt?bHKIxw~F@1X#ye_NmRn(S1k;46yY<%a|qhA?n8vvpx(eRw3#>;Lb% z{6DiEBjZPuJK2%D4`o}W)~CsKuxM)lEJ(~Sp1v-r ziHIOo>9-OCTsQ`c4s*QxAsBVBY#ofl366FhrZP%v&-$>2n4m-&VDB;AC7aNEsu z3Z-9Isgd($^0jdhLv&JrV>vIma$wBxy-iEAy@HR^xPu*oH_rv4 zXenrNj##u#6dhZYx47PApAz0yN2+sA zx-rL5-D-k8WONT|p3v~?6ySZF1aYv=D0}Xfj*-z_er}n3;!)1Pdx-U6N?2+a%cZHj z`a13Bk{xHQ7)Ey29fT*oiHWl23siWhKxM3;<`6QtBAY`Yid$|J4MOY`8UvIS)oSHt zl=pn^OkO%+_M}-{(KkSPUHkS)Ie$gya?76J2U)66^`oD3n$k6usWBjJZUfJdjSX5d zhdEn=RzXQuv1IL>&+>xT7k!E_UeOy&Z;^S|qR*uA#I$y>x%+f=-*rUao2pdK%oxl} zfELK3ljUg@6#G*^(wPNgK8BHE3db&X;KJ+iQr3 z7~u~L2vcwjDm8^x1!!SB>xLwQSP0dedN*lK)4_eF8c9X&?ai4hDj}sbKHndDj^ElX zzXOpS=1;4Xi32N?HN`DCY?gxH8mco7C2I1Dn;I=K1V0&(cJAfmO>6(uB;ukX9!I^g zoss_1&A8ikz_IJl5jKe4LU(ssPs`o;3ii_%&iB*1Zr=5oSrCRxa7q=-Pts5CWGWKI zm|yRG{{OL^Nsc>pcV_itXY{J~|zCZ=82M zf;BxJqVc(xFVpcjhQ;P2R7n|~%e{RiFA}22KhN^U0pOFL-hoFS8Z6w*9f1%t`A$3H z$d~mBFe)C56BU8_Bt1`>) ze&*`4dn*4S|;84HWW3Tt%WYDdb_tFz5%vV5_uS3kZV7hBY^MCr}B34E^Pj3yL@SW zrxRVn0tVIJ9br2Qkz~0-z}&0})hWtDOZ=D~>SgwVhbc*ragim$CXvw^;GExrgjF8J zQR)YyHjqCe(0Rl7`DYSCAMNgz2$P=rHXTS!E+qM$vxQiG;{z^YCgRvs`hv}i#8}Mz z1GSwQx0tH{1yQvv(kCQ}3QC*>RCKYp5$;yduhLSsk>9)9)_UN%@iR?$g%CH(%EAxI z2a#cJq#g@qF%T!k5wC{gpuEB=t5WO*y|{StLWS|jRUH3m8%JD??T{VB6>jD$D5*9^ zw`yGqf-L!-SS~N6500$(KC7t*lZ^5+8;iwiz^g@`eP9ecbmEgb5xRirVOsD_ z!R%Qm#tRVQc9hnI4$VqK*jJ$cqI?@=dE?QQs#em?3%iK(t)US`PeVt3FN+>j=6Db4 zXbH497WLOQSOhLkE_k?2d++WZyn7*PQ?HM$UDDR;>39iN}1^gW1^SJg$vUVeg3 zVTQTe7yrxdAVl0+ND*~VPhFExlel3mEW&%yJVxRmet*)I4J%52ML9T3nin0j!^kP) zQ^2i>BoPAMlQhs2RM;db+5C!|NG1u2GpG3g!r&#BN!@#d%%s_?q(u6XTfsqV-4ZUB zGJ-M1QkSN6#z3?b%|OuC^wBruTl$~+mH6{)rW|=ek&*Qa`>>fGx_~Q``qWaww52h1 zivyzX2Mm&qPD?U{e_O(Ma*7BG3zHblE-)Q4IR>uV)v2C}C#6>f+ z($YnYsm~bBVpF`vLmBHk%JGx+EK%3 zzrCPJ0}hj75;Zv&1%)dq7!bij$8@g?HRz>=I3w$a!Z5{0j(^9)@G1JeO`oZxkEW}; zk@WmWg6TArbv9AnFEH4h^zkgg*8O$!KXc5sy+d*?xu4Ihp-S%(~Ef@Xzl+nbf>DkB==2^{suUg@G?}+fNuqPRsBQ=nzI1K^M z)6XgyIs_VZ^{SJ(U)q><_~WJU1cO>B4$Ks41B1X0>vpP4v$|jpbX#>N3T@l>w z)|okiiEjRGpGn47z}5Wgt?)oczLC3cVnjeqVbajNj^wMOEh z@rsY&4e6iF7R6<6{uKC16S#z+`eVy_C?8xb&CFa~{;WlRzTx_JC0ZUkU=78A{N()2 zP2gDjlqqguBCrPGjjhxbM8}ly)`kCiQQy)u#i%>^sFFuKtDvA|t;n^`TmAv;K>vDs zai^*=B_sADcOPFLSeiEh`2w)Kjz<1=jb1avz#{b&)6Do1c?4raW|}tgaFW@CtyKOm z1u?T4rFNw3alXa=)pqH0gC7Y-SyHVJk=NR6BuLmwU*2VqT)(PmjX3Lcq4!YEiMk_{ zGgBvR!XLfzp4r(!1R9v!W)sqh9di0kk+DgCwWGaAXl$O+qI+@Eat*GV{^^$1G6atQ zC%63E)PEM}zua;cI3_x!h@1O6x8(lCEiEkSM;8ot0J-Wc+Xzx7hbf~-uAIiP4?+1 z8%xMrxQcF$u-`!y5^vxC+buC?3xB!=_mNxjZ&jjdv(M8+cVh4;4{ZK$3*S$-K>yAy zP4WsXSYW0!g75hMV9QUQa6a%PQWYfsnf*azkxFe*($YyP+^1xYAce*_&9bAjo1w;v z*U`FPtTV#{MKpSPI=tLyv%Xy~zxbSrd`XD=1Od`l;q9?6%Av!2nKyb^(qSS^43CdV zfSZe_8VSR}#Oq#4p^ImopNOBU7rBcUt4l!mnsYxX=I@*tz@~g>uj~B$Wy*SmYd495P` z)zY$D;==DS?m~sYuso>>)MSJ0i_ju6eys-K76i8^ zYL(fcIhI025Otw;xClJuj&HhRd|{vR%mVM(l8~IGYAaAXHOALru~lx$z>aFz@-1yb zKRtY*c!EZ*Qc{1+`NBdz_HZCW4n<&TYfi%M-Wlz7AE|!S77Q1DO8?w7>+}9~o`z+} zol(bP>iK}pFVFkkaC7k`?D7vfk0k1IqB8M~ugOpu72bYJ{;m4C@S7O3z%A}Pe&D+ZsB0SIfyib_QQGWS% z1a6w%?sfvT4j`#y?Hw2gtZFwBECjGT z;c(htSJXM>f#N@OyoPWe=j_gEu5M0T6<^fw9-vGm&n_#>`WQ5hTs3ozlqW&1<_Wa@0>VP$XO!sc#gEB(i7PFC>i z$Y&t?d3NMBv>7q@9g36!xz-|W&3W;$+(tWbKEoc#>QwCtW5!Al1gRXKmLpd z-iGt?3(JLPo@tz{8aXfR4BzsPf!!|SxqEpKG0ze)$BX0=o}{$$BKuxGs3{Z$I1?uy zES?LSz97IX5dcYssb&;OYNLB?1e8NmFJ)-r$xKFs zzSSL>@RjARwAS2ExhPQllgbmpoOz6|GHOZo4O1h;wz4c*Wdo*~6Z`no<&&FhoH8#f za@7}J{Ahq~VUgqDrCl_iBHH>=V_{=gWwXn?uW1zpA%;P;`@$bJL-D2Ug}>RCzA1pF zrFP*W25DP_;R2z~v@#ys;0M&M_YepBTeD-^VlPg&R@(4B zch+|+N78CSSai#Ln=MUqFEI8@;Mb5W=x%^B6z=hCIc4zfd;@O~laFexk4>anxf z_1td-17~S^Wbm%m;fSxm$yR!K95d ze#sF=EBiywUKIXNeoL8l7!4y*HFO#14&n8Qg0p1r3sw8ZGzS)6$g&8U()thT?jB#o z)5@ow7$m3X8bet}9~JJvEc@q%!}`fS<0CjBG=YU6ls{=E3O{ z?@SDS!2<`bxuEMiDWbom2ZJqQTYs`G(uq-lQ`PmZPz{L?YhlXE^5WyIwlqHVJ5Ft> z_l(l}B*n86{+R2GZ1pz$w@w0LS@dD#nb_SzUE3v;z6_+IYZx;5i+Y8x$=^g|HUJhZ zx{W6pnN|}H6FbG~(VheBr89!AC4}6F%4xSulW)-YJ-8ZO_vK`o&lCg|aX^Ze;L<-DlZt+&SoSe(`=~+REgub;77JPO^H! zqOT#Pg{Xcu_uAdNqttgTvC|wxp#+c)tE5P)eV2o!KAFeosN?6{yrMg ze{vq+0bko#svjyFA0Vlc0|&ov2{lw;UX<6!N4fgFZ6wfP&LZ;*6jz8eZi6NYVyTK$ zPR5&o5A%Bx)j-0VM0l3g*j%^WBqLl+U1OyDUZ2tQYmEF&#vxg=4@Jan*n@mSE;3?A zq4ZAUO8SScC;sB6Rut8vuXX9a;tAi*5TFi+?y7U@W{#2FyvrZ)D;z*O2df+ZB<~WB zK#V*%hnoMdIh5m%9Qr4fugHSVVijtths;x~_dmtgG&YRldcD9_2&<3d=}rf}y*(w- z8P;MbgnjybaAKmBPwIPnL*&cNCt63oyFqMB`U+xYO0xkWD8uq4Pqg&zjypqx{3wiS zISO%|1hTj9zeOG>*j>gS^hsRE^Un1{?ZvrCo@+Z+Afs-_^1VwQ(hOz@t665qM#KKW z6m7QnL|&LXoj~&_MKpI4rk46!IkaUG-(mBzj#oDI#7YX+s^!+k>)aY`s%l1{NIl1r zOnH<-WzXsst*ueQI5k&FOV=|n>)9pg0&q*BZMUn{;`cRc&w{kcJ>QL>@^*cY!v@tl)Q>q|g5hAz^ zDMif`ZG^OHm>R7e%b)91Oj{Nr;7P!JLhpbn6JGry?EoHmlis008G4s?z(;u&B4tE~ zW4!zDWx4IviAB2Vncc1Yo><7;D;VP9;Pti($hUW9Rk~I-zF&wt#4K6wF<&{G!PqfJ znrS4n7BT0(S!}s@GN3Pd*hLe6ktI9IHQVZwJhcnq7%839c7f_Jz-`*Gf&XG`@Cz$A zf6Btl=)4q%yX{m>pgY;DxdErge^T(Qx2;MaoIfT1b3yg9RF72e1Ix$YRCm)b>mQ|LC$i!5DvubH4vG|_EE4idbw3b?}$2F0IIBN zyn*4O=XztqpNp)ZqCf%(M$CV6;QJLL+33D65;{JIw!sl^kSJD18Lr?q)gDN_SZ2CU zywoP>kfhfd(oLqo$%js`lqH*h^PCK|+@``XYG>3Rg)0n-3hMH_;y{G$hrEUZai~BE zQoDRjPhtdTcA^*$^4-(HfUj--5I8w`Fpj*a4N_lY@EV_mKP|ZkTu=;b<9{x}f=1iA zdIa5U1r^84YHdScnkGbyuk>`SJ>}c`cxIYxYv%VQ@~LCz)sZev)ErMj*zpXnkbu=( z3FkHb7Tbh&B3!CiTEq)38|G{p#8uhvkcQpyA>B923(oIogH}6?(#Df&!RyON`CO+ zPbFV7y%U~F*(ju=8#T}k%@DJYcjkFjYy83XCi9S?VdS04$CEOVK~NbIjT14?o(B$n zxCk0+7LQL>G#UzhA1!LP3pI)sCco>shusK;>R?`f06n4Y@B~)gNpe6Oc=)4Wv4*Y4 z%K28UNZK4-Au+J0`5h?-K3=ZPkXA#cDO0mnLv>e@yPn!y;~*d1rAmo(e()yYmmNO> z?q_BeMITbtJrCCt9NaiwNrOE4x51@gV7H?h9{`Ap?k7nUPp8qFvEkRz8|G`Z^d#Pv93lDC3a|j+#Sb+YmJNCCKeJPM~+>(7UY|Y!Bny)&K8y!wUmmh*=lUA z>U3^yU#JGI8k<=7h8L{JBYV5}P1i9%hrdZ;4lOsLH}jEMbI&pKOppm5-_}5F2pzOo z<4D*Ck~4z7y$AbA8xO>NR));*LpODr`!g(_^_1j;pL*6+N)+3IJF!Z-?#Hc4Of7Qd z6m=Fud~PaOr))|fHs!KwoU8N0KI=PJq{c8Lce&O`A-V^*p$r?~HD^HF*)wg3n5nx+ z2gV1)zg8e$w8uU=oYSnn1oP#eZdz{hAr%E*(E%@T4Dh7P)WJm6*}>6;&BVdk>|xI& z&-?Fy3`{UUWW16Bxc3e|47kr-TP)fRFVEc%=qLRQ1H$RjlQs8<7=n|loA-0Ls;;jg z6*gb=$nvz1Z5Qkpyn@_itU#2G$9|I2gqt*nw`JB6okH)N#Q?Id$Ps7ZuVZ9mbGE0S z#?30{HS~pHMWkM*=YtUhI?#7VlZkVtK!+f%D(m(60Ov$_BmJsGmYJ0*tD#4re;|8& zno*SIri96k-I$mX5qCu9kyTwZibucsc4}C7U7M?AuB>x874){d$)#aAkpDx>`}9bq z3F@J|7>w^&Hw~UX2U<}~lMCTcj4mLC#!syc>w%&r#3P6c$JQG+v|l~7{G05aj(CbV zUd=!uOJfI&Qk=VI8um@XMqms~xe9ZsHS&esaU{Jt19s=VvTa+Z73f zOcW5kMqcOp?b?ieE*<}HROl(VDo6h->5Lp5|I4`tw*7I-Oi;1;#*W@~h;&U7c@ULZ zmq^pgNV72B$x0+UCsOvEA2*36KP|l=`&AFGT;{Ed&Rdxuq%qkt@y+vmqPyg|5xM9; zbXYmzd6QZ95RY4ZTdycNsj=k0nY@-H26Vl>yOFEVu%(ED8sw)mq_EC&XP}uQBn;$A zhGNoGjG|a9dK;;^&MYi_y(mupq4$R#f_H$bq~~N7WLIj_-j<)#fTg#n&n&4f?9@)X zU0ZGNMdDRUErW46{dIHWa#Ch^_%^TMQbK;h(GG&5SY5pn zLj8{AtbbuDrz@Fo!<*ihD3UQ6p~KV`kQ~n)i1?)T^$H67x=(_60J-tD_!&|Hr^+@S z`IT@`cq!eYhR{YUm`=OBP^fV z^5tpT>SCcXXTEb_ZC!C`i9!or1|p;=)t4#-#2@aLWE1U#ZD#wB)x1-hk?=gZVK`CQ zFuTF?YENBn4Nh_1WAMC#xWWU6Q$#*b&Szl?Pe ze>DTJ^myL8_uYT@-Sd6td^sySdna?OIp&ySO?1yQG4n96 zGO#f)FzjKFJ-7Zv2qOc77b^q9R)+VC#;2Ve-7h=3n;G~xUv@K*^mee{o!-TG@Dam0 zaQ*-N=l`$>Jdf>x)vIj2F~!oiXB@Y$2eJ8XW@Vmlc4gaoq-^H_cb##29j7UKOLo}J z7@i87gBB}+pOa2!PKO{~Z90h{eX>u!gZUHE@yN;t{de>zZ%} za&!2~BSe1~p5{2qBG<*9vi^ekhRuad0_`LpMzMVO9v_9xL9Au#Lx*GMSdKk@S>`7$ z7N)pqd3^D~IIhnRSzam~t0B^ve$?TE$!V2dV_(W0{oM5;4BS49>SG#P{GRqd&uLC# zJwh@q=Jr`m;AkCKvQC~^+UEYrWl6_J1N%{OAVa>e?26^K0G<`=vyIOtSs(6+H1p2S zn(lTBL+y%}%p*>G^h3$pC?+dknXrj$7mRvAdGlg?D+9yoDiee5Z}~`oAK5z#Hkl?! zw~Y*~jApKv?cEMbLjT>J-t>R-@c)?nyLs=8cKs2K7gHJ)r#~%?Icl(_Wt3}blrBD0i#_dg!*7(0KTLkMF%a5md04x??4WA?G0UrDTW?${g6ol` z@D&@LMuFQqEev}<*qeDjiAk~~FFk!06(vv>^JA~2w4e^p#;@b98F!CdXtJ!2cdR_Olfyr)NR?96!6d{7QLyhev#`~8-4(lTj#+K-q?Y2Vk{62W-5{*&yDSMlc0 zcGO=we}s#C-D3MvSHVJbz>MCHWtorri{b-1MIt)oa~a-@nfZYo@$da|FEQz(8Q25P zfE&5MMgaSTey7~jYX%v$xnl2EuDB`b=mQswWwNg`v~N6>UU+4zlK}R%;JZyWs}DUe zF~V}r-GWozCr`X`^|0T!zGg-FDyy15E&3x}mTff@gp7!9l5z5JWJwYOd` z3lmzOX*YFZ&$2Y9z5q}Belrl^eC;w$ye#ITsmqqe2Uivk7OnGL@_%*O_5FqhkJL`N zT5FxXO`B)xFNu_3FC5x9JnB8iVQE&L+S_j=5BhlHn{7T!1H(t|a@`-VSP66%z*zUp;j`TUMu_t+aRzV_Da z{*bIO)lFKOGHFN>-N&t(rxce|G7$>fpA;K8W3jEd`Q#p{*IFgvdBKL<1~1kLG4_VT zNf@Hl{yOP7-J5%NU%h%XO-c2%R!2`$`24nt7qBtL)QF%1TFJbuCmxcdPqki--{2l@ z)Wf87F+h4BS4`NpIQ9eGro&NHQL4w}n>Re~h$$ ztrwDzxH2GlJ7lmfzAs>Im@M>I`C-8Hx3By|-bT+?7wbi;fBf)~-+a`1PkYqi)E{%> zucC65mY2&L_kVE+I;;Nu&Kb3I<4z0|;8-{mSd}5fFyCNtF<8(efEsFG(iwbf5Y>9X- zXzqu6Q66=%kx*&04t{JStZ}aQj-2J>v7KT&?<5?2GWZuGy4_?#zns50H)KGq8>}TlFmx%9aNBb z?6cL)RBiH$jqLLadN%~y)Z9C$93J#IKnzb&UcBw@Za{9 z@|yAPr?AX@*Bc8ye=2CtNgQ#aFFJ?^;`C%>)y3;#CMRqM>=B_ zw;!K*P)lNpAnp?C8Sqd{7UXn2YSp~q^}?}>h4>B0vF{dRV-7xDR(NfTdV0(ayS-gA z+pT0sG=wW29#Z>0u`P7>h)GS8mR!0W$@zHg&6|YY3D)G&2JZo3lYOU89>|U^{Lrj~ z*)DQ9-%^rkw^mpmX6 z)qbJJPKG3~>zhl*+yUpD(8hy|x#xSoTkzHN-pRerxuk#p-KB-PB%G#{>;_wBeT#{z zCwcnox9lNqic90Y!$`y+V=u`mqsAxA(HIwZ7VW|YHs80UR^rn2Y2_h zQ0r-}%FKv)!^0io?05Z){eewKO3cDUM~-D~aTL*1Z#(Q9n~8JCd_S0cz~t+?=KgUx zt8?L)j<_nY+zry z%g(ZANjFY&CgoI4+@wj|zVjO%go}oYZ55jf-?`=ZTbaDt4Iy2w8FIZUnsO9IV)*e(ZXKhozV|}?Tf=L z51LzFHAG2!+FCS{Jnb{GYYRm5d2Sqdr}{$fg&$Egz` z&F)M%dF@m2&Xx{bH7H2`mJfRtUOpy)cS?YS*h?!}kbH2nzI@r;ZB2Pn|L530x-;ia zvn`Vphx&ZzkC65%(j~r(81H8rLhVm4GS-n!j(iUq%6!#F-qYr}yG_gMo}_|;b%ElN zQHoqMPe>5KQN<;%UCZLg7f(NbKi2)B!H?xxk2_lgITZVL?exyni@%h7?l(>C zvdvquGUuVnxwL-k+d-UYBXhE%l6~|^{p|Bcwa*!3FiRbYyZ4a0;r94Bcg>x)9gXAJ zZ;x{&ysT=kDRE5X{M^Se!l&us%=)bsr<#KQCoIu(y=!6N`y(ummvwFxq|QH38jI!^ zp0uNdMV1;C=D&rd1a@9xE7&Q;;NNZk+AY+5aunJpH+5@a&yQ@b+_-W&p`xqeU_@ED znk7-rW?HfT{_Fb{PTyv|`mzsixzfdV=gfwK&v@-FCaqKYP%In$DETZl|KfR;E8r8&{kk&f(mpx9yIa*CSi2K<&EEf-=aIGo!U=GOh^`Jrek8MlM&%o)l$j9vz3+#y#3Gg5GFTUF@uk?Pi?)_LI`{Jxcm<77w>J(Sh zg5Udbt=z^3iV1rR1Sq}}^=UmVjUmT;ucF?1O{-;FexP`Uzd0fPes$a;DYG}huH0v= zrTPR~Cgq)X z0VlJIG-1ws;-mY3gIq>t#&jU*Lo%w|t(_#jm_2Pa{`J0%*Hxk8ugs=Aah0ZrQxwmn zRp%{ux808uf0BS-FwuB=M{m#LQ`(`y?E85$S65mZaoM{EmVK^9m`??_Z1gQFWba2s7svX^pnsbT7V)Ub7BnJtXkN`cArPo1D0L;Y#9> zQ`tt|?eBe(6_)4boE3I_=w45$oG`mRXYo|pqC~IY%XVqfVtmLEQxB^w**9cup?Z}a zrRqCg$a|H?cB;)Q)ZqgGWBj{bbu`@(gWS=}fVM&N3Aq55TrIq89IvB{c4$XW_jy4EOTv_+4XrVLvRyIcT;3QNt zhTJn(qmQ-4_kT*9dUYWbCfh&5IKNMUMUi*Q&5S*Q)11e&M)< z3Ced{)W@W!4I-PPt^FT`TqJdGDa<}vo1rG0Z%4@Qx49Fk6Z*v4BI3j8bNQ{OiYiWw z`Rwh7w;1mq5H4R7S6A?!(>4{g>@d%})Gk)JVPuY(e8B+MlQMg3y|Eq^J3WL+qez|U zdfMQ_anks7Y+2bS2Nq95DR-HDrdFgKBSg#oG5fq76mda=%eNSnxs564h)q4G9y%-p zsT;g{!9cV|KK`(^N`b%qJ?D+XI^WaM+K+oS^%tlpd&@j?OqNVdaNFACzw!OM_I$(C z)h}-!hv|!ipS26A+$MoxYQ1@@s6gk~ghs=+11{;;U*4LyUE_Uw+H)B0?)a>AI9bqn zp+bMJgYOg(7j8Xw!jD-6x@L(JkZ#?F%aTBLr2G=4igu)_?| zm09=PQj`yy{W8;1o;S7QJX&(vC-;N!gQ*)Vz4|!Z}a{?Y(gn$6OO0oIPI`$ zb(&{-?X~%Rl)ZwJLB^`>36;GeUb4q(QW&k|@8DqQ-bm3rQO7LhWq;V9&TY;Q8%6ik zaxy)M?Rs|cgve|ClOja2PO8`AZ~lQu?m|n&UcJM! zABTI1W~-;(jIRW~8CMFhpGuq^9awO9$n4a4vR9@YJ3mA&og{@VwZvN$FOd8v&PG!{ zXIGcn7C8j^&3;+*T=JP7YOp)gqg7p6iV466K15fZs7mrW{Nc?KrLnVoqT1i)%*5R1 zv1*-<-@yMU;&ab%k}1vQ1?i4W_5Mx;*aUc znZ>Y9H?OR)tQ$%--^6CQdZ$`Oit)Wdi%#X=OqGlMr=1sHo*6p5{X_dmXT4R=@uijT zqn_1$=-K+i-sFk-787N^WLZP5p|jups+O2rQ(NX!U!@6<3$X2@oPoYyM^lu-WzEV? z_Z0UARF*2bm*@IVH-9o=B^7@N!sn@b7ahZdqv=xme!Q>rp;A@OdmN zddOIxGxhR9o%Hgsqp?zB3%Tk<;LwdL@qM?x%`7e~H1@R(G$X3?dM2YU6e|bdMk+r{ z9`~w_pZB?1vb5M1(`U38nNr=FTa~%w*mrVOdxj;qXVON^Ed5Q`|r(CkFkvhVk%N}s@Ge8t@N3W`^>oo{hgsxnS_{6^nMHLu3m z)rw_Y#eASp;MLXkS>~3JMVsaM$@~SKp1we9&+__Fw_T<`a)b9z51P$;&G*)iGVOpX zM`PZ-dHnUw)=^=-2F}!?TsmK zlC@3baktGMCM;jj=0|Ba-}zDNqi{}}Nj`(yH!hAWxqWHm@boQ#&+1F4h7<9WS|Yhn8C)ev9R1H7?*nj8lss8(_lN` zA_j)6PcbQPKZPC6#1!hKA;v=EFxJ^)*KIv`5KSQ%gl0>evY#qE;{p5S+0aBxa&Jsf za-#LnTfcAh{BU@8wl2cvUI%v#SqIJCTK43R$(^KI53Ht1=htvb>2>qj!e1_ND;5e1*y{t}aoKu9@^o$AWW%wbob zEu0f7S{*u(V$z)7FUJrluB~XQyzo3*AD1&YXmIGM4OUpy-;|N#QAaIty+H^|je!_e zG*de^II&1{%iCefDx-k{Vey)+^Qg@VJ6 z`D1wA8o=*9QZifM+8T@h;jcm{nI!vS5;quRJj%?9WS(7k7RV4Qxdgp2LWYUrb-_dK zK@U;$V2E851-IHl%qHn-C8)vKZ3QqIx~V&ejBh{Ay9#{SLws~0#dev8{1kRhOPTlF znA;QICoyB9LVBFS#0Mm8tp^%#m~D5qhMttVl&(#|SO`Y-iGqIfp#yrc>4~1AJj7v* zSq&UE^;x*V6_jd&Iz|tnuN(Sou|qdaZ+1I2Ht2DpjUX?7Hq1!tEN|1;`ExQq>Z=FJ zw_;fnZ8M1m=qI9=Jnkie;GY^|5V?)Tr#A`frPyfEKB3RsB+99mH#UY52j2#PaJh?# zA;!Sda;gg9PYqI98Ca`qGiTwNPf+A%Fpqmx;5MDy=3T5Y?XWQNK#jtWts_EMgXl25 zRG0_24xJ%-KqeHvFT0R=x8CT;*lA1yy9xzcq6ycuWyfmh*+g#1c6XlLLo{bRLS7Wb z>+{}EtP{OwkeIF}DroGlA;ShAMwv8(Vl4GK4b|_#^kXzKuz?^{Fk&hAav7L}w}tS# zPda$^>m4fiwuji?5r~Jw(HQVl6Yx}N^$3He96t>iYjB$j4FrsyUX~3;_*}*z*c0J` zZ>q%Ih!i_TUVd!k1+5Fb5u&#)JW@Exr|qvR^AvnQtAU8IH0abB;UY-R>%>!HIVF~G4m)^+jW0l{>I(54_JX~bKf*ec3H3LbJv zemE%l-UxcjfzW0vhKJtj25-fGU~k84&0+m)nOv`!rdCWO>b|;Q6 zfVX|@Yp!pqwMQDSA-Yg>zen642q}trbGExT2=#81iFG9}KQ8i|mJ{zeQ3u9w1BKGX zJGS{u;4zXYfIv4L0t6(3bq4Kf8*sJO1>sm%F(Ew({0iYkWE7&|NoIcJzSzuzcVVTm zT-ki_D*!Hatfij*Pq@09!VE&JJ+eLE;Jf^Ssel^xJT7}M9WI3ds9ea9VmHVp zK&YThYym1bfQk-4@+FiNp1Tc@zBBa#-*-=c5Mnb+Y?IZX# zYzP1w5Ih(KQ(_HLuM9&=y>OhLu1q+>5Ty_Oa)V9-^>o-po~L?tLx6O^5AcPe_ZV#T zE%4VD@?bjDbMJt$*CZTC*F4dx3&e0|-H|*a08V#wvvn z`?aw|q#YJKmcyhOv3ypxh=Co>!Q>;5F723YBpyUsUX?T=9FcMW`@sl)5m!J( zXB0x`5Dr2ILq-shSRA+sJLM<=N~TKQ$-*&a><2WsywozCSo%d9+e}RZ$(68z8i2#j zAb`WV(JeT{MO+DV!|X$NkooEicVS_MLVC09wgcm~Zv&S)-lLai`m$UW=K893;fppc z7n`_Ee?ISJy;RhbD_b1qI8Sg2SfH4ldHg0ROlM+c0>99eTM9+d7b38YDo;9I5yK3k z1g{~C!Oux}`xZeXq|q*j?$)V@LEl@`7PdxrLwdHl2neNRL)1lTs&pc0%q9`Oc-Pi> z)Xc_%mTx9&`DQDY`T!G+!2S(A;#n*_Lk3E%GcsE6eHiI8L81hZT4Rri7%F_R>1D9B zB+B5#a$y&(37EHukr9Z7oXjlt%dxD7kQsttov+BVvOJFomtOgehPToy{mbRXFC(aik4FphjmK z=`95I3>2O7 zsZ>a_p@u-lVM!Ebh@a_weR(?VYPo|@)BW?2D{K1q^LNIZ_~MDZ59KOMeEgSes&@^}$WG<%1?&fV z+EGee4-er~!4VK>g6LTp0)*(<4RCh6pDv+fg$yc$aqQ#74v>$DkFVR7P72NCC_P1? zm+fxxl5tHJv@Qd>Y`JiXaAYhDqgsNp^XKKqgJlcaGH*ld8>GxZLpvOPun8`Y9mNV8 zWvH?vvP-^M5OU5^=0^w&?^zbhT)f%mn-(PFmB&(PP#yHmD}inuU))58NdPx=ToC zFwv$+KrSbOWFf^ufqC~3U895Tk+1>RNQV!K_(Qkkmw(tSTaZDQ<&+d~Y;kzPDdWz< z{8y{nt0Rk_W2SE6Q%QkSyOLH5mzTq=iccgTO&(}iwO##W`h&7ES#?#eL1u3^8^=o} zk(_!iUQUY3>QrZ*cHyQdJLFJKW_{$&oI365eX7kfVXH*`bot zu|SM~hurqScp%Y@qE}=D5&N*x=OTy$L6U_#fndU3%SKqZ?7=1z4Ul(pI`-{H*l_nt z8*=h7>f>q#VF-Ebp*(=YIarQf(9m;Ct$;a(XozkCcbbLM?(_`ndXi~dVU9koVi1Lp z!g@4e!K3eO!PcvITqo|fO0n(b?rj`pnj8W+xnXyQ8yJ(}#e#AliVF|uSMDD0`v0Tu^bJgjf6^`u-$AB(frVn&Ter@yGjPqTcf*q9eUTyGfvwN zF=1873g8X=KW~WdM~<>ln6V9~RKyBO@@Cf|JFV2~UG6d4$T7ukKc8W-sfrycM*}6L6(sfzk^1IVhG z^Wq1bDjCRy$h49UITrkgvA;86#CRBWw_z6NYMOQE*(o1?n`f;-*sZy&l6H6xBXqwn zW9}%le5l~Ih+%aKR((K$JSL8JuDnKmYm`<|HDfgVoM=-eunkvAvUZ7}uvBEWvk%Q? zbSTK;wJQWPaHR-ams`;CTWFbBcgr|sXO8+O1+uS%q$@6?*fAxO1U5pUz2Nm)ue)}o ztomyB#*|o{Pi1Yk631P^WL7+G{=Y*sE&1H}r;)RH)Tj-&@Od^Qh+&X5l6iYE7 zB&RSr6k(fx7XTN70{8$JxJv=8^01qz82Juvb_cUSRbv&yiJtz86LOMDc&-7Q0H-PE%zEq{*wiNl9UMpoo2xQ8=G9T?<_8Gw(uZ*jO0g6@3ZbhBu5IUw(?HqC zsuWtv^s8YTY*oPW>z~WVR0=!fJ-=65`@BYkw0dOEqU?tO16LyU;iR=95bk*GLR1b* zAu5W~1h6|P@=0vwq4vl&Vgw>2qXs5M=;{D>fd}Ri%`5uuBv{%cp3|Vq@aD1GbyX@er6MfL7i_K97BI$XR`S%$@JZ zQxiiFC=%Wh&7WN$?9y_u_9=4J}GX)TkKP0X=DM3fDVZ%0K_kQSDgUHq1TCh|(|6#2w5CHac+bj1fy-dFfC zVr9{@NV}4f5sw3v1r%Wx!-KhnJlj@=o%-OpL&=Xo;u#_&iFS%40m&lY*pG=f#I_iY zqc<2{%GE9z<7eK_R3R|iS0ycLsJuLHgRWXGRu=Xfsh&to`j_2jwt7^6&hTro$_2dY ztUd@^3VJMm@TH}4|8;EDRa_&gzQHrs5*=?-0wN}tF&g=WxWRDQ_0cXn7XBSoVB^;E zMu@#QG!Xd>tVa>AA)NAu02jYCzFT6Myalgemf}o6a##=XB|#UL(k@nz!&~$#3}Bqh zDESD7{0WG>*FF$VsPHB>=7J~`b$thjB;qtorF3e=#tgF-Fu7(Fm{?KQB3O?E>u;EN zYb<#w%sEPdIIwdDzZuHs*%X}$YEWazL%vX8v}>y-#UdQ@m$AuRF~(`nEuRE;ox%*u z#H%tG8=|TkSo2&hJDozeL_J`)%)d9An9mxC?GTkLy2iV;9M-@K6Soo#Z$OZukW9^R z8wviTnx+V?=Cx%@Xt^D3qt4$0Ew`^NJ3`B3xQz)vf34&buK<&bF?No;BGS(B)fHE$yKI=tvh>iEy`T9dUOsj^G(}%RHkbE=g$TD&xnCSFN3T zOnL&1`-*!U3IdNUc1?PE_2RD{T;1~R;sPl~|9r9Id|2k{dxz%g==W4oa#d&yx|Co0B%u$moT_Fo`Q zmMvG6Qgl{#x%EvX4PTjQ(GToVyfxByIY7zXsd(bFkLyC?11H(?07XAWT;d-D@<&G8!`#?Eff6>C@`((Zk^{sc* zw`{;$id+T^QSh9tc_8RGK4Ubq*!trwh=X|vPZ{`{ks{_2!d)n_Vh}}b`zD;OrIenk z_*}l}bAj(T6ot0D2rcWY+V3Gw^Vm7>=<-d6qXBjz$9WA8<|kzD<})_R-EL=IdH~f7 zMcuN~b#1F9%nFlK+_ehRLDYwyXO6J%=R3yx+)|dJo)bh#w5INzP_JN#`O_L+y07cb zxmH|7-@hmKFB#gP`D4l3_*J00zgx$F$CRwuzVgP;$_|06Y40XBi7ZQ1d^ej&eIFIr z+t=&GC6X;AASakP?z}u}!fbqmlw%nkIK8Bnw-q-AVj_iyWAHB*e(l8b!4V|)eD0i` zWN8zS%4bzd2IVN%v8v1`+9M^82LtX&2i$X0zmE5ckqBCX#Dr?0Z7nz2*0Pib1k&KB z@|TSW@c|$YXxsiI59{U)h9PX87=kf%^G_hCo1aqp&UFy%Par}0z~w$##k{Y?Krw6( zCPh#$mw_%HmT5uk%N-74HxFwZV1K;<3Ek-LfMx?S;Lb_tQW!!U%-;qOT0$PS4>JXtLxZU;DNIGLRus zBeOLrT`UIq+F)~3Zw`MxE8Y&{VWP=wyb|BHdJG(?d^=k{F-6AuEDZQ}29!;a3*LR@ z4XkpQ@A+(FLe4Hb6_jzT#yN3WGFT!6v9)$rA4w!Q#Rd-O#PpgS2BdRXzQRs+Hp67= z6p+zdUKeIYMVTM2q4Gr+_h!6YtHOY7-ktt$&CVpluk zecTyPiR_V11xZPOtHQa)H7;p^xFjm*0m9jZdz_^tvp@1V@5dNt^}-?QKIrV0kPL5y>}v5wQEpnd2YD(gTh@K!$H-6iJt*ftOSv3wG4R?dhoTb8Hmy|gONFxD#K-T z(Qddw+U379w+tBteiyjB@E;T@@*w?M2WK14WE$^(Tzi?M6!h@O@22fsTfxJ)ydC_oB#jcD>xrq=)`Zdls07)kU zlD0hkh?hp`>T8tV`b@h7m9rio0n)!D^D`~14X!mH2YPEx{hl%8;L)%4AeCw@&C_@x zdTEqlS-VfVgb12bxGR(dZR(x>!XeFrmL0tyI})<`d0)i=CC@#63KUj0#A}N9&#cLh z^jJsQ9c=QnfselyNSXrhqYsE)YdA{UfQ(}EOpAqXBXWE#WdJ;JXeteclANg7@dpeW zaf8(4$S$EJM^}?CHy}a2zmuQ@K!KhF`SeWQKpU0M*BnZmt#~iSUcLZl3ATrxV z_W2_p#0h^vA*%Np3CjGPWOp=8#&mJW4cO*yYh$NTUoegO2fd~Kfr5ht1qVPGi?VkCkL{8+POGs@UxyUG zm~xE}Zf=Mwr4|rAD! zXuhBqGA3eLAPcwPt&1vNV3mSXZW0`!XjNqPM1BGt zl{ORaeq+;$G3rO4j98PJx^HSaL`fTw{>&$~W9c$N7@5pwy`93611jKlyCJ9^ML@(f zE>`aGL+GWvq`6pSU*b)|FG%KM`xRL89zunL28gyjILos%lgEE_A>Ciol+@b*O*t;s z0++vvZ&GoGquOa;16o>Ya_3wXVbbx^-Clx+QOZi#JS8i)jvs{^9?TO_ioQQv&Q>J8 zS(N>Bvsz8@frESdakcgJE>^l)ra3dQ{H%1lZDh+UTmeI2)Ux zLbESe-ql8ji2Zst@_+^MioBNLADJPeQet@ws`HjAVl=_Y4%p8o+49&mHWF0Pi%8uP zRI%U*kX6f*Hfgu_Nnzyx(uzWhc^Q!L+_h^B7o^1KO8T~^a$E-C=Ah&HL!wDvcvB-B z3|LDTNLN>YYR2<8`G1>4XqD_O>lI2|aVvIk#2+_XnjBmHLLx^~2HrjX z*hv{4uCBM^non|=Z+zMV<|z!bvU<_I^y3KjVTLJv{a{k#aCNsyai6T0|LVl~h1t2@ zDxZKeeJ5v&dpk$k0()d+os@*iihJhLPL@v0{il94na&TKUQvkYGW4YEtOZ2bUu=u( zqw?oy@u>*^{Uxp3kcf!Hl0=~t*_sq71}?r$yV$(cmQIQIXO!L^Hah#}GIuTM_dS%d z7YT%Df0->cqeH)F=M4q0`3%4%o3#zYr*@Iu6}=W0Iv4Bl4s*+W$Cs^U-t_b}UGA-p zCSQXrPWKl1vH;X-H_Jb;8_it{$&-Bv)><_B?gaGh3Fuq!iXA9s$Cw<`T>eUnp$(?yS6~3ZC_g= zje;{lqK*R|fTD%6XQTBfC7C0ju5Dab$IA`MDIbLq_n8{Q>{3ASo(I_Jb zP(pHU#)IUA+RoR7!BAXj3X)rVK;a}uOA%lt?r}{jTi>?p#%X}9K_zRbg}Z_(rqxFW z^>46go+z>#GesJT_XCnWsLKiDb5vV^#?^K;0M%ol{TD{k+JANCsI96Gavq96@+Ff_ zzT}RR7PRxUuLL*MIJM9f&p4~G1>Ju85>Ws~=fA)J7&XguqG;(C2Sq)R7WLg_pZ}F- z03$RRO({!Y^E?^{)XP0cy>xT5wHzDKHI9EAd>~lS)MNqDwquZ3bp{eMLhN|%bunQi z*#B|NZn#m}I+PfYt->2b)i0$8cSsqKZGP$(>Tb?i{~5GhKvnlALZuVx#;5?5GvYqd zZu3*iLv$R~aP3bi4`ml9XyiYnJgAw+jQ~7oN>K^h+&a@vfJ3AYyXm2K-=KcX#JUAY zMbjFpIkRTMxbHp^Zq9F;-=LPq&yZvVezhj(uP=lCnh$7Bv0~^xmOll|#_UG>9!Sd* z+WxNPfry0Q%r97Ly^!{ofhNgAZ^R8jzbpI?8-@U?7**r{Az?OB2@mBf(q#lC#i)W^ z*ESw2m!}dThM={2g8>>jA$bW5ltFPCJ!R9;6Xe>& zF#)a_XN^NOLi-rq>9W=oL&XwMQ#KtnL6@&7`3$O(2OgD zdAgDV^|PS{Z}f8-Qh@Gkph*#zwGJs3>X72>G6)dqiX4|%^Esl7sMG|bQj@lQm6V zl{ZEK`rOHc)PVt$QLY&jmEScBlnTv^z?kr4`Wptn3l_*BQ!@q* z>fWwVntr4)(mxOY`FNX7KJrfURBWnyw9^`ZFv~seoEs6NEmv*y(Gf1v=X11K|% zR6TEL8Gi`W_y!G@{H{h{ZtT2qXZ~MWDxw&E6iF5IH2t{dIBhc?l_0z4{0oWP?O1-3 z&iubfdSW@a5miGT_^Bb?S+{8yKz&u4EhyMMWomhyFuHsIR)f9ihM!i0!GE+?G}v$3 z{bZ&!r@`P4Lx@i^`U(?(&yy*Z+FWfT!~bw64d_KZojX|wxs(6ytztCxFkJfU<{yim%^rC=k0NnO9YC3Up(_{_Q;1-dZXjR!2xHdO|YvT_It^h3QSFxs= zIkErJVbob;e{i+YpvayDTVk2m+P}G!w*_R5|9S&S>wx0Q~p#evHz#_6rX6Snd zu4g%IRN1TN^Jm*>!W8}km(%~&)WCdSRg9Svy|=nHgz|HGpvQ6bzorL1$OtWg@qyD+ zldG!*QY&P*9b`|ouGy338(XuKjevxl8SbB%&YBG~=^078T+R{nWgMjAGJzcc~$wHyxi+Q@4R?lx-QdPZigS>27AB_cVVdj91KmCV8mRj z{@ZNH^ZNg2wy4qdmr%C zqVtz{|I>H`24GF*J9ZYnt>7eSJ2**FXFk#SS3a=I{2c*LZ}C1mDh8TV3^d30{o%G~ z{@rbP@E2+ZZp$&MREY&{%Y8bxB_&1(F!N*KFA+c!|9)Tu6G0tDOT;n%!OE~kqr*@m z<@QG-CAf?I8JL!!DMm<>=mVo3_Ob8)+D5)V6kF?Pj`yIoSde&^)(#SLV6C8vcLJSw zFGnQeF9!|zYy)C%7*!6`fh9W226R#l>9Nl01w%5BC@G~Q=8lsMHjmOTHAg|s8eWE$ zBv3MJdwRv(hL`527}8O*TAmzt+fiV^0PT*a%ZA)>#1O8S;|B>%=hd5Sz+0T|FWvu? zgNJ;IE){^s5?S=jF>4)DGokGlGXv$0b)7@U292Psp&hz*ZrAd58dGEB0SC5Ynum!d z^9kG&ui%tSaBw&0Zi@?hwFq*7j}7EPiR)~HIX}Qb9mMa-Pur^@4l-`{*PgNi{>pHP#vjCA2J^Wvbs2S2Lq)F#JKGQB*w3;^-vw|00s{MfKNPVqNs*Dq#D)o`U$V$ zZ#t=JGLh<^Q$?fYqrsE9dxAX_Hg5xNC85g_sOxJ~3s~#zu{_YfLH=Vr&@134TMk2d z#s6|K&65bQ%buk={rSIJ6?WNhG;{!v4>`d8Lu(F3oLx47whWz`0Iz~}lXSamUf}eC zt07YIzg#AIg$N7`Ndg|2^LC>t=ED#pWw3en&-`XAn1CY@0H6!Ce}eqqwf;%$5A%`c zAKWm{_``dob*5Z7^d@`{V_hr;7XkXM@}Qp@MIF*8`2xGqVPR?SJgM=vVWB-4I=d1^ z(mF2CE5YbmS({|ib+I*50sp(H;4J}bo|ZG;T#GNXf>^I2^Iha8UZ&ZT=FO@#TOsY9 zL8ltmoGyi0(Bc;4)2a6k4aKb#T=7hR-Elgh@{g-fIhc5h5DDG?dFCMd{yA{0b2iWP z`IrRRo?bHMN|WV-XN3`m%d9R^`9dnc8ET=bj+zX!fy0(c3b+k~L{`U%T1kD6&~;Z) zmx=Y-a{!B-j=bFCm;AtAzf5eYZGAzGw{mi@VJ|7E3Q1 zf$0!w1xSdx{>$V6KJalP$h7H%9AY@tmA5oCgpP+BR1}%cW0ARx*l6=B^uD}sa6`l+ zO~jk6pjH^${B8tSZO!R?@aX6l<5)T&-=V1}#GcG)c==I@DBalzEi=eBL020~SFWo% z(A3a_*#f&?95pW1>2cc}6me%00=1pe1nm?!vDG!VIi|a=Yie(ot3jJuUL#iQH_f!g z5Yq2dt3Z5%EnRA42-aSAZis}4i&GUx&5Z`a`wau})hm~!eeJm>9LnvMoG444lZsOsYu$3Nssn7I6TV-z%*U&46;=uBEvy_4Qt8PCS&X9ZNxOH}Zz*kf)7n)5a zMlUDLlWCpcT3WyTqxYcgE!jtO6%NvL5NFVcm0A#g4n@#-l{OUdtL;!Pka2;`58NM( zAhSf*S9R+b2`L;!c1ciM-pi;7@3&%a6aty92647C8Q zooz}y*;U{G8eM$@z248W=m_drbV8TtA1Xub?k&%ca2)VuvR|5yLcF`(VO#a|EV8nfmClj|WI zoRmiVl8w{6cA$S3L4kn(E?FQG1sW`um_bxI8lu4>4?R2DhF%Od@s1iM!POX48l01-~r$7Zd#rE%rX8i}Ew!b6#^R$}I z^rdx%vnLx+?>Bg6!f7VTFFdKkB35*^FLf@7HZ0QeAHyQFK_k$Or4XrwQlIj3z5}p9 z^#TCl0TqO8(%f_hqw zrk+;Q&gLMeaZ~hd$nJHcvwPR3y|jKQ#si*-zc=%C_}|6*&-MvTry2k8RkkDXrF4W0 zO&Eaw$tovkB$1Ahsk33UX)hUandE`slv^2VsxhvWq22~6ksK&3=Bs|jz~M@RQ$-H- zs8sO9Se_5)S0SU({9GO|-l=00zm3^|Y)GT&qCY3T^1#U)_4>EJ+M;(5k5eZHN&rOQ zPtSpZhZ1NlBl!MNj+E7BZjzNQCXE6qJH7;3w=vyjo*iu*4LipebWH201_RgO@750l zix=6(_a-*wRxe3YP6!LgJiUcErPZWTqn2*Tg&1=TuTG zA+(m5Kyr2Awr8Qu>}WwAC)o=?iUN>=sfP#$>J&&4I9qS=?FBXmfm8;O&xd&6`|zjHJ2uJLpa;q2+DBmLNvqJ!!-*a$Ftw z$tBsli|&jq-Ue=^Cm5UpLm=@1TgFA96YuB4&Nh%)nGPm#u4D{tsV&l{pFq$Aqd4lI zk_7Ct&Z2LMZ(Vb4a0AC5%ct73snaG6C3vba;RZ^N1oVMj-~)8iYhc}SC!}3>v?_1Oz2Z=ky&g5~UREkVS2Yf!`0+7;u~0Enwzmk*(;r0k)s1 z0IuJG`!$a!$OY_s9%z^qtkct*I!oR*Fb4vtx#x`7uYp7tXdsa~zJ_>A$8I2gp5IX` zGV{VD6&D$*fp2LkOC84f1;i~neuHv^gEk0qhR(o;GO7s5D74{Y2!`@I0EPn)3^TTZ zCCi{iYpRBDQ19Pbdz?=EM?L@(ANx%;3m2 zV>k51$s?V%v^Su$EQKt$y4V+B41}{N=mFg{PKP`Tz7R}-*dF=)SkYL_gnZQ8M+q7( zctADjc*x&I*H^Fn=)CmpnexQce3<{L|59O~-+RGak<|IH`qN$`51n!Y7SJo*v4-c1 zdxdn$SI*CulvkE5k-Q93Zz%guKNGCF>bf%bE_!M;yiZZFSJ5+2tL)id3dMcX>3jpwx-6lg%C$; zFo!^cdCE-w>FxEPpBen8wwp`ZXXsJ){%Hyl=maC>x19}8EWyvl} z{rFZrzxpv?!KpDUYjQ!$Rx^)>BOV%R4KUhT`%uIXurr>@&Mye(h`c^#Qd>9UksH7O z^tKJ3|0D)AXnN(;Zxedp@OvDxXkPy|(&}-|Sr9nsDCn^KH`S8~9GZXgLWg|HR$A{0 zPDSZifh`X5$z~yghM(?~JCcpBBCdRF!54tWPd6PF84@|K62e9rRRxxTBp8+a=~#k_ z#3l|J7-&v8luFMDT}IHL^5JpPHrgZ!-|u+7fI51n20|O%3HQcEnKQr_0G4qm9VM7r zd%N7n|CJhSNvq&sjofjcuz9o+ln27r-|O4b)T z!5(E$s_D@wPZ!-rCS#RZziJ>ofl2nyq=CfU8io-FhOCWX>Z6D*sm_ad>?0XFZU2r# z(DieNnaKbfpf4JTw!Bc`kI(|cs^g&_1*p><;)Ynluai~&M7x_%%fHzKM{VX3y40bQ z)x(<5J;0<;8H$=yw`T5}^Ha5dhI!W|2n81`*h8I3(YZi*77_nBK-ofeJ@clXZy+)j zXpE@4^z*9uU~qWme=zsv@ldbt|9BMbdzM3`qdi2YXpvMW<)l4XM%HATK^ut>k|K3l zEG6w^8~cp1OQ=xDj4i1gIhG-b%KE#m*K6cO-oMBD^Z9*$kH`1ld+vG7%zfS0bw97? z^}O!6rH^}i8p(_vg^q>`GQtCjkM=WK$B8qKK%H|2bUc%zsMI~!fwZSF&}}4_j8aw5 z-jsdNSo_$(7CGP$iXBjuvkF9bu&OeY6d=TD-PoAxj8i0tM3yWkbva5Uy*<_mYV3~^ z1Id2#b_!CNp7rsmzf|ISL)02UjGbsP~`= zZy(jY!Ch{*5V%8M!%L8>(ow;!_5LPSxMc_A9$Vd6T#$7zgb1x@!V3sqJtP-Ra{M#dN>``u@H z5~>>-9po5nK|(Q%00!h9sJba52xAdKAt{1jk?#px=>Q7Z?gU?aX)F{!8^ zySsahYg}tK@T8^^`AAa{j@%pyq3q^ZF72CSQ)Jbiy!rM8=rLQtt)kQc&i#<`|f1k z)RSH=%00FEclreut-jnXcDnX+{ma(Y{xvpJQ(pIWJPINlnxA!WcTf&CO8!ljLrJX) z3}biHZ&l(t(KjdngP(J(#x@KJ!XS2B5Ng})3W+@w@N~Dk+#RpKRUQ1K^53~^?)e@c za;N@XUwcz$Qnt&<=6;v5nw@=i{WV_>RlB}Y9o?7EbkbJ!_%0iRr2c;m>@!-sAJy1< zWjFB5^F9Rhr}ayH%Xd}Yu-@*qt?Kk$fEr^ePEKd6#WHt~`OwaeZ+2@Uv%f$vz>G3W zU7?IZmN6i7+{U&%*`Za|fqiK-HxR-!oD*bQ9=JSBK_z1{0MK&bPedKVYbT)S_*e&WIer1du<`C^e*owvC?8l`ebF{XXhjW zhYXe2f-E+-;<%#lefk3;yCnI=6Z`YRLF}OjVh`Z8b^zoV;CMTEoMiFkO~~7$*kk>Z zt}U^WDAR^sCbFXj^4K_3%PY)a5N($-M|Pr2TM4qHnW*(Zl9zzY;i`7fJ3IGd_!nQu>Ii}XN>kGd_lZy zC!t7%#)a$vRD0l>OHxW%YGX7=nZi7GYl^zS?^T04k~(%Me%eZ5=&-Tre&YH&qoIH4 zf#RU{X-!zIo7_}^-PrO+W!xL?U&6j7$1EB&G*H4)$hCDdK5za6I;JlF`Msn5&Z27py_T*Ss?FW4RgZdWYg-&tex$0-o}2vVx6@7?`SoAQ zqSqTH^ZFbrI}DTas(GC1r1X@oRXq(s0ld%E*V>a_yPWRd*3;6^&=S{8FPUQ-(-oOy z3nZ%QTa_N&KIPWkQ)_OYyqs<8>-xS)zhAS@W@>fYW2b++TMhGTTFd90R84wW(b-^Pf*g``vA4_Z|Gv+bfmFGye7@ zqwmB_Y8JkO*hIhgEGYkSYTS56)(@Vmu9PABYlcTse@}8RFSce*cScRe$!o6F{YgEq zKd9FC^Lh|4_Ui?|O||JSPHT3l`L;WQH^u8sRsW}RhJD}qT;Iy~^S*yBRmtd;w{)ix zECGCi1?sp1VC5fYIr9_WH_;)BC!{Y!dTJ0fs+b2r!YMn3T48nLchBUkrz&3{N|3S* z#^~eBTB4RZ5F_@n>N>;~h9O2^6@i`f02-a_=J=V>Y>7o7lKd~SJe8cQ5WDRh)4=7A z6IF;6F*~uT=IEYnMwZ?k>yTqPNQu<)rCR%!N7he-p1WXb`=e*Igz9*1!_Fe{?{MqS zU`KGt&N)&-ug*oTAR4oY6BdgQX-6<}*d2my)Poe|g8V9y)S8zpN@=NaGEOE*%Q6;$ z8TBW?aS16SR4YTRs$QGD#!Vs zm02VRphDE$lOWZ^%XCq<4yb}$?}E`2shr~gmok*{#U!3+s4=FtJ^p;gme`4u9y8=) ziWzbQJ|DvPLQ)Al4U$~TM@s9Ff~2hf{|jQ=+_<)?6B~l%hNbm5?2&VOQgC1@-A45%UWMF620PKeV4P2v?H|^C!D=8Qe=>61aLmkN<%`% zi%rtQiqdQ<=MEO|it*%xpwfU5NeR%Dva}wB?#Qc`Q(R!f2kXnyu zs@7wG+=BCtMUmTl1i8&7@#|xGB^QtyruP1#1+ulb;R(s2^jPw7yBI_veIg2ZL**X3 z;;#X12u(D=GIA+AFJ_%9Ucm6($6RVJ6AVubzx}#AH}e>iQpF39@Md=gOK<4T+gkUjj6gZH)vh1LP|9U{5i((*Xe`RQmQKTlS0YpT3+cy?l{+9H5fm=o z)PcO=GN5qZ9ka%kS1RKRmqFCTITpinNRfvABfKgHnd2ZZl4BXXfR!kvpG@OWCINWH zm6YL4jyCZvOl9P)Z&(pwsx-y6^<*P^+;H1??jm-Ee))OeWC1GICIj z9fU}OS{d*Mvti(!P-%F08j&B9!#X|Vzhz8||F30CNoav#RWWd^+5Z8R3D)}mJH{IK z5P*wU{{dW3!5sx$JRxeyzc7MSXom;e2l|h)5-cYxmMw#GS!8C_Lr4JmB?G`BL(~EM zxQYd3ymg0Ar4M>CLH80Wb1^>MTde{AZX_6dSzhm@Gfdd6&nr<@(`9rQ0HIyP@MbfP4;X1USOd@~8aB}Ev?-Co9;q;vbS0EEzcL|HV~&L1zzn8YNy2Z;JKMZo3PveaPo zQK()qtH^sR$X1$rewcoexA=(JN49bGpsDO$2RVpM zWn%7hopI~6-Jv8(;zfSvz|*6Oi7%7WCQOno+cyNTYu7eEkr-XfDjnpv$|t_ztci@y z&9svv?5X!*ah8o4#sp_4)^e}>E{F=Fu?2p2G#r%sBJnPQi%shXXx^wXkc?j(qcMzQ zBub(tLoO--f=i|o(Z{DDHxYnBV2a^Oc@`vDj3I5mmLLK912K%kT?$+rpjIFevZkVy zpUeu}U&2Wq?7YSv8iH8aG^UE84W{ra@InOT&X@sO1p+gZFVRq=uqpsnXcU%lfK+k& zXhm(^;BzWagyRenj02Pkw3up9L|enP0e(NV0pskCjDr%~m)byBet8{OerlsHB1J+v zFFsytSWxCODzPMu`IBEH`)xkC`3=(rgjZTYs4ExN;@3F>2GZ6khUd(xK->6>Hh|2# zPys->m*aTy0!W%p1rv$^yTNMcd7~`)^f9+zqsxRSj?Ab~n>9+*=D5Kx`05Ujy=+8z z28(hq8jM5T3&1~bI`aT|0-`!w>=G%&h&@QBknF=%0fHHc@VMl@@ZOf(CC-^87?-F*x_>O2I@ef^6*@ z&}eX?Bd0~$=>yw)14SBsBZ)NTyUQiQrDB9&f~wNs%-@uCfKDQG)M%G%Et(M>IPTVw z1M*{pw6VDl_~E7j?2cY^;Pq&D;S=cHCJgV0Q6Xz!Zw;K*1D>oQxeP z{K*IAWclZv;Q|+n@(}E>BzOeYuGEP6#~#%G;A0&QY>Q2>90|KeUNV{( zJ4{_|0&OT-Qdy!Va8nHd#jy`%*Ru0+4{wuwC|V#Y9YJ2L4aR`0G8fxl8(RI~fHemA z-eC#>Vi>PP9ISqD2E`hn&>K;#QAH>O_ziw$z_80>@5t66my5xQwBZ$dM#Oc8M4c2> zOPKj2VSd}5xg+ukQ8~h{1A!?C+d4O?HPTo0c7vNhud)3sxlR z-+TTsLQr>7qQP^CaWPM{9O70mgyJnzH($v)AQWG2k@mOZ-v-B-Uq4!vKlwc0*k_A+ z1AL>jgI-ck-_^x>9@;s@sZX_U!`kM9GHKfyi|0aee?kX#x%%&m&G6%dO6OS&7 zdw&dWUi8+-NR|UaDu0<7I-uTB4)pjEW{rw(6EQqS z`EqY)iI_tw=3%+t__A<1$k^kKnVN?`fpyvo>vR~_NkM-iK&l7j6HMQn@Rat6oM5`5 z(D8TrycAJd1wQ?GX!8U$QEiduqM9mq;(gs(X%^1-h2{mN%goX}cwnB78@K7D#F7|K zty6J(!3yD2E5sU1WOjVKUmo&#zDZDnYzTa#uam?L5?k+=AG1>2+x+8o*OoX9=2!dz z2Kyb=zO0+gHFjHQ&=vuGb4JT_tb*~gKHq!==8*;S+)f1ZGy@9!LcRmd9Dhtq!xL!s+_caj&pjVD{t~G6qmcw)%c>JC5Q59$YDS!X z6sN}mo^hMA8#s#vu<^kXnnNuC(hBu4i%sP2muo@jSpqw`^dRiyFhSksc-YB-(OUFm zVt5sQgYQUy;pc$jQi$Q}{NX4Z{vNH>L=Jx?hb!D8n)J9EPqAoqgq_7GmWYmW>#$;R zeSA#x+7{<5-{9?&6!9pHa5iKVp~3uc04@UVykmOi=Nm>HYLn^atz`AHg5AhWUd@aK~nD=1FE^x{t33#Ht#8 ztlHS*8RhncM)$+ToEP+x^b);2cFy?{Ix#G+@~<1*dp6^4Hq%)w$3I+a zX3p(8|J}1vxZBAs90D6seBycSR4XVB1Ww>mtRSKxK$<{UIg1vVTD}AV7<|rE?b(_i zMOt-XwLv=yF%dWLQoe0gOkdN6`dxGb@FzBZcJ~=DB~(;9QuNV^a#0o(Y@_dHi!>37!kHQi`q6`8Fyk^3R17 zJQqUgDD#^I=fX|$TxkBx>WPH4-e_-~7!PY*_dI%^JsGOvUO}jOpQ1#oAwyLw8LCX~ z;`CG2i7!dh)bUNT6;0DoX~7f|%dvTOXI9E~@>m#q#C1ztFD6i0Ed5AML*H3+(uKKy}8d z{mjo~oN|Dx>c+Ab8dyqy{&*l`8oap*8~ggqZ5Q(YE+ z=v4aHuX`CQIgYSae(lM-Uc-k7Ykw3G3Mg~qY>O+@q!arXu5-DPS)!KXarfSte7K=> zR@DyHCH(nkVAaRV?rKz*-3dai#s}r`-wk(mwHlsG($46qX-e$raP4J$U)#(tu-ZRz z&S&Rg zaFf?nks2aZt7uI*2jUbnZT2| za|7mY$NB1aMGOu~z%Z1$f0XZj)92z^{;eiycXwW1 zb(`atQ?6^)pYUpV^3SI``GzT9&NZ;RYc0!M8|G@9b@|3@jovWX_eWvA{r8p^KdQ`C z)BESUeEWLhTY0Zy!=)tcUJm>rtUtp4wRUf2UyLzT7JUR6?3>gQ94>L4kxt%ndE4 zqn-?rgzz+&{Mq1;G*13;)3Q)304IOK$sLMHIQcnDb~)+`lb2?|7@80!XD(mG`3tLqgm(vv`yX|fJf7o?b8D}VWx@I)BF6vM2xBVf|3Q7R6 ze_ic+TX>INzbI_#l&a2e`R3gJwW{UFnzY_oYlk)*@yxUTdJ>vFkv&J}xnNpP>&@8~4`8j&J2ftF(m0(h@c^^+HP_=&8(g-F1<*H#)QogkQlmItK8REXKN+ zJ!bL`%fw$eR_l)D!9PvPZ@9`dQetT@Nf3KSzi65ksHk8#k@*9^q}iL(o_{#qOV3s3 z`sj}S!76$Qll@jIYy1#-+3eXXF<)2SY;sT8=BA8tb^G6Y7gU(}cY$+wn|k?n>Mzin z`@~z_UNP;*_vDTXrecGZ7{k6*a-6d>(`3aFL9U5HQ6+wbQA#}U(DI3-iQxZeXg|mm z?|~IRBp~3Uj;##g8za-dtFr~mH4S0Cm(DYq{sL~SkDV#YdWWlbW@jrzT{A&J(JROwHjpBx^GRoyE}4ek{fIK-^D z+L4?f{D~f}h#NACaTD!UigsH;pO-;n_7Dcz?*axg&L9oc&#~$ilYFr}=gtD<-Mw)7 zGRG|Z+ga*`|5%SjS5^9N)y;gtrdUB#LzY}skF2{KiVYIQqQ>bI2!WqO2{E-?+OELu z=W6H=NB4~<{bvV^I}^J*JRQ*BUkgby#Lcuw!>ux3;U zeJHr<>utFV)7XQ~sbv%Jn@mXlgzC8XmFQ5q05zXQ8bS$Duw6 z{s%1LmF9!=l<(?V8wqb}f^;mf~}J{i-XsLaa{rPc33H=78j zFBmNj(vl0zZ6eQ2ZrW|eH-sIA4LsrRomD?6h_mQRkles;B}3Y+0}4LQ7?0}VA9TrK zIIeWx9#GKJ={Q|TJAD<(7~o1;nG7rNnWNfSvRbqlWx7$iKdl|thKOoDDHF_AloGz= zPed9)GxQ}Kj^n`a`wV~L%<}P3{mY(+YL020t}~owz7YLe@k(=Be}6Xyi$3tee7E37 zeGsIsL68byLfG-h+DFfTFEENx30@N?HZ9hHi~p+uLF5bWrp!=`X^Z{N3S9_A=YPWlPkCt8*q3F%4V@I3BfJ9 zWDJoOVIrhc_{>75h;r9$PB18F3@)<>8^MVjd5RKNEZjGNJ9#26Wm}d7%0)SDFqcgG zAu*ZtiFC&w;EpFn3QWQvNIL$2H`I}DfP)F&kfQ|<<(;eVIk2HxeBozsB>jizNCtwv zk)1mi=v`t$MC_vlsN0uXbikI|er9K{$asb;t$U&WnVfv&+p_K!SIR40^&1t`2}{}x zc=a}mh>5;hE^$|)#*|-&BM3LU!y7QJg2*a>2wUT480~i^eMWT8W%)7SWcKb`o6^c* zH#Gl|24~Hh@G-Jl0DK`^+zK?9x|SegJKyeO^ofAx%XJ{0Kg^k|G37N~I2h1KAG1zm zlf!%?`dD(u>w2iO_vSBOe${UtfmVSps&eI7yU&alz0#~MlM0{ulraIFLn}+R6A$rQ zW$I1jFyaIBnME`hPApT8#P9f&;z}@FP^L~#Cx-DH6L-nn{dF}l6#Cb`- zRb3ao%)t5I{=yeQ^#}T^J!+FK8)WpWxO9Gg>|)VBC$YOKvZe7Wborl5>F8Chb8T-q z(%)g(UevGID`d#6KJ+7{I;p$&Q@S$zxmdS-#;M**Hti+VEcwx0+Ur!MDo;+Wdi15R zuBPd9(mT7zK7hpErz$)?RlcECvfEJG#ihH+EJEfK}b60=w;f|w8)y*k)%BmedS5?>4t$rqz=Q_Qsj9qoh zC25L{fmTN1>mb#n>e{?Qo0DsLqnlj6f8)8no!Xz6;mWwPDN_lp$J#@^xi5t`qTeOd2qPz)c8ekk2k5PlvDM=^%G^uDKjM9=;9N zftWumA_NaRUe#XJ0spx`nNztOPRnWF`d7;xHQi)Gbr+k$Q^INXad6iEi?0Qby z5)W(%Zmfi*sj%&JVcVNS3UI_=KeL?}ZopyihTgI-uE$|f6U#Kw1f9ps@-QF7mvk(N zq+vfM4e%h#baGhS3WonS+>cNdpAQKk->m`_Kf`dp$^42Jh*ftXR!uW-I%YV(LhZ+8 zu)&haV1w{sHfT5uSCi%#-DQ#6eIQ+M-09rQ41g*nLC}5zYVd9q4xm}E1pnH3_AfTL3 zMH1y)Lo=8-EHnb78I15Jv;z_}nR!St#GTmRw#Z^h{TGBk!IsvzTh0TT`Ahbam70I3 zt~?sz2E`DNGNhczg>g9;*FrO&slZe6_HQw{dZ&I{6LW)miWoeBgMY=i$bCLINn$il zvCN9ZK9!JtuWaQMZExySQcy5FPBDVYen`K&ttR~gC7eM&Xo%)kWa_#q3irVkT!xGV z$|>KknJx~`*nCAL&7i*5&Uxkw{}r2NzL>jM=ME&F99V6qwXtsoJRwee4Xmno0jrZ- z)n3@FPJn!#;LY8vjl>6y`k6kL1D<3BPN0|ovuCi4Y9i)-i;Emd|GYF{YDYl+!gqrEQ|@G2##m^ixN+@*L59>6wX6#S%aaANZNeV2e=#@uOi2ZoXuN% zlZQIDLDvX?SSe7Ne5B$ZZKa0kBA$(#W#9u~Yn!7psH`+lyxL*H%{(QS_=g7nE(Lgre7_+P;`k{|y=Spckkl@M#e-A`Z3xgqYr@PWx)M z#MezV10Q&7{)3M&$-}@c$8;mI2pETStcJxQVu9$8?vl6@vo#8P;ZHpaLV)eKPyYbn zX;*A1E`QqW@>7oZhMCi1djrxN?J?}@Ls^12ClKmO>+%z7NOvhFXn6RF$eR}v^C?hY zT6db>s(|_jnfRO`KA;j`xAy%7%)d1$=RtCWh%T@f@_<|H*Asa&AM3A+^wYJi3pr^} zdvB;4NFaTY)+sTSRY_W=6D(8UL|6uJ;#3Mw?A+l@4)?$?#t3Lh(E8eHn@ZR&==a{E5iqkKc=Fiuj{$lyL)vbnF%da#$grPG)#OQ#CQFp!5 z^&i1lokBhQYFnfr?C zIu(AeGc<__DYIqyjH4Nm6oOeC0j_9&#L53`nmUAiW9+^45>sw4mv$H)mUX z?N&^GJCB(JVCI#xm-)P8Rg;}*$Cc*C02qHB&|Bgi#I83r{ZIVTTz_Czs1T~VvSx*?nWm?8V`NWY`+sQ6Q9zo7BtzJPcG1YpomLnq*Ms!ym@2f zR+22dLP>vbSHyr-G4t9=p!NqJ;A}|Q@sr1p z6hLZBye#E)ro{$M1aPA>kq=Bq>upf}KqM`&IQ-gXt-oLR=O0hl3 zd&A>Qz4OvA=!=7;c2%$O8)q9Sr6ld97=5RXd;Of%uOs_X#^%{drMdJw^)!Dt+FR78 zZ1F8gyPDHb1vdlOH+8$d?Eh5#p|9!9r+fY3T@%xzQu4l>OtJmeUFclq+`X^Qg$hkQ z{`_X0^dznCv_*{yEf98(JZ<4tq<{|_6I}tXB+>uCV269J!SXHMZ^cN(;Dl#iy4^AW? z-WBdi01Rh;LOggj=I#LTOy=v3AD+9y{ zTxZwbGOV&W?(HVLT0YXSvcB+6RZCl~S(5Ut`?veNTxMwBV32 zw}XV*KKI~E1)y@D0#qpJ;{~MEfV;WQN0H-_u_9}_m{to87%*CS`!K|QVJKoR?*mTw z0|6L|M+S@+C^!X$AAG<#lM)apfN|z9z~~A6u2DIQU+kiUXd-71A&JchN<^U8Gs2Gy z6vONpLD(}L?3wofLv%?KtN&U#=1oyxgZ}=S#aDx^I$-$>rZlg?_>5$KDg(%S@kyan zP7KKYoCM78)$PfT`mYkFp-2U(O&LV;B(?dg4a^A}0DlG~Kc)lR+xZuUBgMzz^Vr(s z>Wov449AKLm57b({>Jl&<@ZyfGRi)VavNR(XkUposK~z&{E$yfn%p6fN+BgE6N`Nx zhBrDDCJr8xI4r8Aawox;tluG^gFhJQTMre zN^8iw^&RM5bX6`-3*e$Fb0{fU-j~>k3^Sh+J4h#?$q-5mVEagvm`Au#^a1jy<&a4& zL!jgPW+H(NBobJ#@~UBM3Q=(R)fDyr!4%OH&<>^$T^lg-e?Sj+$E58%viWDV1*obI zu!66EdD=i!(r6aJ=*odR>-h`21&2Fn7DI`xob5y@jb>3u&gPpzE-E=4&9E8GK)}(# z+zBZOngvo4ua@^MsL*YU*<;!e(J6m+^%aJIHrV0G*MVXSB>%3k|6ah3M@GvhYa_)5 z-ANlWvVpupW-?CHg5b~2?EJxxJgb4ngwp%S?6ChZW^#b(+6sT^I$}r)_zyM+AK3=` z_!y;rI6E9MGCNE~jKB^9)#d39hrlcN$6AtH7{<|ubI9Rg2MkeZ;X{;xfCSv)Z^O7n z!Z-jL{~$r*K>&^I7?Eb&>t$i?^a7nhF+gL1@^3^y0nH811ao! zdhi)ejL@}vd>Uc-5E{W{S>PnrU=B+V$9u5mz3c7>2gA_p2~U@aVR^d1(>voA@+A)# z>d64u&opcVLjb8<0>YJn1Qvsm=o?6raC{`19)gu6DDtS)5`Iup1QiXzyPqNn_U@Rv zeo^AA-^!;F0)|luz9UylwvHHi$)#)??t1NV#dhwi`bVa zMK^z&W$(sJt|QB;^C|Tne%9fM$J;*2@rFr9(Ham7sVz`0nLx4|P{XcRS<7*dksn>J zSTb1dsaV+n4s+XQ)1kX*xkED!37Tc~J7E$qQd5qj_J+Q0JrZ<${9Gg+ytA8*@YFk_z zy~=u*^x-4z@m(R#?|Po_=BnD*r)2+i{;O(Fcc*HLmuqE>Dtq_h!WEfTMTTV%oqrhb zT;O!e$Qig(Ehz87QMIT;n}mx*CEQ3soX#U_5d-w%aReE3H%h_5M0TE0oCr`bD?|V- z5`_yyi~fRw?EZZ&77hHc07zMYAhzHhY#nIg$)v%ZdJSZK0{C6H3Ud?5B>+oh;Y&UT zg92vyDgk9dkhvqf2+to7Buvw@iG;>!90xTUjuWibG6SK>(z=h#-N2aCk^?`bM}iUX zBIFAK2@m+Z7MbOxE<$Zx{0y@<5EYyjVWNm92>u7t zN1$Da3<9gCkUr+>x&nHwbJ7Bzm?z&tB zSuvpeM?QNG+?FjUnmQ?(M#H_5B{ACY^MBV zfL#pX+^w-dcI!&ydDDRGpl|XrkT`{M$fJ;?ROXs57ZmluP4D5xMdiV$@whS z=%$msNVLXWU-fG1l_f<=C{gCYw70Xh-LvGO+k#SEamj-5z* z7fZ+ks1)Oj*ngkErRpFLpfHRe;T14h%BOhgctRTNYF=dx=gL1%#jXZKaRn-f76qF6Q<-E@;9J1pNbKyf#m=LN=!C}n@5l(L;d(@E#) zp^-2y5MUhD5cdc}q=6wG5$B`d-7)dD^DDj>6@ivO6=}waRDYO3h?NMMky`HH326w5 zVnBiuk`GLc%m*@9LWZLtB1nWQ5RX(NSC=v^sNAwn+tS=2IXj>N(ufuusBFZgishp3LO!m1+|*yx`J0o zXc6*&5+JGmcVYs9>Aj5-sjivY8FOb@wLO;0L(E5Diqoa;y-i z+5j~d<^avRSVrqgUy{^0RngJ0KkG!;(9B8p92A>7lwo`Xu-Gvl#{I}L2uPoa;9X`~m6OfC$yePhaw>fmjcydE##sMx|S2_ZbKs?J|A5Nr=O4cAreGE9V z7H}ju+X+G#Fj)N)?S^RYSg$z8YH0QZ_5XCU$s7K%ulzNC`h{H_fbPOyB8!P7QUK8l zp%3w4qHLs}Lu60OF?&)YvZs#9{=b?*6tIyC{?OmwI^)GINxrf}AB2NcWyhumBS3d> zcgc|$9g}565u}a&H|0OIqB<0+DVIo)&!7rBVB60}p&Fm!#mfcLHZPf41!jO{Rmc2^ zB5~@~-iBqTE5Fh?crMH#&IN-4#*d=2QW^a=3J&k)vQJi3v%A@jZ#S%Gbe4JShCiE1 zuV(E|(pEi=RsyJme%SL@u_8O~q3dRyBPyz)B!>~6hVU1MOU z{Rl6gs$#bGFYOOH>D3*xJIJf=QKS6#_?Ef(HN5od*45wizwtDFbn~iuKXoW47iyd= zOP@5QI;r|oXIJ|@=Wnsj%W6`szqN8xY&iY3`cnM@!mFoNnV&w?-}LgeZGUBqebDae z4zHl$iUwGQUn@cN1oUI z>9zH@??)4==InCyZXT)(a8;UZr1-BP%NG6~grNw6qp~h!Ui*-FC8PmEwJJtYECfgb zKSh+7QUdRtO9(PRlrpR?&5aF>F`5Uc1u&lpiYSMP14b6BY>`1~ z`WLavmL_s|m^ffdWEwG>Z(7iO5#oR?bmYjN4u}H=#43x3wGlMiMyX6AyK<9=4=DI( zK5-p`b#g@S#19Z3GC_N{Z8_lg5!C^FAuC9*Krd9@Cs))OOA@{VT2`QAs8-0)ygNdq z2Dwo9Fm-@XY3;-;AS|yKMT`)tv9SLiFQLvmaz z8qao?p>AuD9^_D^2jR5bG16%Xb?Kp%IP$mz#6mEdoyUw84tf{5Z)rIVqJZn6*viE<7<@O_Jj1+M_{_fRK3+E zlZ8Dzxvp)&C>b)m@(duoNw<97V&IPI@Q9KVXwC3cYzSl`jT0>jpwfnjF<*Q96TUB@hzGHzZrqtld^N&$lW|sHu z4squN=!dbyg7SgDZG~*0{OifkOIb`(IpEfghYeIsLs;<4iwCr_VZ?o?1 zE~T4tLGFugs_cr}P~5yv=IM6}DCyQOeF`PrBO9M_t23e6P+=6_VRU*G=u%X47R2dH zKCk0gsq`oGK`M=A3fDdW=n$R-^G#uX4$KF3SSIVc1;B>wvFauuT8--ss8xWDF%Xz3 zlFyk8&w=K+@??=U(~*Awn-x-QruRU1u}Md9Gb7B-koeqcNQusyUC=4jw{iVivMnLu z9vrd7u-2;#uRNE&DHLD#I62*vcn4t9Re%qH(vM#g=^(yJ@ur>QZ~s21sMy0yE4P3{ z+!0RehG;D_`l_a9QJYpNOIm^778*?9NprN9B$~H0Dc@Z%CKGhof% z7{(Mwi6uG;C`nogiNm+OyHh|EOeEmsYnFDGqa0hYrzsPD^eF3fv3o3DFDQq{3o#8m z3eV9d=Wa_7wAkO;@pdu&A=tb`YaHQ6qrt|)np})t&!xKTjBKI2Bs@m zB+ekGkAdk<;6cvXD^I|4dP+iw4t8m^-5~M<~Qt_7Q(pS`0X>9B4xMmg{ z4U1caGX6qPVYiB0kbPyPk|1=TED6WKCnO6=pI|hMbKEME06#A|LA1CT%Fx6zM#Su~ zXo%Ias7AMtumrcjH7DJ|5!QS;(`}Tb<*%ktJYqDTIU09mVgowMH6y}s!V-*!9N2tL zD-!EGL|l6MQnKEkaw>mkeX^>hs_|&|=hzCvsT-_MrgYnvZKyWr{T$xIS}K3To>eWS zR9)X`eB6oESIN?r>N0e0Pi@R_I?~fw-1IFY@AcfdWm9<>9`>$I9R1*QUT33CQhNWR z9@}?wS~50R&+WPsk?}r1*-6$l399uqZ%KQBkc0J=xn{RK7jA}+NJ)_ckgB{9B@2Rx&f7c?$ zLq%i8s+82P*Ua)4%PdIn{P&G*G=fLwk*-$x$jd1_vzp%27OqE4OW*R0`F(5pI)AWu zN{?!yf5DC!ye_Fe=WeNQ+3=TIQdN)M*oF$QJzOH4B@6unqNQ)j-7#@(?>k(#zwlDx zqKh$RAkZG~vp%6NaBltb&Fm>-4W82`jfI_Uo&e$TteQ0Z-SyugdDbhxZswXO72Q-w z4+7cdBH0>BDrJ-XchIAIVr@8zaRd%9C+#xoO z>(M5_JX87A=w4}wHFN5ilQX&EVX<*qo^3)ftZ96&^!Ah3GIuVID^U)!&$W$*;Y1v6 zh%GzI^`(veU8#7&iu8LTdxAF0Uawp^zq7I^c`yC_vqiMUxoU}TK7`y^uT0-}!E>`u z&fQsw{W16Drj9+J=F~GkV7Hob@wyf4`yh+hC404EUEz!b#RrgA7;Ol&2o?`22L5v~ z{66N`x|-!{swmtuWsNhn`1+_o)T9>WR`x60fsK*7Z3vs_Q&sJ!f zTq)qDY%kzmg+$lq!$z5B+W3@4dahGGv*e+wNWT&$rp(A=m0xJYQ z`EE$C#bNQP68PlbL|-t{BA?0Lj4}IHh`DKpa&fYPQmEg?pyjeNDjjtMteKW+cNN1d zAJIf|xrs9@_Is*r?snAOWL?cj3sZb<`N&Enw=Qvp)f-RGeR{iP!kCdvF_x$bPct&R z{I-_6bz|%vMw7J=+mpLrxaAzvZF-QEuefCC_Sl{CMZK+6qBGexNjBvY>T%&(-f?Qo zQB_B`$H6;}3bF0d?XtO)afi?eQ(!RT{7!PlSa~P;{%#br!%^?u=X=&*FK)^b2H8JqU)#^|+yR zv28CCklG=>AsRA$bUo^fLRluKHG$_~@|WCh^=B})QlO=uxD*Eh>OykK8P*x8fr{s? z%xO~avB;=OkuNmUyXwFcnsCpY!iESy4oZAe^s_!!P6BJ-d&KaoTD%!z4HyS&Y-I@! zv<Dj>zoqVOnQ?TEp%!F4pp`yla1+jb3f4(@#`@5`+Az_U6zSu&X1O43)?fIS%(;!%IV*) zSuI|gw>doLPj>&cimEVnQIxmU<*5R9!tA5{4xQG-4yx<8h|}EynXhqi7oB~=^(7Yf z1da>AxGj#~uY96Kj%yAq`q*f(1kqZzk!;~pw~>y&Ba=bnJ4KTXBs1U?2S^L~4LC*b z7ZdKh2<`HIp&(0WD8)mTY-MaHl~@7JXB4zmc2aos8C(MFI%pDoy|g(&Q;ASzF6O$ACyk+M z4YOD8gSDpK`vYpMZL9R}SHfov+NA0H#saQ6ZuXh(6yNByb+28oQNUe|uXMZp54^*p z?rt#+f1^UsfHmA38<^lAS&Sl!5n>f*8>@Y569$q@MprC(${4VPM-DTUb4N4myRGxaEWz5i&xeK1Ngw>JgUYT6!xR*YiqeOTJ z#1b!vCAKoLyo|YrJs_HFjVd0&B8q>T_-c6*o^yS`b55h@h|v=bNW%_0H`F6@;TN3w zd2V=W)h99C=15xd#3ebv6F;mx{4#EjRo1_4Q;qvqhRIj|@hRed<%2Qp7p%6?*5a-+ z+kXiTh;3s`*c*<6+&ggE*8gyD%*TVH(&vWUovhEb-)bfczpl#ev`MM5uhF_(<>gh_ z=lIz&rK&n9rL&>4(Q9{I%+6CeiIKd%*oF;BQ-i7^JIbQdx{CT6|GzazHKjW*b2 zsY!L&mUOD2r7|GvUN^hyt%2)?#MZg&-E~1pyuND3GG-TC^k1u*`Tmn}_eI-gSN$J9 zc!u2^oA&UOz9TaUl)&J7_{GFw9=>ZV9H6peX)?t6yz%BApeIMmo5`7tX3ikb&u63) z%Z1%$FDHWe6|NT%~afv9q_V*3~|nyV2X1wGZP{9{aC>aQh0QvqM}Q*N50+ zUkCP>JMJ+FNjeb$V|Oa@qw}T5Fufe6Uy9qy`%lIb!8H6z%UX zUKysmd3S5j?g(Cg!mj}p*fOezPcX0M)!#$ z!U&Ft1t7Wo(}dk$a=a-wD$7Bk*qe+8CbCzt#nz`G6W&l~be{rfu;7y7CYd0dkhLWe zmobyE3{DJe3%v38bd4|s+btL)K*+WYqc+h^U}Hr{3i58b=u3#k(vM0B}xCbq{O;PZQ=>$)O99*UOT5I zW8dHP&7g6$m!q5Rkt1dbK`Z`V61POdXYAPQ_FHcsn#g>ZzzBKfo6XI-RH&;Q>T4x- zP2-McO7r$hA^WOQpE^7*|Ms9V-RSOC)*~w=Gfidly1;c2iVDhdYyDryZajhYVQqKy3WA>;z4zyfaameW}pddW(z+<%rvj@DC= z^6k!&UQ7=$y;y@Cb2zkEIeC9?xVSggPk8kdy|IdHBM$ypBRwlw5xw!+MluU+X0xZ* zGqNgSX{z*l9#-nUh|{z>tf`#Y<)T@YvHt*IWN*B)QI zw_u&-I1R3E!vosrgu2{6Zm4+_1^P*@zNqWfXY?Xsg|jBzh;M`1+^E@)jSJRYAFH8t zk(j>_=HvS9owKD{cuHZ`RhCH?I!OKD9R9*Sp0j33zOGlL(Tio|qpp*7i(+Iv4YU*I zEZ3C^y=*nPz<+AKsn)RrfjM7gCedJBudF!=%3STp6}0o+`B^p?x|(JpB1kzrbkv*tdi6p76Kx|bgfo4j$ySz()+KWJ@?g0yz;drVIU^@(n`dS+xkhhcjVz`~Bpj)dF;(Lgr2e_G) zhhpS2V)}qDWM5D)wJo=PuMnZ1uz~ylDg1y4h>qIg=n9H-V)X`Gfu`%?xv2@KFUKD~ zg%^KNXHlX$tadQ0HqK{01=n$_JZ2nTx;@dIW@()r)*;S`)}2GH-xOHCc-Y)Was8(2 zC2Yk{jg_0v-`p)qu(_AA%|dXG8tLuhZ|)9j*xUw;LPz{nne0^lVoShcpHgH?BoK=e z3yVX#$P93icbAyCQFFl2+I=7*$*%R^&U_yG!PWB{ctg#U(y-32Y#CF{6j)&LcSA&q zx<2kvBimf-)!V;jC7kw*l}NB5Ut0~Yts3_<>MiX2FTGh-1?Uox*G2AwWtY~jvYv<5 zIDHDwtQ9_u^BR>tTTIcBAvd^4zNzSw%0M;SX<%yWOo}@^=j5V0*o=0; z?=)x&;wfm0o-lhwGizLyje1J5GihTYH_GJ4C|La1%d>i#tN`nBhK;tUHx;oeuGm^l zyx|_jCALKF@!=|Yqhnb;ZNxWZvQNN5KG(`(KkDFR9GL_g{rpay`J|zyYvhQ2SXO>N zWike*oe-Q5JJ{=^NzO(Z`E)5w(io~1H@w|?XNv2kDcvcNwVbN_mgu?m-!8j;b^Vl`Uz-#Y^ytI>@9y&7 z^6L9iROWVe7uL3Yuhr=9?eyyX)Z<bttYfl^G6B<^9Jp zl;2HKeb>=2R=j$HwPBrSc3H=rn!fam3hNDXVpQLm{o!-ay5ROB!@Wi8E?Oyb&tdpU zJe&<@S?q+w?7Z7$FFq&zeGVdNRUA6E#9cWt|M9%Fx}LNX?7gM<9vYs1qgi!N_l2t^ zPGDJBKS1BMkD5Y;Os^0VABG5H@7r^D%&nsYShdF!*ecAp0Nyj$O1ij}Ae6#6N9?fl z$r`wm4v)N(4v)N(4ih^`zw2RT-(Qm!N;pn#GuyHu_1!Ahw$9cXTbC;9)!nr(c%$yXli^Rc*U1z|guvzeyRrF}(i7n!cvKwjZB6 z3eQ{=*78aXp43|K?y{BB&G46#!E`7zhmgnLgl5Do{xPsGVd zM2^uIIldUZqF_h}5sb@?RZNJ=e;!vbM;M79RfZqL-_3(}R2scnMt(G+7Yr(FLx>(1 zt?Hwu?guz$mhVN{bHKyuqbB3HMgF+RSu-x5`9R74mQOTMm%h*Ck zi)7z2Ly~OSm%+UE^Nf=*&HG++`ueWx`>r?FmF4!__j5n@fBXG@|EG%EXCVN`!y(r3 zAj$wD*XnaLQrZsfxCCXnkocugG{ay%z?Rplq8TJV;o=Ym7zqs@YXQS#Fw954Isl|f z=xQ<;zE`3Z&kql)UZW4Y>G|M6xlBI|CAn~@T)IHz(zimyp(V(Yy6rY7mg{vuG!&Q7 zj}M9lhJ2*ODjxwTU;LPkTLS2~MIcqy7UVB6wATM5B>~$QXam~-q}>H!MPmJc3EgTP zo!ToP49UxYA=m0~=!w&9lb}0`<43~c_ejV$idh1hb)I#ZVT6z?>j5&!1jwY|@>AWp zfQ3iMKW~Fco&3RMX)JIeki#S)EYDzs8rDXrVKTz8R;Y}X*1#Y#^&5y>lY-3c+n;7Y zD+d_z<8pxH&ohO>Eh#{pf;fqe^z8o}cxr|4cJuZUZqtxHbd9MNoo79Oc~kgybAL24 z1K`T`0@v3J(GdKkF#aS8jK2ZnbJ2YK&_Xbt2F5qj%|FAZtc>i6KP&`5ai_TlfM2;` z^_t^JYUZNazPf68h29}T)d{8X+>ociNeNrPdDd*4ccFyxV}l?DTl~$b;On1po&eWd zzVdx=99sdnRvMja38uZEz~TU%+tT49z2-A;+fcx5 z;Z^2x+d1fwf!h{B+%`LN>`>a1VADrcJ67*ly=`PFE*-Zj?pV*GKe<;|Cv2Q4(b1Ty`M<`IqK%?vMH0rZv%u#>MpE)9|p#%Ie)!*5rycNm|f; z??9CSCP`3b2x_>OgOqBa2VpR`Bn@@fO3)(lMSfRiiV;$=Ny`pGu6InOFE60H%M{e+4y zEvhc1Ji!~`{j`IQ;QHPs(u5;DTu5H>lXCLieF=fucwBb2YhAPQbOr4$8W+?;vWf81 zpm33zgd(YZBZRs>nV{0PAl%ewqtHm`AT}_xuhhS2wBl8Nsg1q!)Oh>XSCu2@X{|jb zv{zHT1gS|vZLE;71NE85Ej}KjS}#v3b=o{*+#zS05IXBhsC6~TEU95l@^QM$Y?|SE z6A)v>)g-g@q#Br?12<)4hA4%aWG8?LBmvH_rmgpLJy4Tp?UTvt zNe`Xs$B&j$E0l$%8}nMIqn&Rk$~>iS8bd28dPYO@gbZb9^@5RJjonT-Jbtr(%XICC zR|B5rNE>q>n;6luyCi6GaTqwKSM2_4mKvhkAX}Gx%hu-a@9za7*WE@yG>(d>>B`V+ zqX4Ll+0ndbl6illVSytQv5dTdK6lX>RGljs28I&l17^F1g=aqm_f)sp$fgRj3 z=6LqL&JTu{DYL{tA{e-#1#m+EA`deZ&1FyTY3Vg?vD~>oO-jojMuklJmpSVk@dl=t z-{%O{mZd|;+%=5}Ov=meu=3*3dBd{S04kcH`Qg8{_fU^ndkt)hZm@BuQmE>{7A4y4zU&zc+;YT7_bY20A|ksF^uG|;XIG`Jo^Z!#lXoJ&!be@{R&hzBkH1tx#T1IH6cBqrAD(g~G>MLU$NqZDGNQ2wgrOh`wrP%m3P3 zbwBl5);VAuRXyKx;Hj8NvLq0|V09M=U=Vs4c}AK_v?+u*m1@mp=p0}r5+YACoJv*e zDi}tl4+fupRJZmNhkjKCfHj0<(XL`}3LK3CGsD|6!Eju%D10~^jmJ&tSKow&>yky! zmsTCKb_POZ2P{O68mUCR^9fMRK$}>ebjvaVP+pY@zp#Mv5Z$qXXx4-eFGDCm(EL9^ z$S66T#7!Hpk1&kzx3%I2|a)=K~lmpBqau7%P0pksufRJs}{Ds_7l+!l_E=|fSwA;kcMx&Q<$K94O2q;-<1|rgDhj) zwz7ipLviW;Aq;x_()js3#(aL*i~#Qt!RnEB43zLGOkhU`& z7L9J3W^V=vQX)k&X`h{dpM>=bS1Tqw4+)SVA*aEFSz#Um^KN>k8jXv4iot~0%=ArT z(7QHVmolpgOB8||T!C&-3a*E!Z3jecH6d#Ie39SsQBbJMPl14#XuW)_$3f9#wntJg zVx+-gZMxC|QWn35k#=oBo_r^y#){smxNI?Dz>z@JNk1^$qXp!iD7iZ@lmrIPf+Tt< z1ZIlFVum8YsQ4uP8^tGSuHsWXfaQcF-b;@3-U&l`x)0S9CPi4mUGc6T92XddDghX- z{t77DtV#})hWPeH>$xg}PU&HYdE}?Z00DWCcEQSF>s3IYk2jqg6^)hr?u{Tf1wumD z$Z@Oe>D|Avi&NeHFXPl1e+mH|w|F300ZseVSWH7i14K&>KjX$z#VNWlWySzEKzAXa zozC7jL6oGY{oRo=U$Z@KM}S5c4m3hQZ#FTsde-Xs>OBK0F=%_nGReU}Yph@52bw*h zDYJ+kK0s#yFuWo(WfsxH!yeEuUueoKqKAilpke+{21E}H_nhM@im}Ksd#OHHA=vt0l=gO zC=XNs%Y0U2XIpN6+hLf^77tJv^=o;H;~`0Q0&2k+r@PZv?{=A>D^n#6^j?k?=;bWZ zBCdRu2v<26^s%UNVc+FfIGXKQS2;$^2UWSEQc9vE=*(=|VetTBl4}~zG3kb3!}R!{ z(G4Z|n}D2HRTl4_D3KFlrjlp@@!)RXrr)t ztG6B-ckAtr-=TC1UIF+9Hs-zp0PWvO6|_dM_26BJOsJcDS%RU-2FoGNP&agiK0_qH zt$sSUO$Yz%Cb*}uKGb7_36-XtX>#h_(>{K4>%~;;1Te z!3xAe#3Jys2tqR7CNysz0+ESCAh^K@!3t;Z;9_NFFG;YriaDVk(70tuF5igqqGo4a zR8~y!fFLJvkMkxg&fKyPE3P4v7`r5DmqH3gLGroy|G`CJ-+-`mM6*T7>FUkc z)*F2{ig+^62soR|>&2@l8!NgtV~2UmiiKBmmLoQ2O4+>h#+mv{$T<)st;(ar6%XP2>(ChsuY%?*1+Bk%WL zeMF0QZc;mBN}|Z!)W`RhAFsYIW2@_RA&a6d>WEiH)y)mCI|_Kb1kvTlE`1iee9e=$CuPK8B#BK6)y^3 z_dlSBwjA2ygZH3yqfm-Ks+90mIr8iR9 zZDOSVRgjRE%jCe<-f?BYhzQ#FRNnNfQDsHX>B+C{jg_gB-Z+iE2-;*%VyI1%OC!ig*=sTcpEo`Hsk1f_E#&1sF;1a0b{N_8lBm7IZ#se^Ns3;y zsZ!d+$Y5J-tTKtvBIq?eF!iP{kwj|?m>i3YtXJOYLCwQQczBF;HV;xp##38bjJ(`O zC$lHt;?Z8^p`=gcxAG#Usr4;N)8KKY218q>+i^igkscERZAC3Y1HIJ77Q;zB!8~{B zl)BgW*kD^nP+sm>Wodb&=Xhyf;)kh`!Z)<(bbKGJt$(m6wMD6w8tPuAu0;7Xiq092 z?Wc_^kx5HO)id!W9vq_*nfPK4-cjkM3<@cb>eS>+CM_Q|$i!zBQPHE*apll#TP{;KG|9A)ys0ajGFnMjsrG!D?av;YcH)3SF^6w-0FqvQSA0tC~ znjDO4iP=1g4&+Bj97eXO6&0`iIsX{yh`enLaUV)j-nN>kiQ0k5?95L1iqdN!8p9r9Sl#_pUal&-MxunHcLd)YX!mW}PZ!H%O;jvivi z?|$mB;n+LfUzafj!%setVYYl6_v^B`De31PNFsZ&@ar^#Ed1O9 z!Lt_&zb<2ng`ayMqwIkiH}*hHE_gFV z8x3U#3k$jU3^NA`%eVw^O$3t;Uj>{Zf$|p@&JcJ8j^ITGPBOXrA=(c34)7Z`zvPj_ z|B-urbHqt+=YMja*&X$BefYKryG~7?fFs1R`&hHLgU;dq%R_1q28-G{%F#xoVGWIz z58(fVch=~m9RRvFizdjJr=0v4_>DbigfL0t+aPKDWMH#p(U#wQ`%rGEXsYHR-2PQ| zWj34w0^RrB5I4lq3X}^54rj$irfl~iZk3M=bf^;iM6UMTmc2r{Dw}gfu@bjrgtU7^ zAeviDasy9E>_Ps77q*{!`x=ydcO@qUbNnG)cl_bTy}c3~McLbr-F$oJ!j^q!l-6n7 zeVrc?w;+i6*!6|+sY?mcM#T?b>sRD%5YRt$E`#?y$&4f?>9xm>=G>}X;DmJNBM?gwi{_odn;yM@x3$Na;xCv% z$0U4z!91~h2yn@N_s8$SuZ|RLS+Z~R;H1h&kJgw?IP4+OT?WFNMUE{vZBbuYv)1$F zj+iO8umdVbb(4mg6fAmwKP5hr5=hFIkJ7x7vVPxEv4_u(HZIGJzuhl=H7xdOr5;wS zV%{3PlN}ZuQkTwgX=n)~8D0#Se@L!S?TXU}0qv+e8xn9*bw%Wgz|9aT9gM;U~c=A{K@vj%_vkf^jR@zl?c+^5ARxRxKn9pJb zZo62kx2|>$ec`LFsaLL7|4_VWTxv4i)xB5iny)3k7@^5waY^@{VdIzGeJ6<}?M!TKe|g<( zq=ntkIN!EqNpT++74v)VwXkP5MPF_fZGK-QDR8i1=aT{1mA6Er@BSGR=~=qtP}r2c zZutH|!Jx?f!C8$mN?pQH0Z~f^btShg+7Tkz&Do#hku$M&+xq2)+e|O+S%WAJpPoGQ z^n{hjAIR6TYmG%L?>lW0ZQ$qkkqSX#HPx<(e0UiCyJow%;$G^9UBb^_4(cy<^FF%A zr8;7M@tw@>&t598&bleN*9+<`tocH+&Q0|jyE&1ay6JSh7&R+{d@Jng)y^HpEjOZl z-rfqcsV?D1V|i+B^)EUcA@F6fn)0(WyTxf=&ejtj+}Y+Jq?qpe(vW*k(22`$*IUfr zs)ZMjQR&f-iSa5K@{>kH1zW8ORgoJ$a^5*Q5ryQtU7}>Xz=X2QuInXoI<96zh0*22 z*cZs6mkO;`uB7pseS75(26(Bi>l`{+bni}|%zaHOz2!XOVUO{WITZWsYiK_*VexJC zv`6aR!F@e(kpf??bVRxfr94)z+wC$mxoJq_li4JnE9p*qj^FJI-J4vd=S}bdv(P0)bUZvZ*>QPsH%q-kzhHw-RU z138#$&H~p#co6@YFf4 zL@>{0#)-4DEtk%NZv6LbmPVb6pJwMG5Z9N@Lwtv(m~TF}4l+(h@iI)GsftHcIY6L6 QKy4|)6U`fkrDx=yIZ=urKARsZjtT~kQV8oJEaAryCfyQ(R0rA z9L~A-54hib=XrKKd$0BES?jmr{Vh33;2nGbG5{3-08jxYZSle^5deVoI{*M4U=~4B z*v8t?$l6g?$<5ZtL7TVB*J&}5PlDUmK$Ai-KjZDHbUV$82e7CPXJQ)*XjII7>mQ94r4 zQ6hQr{ovixa?Gq!2~(2{Ls7x%xASv-0^#IrS>_>h?+uP*le3CosE?9SB)Q`-UWlAy zS?mUU33|ML(Y@>=X+kskw$mTBTH#_&2}UoELp_itUR*%wr^$GBnnLK`S-w`nW|tu4 z`hoNTwbOu$%9UQfw(?`#*ceS=t>+ld336vm{=^X^wqSyDfRG**P8bW%P^ueR+~nrl z1~M@pnIixRE9s7CpD)wQL76pQsvhLStef-+i)CZ-jFx6xwv3*L!9`=Bkl=2y?_pQ> z)-aY}w3bf~|1+T=tYk3hVlE(eN|Q9|W9J$Eb$AC_)5J?kP+iogymKQQ_vtlGSi(Ry z9n1oIa_Qs{Ys2H4S}hC2u_A-Qe*$Gy*&qyWIp4G zY6D;@{j`?s(M>hPj#@H!NbKt&-~1Z~!L2PHmNe6XvxWh_#XSE7X~Q!;SUa|oF5GQ@ z$9h=cwCz{;6J|KpNdTw_u9mF7!QI8i-a_BT#^Ohv`5pRqen1~yk^Xp*Me@^mR(2LIEk zjBwOSdY8KHY022WvL|1IuRePTm!C_v9yL_^`&sE#HcX7CO`Ae3hp>}`Cu$$sRh>0# zy9)dSHuE40%O=*u>>!tttm!@^3ccY?=i5@c&=CGIF_>QXfcD#t^*p#C$HgXs=EaN*MYS{bz8oTKf&rp|+mDMkj-_XX< z^RTq1yJGlssz9Z{JzBYv%#>4O(DRawbQ9|gEXM81>61R1%FeF)7@>7{n@>3;mTH1t z(c?F86zzCJ9B1AjlRI)ArC-Li`5KZBx}>m$j~hLt-Tv?fm2)-V*!5IhfjaRpLd*3l zK|jvsy=(mKm%AubdDi4>;KHWojT$W(Mt(=UvD_6|x@PgY?cgWsWkhVXX*p=ohtC*G zI^X2p*Kkji_j!q#;`g;tIG0*y#bM`uTiGFjHerqb@-XYkSBgelYWZhhW|qx-DIT{o z&qaLF#*o^MXYkR}v3P4K*N@-w^4&@|lT;sthBw@@e~-z0Q&8k4+}?z6gTo8d51Sj> z>$#X&n>esKTUmoyRu#F^dSONS1>f(Sq6toddve0#OKpqr%k8NWg6*pk zx0$*#FM|WGy>CBHij6#b zp2m3;wKU)lD^(-^1YTBG223$n)PO~8!7;ZBTL=r(X4yLF}jVt=wUID$^)GPLv(HiK3WbUdwL3$ygi1BJ+ znJ%g)Q@=z?wl@(kD@4&dwyxKC;2BH3;yYKlV9EA&;=lr>L;Q@a>HSGX`lz^lbKRVB z^TJAd$*4lwsN7a#i7jF59uWe0Tsru;veo^QJcEPw=FMIG;*i*Je5UbS#)=_H{2b8X=a5P~e4Rl2 zcD2ZSd!%e9Q&k5VMu!yfAyJDkjPo4RS+2pjnsLXWJ^A$Q(WAQ!-o+88G&L&VNBM0i zj4LiHBNhc!R*=ynqSzJL+z(R8Lc@1D>RAq7XmhP|H1$IJfwLXYns8^tj$8u#t=alx zlEgICfhwF>y%HYK!0y+Nz-rPuS%w^=eIE@t?pX>)47D|lAF&$o2-Hgry2KQC$8LC2 zDPreLZypu(4(PBb?hzQh@^Vc)zqaN92~3!2t5@-jD;7a!kk*v99>k4>$(1$lQYm?h zX5cp19FdHbd+s%)H#hRu+XwQhOjfM*@N*$f>RztA0Mxx^UI;(M-L(~6z}=Q5^$BU+ za;!860D+v&_jnP^5x9^}JXll4tK8!~U=ORTSB44rIstnYV`p#-#`|VqMG80xX5k5n9Ii*3W!aa=YgzGKQQf zhooJiS$KF%>*~(bQ=atV`}wGFr$Z*!bJ>Vp`5TH-1;sPInuhyeX{o=Q;1{rr;8QG+ zHY+pfmp<{ImFSgZ4x>x5EHw#eUL#m`bo z3UscMKo?%+^${nt4va1`>0#G|;zkPOHN->b?vtCF=&rh1SF&LhX{U(-n-N6RyB73k zF&}Hkxf!%B>n`BC!oQnkBbTnm7a0Ihgr^O>-^}7@YGh@^`t$hH7TfBwHZxrKP1utz zl#Vu54ArD4PH!g5l8U8C+pGi0%@X6)6f*@!#63x;mtG^Z*6Jl7)a|fYJ{&@FVQ$1Px}RB2vgIp*v-7xE_oHyCO|O?F?Dx= zUP+RYN27Nd^tnn+&n1iPO^;vfX}2da(cXJjDiEJkvlAdemlXHrNRPO;mye3QPr4?k z)K~SMQU@Xh!fcT8c;T2aT?_a`E|iAS-JK&t;F+-W$V^YrDRF&=#3n*oun)a-G66Dg z>Nr?vz$gLDF-Uy|C}7|Prp1m8B#uX?pVk?>tnV2gL!Xl(Vz1J-($k*59C6z4J#g&b z$A>YRXlzYrsyf@BBR7BMD4E!Da<58GMR+-nB3=CG`Mcxm7sWBdk6v#taqL)E$rsn1 zAzmHRC2FR~I$a@&CKBRizY<^cf+Wa1~6gU`8 zaqW1yK77zh!OU#3z!L7a*pe{sb+R@=k@Ymy>+stF(Tb~j+s%0^L(}D@+Y=lPQ>x(* z*0jr$?e35d0#{dcopk$FKBCXhB43DPYA&OK_E8-

}i(GZHxO7=rxub4}6KNcENhrw|hmiv*Uf@FA0$DqsC^Rt6u7Hz4|q}^ki|uj zEd~)_dEvb;mFj|Nojuo*W!iTXBo=L8k1Dt9w?a54O!fi_H!wL}kSB?^Tj$o$m>?ksC}fwQE=(BTBZgt6Gp~e*GJ>{?{gg`!p4EY zW8@Sw3r_sWAS`LUJ3iHtyL{!f83KZUFFe=k$0a7|k zcw`T{<*5}n7w2?0B7CfX6m*p|`f9H>a33fT!Z27j>iMI>AF{3M(T&l6V@fld~ z+6o1PJZ5-!qXF!DC67KS=8g4WoSTyP`tgJ2@Dht|Lm>}kM`t~enSsXKoY+D9dw#Ya z{x2-)@3PhC1~RR4=Hgm2NIf57wT;Uyw#xmom`tyIfY|$W8)7UTG;04|5!`Et!R?mZ z`*IF_(Z@QcoOEU2!yEDM7z#PW9XFiHM_z~%NLvOw2o%=#NH2JFXUK_|mgNGXBhNh% z!u$$AILx*nj8PmV+ce8-4Ey|>ndAzvltycTENBRThJF!+jzo1K7cX^`B2*N=s~t}( ziIy0|6!Z}9;Ykn~F4yPBr3I(X*a0}3x$m-#n!n0)q+O&M4>1l&%dpQUlSJ<|aG_jp z40O_SGOmp4jSd-}3_gD}rM&a1f^Wt>Ta;)xIk4PCh(ndGb+1vn(F0 zV`13hLzM6s3ah7*_&S2J@wD_teu4ACPRJ%-*b7ia0Q-n6joE^nc^+-W7s(RoY>cQC4*@%y>vfJhTjmtNDi{>; zq4ZPFIv^b$V@zJIi&+<6*FiOlY3y{;Y%mi#pNwO&w|uXcKa%y26gT~2C1+{2PHKWz z@fsiifc0C+a&UCBG;;V+xU&_dLuS};8i-DwqRzSypwwo!eMb?~V&TGnmkmPE>tVxId7}LYWn7!E6r&ZW00Bhk^eWKV%c2GGK zgdJ#N9N=8pWwA#Y-E(>u!q4JE9nsXKOkDIZtElUtjR)oHBegDkyOzQyOCfBvm^?XS zE#{m93kK*?1k7$Kyc7zNblvK%qfe|jro|sKc5iH8=Y*Ix5O@=4K=SCQKXMt*<0HSR z#{)3ca9VfyvKj<3KXG(rvFefRyos8W4xO)iR58OTN9 zIGRh~5jZ~rbPW+SA9iKH6msS*16bxMbkye++!VQd5ti1n86<4X)O7WZF5?bjeq_6v z#2tK-YPEW8qrvgn-OI54p}Xo4lk5m+@rYSlQCJmS;bi)?vwKT{=W=YTF^ozYAQJq6 zDzTz49Zz8_liOC^+rD-|mCniLX|ekJ@mQ+}Yy;YqBW^S}8oPA9{AT_iJ-rjwbgl^Z zv>AMBL;Rbke{3ax@7aEJYoQ9#vT#1rk2qLoe%ghb>@!yS1{Jz)yyeQ zCdNIbxLd@e-HaaMi4jkai}K>$)@j&oecz~pgB2SsnNp%wN4GsoD<6oV$$b4PLM?Jy zz{kfoeHlrNop^?N7aB+k&CGawIa)^^#t)Gf$~l3Omo)*kx!Y*xM^)Y^rSfbe)fw2& z7wDm^XWIhsj?hkhF-Sp257ryy1KMUBW|&zD@Xs{mcmXzzb?M5y<}^}BFK9Ku5m z=Ql?f*jQP?XA2HLTf{!apA1Jm(K)vMUYOiL$znNA6ef-7&-SWmpZW@OX;$*p?Qb`SP&FA}rhQO%YrKeC zb60a~>>OLs)Y1RWj6wY!Zk@k(*z9tG!vb*Isl!2h56+hv+8D^$+t@m=8rawy{Wg>N zA58|w6d*KOTC$A|2fF8bld(KUZ}l8psVz6{?1ZF62$RZh-l zJm-??W+GMw>7qYJ+%hkgh^4>tJiUgnYC3M+sUg%xZkbQf_p!#a;P9_QnYHzdH5po( zuGQ18j~~}CM#g4+i8bA8$ROrzMCsPTC1VQGw4ys)Fmst?yM{Tx<`gO^yEAy_5Ywf3 zQc46&jg%I-I?BZ_Xp4y@CO5YL7C(rX@Qd3Wgz6+dG1`fs-v)Q2R3K5bb$i+#8(PkA znH>te=t^7sI5sNTWUlpoSA|SY8CB*J(rQ&P`LPaxtIB%#1lyYG!oUnk+pOJD@&qt1 zg1=c%gbEd;pB5|abyKR7$wXsD5;Fr~u!tUglruHv&V8clO=QW~Wp{QHe80ad)D=+) zN#Fu=jQbx0mH`sd76b>~y`KU5C(!BH+Wrr7KhXBuN{)tE!`U$C9`NKYbT=&dB{5V` zMLrw!YO)j{UROasS|+ECD>5#2a+GD2ZC9I;i`SiYb{rgA%kSK{PL&msf!q810Xizz zI13rmLFLV>!{_*7^q)IA0dWw7H=0)me$%UODPw?-CaGW2X=GN$<62>)z>s=<;^5$6t{uu+iRU3HRU>00x1OcWP^RGY9+gQErN6l zns}3FI-qX?vyeV4Ybi;61YW!Yr8pKW^C|OM@L5i(ad*%#x;@AjWosR(?lAgXnCFBY zug@}cwOUpza?neO35i>mcBaQMz_L>0=n$YXy5d|VvP3|QTZR@upoepA+X5Ralg%)v zrI&0zrM6);F3SksV9l5finiV-2VHCjGLSkSn_7nfZh=IbS8ENy+8hgQEt*ID0Yn}W z7p^v_155QRh1wPzpX+@pyx`xFYIx$;o%?tJaQtb_Mo;bf@Tm3JzKZdCDDTsW*!oMO zE0jM{CITWe;NOk1|M1oy=YJS%%SrxK!CxnGe+d3Kzk-LvpXPJ71^+tU__LrfoF@7A z!;af}ZqwkuG(q9h`dg&<--K`Rxxa+Z(f%X5dt1?M*6Wud5WIE%{VVsRk#=YR!1^@+ zfCMm)rYGa*;AY|AX7tG0$->ou+sodLK4%(@Jqv)2`u_ip|27MhKI&KL_`Bc_gWwv;|Q{Q8@{cApz1R_(YN1BEuQp zI0;jm$t^VU5S2p!6%X~!!+`*fSwxkCScVCxV%}T%h}*6;ZB}0|Ay36b*6ge`L|SUM zEa0H0cWVS+DpnuTCw^Z#6hBRndZ_?VFs(-&^A>(W`u$ZmPTQn0J#ACWyF#P|k?+hG z0a(&t9t+HtaBBJJ0Dr^pt9BzGDRpj7x&-Ky_;&B(IW+)qd5Hl~`%C22Y4I|BL&-@A z6+<#4^MZ^xFLhMHOo_w>+%GsuaJa%?|lahQB!y{lj z^DbRoWG=JR@lg;)0N8AH*nY)0^7B#pCSxGlkZaoSdYF{g*jZ{XGM zL8|1p#LiZ}Qv8!Xik9t79-@3siqZ!m%JC?D{8VFie#63Ym7Ii(A;vm2Xo^(#F z6^SF&n^*SJ1lE@sk#F`JoM+PgBRsoT*X9(z;!NO_alF1`3YNP28qd9S{cLx`LE+_r zk~3>vK$v0sh_b)0=Bt?uiCT%9%xdL1>Bp9|a^)LYRu0+t?Ax_7M*~c?@SashEN|?(iZ;Go#!C8*)U9?o4{e#c|uo!IdI4`o%hv*1t;@%MHsfgTJSfoV;v2~ zl!GXJuTNwjh&FG2C*3yQ#bPLQ0DciHX_IT!?Z~zWJQR%=uE{mBPAqsYcu%K_g10d< zA1C(UK6^Pltl*ZeZ;Cp^_(po*r&gH)M#ELtom*X12V@514M8g-JV&4CT8SCe@6XMy zSO?JEeb2QJ_09lKX*-b(Vq$2UY^U~tw8QxIYA=V<0G;l0lx6=OlLbNZ)SSB|Uy1UrEsJLEwI0V@ch&<3NapLr4O^5Y58@gY z_-KcfV&c0B3}l%r-`+U$Y+*a^Cs}iiah{m3x1AB6*cV!kRdLRwOy(X9>vy}ZC_~k) z7pIniw>&Vx{guDWP+LCZwO}?H?K`4e3cdp8v0%YBVI(nZhppcbjl$V<8nI{}-;ocA zWt;FK{D&?y1Nb=Rrjf?Fd0^$3;nBGCmR&y!Ph0Oynq6}nuayhyj4pT?K;rm!r)<< z1G^T1>a}>1H&G&S)<>ayRw8;6GSXM#_QCh-Er(+bb2U17DIR;0+9ZQJ z?R+r8Ng15EU)SXtgm_e`4Sd-sw~DrrN^jv-PH4IBhALV@!&M`@w~mJ<{Zi+k25s|83bM_uBwa-zDzu z%Kj(AT`UJL^kYyvKG8zP9A1?Exnu)OFTmH^%c;{z(gn`u67@pg_p3widw~FFy7zP^s}q_~7#l$d6*BEO2lqx@@pyos?HH{ZY7R&Z0I zu^EML7k?BYOzvqNX>V|NjlZ^dW1U>d-3jw6tn7Yzd9mU5RNAv=)y+{y^RDckDp`@= z$rUU#fDM$xNZ+Z=O@}bvBwLp*8LB+Hnq}H@Ri9>V=eHAjyv8!PS)gildm+6XPMW zjL-cp7nhmbzs&@OX3H=#uR71ZF(~&5?UJAy13mTigkC6817mUFW&>VIGYe_*dovSf zdj{`QIhb$WuarnkZP*D0v!o`39hy+~_lq&`4JbE+Rt7w|`KTM64sAYEYocV_lBGlN zNG*bi-q)8uTjIWq^5|?|=rLt;H+T~*^Cg5;IgJccG-E+}a8%v5_x=P^)*rG}nzei#s zX`g!B(%<(3j5_+gaN_>AL65hyZu^+#hz~4&m*6)#o(N1*ek#?Z;hF4`{eBYlN2^f}wl10Y6W-|})4_)GZ^&r6X1Hh43Kem4Ft}So=>C@>==fb z^Pc9kL8b*yalTNStN@NdREkY1V?XWgrf@TOU5gI7mH-|Y zel#BqGTjxR;iQR9X3zU_5+6;xv#Y*2P6i` zm6X@Gu1vY{4avVrE``KMQjTYa9-?ATP;8eh@ zwj3udVH%kb&t7yPGGL=yB;fky)`(KEYr`r>AYqbLAZ+gSa4`M4L3^JvD93ifNeC7Z zQ8&MXnfC6z*JnlpW*ITo;;7n%euY!+3f+!*v8V86T}??Uae_OkrmRqCY4g*L)G{SLVH_JLO_QD0yh=?hVuo>o#B6+gfdnWZ{4N{kr7p&Rk2>y@{B!)< z6Tp}`J3H7~kRLxpdNa_;FX(|CD*o08lx zk5fWHnSH_BQW~oP0=@s!Hpo&Tbj;D>kmYXW`PtOD{8^9(MW%^$iQM+A}%LdyfLCjPMLP!@mz{7 zXVYo~m2^9cRcVI-IJjpqxYrrZ)Do2r)1^xjcMTGm6>(BRIYK!}IFCYUh=o4htt>wF zAPgqjEO?z~(f&!LJM%2Va+rNsS%q&gjVgApMF{Kr#vq(kfPHnsWNg^{Xh`nPwAPMK zjo7Sjc-Z<&xm2y8YV>5X`hdr;k1B3=+gT2L6zR`i1gN(Hoi{_5@aJm*5M_UWaQ_vYJD2uDtt=BPfDoi~P zH?iZI);afAG5hi%rGK#ZeYl{9jR}0h!!ExVR>P)A3T2(X-wo>a8|QfDwUm4IX`QxV zDU**ynHOfo0?GbHlPvaP7K$TD?0HWztj4_eUxBy~Y0uksxGjB&c6f&-dCrw*TcXvpqTKmOhNXpw^_CQ2 zjqm*0GUXhyJG6zmW-x9T<>=ipq5+l%j%tRs93aFFRVrHGH_qpgP05{6h=27A=@*zVEC_8k*|#JM4i zQEfe1l%<@xr9GUEe)KO6wR=dNJ4)^?hx0bx5Xm3!un`#iY=)~u#^wE3lujd>rB}ym z?4CXUjKW>^-i-~y{P3qOWY5WTL4_=gZ-p!uNioBkNdO!T0uDU^JZ2$W_uRa=?fVqr zmof9#_$=X3o>)`N+AH~l#B4q_0y3gs)MBW$Fofvb#tO*%LKa6cyuzhyM!eW=NPCD@ z0qhDjx|<7&FH1e=WR&$ChsaxV^t_(ORtbl4-Emq=6@Gb{VZZj>QJ4Rt?=$mePTwbo z9IB(VONU$rnlevtHI8Opc=&b{`>(`zTEZBV0kSVE7*c9VvPd+>bA+9Ap1U-De!}AJ zC|Rbn_-(vX7PbLx%U7@%8jD{>u7oY3{P_3gWGAu>sfqHmHR{g`<==X?U)@@ShO#PZ z?$W}9pv#%tfE&LksUOR;HpOD@niRL47t*DA^77$y8{GaD{V=n`k?Z++#_#j(d>k1|BIuQ!>wEEbzQaV91#%{zZZM@K|F=UgX1IpP2nazwv5!pza$9<@Di{n;W8X#SjGG*ci6 zP4^=0j_+e9O7lPxfw;<&Qd3*1bxZ~iODoSc`nt1T9gl8#Rxh+~ z1?bRLycPr-S|+@6o}7|p)z>2MAsPkqRR3&hJ>FopYS&kERdhG-@+;R(K@quB-khOy z2H8RlD5A?cqR3}7v{z^0(Kv}`pI3Al5-cLCi+f5{>*#H=s&EFF5v;Ko;r)U&M_LKMeNaPN8SA*@q6C0NDmM?Zx8|M1CAOxudY+6q<#bWkF`iJFL+JDRDvI6ArVm^r#w{I+%Z-yjU7DL_Q5vSJr65p*x$GJ9o#)n0C5 z*yFwh;Zne-uq7_$nx^+_fy^`W&mECfb=5#I%LUI2Z!7sKu^v_=`j$-@IG**ITvh{l z-Aux|MNJq5yGzDx7*$!8>_exgItGTMb86*WVmkKiNGg3&`95_AaP9GeS_JK4pgA`V z0a;gBeeCIet*9O4rLk!HErv<$0IL9RsHm|WD8S zIX9*kko0Dre0v$+DDKg^&X60PP24Yc8y8z>f}4f|QG4lgAV(_C`mq}hNC2UQ>0KZK zXV#MG<1p?_F&eYz=F}z<+vBCfuJ=O{Sl0%L3Qy_H#z1(-Bds1p^5uPBYl4{W7iG69 zI6_Tb^p6kQ3fyZk=p^=3Uqb2{6?;W(Lyd~tL{pi%pcwr)b@ksv^zb57 zKur+GM%O`Mi4;elFD)+(_&;}b=pB9trtkxwc{ySaE;nYKng) z`0LK?ci@kAA5=*EX#;l^_}2x-pMjRBG0DGQXI$mEI_Unz1V!!RuZ+E~g0D_&e}R!W z|8ahMmFVi!>le}GjX$2^Kh40d5?-Ab{URhM_(^zmdUTcGYWMz&pqKO~!LMHaD)eeQ z_zPM?e)Y%vY7VcmTkmKO15u+Kkohy0G{9w literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/comment08.xlsx b/test/functional/xlsx_files/comment08.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..92ee26b2d8dfccbb7f93cbfc643cd55f910f44e7 GIT binary patch literal 8693 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 D@dNTf literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/comment09.xlsx b/test/functional/xlsx_files/comment09.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..192b443d68753dad52bc67240adb0c87aff5ab7b GIT binary patch literal 8602 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 TjyV7T1O6j`!`7Po$JPGaFZ zZs+ny)6>D+*?`T%)<#Pc1ptSHAggw>Ts_bLFz_ePe|!Zgi5~!Ua9~TH$$ggmz^d@R zQVf}A&%Yg)Nlm1+Gp@Vn(P6#q<(blWFuZ@hR#j-iV7j+mcc^UK{H7TOOhOeunFU

AcSrWp-q^kQ8J1ou=5lw? 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 literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/comment12.xlsx b/test/functional/xlsx_files/comment12.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5747b39ce0c854dd1bcacb172c090213c12ac96b GIT binary patch literal 8877 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 UfTH$$J&ys25&;DFJDak^uziZVBmb0YTu7 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 literal 0 HcmV?d00001 diff --git a/test/functional/xlsx_files/comment14.xlsx b/test/functional/xlsx_files/comment14.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c04a7b06e349b2e8f30ccfcd10fa61e44c8ba7a9 GIT binary patch literal 8886 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 literal 0 HcmV?d00001 diff --git a/test/unit/Makefile b/test/unit/Makefile index 0fa27df4..a756836a 100644 --- a/test/unit/Makefile +++ b/test/unit/Makefile @@ -38,6 +38,8 @@ SRCS += $(wildcard drawing/test*.c) SRCS += $(wildcard chart/test*.c) SRCS += $(wildcard custom/test*.c) SRCS += $(wildcard chartsheet/test*.c) +SRCS += $(wildcard vml/test*.c) +SRCS += $(wildcard comment/test*.c) # End of SRCS OBJS = $(patsubst %.c,%.o,$(SRCS)) @@ -72,6 +74,8 @@ all : $(Q)$(MAKE) -C chart $(Q)$(MAKE) -C custom $(Q)$(MAKE) -C chartsheet + $(Q)$(MAKE) -C vml + $(Q)$(MAKE) -C comment # END make all clean : @@ -90,6 +94,8 @@ clean : $(Q)$(MAKE) clean -C chart $(Q)$(MAKE) clean -C custom $(Q)$(MAKE) clean -C chartsheet + $(Q)$(MAKE) clean -C vml + $(Q)$(MAKE) clean -C comment # END make clean diff --git a/test/unit/comment/Makefile b/test/unit/comment/Makefile new file mode 100644 index 00000000..2c05b8e2 --- /dev/null +++ b/test/unit/comment/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/comment/main.c b/test/unit/comment/main.c new file mode 100644 index 00000000..086baf3a --- /dev/null +++ b/test/unit/comment/main.c @@ -0,0 +1,15 @@ +/* + * Test runner for xmlwriter using ctest. + * + * Copyright 2014-2019 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/comment/test_comment_xml_declaration.c b/test/unit/comment/test_comment_xml_declaration.c new file mode 100644 index 00000000..adc8e43b --- /dev/null +++ b/test/unit/comment/test_comment_xml_declaration.c @@ -0,0 +1,28 @@ +/* + * Tests for the libxlsxwriter library. + * + * Copyright 2014-2019, John McNamara, jmcnamara@cpan.org + * + */ + +#include "../ctest.h" +#include "../helper.h" + +#include "xlsxwriter/comment.h" + +// Test _xml_declaration(). +CTEST(comment, xml_declaration) { + + char* got; + char exp[] = "\n"; + FILE* testfile = tmpfile(); + + lxw_comment *comment = lxw_comment_new(); + comment->file = testfile; + + _comment_xml_declaration(comment); + + RUN_XLSX_STREQ(exp, got); + + lxw_comment_free(comment); +} diff --git a/test/unit/vml/Makefile b/test/unit/vml/Makefile new file mode 100644 index 00000000..b0a0426d --- /dev/null +++ b/test/unit/vml/Makefile @@ -0,0 +1,8 @@ +############################################################################### +# +# Makefile for libxlsxwriter library. +# +# Copyright 2014-2019, John McNamara, jmcnamara@cpan.org +# + +include ../Makefile.unit diff --git a/test/unit/vml/main.c b/test/unit/vml/main.c new file mode 100644 index 00000000..086baf3a --- /dev/null +++ b/test/unit/vml/main.c @@ -0,0 +1,15 @@ +/* + * Test runner for xmlwriter using ctest. + * + * Copyright 2014-2019 John McNamara, jmcnamara@cpan.org + * + */ +#define CTEST_MAIN + +#include "../ctest.h" + +int main(int argc, const char *argv[]) +{ + return ctest_main(argc, argv); +} +