improved hyperlink implementation

- hyperlinks to cells and ranges are complete
- hyperlink::display is now set as well as the cell value (in excel these can be different)
-- if a cell is empty, display is equal to value text
-- if a cell has a value, display can be just about anything
- This version copies excel in that display is completely ignored once value is set
- All hyperlink tests are now part of the cell test suite (not the worksheet test suite which the majority were previously located)
This commit is contained in:
Crzyrndm 2018-07-02 21:06:12 +12:00
parent 054f509f7a
commit 03020cc793
8 changed files with 144 additions and 95 deletions

View File

@ -262,26 +262,21 @@ public:
/// </summary> /// </summary>
class hyperlink hyperlink() const; class hyperlink hyperlink() const;
/// <summary>
/// Adds a hyperlink to this cell pointing to the URL of the given value.
/// </summary>
void hyperlink(const std::string &url);
/// <summary> /// <summary>
/// Adds a hyperlink to this cell pointing to the URI of the given value and sets /// Adds a hyperlink to this cell pointing to the URI of the given value and sets
/// the text value of the cell to the given parameter. /// the text value of the cell to the given parameter.
/// </summary> /// </summary>
void hyperlink(const std::string &url, const std::string &display); void hyperlink(const std::string &url, const std::string &display = "");
/// <summary> /// <summary>
/// Adds an internal hyperlink to this cell pointing to the given cell. /// Adds an internal hyperlink to this cell pointing to the given cell.
/// </summary> /// </summary>
void hyperlink(xlnt::cell target); void hyperlink(xlnt::cell target, const std::string& display = "");
/// <summary> /// <summary>
/// Adds an internal hyperlink to this cell pointing to the given range. /// Adds an internal hyperlink to this cell pointing to the given range.
/// </summary> /// </summary>
void hyperlink(xlnt::range target); void hyperlink(xlnt::range target, const std::string& display = "");
/// <summary> /// <summary>
/// Returns true if this cell has a hyperlink set. /// Returns true if this cell has a hyperlink set.

View File

@ -44,12 +44,22 @@ class XLNT_API hyperlink
public: public:
bool external() const; bool external() const;
class relationship relationship() const; class relationship relationship() const;
// external target
std::string url() const; std::string url() const;
// internal target
std::string target_range() const; std::string target_range() const;
bool has_display() const;
void display(const std::string &value); void display(const std::string &value);
std::string display() const; const std::string& display() const;
bool has_tooltip() const;
void tooltip(const std::string &value); void tooltip(const std::string &value);
std::string tooltip() const; const std::string& tooltip() const;
bool has_location() const;
void location(const std::string &value);
const std::string& location() const;
private: private:
friend class cell; friend class cell;

View File

