From a160ef0ddc1b3c7a3be568e0f2ab62718b7a811d Mon Sep 17 00:00:00 2001 From: John McNamara Date: Sat, 2 May 2015 08:42:51 +0100 Subject: [PATCH] Added internal hyperlinks. --- include/xlsxwriter/worksheet.h | 4 +- src/worksheet.c | 107 +++++++++++++++----- test/functional/src/test_hyperlink04.c | 31 ++++++ test/functional/test_hyperlink.py | 4 +- test/functional/xlsx_files/hyperlink04.xlsx | Bin 0 -> 8434 bytes 5 files changed, 119 insertions(+), 27 deletions(-) create mode 100644 test/functional/src/test_hyperlink04.c create mode 100644 test/functional/xlsx_files/hyperlink04.xlsx diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 1446a641..98ea4460 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -111,7 +111,9 @@ enum cell_types { FORMULA_CELL, ARRAY_FORMULA_CELL, BLANK_CELL, - HYPERLLINK_URL + HYPERLINK_URL, + HYPERLINK_INTERNAL, + HYPERLINK_EXTERNAL }; /* Define the queue.h TAILQ structs for the list head types. */ diff --git a/src/worksheet.c b/src/worksheet.c index 3a6ec03e..b69cb1a6 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -138,7 +138,7 @@ _free_cell(lxw_cell *cell) return; if (cell->type == FORMULA_CELL || cell->type == ARRAY_FORMULA_CELL - || cell->type == INLINE_STRING_CELL || cell->type == HYPERLLINK_URL) { + || cell->type == INLINE_STRING_CELL || cell->type == HYPERLINK_URL) { free(cell->u.string); } @@ -391,15 +391,16 @@ _new_blank_cell(lxw_row_t row_num, lxw_col_t col_num, lxw_format *format) * Create a new worksheet hyperlink cell object. */ STATIC lxw_cell * -_new_hyperlink_cell(lxw_row_t row_num, lxw_col_t col_num, char *url, - char *string, char *tooltip) +_new_hyperlink_cell(lxw_row_t row_num, lxw_col_t col_num, + enum cell_types link_type, char *url, char *string, + char *tooltip) { lxw_cell *cell = calloc(1, sizeof(lxw_cell)); RETURN_ON_MEM_ERROR(cell, cell); cell->row_num = row_num; cell->col_num = col_num; - cell->type = HYPERLLINK_URL; + cell->type = link_type; cell->u.string = url; cell->user_data1 = string; cell->user_data2 = tooltip; @@ -1658,7 +1659,7 @@ _worksheet_write_auto_filter(lxw_worksheet *self) } /* - * Write the element. + * Write the element for external links. */ STATIC void _worksheet_write_hyperlink_external(lxw_worksheet *self, lxw_row_t row_num, @@ -1686,6 +1687,37 @@ _worksheet_write_hyperlink_external(lxw_worksheet *self, lxw_row_t row_num, _FREE_ATTRIBUTES(); } +/* + * Write the element for internal links. + */ +STATIC void +_worksheet_write_hyperlink_internal(lxw_worksheet *self, lxw_row_t row_num, + lxw_col_t col_num, const char *location, + const char *display, const char *tooltip) +{ + struct xml_attribute_list attributes; + struct xml_attribute *attribute; + char ref[MAX_CELL_NAME_LENGTH]; + + lxw_rowcol_to_cell(ref, row_num, col_num); + + _INIT_ATTRIBUTES(); + _PUSH_ATTRIBUTES_STR("ref", ref); + + if (location) + _PUSH_ATTRIBUTES_STR("location", location); + + if (tooltip) + _PUSH_ATTRIBUTES_STR("tooltip", tooltip); + + if (display) + _PUSH_ATTRIBUTES_STR("display", display); + + _xml_empty_tag(self->file, "hyperlink", &attributes); + + _FREE_ATTRIBUTES(); +} + /* * Process any stored hyperlinks in row/col order and write the * element. The attributes are different for internal and external links. @@ -1708,27 +1740,40 @@ _worksheet_write_hyperlinks(lxw_worksheet *self) TAILQ_FOREACH(link, row->cells, list_pointers) { - self->rel_count++; + if (link->type == HYPERLINK_URL) { - relationship = calloc(1, sizeof(lxw_rel_tuple)); - GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); + self->rel_count++; - relationship->type = lxw_strdup("/hyperlink"); - GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); + relationship = calloc(1, sizeof(lxw_rel_tuple)); + GOTO_LABEL_ON_MEM_ERROR(relationship, mem_error); - relationship->target = lxw_strdup(link->u.string); - GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); + relationship->type = lxw_strdup("/hyperlink"); + GOTO_LABEL_ON_MEM_ERROR(relationship->type, mem_error); - relationship->target_mode = lxw_strdup("External"); - GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error); + relationship->target = lxw_strdup(link->u.string); + GOTO_LABEL_ON_MEM_ERROR(relationship->target, mem_error); - STAILQ_INSERT_TAIL(self->external_hyperlinks, relationship, - list_pointers); + relationship->target_mode = lxw_strdup("External"); + GOTO_LABEL_ON_MEM_ERROR(relationship->target_mode, mem_error); + + STAILQ_INSERT_TAIL(self->external_hyperlinks, relationship, + list_pointers); + + _worksheet_write_hyperlink_external(self, link->row_num, + link->col_num, + link->user_data2, + self->rel_count); + } + + if (link->type == HYPERLINK_INTERNAL) { + + _worksheet_write_hyperlink_internal(self, link->row_num, + link->col_num, + link->u.string, + link->user_data1, + link->user_data2); + } - _worksheet_write_hyperlink_external(self, link->row_num, - link->col_num, - link->user_data2, - self->rel_count); } } @@ -2107,8 +2152,9 @@ worksheet_write_url_opt(lxw_worksheet *self, char *url_copy = NULL; char *string_copy = NULL; char *tooltip_copy = NULL; + char *tmp_str; int8_t err; - /*uint8_t link_type = 1; */ + enum cell_types link_type = HYPERLINK_URL; if (!url || !strlen(url)) return -4; @@ -2117,12 +2163,21 @@ worksheet_write_url_opt(lxw_worksheet *self, if (err) return err; + tmp_str = strstr(url, "internal:"); + + if (tmp_str) + link_type = HYPERLINK_INTERNAL; + if (string) { string_copy = lxw_strdup(string); GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error); } else { - string_copy = lxw_strdup(url); + if (link_type == HYPERLINK_URL) + string_copy = lxw_strdup(url); + else + string_copy = lxw_strdup(url + sizeof("__ternal")); + GOTO_LABEL_ON_MEM_ERROR(string_copy, mem_error); } @@ -2131,7 +2186,11 @@ worksheet_write_url_opt(lxw_worksheet *self, goto mem_error; if (url) { - url_copy = lxw_strdup(url); + if (link_type == HYPERLINK_URL) + url_copy = lxw_strdup(url); + else + url_copy = lxw_strdup(url + sizeof("__ternal")); + GOTO_LABEL_ON_MEM_ERROR(url_copy, mem_error); } @@ -2140,7 +2199,7 @@ worksheet_write_url_opt(lxw_worksheet *self, GOTO_LABEL_ON_MEM_ERROR(tooltip_copy, mem_error); } - link = _new_hyperlink_cell(row_num, col_num, url_copy, + link = _new_hyperlink_cell(row_num, col_num, link_type, url_copy, string_copy, tooltip_copy); GOTO_LABEL_ON_MEM_ERROR(link, mem_error); diff --git a/test/functional/src/test_hyperlink04.c b/test/functional/src/test_hyperlink04.c new file mode 100644 index 00000000..c3abee36 --- /dev/null +++ b/test/functional/src/test_hyperlink04.c @@ -0,0 +1,31 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2015, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = new_workbook("test_hyperlink04.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, "Data Sheet"); + + (void)worksheet2; + (void)worksheet3; + + worksheet_write_url_opt(worksheet1, CELL("A1"), "internal:Sheet2!A1", NULL, NULL, NULL); + worksheet_write_url_opt(worksheet1, CELL("A3"), "internal:Sheet2!A1:A5", NULL, NULL, NULL); + worksheet_write_url_opt(worksheet1, CELL("A5"), "internal:'Data Sheet'!D5", NULL, "Some text", NULL); + worksheet_write_url_opt(worksheet1, CELL("E12"), "internal:Sheet1!J1", NULL, NULL, NULL); + worksheet_write_url_opt(worksheet1, CELL("G17"), "internal:Sheet2!A1", NULL, "Some text", NULL); + worksheet_write_url_opt(worksheet1, CELL("A18"), "internal:Sheet2!A1", NULL, NULL, "Tool Tip 1"); + worksheet_write_url_opt(worksheet1, CELL("A20"), "internal:Sheet2!A1", NULL, "More text", "Tool Tip 2"); + + return workbook_close(workbook); +} diff --git a/test/functional/test_hyperlink.py b/test/functional/test_hyperlink.py index 7812fcaa..50a0b6dd 100644 --- a/test/functional/test_hyperlink.py +++ b/test/functional/test_hyperlink.py @@ -22,8 +22,8 @@ class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): def test_hyperlink03(self): self.run_exe_test('test_hyperlink03') - # def test_hyperlink04(self): - # self.run_exe_test('test_hyperlink04') + def test_hyperlink04(self): + self.run_exe_test('test_hyperlink04') def test_hyperlink05(self): self.run_exe_test('test_hyperlink05') diff --git a/test/functional/xlsx_files/hyperlink04.xlsx b/test/functional/xlsx_files/hyperlink04.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..547a8e7de979a13a40abf69e302f1d5429bd057e GIT binary patch literal 8434 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 literal 0 HcmV?d00001