From 410e73d594be3d3967c8642a539dbb708a881bf7 Mon Sep 17 00:00:00 2001 From: Thomas Fussell Date: Fri, 16 Mar 2018 21:21:16 -0400 Subject: [PATCH] work on hyperlinks, x14 extentions, other round tripping silliness --- include/xlnt/cell/cell.hpp | 11 +- include/xlnt/cell/hyperlink.hpp | 61 +++++++++ include/xlnt/workbook/workbook.hpp | 25 ++++ include/xlnt/worksheet/row_properties.hpp | 7 +- include/xlnt/xlnt.hpp | 1 + source/cell/cell.cpp | 39 ++++-- source/cell/hyperlink.cpp | 91 +++++++++++++ source/detail/implementations/cell_impl.cpp | 3 +- source/detail/implementations/cell_impl.hpp | 4 +- .../detail/implementations/hyperlink_impl.hpp | 41 ++++++ source/detail/implementations/stylesheet.hpp | 2 + source/detail/serialization/xlsx_consumer.cpp | 52 ++++++-- source/detail/serialization/xlsx_producer.cpp | 126 +++++++++++++++--- source/workbook/workbook.cpp | 25 ++++ source/worksheet/worksheet.cpp | 2 +- tests/cell/cell_test_suite.hpp | 6 +- tests/workbook/serialization_test_suite.hpp | 33 +++-- tests/worksheet/worksheet_test_suite.hpp | 4 +- 18 files changed, 474 insertions(+), 59 deletions(-) create mode 100644 include/xlnt/cell/hyperlink.hpp create mode 100644 source/cell/hyperlink.cpp create mode 100644 source/detail/implementations/hyperlink_impl.hpp diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index 7ab1dc2a..74a5b6cb 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -47,6 +47,8 @@ class font; class format; class number_format; class protection; +class range; +class relationship; class style; class workbook; class worksheet; @@ -251,9 +253,9 @@ public: // hyperlink /// - /// Returns the URL of this cell's hyperlink. + /// Returns the relationship of this cell's hyperlink. /// - std::string hyperlink() const; + class hyperlink hyperlink() const; /// /// Adds a hyperlink to this cell pointing to the URL of the given value. @@ -271,6 +273,11 @@ public: /// void hyperlink(xlnt::cell target); + /// + /// Adds an internal hyperlink to this cell pointing to the given range. + /// + void hyperlink(xlnt::range target); + /// /// Returns true if this cell has a hyperlink set. /// diff --git a/include/xlnt/cell/hyperlink.hpp b/include/xlnt/cell/hyperlink.hpp new file mode 100644 index 00000000..8034098b --- /dev/null +++ b/include/xlnt/cell/hyperlink.hpp @@ -0,0 +1,61 @@ +// Copyright (c) 2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#pragma once + +#include +#include + +namespace xlnt { + +namespace detail { +struct hyperlink_impl; +} + +class cell; +class range; +class relationship; + +/// +/// Describes a hyperlink pointing from a cell to another cell or a URL. +/// +class XLNT_API hyperlink +{ +public: + bool external() const; + class relationship relationship() const; + std::string url() const; + std::string target_range() const; + void display(const std::string &value); + std::string display() const; + void tooltip(const std::string &value); + std::string tooltip() const; + +private: + friend class cell; + hyperlink(detail::hyperlink_impl *d); + detail::hyperlink_impl *d_; +}; + +} // namespace xlnt + diff --git a/include/xlnt/workbook/workbook.hpp b/include/xlnt/workbook/workbook.hpp index 604209ab..2ab82728 100644 --- a/include/xlnt/workbook/workbook.hpp +++ b/include/xlnt/workbook/workbook.hpp @@ -682,6 +682,31 @@ public: /// void clear_styles(); + /// + /// Sets the default slicer style to the given value. + /// + void default_slicer_style(const std::string &value); + + /// + /// Returns the default slicer style. + /// + std::string default_slicer_style() const; + + /// + /// Enables knownFonts in stylesheet. + /// + void enable_known_fonts(); + + /// + /// Disables knownFonts in stylesheet. + /// + void disable_known_fonts(); + + /// + /// Returns true if knownFonts are enabled in the stylesheet. + /// + bool known_fonts_enabled() const; + // Manifest /// diff --git a/include/xlnt/worksheet/row_properties.hpp b/include/xlnt/worksheet/row_properties.hpp index c200ad39..4fcb6fa5 100644 --- a/include/xlnt/worksheet/row_properties.hpp +++ b/include/xlnt/worksheet/row_properties.hpp @@ -35,10 +35,15 @@ class XLNT_API row_properties { public: /// - /// Optional height + /// Row height /// optional height; + /// + /// Distance in pixels from the bottom of the cell to the baseline of the cell content + /// + optional dy_descent; + /// /// Whether or not the height is different from the default /// diff --git a/include/xlnt/xlnt.hpp b/include/xlnt/xlnt.hpp index 1b53d036..01fe4981 100644 --- a/include/xlnt/xlnt.hpp +++ b/include/xlnt/xlnt.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/source/cell/cell.cpp b/source/cell/cell.cpp index 14f5b9a6..13761c0d 100644 --- a/source/cell/cell.cpp +++ b/source/cell/cell.cpp @@ -28,11 +28,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -196,7 +198,7 @@ cell::cell(detail::cell_impl *d) bool cell::garbage_collectible() const { - return !(has_value() || is_merged() || has_formula() || has_format()); + return !(has_value() || is_merged() || has_formula() || has_format() || has_hyperlink()); } void cell::value(std::nullptr_t) @@ -360,9 +362,9 @@ cell &cell::operator=(const cell &rhs) return *this; } -std::string cell::hyperlink() const +hyperlink cell::hyperlink() const { - return d_->hyperlink_.get(); + return xlnt::hyperlink(&d_->hyperlink_.get()); } void cell::hyperlink(const std::string &hyperlink) @@ -377,31 +379,50 @@ void cell::hyperlink(const std::string &hyperlink) auto &manifest = ws.workbook().manifest(); bool existing = false; + d_->hyperlink_ = detail::hyperlink_impl(); + + // check for existing relationships for (const auto &rel : manifest.relationships(ws.path(), relationship_type::hyperlink)) { if (rel.target().path().string() == hyperlink) { + d_->hyperlink_.get().relationship = rel; existing = true; + break; } } + // register a new relationship if (!existing) { - manifest.register_relationship(uri(ws.path().string()), relationship_type::hyperlink, - uri(hyperlink), target_mode::external); + auto rel_id = manifest.register_relationship( + uri(ws.path().string()), + relationship_type::hyperlink, + uri(hyperlink), + target_mode::external); + // TODO: make manifest::register_relationship return the created relationship instead of rel id + d_->hyperlink_.get().relationship = manifest.relationship(ws.path(), rel_id); } - d_->hyperlink_ = hyperlink; + value(hyperlink); } void cell::hyperlink(const std::string &url, const std::string &display) { hyperlink(url); + d_->hyperlink_.get().display = display; value(display); } -void cell::hyperlink(xlnt::cell /*target*/) +void cell::hyperlink(xlnt::cell target) { - //todo: implement + // TODO: should this computed value be a method on a cell? + const auto cell_address = target.worksheet().title() + "!" + target.reference().to_string(); + + d_->hyperlink_ = detail::hyperlink_impl(); + d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink, + uri(""), uri(cell_address), target_mode::internal); + d_->hyperlink_.get().display = cell_address; + value(cell_address); } void cell::formula(const std::string &formula) @@ -420,8 +441,6 @@ void cell::formula(const std::string &formula) d_->formula_ = formula; } - data_type(type::number); - worksheet().register_calc_chain_in_manifest(); } diff --git a/source/cell/hyperlink.cpp b/source/cell/hyperlink.cpp new file mode 100644 index 00000000..8d28d84e --- /dev/null +++ b/source/cell/hyperlink.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2014-2018 Thomas Fussell +// Copyright (c) 2010-2015 openpyxl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#include +#include +#include + +namespace xlnt { + +hyperlink::hyperlink(detail::hyperlink_impl *d) : d_(d) +{ +} + +relationship hyperlink::relationship() const +{ + if (!external()) + { + throw xlnt::exception("only external hyperlinks have associated relationships"); + } + + return d_->relationship; +} + +std::string hyperlink::url() const +{ + if (!external()) + { + throw xlnt::exception("only external hyperlinks have associated urls"); + } + + return d_->relationship.target().to_string(); +} + +std::string hyperlink::target_range() const +{ + if (external()) + { + throw xlnt::exception("only internal hyperlinks have a target range"); + } + + return d_->relationship.target().to_string(); +} + +bool hyperlink::external() const +{ + return d_->relationship.target_mode() == target_mode::external; +} + +void hyperlink::display(const std::string &value) +{ + d_->display = value; +} + +std::string hyperlink::display() const +{ + return d_->display; +} + +void hyperlink::tooltip(const std::string &value) +{ + d_->tooltip = value; +} + +std::string hyperlink::tooltip() const +{ + return d_->tooltip; +} + +} // namespace xlnt + diff --git a/source/detail/implementations/cell_impl.cpp b/source/detail/implementations/cell_impl.cpp index 49b20a11..8be6324b 100644 --- a/source/detail/implementations/cell_impl.cpp +++ b/source/detail/implementations/cell_impl.cpp @@ -21,9 +21,10 @@ // // @license: http://www.opensource.org/licenses/mit-license.php // @author: see AUTHORS file + #include -#include "cell_impl.hpp" +#include namespace xlnt { namespace detail { diff --git a/source/detail/implementations/cell_impl.hpp b/source/detail/implementations/cell_impl.hpp index 9cd4fd26..4553d133 100644 --- a/source/detail/implementations/cell_impl.hpp +++ b/source/detail/implementations/cell_impl.hpp @@ -29,7 +29,9 @@ #include #include #include +#include #include +#include namespace xlnt { namespace detail { @@ -54,7 +56,7 @@ struct cell_impl double value_numeric_; optional formula_; - optional hyperlink_; + optional hyperlink_; optional format_; optional comment_; }; diff --git a/source/detail/implementations/hyperlink_impl.hpp b/source/detail/implementations/hyperlink_impl.hpp new file mode 100644 index 00000000..a71fa1e3 --- /dev/null +++ b/source/detail/implementations/hyperlink_impl.hpp @@ -0,0 +1,41 @@ +// Copyright (c) 2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#pragma once + +#include + +#include + +namespace xlnt { +namespace detail { + +struct hyperlink_impl +{ + relationship relationship; + std::string tooltip; + std::string display; +}; + +} // namespace detail +} // namespace xlnt diff --git a/source/detail/implementations/stylesheet.hpp b/source/detail/implementations/stylesheet.hpp index 9f1d529a..f0b2a48f 100644 --- a/source/detail/implementations/stylesheet.hpp +++ b/source/detail/implementations/stylesheet.hpp @@ -531,11 +531,13 @@ struct stylesheet workbook *parent; bool garbage_collection_enabled = true; + bool known_fonts_enabled = false; std::list conditional_format_impls; std::list format_impls; std::unordered_map style_impls; std::vector style_names; + optional default_slicer_style; std::vector alignments; std::vector borders; diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index 4086d68f..6288d0de 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -198,22 +198,28 @@ cell xlsx_consumer::read_cell() { expect_start_element(qn("spreadsheetml", "row"), xml::content::complex); // CT_Row auto row_index = static_cast(std::stoul(parser().attribute("r"))); + auto &row_properties = ws.row_properties(row_index); if (parser().attribute_present("ht")) { - ws.row_properties(row_index).height = parser().attribute("ht"); + row_properties.height = parser().attribute("ht"); } if (parser().attribute_present("customHeight")) { - ws.row_properties(row_index).custom_height = is_true(parser().attribute("customHeight")); + row_properties.custom_height = is_true(parser().attribute("customHeight")); } if (parser().attribute_present("hidden") && is_true(parser().attribute("hidden"))) { - ws.row_properties(row_index).hidden = true; + row_properties.hidden = true; } - skip_attributes({ qn("x14ac", "dyDescent") }); + + if (parser().attribute_present(qn("x14ac", "dyDescent"))) + { + row_properties.dy_descent = parser().attribute(qn("x14ac", "dyDescent")); + } + skip_attributes({ "customFormat", "s", "customFont", "outlineLevel", "collapsed", "thickTop", "thickBot", "ph", "spans" }); @@ -226,7 +232,8 @@ cell xlsx_consumer::read_cell() expect_start_element(qn("spreadsheetml", "c"), xml::content::complex); - auto cell = streaming_ ? xlnt::cell(streaming_cell_.get()) + auto cell = streaming_ + ? xlnt::cell(streaming_cell_.get()) : ws.cell(cell_reference(parser().attribute("r"))); auto reference = cell_reference(parser().attribute("r")); cell.d_->parent_ = current_worksheet_; @@ -238,7 +245,7 @@ cell xlsx_consumer::read_cell() if (parser().attribute_present("s")) { - cell.format(target_.format(std::stoull(parser().attribute("s")))); + cell.format(target_.format(std::stoull(parser().attribute("s")))); } auto has_value = false; @@ -622,23 +629,28 @@ void xlsx_consumer::read_worksheet_sheetdata() { expect_start_element(qn("spreadsheetml", "row"), xml::content::complex); // CT_Row auto row_index = parser().attribute("r"); + auto &row_properties = ws.row_properties(row_index); if (parser().attribute_present("ht")) { - ws.row_properties(row_index).height = parser().attribute("ht"); + row_properties.height = parser().attribute("ht"); } if (parser().attribute_present("customHeight")) { - ws.row_properties(row_index).custom_height = is_true(parser().attribute("customHeight")); + row_properties.custom_height = is_true(parser().attribute("customHeight")); } if (parser().attribute_present("hidden") && is_true(parser().attribute("hidden"))) { - ws.row_properties(row_index).hidden = true; + row_properties.hidden = true; + } + + if (parser().attribute_present(qn("x14ac", "dyDescent"))) + { + row_properties.dy_descent = parser().attribute(qn("x14ac", "dyDescent")); } - skip_attributes({ qn("x14ac", "dyDescent") }); skip_attributes({ "customFormat", "s", "customFont", "outlineLevel", "collapsed", "thickTop", "thickBot", "ph", "spans" }); @@ -2268,7 +2280,25 @@ void xlsx_consumer::read_stylesheet() } else if (current_style_element == qn("spreadsheetml", "extLst")) { - skip_remaining_content(current_style_element); + while (in_element(qn("spreadsheetml", "extLst"))) + { + expect_start_element(qn("spreadsheetml", "ext"), xml::content::complex); + + const auto uri = parser().attribute("uri"); + + if (uri == "{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}") // slicerStyles + { + expect_start_element(qn("x14", "slicerStyles"), xml::content::simple); + stylesheet.default_slicer_style = parser().attribute("defaultSlicerStyle"); + expect_end_element(qn("x14", "slicerStyles")); + } + else + { + skip_remaining_content(qn("spreadsheetml", "ext")); + } + + expect_end_element(qn("spreadsheetml", "ext")); + } } else if (current_style_element == qn("spreadsheetml", "colors")) // CT_Colors 0-1 { diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index f5611f88..e3c3a242 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -1113,13 +1114,33 @@ void xlsx_producer::write_border(const border ¤t_border) void xlsx_producer::write_styles(const relationship & /*rel*/) { - static const auto &xmlns = constants::ns("spreadsheetml"); + static const auto &xmlns = constants::ns("spreadsheetml"); + static const auto &xmlns_mc = constants::ns("mc"); + static const auto &xmlns_x14 = constants::ns("x14"); + static const auto &xmlns_x14ac = constants::ns("x14ac"); write_start_element(xmlns, "styleSheet"); write_namespace(xmlns, ""); const auto &stylesheet = source_.impl().stylesheet_.get(); + auto using_namespace = [&stylesheet](const std::string &ns) + { + if (ns == "x14ac") + { + return stylesheet.known_fonts_enabled; + } + + return false; + }; + + if (using_namespace("x14ac")) + { + write_namespace(xmlns_mc, "mc"); + write_namespace(xmlns_x14ac, "x14ac"); + write_attribute(xml::qname(xmlns_mc, "Ignorable"), "x14ac"); + } + // Number Formats if (!stylesheet.number_formats.empty()) @@ -1156,6 +1177,31 @@ void xlsx_producer::write_styles(const relationship & /*rel*/) write_start_element(xmlns, "fonts"); write_attribute("count", fonts.size()); + if (stylesheet.known_fonts_enabled) + { + auto is_known_font = [](const font &f) + { + const auto &known_fonts = *new std::vector + { + font().name("Calibri").family(2).size(12).color(theme_color(1)).scheme("minor") + }; + + return std::find(known_fonts.begin(), known_fonts.end(), f) != known_fonts.end(); + }; + + std::size_t num_known_fonts = 0; + + for (const auto ¤t_font : fonts) + { + if (is_known_font(current_font)) + { + num_known_fonts += 1; + } + } + + write_attribute(xml::qname(xmlns_x14ac, "knownFonts"), num_known_fonts); + } + for (const auto ¤t_font : fonts) { write_font(current_font); @@ -1514,6 +1560,26 @@ void xlsx_producer::write_styles(const relationship & /*rel*/) write_end_element(xmlns, "colors"); } + auto using_extensions = stylesheet.default_slicer_style.is_set(); + + if (using_extensions) + { + write_start_element(xmlns, "extLst"); + + if (stylesheet.default_slicer_style.is_set()) + { + write_start_element(xmlns, "ext"); + write_namespace(xmlns_x14, "x14"); + write_attribute("uri", "{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}"); // slicerStyles URI + write_start_element(xmlns_x14, "slicerStyles"); + write_attribute("defaultSlicerStyle", stylesheet.default_slicer_style.get()); + write_end_element(xmlns_x14, "slicerStyles"); + write_end_element(xmlns, "ext"); + } + + write_end_element(xmlns, "extLst"); + } + write_end_element(xmlns, "styleSheet"); } @@ -1990,6 +2056,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) { static const auto &xmlns = constants::ns("spreadsheetml"); static const auto &xmlns_r = constants::ns("r"); + static const auto &xmlns_mc = constants::ns("mc"); static const auto &xmlns_x14ac = constants::ns("x14ac"); auto worksheet_part = rel.source().path().parent().append(rel.target().path()); @@ -2010,7 +2077,18 @@ void xlsx_producer::write_worksheet(const relationship &rel) { if (ns == "x14ac") { - return ws.format_properties().dy_descent.is_set(); + if (ws.format_properties().dy_descent.is_set()) + { + return true; + } + + for (auto row = ws.lowest_row(); row <= ws.highest_row(); ++row) + { + if (ws.has_row_properties(row) && ws.row_properties(row).dy_descent.is_set()) + { + return true; + } + } } return false; @@ -2018,7 +2096,9 @@ void xlsx_producer::write_worksheet(const relationship &rel) if (using_namespace("x14ac")) { + write_namespace(xmlns_mc, "mc"); write_namespace(xmlns_x14ac, "x14ac"); + write_attribute(xml::qname(xmlns_mc, "Ignorable"), "x14ac"); } if (ws.has_page_setup()) @@ -2056,8 +2136,8 @@ void xlsx_producer::write_worksheet(const relationship &rel) if (view.type() != sheet_view_type::normal) { - write_attribute( - "view", view.type() == sheet_view_type::page_break_preview ? "pageBreakPreview" : "pageLayout"); + write_attribute("view", view.type() == sheet_view_type::page_break_preview + ? "pageBreakPreview" : "pageLayout"); } if (view.has_pane()) @@ -2097,7 +2177,10 @@ void xlsx_producer::write_worksheet(const relationship &rel) if (current_selection.has_sqref()) { - write_attribute("sqref", current_selection.sqref().to_string()); + const auto sqref = current_selection.sqref(); + write_attribute("sqref", sqref.is_single_cell() + ? sqref.top_left().to_string() + : sqref.to_string()); } if (current_selection.pane() != pane_corner::top_left) @@ -2183,16 +2266,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_end_element(xmlns, "cols"); } - const auto hyperlink_rels = source_.manifest() - .relationships(worksheet_part, relationship_type::hyperlink); - std::unordered_map reverse_hyperlink_references; - - for (auto hyperlink_rel : hyperlink_rels) - { - reverse_hyperlink_references[hyperlink_rel.target().path().string()] = rel.id(); - } - - std::unordered_map hyperlink_references; + std::vector> hyperlinks; std::vector cells_with_comments; write_start_element(xmlns, "sheetData"); @@ -2241,6 +2315,11 @@ void xlsx_producer::write_worksheet(const relationship &rel) write_attribute("customHeight", write_bool(true)); } + if (props.dy_descent.is_set()) + { + write_attribute(xml::qname(xmlns_x14ac, "dyDescent"), props.dy_descent.get()); + } + if (props.height.is_set()) { auto height = props.height.get(); @@ -2280,7 +2359,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) if (cell.has_hyperlink()) { - hyperlink_references[cell.reference().to_string()] = reverse_hyperlink_references[cell.hyperlink()]; + hyperlinks.push_back(std::make_pair(cell.reference().to_string(), cell.hyperlink())); } write_start_element(xmlns, "c"); @@ -2464,15 +2543,24 @@ void xlsx_producer::write_worksheet(const relationship &rel) } } - if (!hyperlink_rels.empty()) + if (!hyperlinks.empty()) { write_start_element(xmlns, "hyperlinks"); - for (const auto &hyperlink : hyperlink_references) + for (const auto &hyperlink : hyperlinks) { write_start_element(xmlns, "hyperlink"); write_attribute("ref", hyperlink.first); - write_attribute(xml::qname(xmlns_r, "id"), hyperlink.second); + if (hyperlink.second.external()) + { + write_attribute(xml::qname(xmlns_r, "id"), + hyperlink.second.relationship().id()); + } + else + { + write_attribute("location", hyperlink.second.target_range()); + write_attribute("display", hyperlink.second.display()); + } write_end_element(xmlns, "hyperlink"); } diff --git a/source/workbook/workbook.cpp b/source/workbook/workbook.cpp index ac70b1cc..5773d74c 100644 --- a/source/workbook/workbook.cpp +++ b/source/workbook/workbook.cpp @@ -1214,6 +1214,31 @@ void workbook::clear_styles() apply_to_cells([](cell c) { c.clear_style(); }); } +void workbook::default_slicer_style(const std::string &value) +{ + d_->stylesheet_.get().default_slicer_style = value; +} + +std::string workbook::default_slicer_style() const +{ + return d_->stylesheet_.get().default_slicer_style.get(); +} + +void workbook::enable_known_fonts() +{ + d_->stylesheet_.get().known_fonts_enabled = true; +} + +void workbook::disable_known_fonts() +{ + d_->stylesheet_.get().known_fonts_enabled = false; +} + +bool workbook::known_fonts_enabled() const +{ + return d_->stylesheet_.get().known_fonts_enabled; +} + void workbook::clear_formats() { apply_to_cells([](cell c) { c.clear_format(); }); diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp index f240e895..dea1125b 100644 --- a/source/worksheet/worksheet.cpp +++ b/source/worksheet/worksheet.cpp @@ -1085,7 +1085,7 @@ double worksheet::row_height(row_t row) const { static const auto DefaultRowHeight = 15.0; - if (has_row_properties(row)) + if (has_row_properties(row) && row_properties(row).height.is_set()) { return row_properties(row).height.get(); } diff --git a/tests/cell/cell_test_suite.hpp b/tests/cell/cell_test_suite.hpp index 2272c422..04294818 100644 --- a/tests/cell/cell_test_suite.hpp +++ b/tests/cell/cell_test_suite.hpp @@ -177,7 +177,7 @@ private: auto cell = ws.cell(xlnt::cell_reference(1, 1)); cell.value("=42", true); - xlnt_assert(cell.data_type() == xlnt::cell::type::number); + //xlnt_assert(cell.data_type() == xlnt::cell::type::number); xlnt_assert(cell.has_formula()); } @@ -188,7 +188,7 @@ private: auto cell = ws.cell(xlnt::cell_reference(1, 1)); cell.value("=if(A1<4;-1;1)", true); - xlnt_assert(cell.data_type() == xlnt::cell::type::number); + //xlnt_assert(cell.data_type() == xlnt::cell::type::number); xlnt_assert(cell.has_formula()); } @@ -659,7 +659,7 @@ private: xlnt_assert_throws(cell.hyperlink(""), xlnt::invalid_parameter); cell.hyperlink("http://example.com"); xlnt_assert(cell.has_hyperlink()); - xlnt_assert_equals(cell.hyperlink(), "http://example.com"); + xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), "http://example.com"); } void test_comment() diff --git a/tests/workbook/serialization_test_suite.hpp b/tests/workbook/serialization_test_suite.hpp index a4ae2fd2..c2dd48f8 100644 --- a/tests/workbook/serialization_test_suite.hpp +++ b/tests/workbook/serialization_test_suite.hpp @@ -200,11 +200,20 @@ public: .size(10) .color(xlnt::indexed_color(81)) .name("Calibri"); + auto hyperlink_font = xlnt::font() + .size(12) + .color(xlnt::theme_color(10)) + .name("Calibri") + .underline(xlnt::font::underline_style::single); sheet1.cell("A4").hyperlink("https://microsoft.com/", "hyperlink1"); + sheet1.cell("A4").font(hyperlink_font); sheet1.cell("A5").hyperlink("https://google.com/"); + sheet1.cell("A5").font(hyperlink_font); sheet1.cell("A6").hyperlink(sheet1.cell("A1")); + sheet1.cell("A6").font(hyperlink_font); sheet1.cell("A7").hyperlink("mailto:invalid@example.com?subject=important"); + sheet1.cell("A7").font(hyperlink_font); sheet1.cell("A1").value("Sheet1!A1"); sheet1.cell("A1").comment("Sheet1 comment", comment_font, "Microsoft Office User"); @@ -216,13 +225,19 @@ public: sheet1.cell("C2").value("a"); sheet1.cell("C3").value("b"); + for (auto i = 1; i <= 7; ++i) + { + sheet1.row_properties(i).dy_descent = 0.2; + } + auto sheet2 = wb.create_sheet(); sheet2.format_properties(format_properties); sheet2.cell("A4").hyperlink("https://apple.com/", "hyperlink2"); + sheet2.cell("A4").font(hyperlink_font); sheet2.cell("A1").value("Sheet2!A1"); - sheet2.cell("A2").comment("Sheet2 comment", comment_font, "Microsoft Office User"); + sheet2.cell("A1").comment("Sheet2 comment", comment_font, "Microsoft Office User"); sheet2.cell("A2").value("Sheet2!A2"); sheet2.cell("A2").comment("Sheet2 comment2", comment_font, "Microsoft Office User"); @@ -337,16 +352,16 @@ public: xlnt_assert_equals(ws1.title(), "Sheet1"); xlnt_assert(ws1.cell("A4").has_hyperlink()); xlnt_assert_equals(ws1.cell("A4").value(), "hyperlink1"); - xlnt_assert_equals(ws1.cell("A4").hyperlink(), "https://microsoft.com/"); + xlnt_assert_equals(ws1.cell("A4").hyperlink().url(), "https://microsoft.com/"); xlnt_assert(ws1.cell("A5").has_hyperlink()); xlnt_assert_equals(ws1.cell("A5").value(), "https://google.com/"); - xlnt_assert_equals(ws1.cell("A5").hyperlink(), "https://google.com/"); - //xlnt_assert(ws1.cell("A6").has_hyperlink()); + xlnt_assert_equals(ws1.cell("A5").hyperlink().url(), "https://google.com/"); + xlnt_assert(ws1.cell("A6").has_hyperlink()); xlnt_assert_equals(ws1.cell("A6").value(), "Sheet1!A1"); - //xlnt_assert_equals(ws1.cell("A6").hyperlink(), "Sheet1!A1"); + xlnt_assert_equals(ws1.cell("A6").hyperlink().target_range(), "Sheet1!A1"); xlnt_assert(ws1.cell("A7").has_hyperlink()); xlnt_assert_equals(ws1.cell("A7").value(), "mailto:invalid@example.com?subject=important"); - xlnt_assert_equals(ws1.cell("A7").hyperlink(), "mailto:invalid@example.com?subject=important"); + xlnt_assert_equals(ws1.cell("A7").hyperlink().url(), "mailto:invalid@example.com?subject=important"); } @@ -472,8 +487,10 @@ public: ws.column_properties("E").width = 15.949776785714286; ws.column_properties("E").custom_width = true; - - wb.save("temp.xlsx"); + + wb.default_slicer_style("SlicerStyleLight1"); + wb.enable_known_fonts(); + xlnt_assert(workbook_matches_file(wb, path_helper::test_file("13_custom_heights_widths.xlsx"))); } diff --git a/tests/worksheet/worksheet_test_suite.hpp b/tests/worksheet/worksheet_test_suite.hpp index 5acd1e23..d3ddb649 100644 --- a/tests/worksheet/worksheet_test_suite.hpp +++ b/tests/worksheet/worksheet_test_suite.hpp @@ -225,11 +225,11 @@ public: xlnt::workbook wb; auto ws = wb.active_sheet(); ws.cell("A1").hyperlink("http://test.com"); - xlnt_assert_equals(ws.cell("A1").hyperlink(), "http://test.com"); + xlnt_assert_equals(ws.cell("A1").hyperlink().url(), "http://test.com"); xlnt_assert_equals(ws.cell("A1").value(), ""); ws.cell("A1").value("test"); xlnt_assert_equals("test", ws.cell("A1").value()); - xlnt_assert_equals(ws.cell("A1").hyperlink(), "http://test.com"); + xlnt_assert_equals(ws.cell("A1").hyperlink().url(), "http://test.com"); } void test_rows()