@ -372,7 +372,7 @@ hyperlink cell::hyperlink() const
return xlnt::hyperlink(&d_->hyperlink_.get()); return xlnt::hyperlink(&d_->hyperlink_.get());
} }
void cell::hyperlink(const std::string &url) void cell::hyperlink(const std::string &url, const std::string &display)
{ {
if (url.empty() || std::find(url.begin(), url.end(), ':') == url.end()) if (url.empty() || std::find(url.begin(), url.end(), ':') == url.end())
{ {
@ -388,7 +388,8 @@ void cell::hyperlink(const std::string &url)
auto relationships = manifest.relationships(ws.path(), relationship_type::hyperlink); auto relationships = manifest.relationships(ws.path(), relationship_type::hyperlink);
auto relation = std::find_if(relationships.cbegin(), relationships.cend(), auto relation = std::find_if(relationships.cbegin(), relationships.cend(),
[&url](xlnt::relationship rel) { return rel.target().path().string() == url; }); [&url](xlnt::relationship rel) { return rel.target().path().string() == url; });
if (relation != relationships.end()) { if (relation != relationships.end())
{
d_->hyperlink_.get().relationship = *relation; d_->hyperlink_.get().relationship = *relation;
} }
else else
@ -400,29 +401,20 @@ void cell::hyperlink(const std::string &url)
target_mode::external); target_mode::external);
// TODO: make manifest::register_relationship return the created relationship instead of rel id // 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_.get().relationship = manifest.relationship(ws.path(), rel_id);
} }
// if a value is already present, the display string is ignored
if (!has_value()) // hyperlink on an empty cell sets the value to the hyperlink string if (has_value())
{ {
value(url); d_->hyperlink_.get().display.set(to_string());
}
else
{
d_->hyperlink_.get().display.set(display.empty() ? url : display);
value(hyperlink().display());
} }
} }
void cell::hyperlink(const std::string &url, const std::string &display) void cell::hyperlink(xlnt::cell target, const std::string& display)
{
if (!display.empty()) // if the display string isn't empty use that
{
value(display);
}
else // empty display string sets the value to the link text
{
value(url);
}
hyperlink(url);
d_->hyperlink_.get().display = display;
}
void cell::hyperlink(xlnt::cell target)
{ {
// TODO: should this computed value be a method on a cell? // TODO: should this computed value be a method on a cell?
const auto cell_address = target.worksheet().title() + "!" + target.reference().to_string(); const auto cell_address = target.worksheet().title() + "!" + target.reference().to_string();
@ -430,11 +422,19 @@ void cell::hyperlink(xlnt::cell target)
d_->hyperlink_ = detail::hyperlink_impl(); d_->hyperlink_ = detail::hyperlink_impl();
d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink, d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink,
uri(""), uri(cell_address), target_mode::internal); uri(""), uri(cell_address), target_mode::internal);
d_->hyperlink_.get().display = cell_address; // if a value is already present, the display string is ignored
value(cell_address); if (has_value())
{
d_->hyperlink_.get().display.set(to_string());
}
else
{
d_->hyperlink_.get().display.set(display.empty() ? cell_address : display);
value(hyperlink().display());
}
} }
void cell::hyperlink(xlnt::range target) void cell::hyperlink(xlnt::range target, const std::string &display)
{ {
// TODO: should this computed value be a method on a cell? // TODO: should this computed value be a method on a cell?
const auto range_address = target.worksheet().title() + "!" + target.reference().to_string(); const auto range_address = target.worksheet().title() + "!" + target.reference().to_string();
@ -442,8 +442,17 @@ void cell::hyperlink(xlnt::range target)
d_->hyperlink_ = detail::hyperlink_impl(); d_->hyperlink_ = detail::hyperlink_impl();
d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink, d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink,
uri(""), uri(range_address), target_mode::internal); uri(""), uri(range_address), target_mode::internal);
d_->hyperlink_.get().display = range_address;
value(range_address); // if a value is already present, the display string is ignored
if (has_value())
{
d_->hyperlink_.get().display.set(to_string());
}
else
{
d_->hyperlink_.get().display.set(display.empty() ? range_address : display);
value(hyperlink().display());
}
} }
void cell::formula(const std::string &formula) void cell::formula(const std::string &formula)
@ -828,8 +837,11 @@ void cell::value(const std::string &value_string, bool infer_type)
void cell::clear_format() void cell::clear_format()
{ {
format().d_->references -= format().d_->references > 0 ? 1 : 0; if (d_->format_.is_set())
d_->format_.clear(); {
format().d_->references -= format().d_->references > 0 ? 1 : 0;
d_->format_.clear();
}
} }
void cell::clear_style() void cell::clear_style()

View File

@ -67,24 +67,49 @@ bool hyperlink::external() const
return d_->relationship.target_mode() == target_mode::external; return d_->relationship.target_mode() == target_mode::external;
} }
void hyperlink::display(const std::string &value) bool hyperlink::has_display() const
{ {
d_->display = value; return d_->display.is_set();
} }
std::string hyperlink::display() const void hyperlink::display(const std::string &value)
{ {
return d_->display; d_->display.set(value);
}
const std::string& hyperlink::display() const
{
return d_->display.get();
}
bool hyperlink::has_tooltip() const
{
return d_->tooltip.is_set();
} }
void hyperlink::tooltip(const std::string &value) void hyperlink::tooltip(const std::string &value)
{ {
d_->tooltip = value; d_->tooltip.set(value);
} }
std::string hyperlink::tooltip() const const std::string& hyperlink::tooltip() const
{ {
return d_->tooltip; return d_->tooltip.get();
}
bool hyperlink::has_location() const
{
return d_->location.is_set();
}
void hyperlink::location(const std::string &value)
{
d_->location.set(value);
}
const std::string &hyperlink::location() const
{
return d_->location.get();
} }
} // namespace xlnt } // namespace xlnt

View File

@ -26,15 +26,18 @@
#include <string> #include <string>
#include <xlnt/packaging/relationship.hpp> #include <xlnt/packaging/relationship.hpp>
#include <xlnt/utils/optional.hpp>
namespace xlnt { namespace xlnt {
namespace detail { namespace detail {
// [serialised]
struct hyperlink_impl struct hyperlink_impl
{ {
xlnt::relationship relationship; xlnt::relationship relationship;
std::string tooltip; xlnt::optional<std::string> location;
std::string display; xlnt::optional<std::string> tooltip;
xlnt::optional<std::string> display;
}; };
inline bool operator==(const hyperlink_impl &lhs, const hyperlink_impl &rhs) inline bool operator==(const hyperlink_impl &lhs, const hyperlink_impl &rhs)

View File

@ -37,11 +37,13 @@
#include <xlnt/styles/protection.hpp> #include <xlnt/styles/protection.hpp>
#include <xlnt/styles/style.hpp> #include <xlnt/styles/style.hpp>
#include <xlnt/worksheet/worksheet.hpp> #include <xlnt/worksheet/worksheet.hpp>
#include <xlnt/worksheet/range.hpp>
#include <xlnt/utils/date.hpp> #include <xlnt/utils/date.hpp>
#include <xlnt/utils/datetime.hpp> #include <xlnt/utils/datetime.hpp>
#include <xlnt/utils/time.hpp> #include <xlnt/utils/time.hpp>
#include <xlnt/utils/timedelta.hpp> #include <xlnt/utils/timedelta.hpp>
class cell_test_suite : public test_suite class cell_test_suite : public test_suite
{ {
public: public:
@ -665,13 +667,60 @@ private:
{ {
xlnt::workbook wb; xlnt::workbook wb;
auto cell = wb.active_sheet().cell("A1"); auto cell = wb.active_sheet().cell("A1");
cell.value(123);
xlnt_assert(!cell.has_hyperlink()); xlnt_assert(!cell.has_hyperlink());
xlnt_assert_throws(cell.hyperlink(), xlnt::invalid_attribute); xlnt_assert_throws(cell.hyperlink(), xlnt::invalid_attribute);
xlnt_assert_throws(cell.hyperlink("notaurl"), xlnt::invalid_parameter); xlnt_assert_throws(cell.hyperlink("notaurl"), xlnt::invalid_parameter);
xlnt_assert_throws(cell.hyperlink(""), xlnt::invalid_parameter); xlnt_assert_throws(cell.hyperlink(""), xlnt::invalid_parameter);
cell.hyperlink("http://example.com"); // link without optional display
const std::string link1("http://example.com");
cell.hyperlink(link1);
xlnt_assert(cell.has_hyperlink()); xlnt_assert(cell.has_hyperlink());
xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), "http://example.com"); xlnt_assert(cell.hyperlink().external());
xlnt_assert_equals(cell.hyperlink().url(), link1);
xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), link1);
xlnt_assert_equals(cell.hyperlink().display(), link1);
// link with display
const std::string link2("http://example2.com");
const std::string display_txt("notaurl");
cell.hyperlink(link2, display_txt);
xlnt_assert(cell.has_hyperlink());
xlnt_assert(cell.hyperlink().external());
xlnt_assert_equals(cell.hyperlink().url(), link2);
xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), link2);
xlnt_assert_equals(cell.hyperlink().display(), display_txt);
// cell overload without display
const std::string cell_target_str("A2");
auto cell_target = wb.active_sheet().cell(cell_target_str);
std::string link3 = wb.active_sheet().title() + '!' + cell_target_str; // Sheet1!A2
cell.hyperlink(cell_target);
xlnt_assert(cell.has_hyperlink());
xlnt_assert(!cell.hyperlink().external());
xlnt_assert_equals(cell.hyperlink().target_range(), link3);
xlnt_assert_equals(cell.hyperlink().display(), link3);
// cell overload with display
cell.hyperlink(cell_target, display_txt);
xlnt_assert(cell.has_hyperlink());
xlnt_assert(!cell.hyperlink().external());
xlnt_assert_equals(cell.hyperlink().target_range(), link3);
xlnt_assert_equals(cell.hyperlink().display(), display_txt);
// range overload without display
const std::string range_target_str("A2:A5");
xlnt::range range_target(wb.active_sheet(), xlnt::range_reference(range_target_str));
std::string link4 = wb.active_sheet().title() + '!' + range_target_str; // Sheet1!A2:A5
cell.hyperlink(range_target);
xlnt_assert(cell.has_hyperlink());
xlnt_assert(!cell.hyperlink().external());
xlnt_assert_equals(cell.hyperlink().target_range(), link4);
xlnt_assert_equals(cell.hyperlink().display(), link4);
// range overload with display
cell.hyperlink(range_target, display_txt);
xlnt_assert(cell.has_hyperlink());
xlnt_assert(!cell.hyperlink().external());
xlnt_assert_equals(cell.hyperlink().target_range(), link4);
xlnt_assert_equals(cell.hyperlink().display(), display_txt);
// value not touched
xlnt_assert_equals(cell.value<int>(), 123);
} }
void test_comment() void test_comment()

View File

@ -575,7 +575,7 @@ public:
std::vector<std::uint8_t> destination; std::vector<std::uint8_t> destination;
source_workbook.save(destination); source_workbook.save(destination);
source_workbook.save("temp.xlsx"); source_workbook.save("temp" + source.filename());
#ifdef _MSC_VER #ifdef _MSC_VER
std::ifstream source_stream(source.wstring(), std::ios::binary); std::ifstream source_stream(source.wstring(), std::ios::binary);

View File

@ -50,7 +50,6 @@ public:
register_test(test_remove_named_range_bad); register_test(test_remove_named_range_bad);
register_test(test_cell_alternate_coordinates); register_test(test_cell_alternate_coordinates);
register_test(test_cell_range_name); register_test(test_cell_range_name);
register_test(test_hyperlink_value);
register_test(test_rows); register_test(test_rows);
register_test(test_no_rows); register_test(test_no_rows);
register_test(test_no_cols); register_test(test_no_cols);
@ -225,50 +224,6 @@ public:
xlnt_assert(c_range_coord[0][0] == c_cell); xlnt_assert(c_range_coord[0][0] == c_cell);
} }
void test_hyperlink_value()
{
xlnt::workbook wb;
auto ws = wb.active_sheet();
std::string test_link = "http://test.com";
xlnt::cell_reference test_cell("A1");
ws.cell(test_cell).hyperlink(test_link);
// when a hyperlink is added to an empty cell, the display text becomes == to the link
xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link);
xlnt_assert_equals(ws.cell(test_cell).value<std::string>(), test_link);
// if the display value changes, the hyperlink remains the same
std::string test_string = "test";
ws.cell(test_cell).value(test_string);
xlnt_assert_equals(test_string, ws.cell(test_cell).value<std::string>());
xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link);
// changing the link doesn't change the cell value
std::string test_link2 = "http://test-123.com";
ws.cell(test_cell).hyperlink(test_link2);
xlnt_assert_equals(test_string, ws.cell(test_cell).value<std::string>());
xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link2);
// and we can edit both value and hyperlink together
std::string test_string3 = "123test";
std::string test_link3 = "http://123-test.com";
ws.cell(test_cell).hyperlink(test_link3, test_string3);
xlnt_assert_equals(test_string3, ws.cell(test_cell).value<std::string>());
xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link3);
// hyperlinks can also be applied to cells with non-string values
int numeric_test_val = 123;
std::string test_link4 = "http://test-numeric.com";
xlnt::cell_reference numeric_test_cell("B1");
ws.cell(numeric_test_cell).value(numeric_test_val);
ws.cell(numeric_test_cell).hyperlink(test_link4);
xlnt_assert_equals(ws.cell(numeric_test_cell).hyperlink().url(), test_link4);
xlnt_assert_equals(ws.cell(numeric_test_cell).value<int>(), numeric_test_val);
// and there should be no issues if two cells use the same hyperlink
ws.cell(numeric_test_cell).hyperlink(test_link3); // still in use on 'A1'
// 'A1'
xlnt_assert_equals(test_string3, ws.cell(test_cell).value<std::string>());
xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link3);
// 'B1'
xlnt_assert_equals(ws.cell(numeric_test_cell).hyperlink().url(), test_link3);
xlnt_assert_equals(ws.cell(numeric_test_cell).value<int>(), numeric_test_val);
}
void test_rows() void test_rows()
{ {
xlnt::workbook wb; xlnt::workbook wb;