diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index 18161adb..c8cd953e 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -44,8 +44,17 @@ namespace detail { struct cell_impl; } // namespace detail -typedef std::string comment; - +class comment +{ +public: + comment(const std::string &type, const std::string &value) : type_(type), value_(value) {} + std::string get_type() const { return type_; } + std::string get_value() const { return value_; } +private: + std::string type_; + std::string value_; +}; + /// /// Describes cell associated properties. /// @@ -116,7 +125,8 @@ public: //std::pair get_anchor() const; comment get_comment() const; - void set_comment(comment comment); + void set_comment(const comment &comment); + void clear_comment(); std::string get_formula() const; void set_formula(const std::string &formula); @@ -126,6 +136,10 @@ public: void set_null(); + cell offset(row_t row, column_t column); + + worksheet get_parent(); + cell &operator=(const cell &rhs); cell &operator=(bool value); cell &operator=(int value); diff --git a/include/xlnt/common/relationship.hpp b/include/xlnt/common/relationship.hpp index 9d498b02..06112f45 100644 --- a/include/xlnt/common/relationship.hpp +++ b/include/xlnt/common/relationship.hpp @@ -47,9 +47,78 @@ enum class target_mode /// class relationship { -public: +public: + enum class type + { + invalid, + hyperlink, + drawing, + worksheet, + shared_strings, + styles, + theme, + extended_properties, + core_properties, + office_document + }; + + static type type_from_string(const std::string &type_string) + { + if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties") + { + return type::extended_properties; + } + else if(type_string == "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties") + { + return type::core_properties; + } + else if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument") + { + return type::office_document; + } + else if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet") + { + return type::worksheet; + } + else if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings") + { + return type::shared_strings; + } + else if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles") + { + return type::styles; + } + else if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") + { + return type::theme; + } + else if(type_string == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink") + { + return type::hyperlink; + } + + return type::invalid; + } + + static std::string type_to_string(type t) + { + switch(t) + { + case type::extended_properties: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties"; + case type::core_properties: return "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties"; + case type::office_document: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"; + case type::worksheet: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"; + case type::shared_strings: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"; + case type::styles: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"; + case type::theme: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme"; + case type::hyperlink: return "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"; + default: return "??"; + } + } + relationship(); - relationship(const std::string &type, const std::string &r_id = "", const std::string &target_uri = ""); + relationship(const std::string &t, const std::string &r_id = "", const std::string &target_uri = "") : relationship(type_from_string(t), r_id, target_uri) {} + relationship(type t, const std::string &r_id = "", const std::string &target_uri = ""); /// /// gets a string that identifies the relationship. @@ -71,20 +140,10 @@ public: /// std::string get_target_uri() const { return target_uri_; } -private: - relationship(const std::string &id, const std::string &relationship_type_, const std::string &source_uri, target_mode target_mode, const std::string &target_uri); - //relationship &operator=(const relationship &rhs) = delete; + type get_type() const { return type_; } + std::string get_type_string() const { return type_to_string(type_); } - enum class type - { - hyperlink, - drawing, - worksheet, - sharedStrings, - styles, - theme - }; - +private: type type_; std::string id_; std::string source_uri_; diff --git a/include/xlnt/reader/reader.hpp b/include/xlnt/reader/reader.hpp index f4aaff82..2cc38486 100644 --- a/include/xlnt/reader/reader.hpp +++ b/include/xlnt/reader/reader.hpp @@ -30,6 +30,7 @@ namespace xlnt { +class relationship; class workbook; class worksheet; class document_properties; @@ -38,10 +39,9 @@ class zip_file; class reader { public: - static std::unordered_map> read_relationships(const std::string &content); + static std::vector read_relationships(const std::string &content); static std::pair, std::unordered_map> read_content_types(const std::string &content); - static std::string determine_document_type(const std::unordered_map> &root_relationships, - const std::unordered_map &override_types); + static std::string determine_document_type(const std::unordered_map &override_types); static worksheet read_worksheet(std::istream &handle, workbook &wb, const std::string &title, const std::vector &string_table); static void read_worksheet(worksheet ws, const std::string &xml_string, const std::vector &string_table, const std::vector &number_format_ids); static std::vector read_shared_string(const std::string &xml_string); diff --git a/include/xlnt/workbook/workbook.hpp b/include/xlnt/workbook/workbook.hpp index 9cd83647..3d8f43a8 100644 --- a/include/xlnt/workbook/workbook.hpp +++ b/include/xlnt/workbook/workbook.hpp @@ -29,6 +29,8 @@ #include #include +#include "../common/relationship.hpp" + namespace xlnt { class drawing; @@ -159,7 +161,7 @@ public: std::vector get_content_types() const; - void create_relationship(const std::string &id, const std::string &target, const std::string &type); + void create_relationship(const std::string &id, const std::string &target, relationship::type type); relationship get_relationship(const std::string &id) const; std::vector get_relationships() const; diff --git a/include/xlnt/worksheet/worksheet.hpp b/include/xlnt/worksheet/worksheet.hpp index 3598a3c4..70262a4a 100644 --- a/include/xlnt/worksheet/worksheet.hpp +++ b/include/xlnt/worksheet/worksheet.hpp @@ -30,11 +30,13 @@ #include #include "../common/types.hpp" +#include "../common/relationship.hpp" namespace xlnt { class cell; class cell_reference; +class comment; class range; class range_reference; class relationship; @@ -203,7 +205,7 @@ public: range_reference calculate_dimension() const; // relationships - relationship create_relationship(const std::string &relationship_type, const std::string &target_uri); + relationship create_relationship(relationship::type type, const std::string &target_uri); std::vector get_relationships(); // charts @@ -244,6 +246,11 @@ public: void auto_filter(const range_reference &reference); void unset_auto_filter(); bool has_auto_filter() const; + + // comments + void add_comment(const comment &c); + void remove_comment(const comment &c); + std::size_t get_comment_count() const; void reserve(std::size_t n); diff --git a/include/xlnt/xlnt.hpp b/include/xlnt/xlnt.hpp index 31d0bef7..01b1df1c 100644 --- a/include/xlnt/xlnt.hpp +++ b/include/xlnt/xlnt.hpp @@ -23,6 +23,8 @@ // @author: see AUTHORS file #pragma once +#include + const std::string xlnt_version = "0.1.0"; const std::string author = "Thomas Fussell"; diff --git a/source/cell.cpp b/source/cell.cpp index e62d0cbb..5c8820f7 100644 --- a/source/cell.cpp +++ b/source/cell.cpp @@ -560,7 +560,7 @@ void cell::set_hyperlink(const std::string &hyperlink) } d_->has_hyperlink_ = true; - d_->hyperlink_ = worksheet(d_->parent_).create_relationship("hyperlink", hyperlink); + d_->hyperlink_ = worksheet(d_->parent_).create_relationship(relationship::type::hyperlink, hyperlink); if(d_->type_ == type::null) { @@ -584,6 +584,26 @@ void cell::set_formula(const std::string &formula) d_->string_value = formula; } +void cell::set_comment(const xlnt::comment &comment) +{ + if(d_->comment_.get_value() == "") + { + get_parent().add_comment(comment); + } + + d_->comment_ = comment; +} + +void cell::clear_comment() +{ + if(d_->comment_.get_value() != "") + { + get_parent().remove_comment(d_->comment_); + } + + d_->comment_ = comment("", ""); +} + void cell::set_error(const std::string &error) { if(error.length() == 0 || error[0] != '#') @@ -595,4 +615,19 @@ void cell::set_error(const std::string &error) d_->string_value = error; } +cell cell::offset(column_t column, row_t row) +{ + return get_parent().get_cell(cell_reference(d_->column + column, d_->row + row)); +} + +worksheet cell::get_parent() +{ + return worksheet(d_->parent_); +} + +comment cell::get_comment() const +{ + return d_->comment_; +} + } // namespace xlnt diff --git a/source/detail/cell_impl.cpp b/source/detail/cell_impl.cpp index 1d7299e5..b4454ba6 100644 --- a/source/detail/cell_impl.cpp +++ b/source/detail/cell_impl.cpp @@ -4,15 +4,15 @@ namespace xlnt { namespace detail { - cell_impl::cell_impl() : parent_(nullptr), type_(cell::type::null), column(0), row(0), style_(nullptr), merged(false), has_hyperlink_(false) + cell_impl::cell_impl() : parent_(nullptr), type_(cell::type::null), column(0), row(0), style_(nullptr), merged(false), has_hyperlink_(false), comment_("", "") { } -cell_impl::cell_impl(worksheet_impl *parent, int column_index, int row_index) : parent_(parent), type_(cell::type::null), column(column_index), row(row_index), style_(nullptr), merged(false), has_hyperlink_(false) +cell_impl::cell_impl(worksheet_impl *parent, int column_index, int row_index) : parent_(parent), type_(cell::type::null), column(column_index), row(row_index), style_(nullptr), merged(false), has_hyperlink_(false), comment_("", "") { } -cell_impl::cell_impl(const cell_impl &rhs) +cell_impl::cell_impl(const cell_impl &rhs) : comment_("", "") { *this = rhs; } diff --git a/source/detail/cell_impl.hpp b/source/detail/cell_impl.hpp index ce075e9e..755324ca 100644 --- a/source/detail/cell_impl.hpp +++ b/source/detail/cell_impl.hpp @@ -30,6 +30,7 @@ struct cell_impl bool merged; bool is_date_; bool has_hyperlink_; + comment comment_; }; } // namespace detail diff --git a/source/detail/worksheet_impl.hpp b/source/detail/worksheet_impl.hpp index 2d76464e..58a1779f 100644 --- a/source/detail/worksheet_impl.hpp +++ b/source/detail/worksheet_impl.hpp @@ -60,6 +60,7 @@ struct worksheet_impl margins page_margins_; std::vector merged_cells_; std::unordered_map named_ranges_; + std::vector comments_; }; } // namespace detail diff --git a/source/reader.cpp b/source/reader.cpp index d255ff5d..0837c581 100644 --- a/source/reader.cpp +++ b/source/reader.cpp @@ -8,6 +8,7 @@ #include "workbook/workbook.hpp" #include "worksheet/worksheet.hpp" #include "workbook/document_properties.hpp" +#include "common/relationship.hpp" #include "common/zip_file.hpp" namespace xlnt { @@ -85,21 +86,21 @@ std::string reader::read_dimension(const std::string &xml_string) return dimension; } -std::unordered_map> reader::read_relationships(const std::string &content) +std::vector reader::read_relationships(const std::string &content) { pugi::xml_document doc; doc.load(content.c_str()); auto root_node = doc.child("Relationships"); - std::unordered_map> relationships; + std::vector relationships; for(auto relationship : root_node.children("Relationship")) { std::string id = relationship.attribute("Id").as_string(); std::string type = relationship.attribute("Type").as_string(); std::string target = relationship.attribute("Target").as_string(); - relationships[id] = std::make_pair(target, type); + relationships.push_back(xlnt::relationship(type, id, target)); } return relationships; @@ -129,24 +130,13 @@ std::pair, std::unordered_map> &root_relationships, - const std::unordered_map &override_types) +std::string reader::determine_document_type(const std::unordered_map &override_types) { - auto relationship_match = std::find_if(root_relationships.begin(), root_relationships.end(), - [](const std::pair> &v) - { return v.second.second == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"; }); std::string type; - if(relationship_match != root_relationships.end()) + if(override_types.find("/xl/workbook.xml") != override_types.end()) { - std::string office_document_relationship = relationship_match->second.first; - - if(office_document_relationship[0] != '/') - { - office_document_relationship = std::string("/") + office_document_relationship; - } - - type = override_types.at(office_document_relationship); + type = override_types.at("/xl/workbook.xml"); } if(type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml") @@ -214,12 +204,7 @@ void read_worksheet_common(worksheet ws, const pugi::xml_node &root_node, const else if(cell_node.attribute("s") != nullptr) { auto style_index = cell_node.attribute("s").as_int(); - auto number_format_id = number_format_ids[style_index]; - - if(style_index < 0 || style_index >= number_format_ids.size()) - { - throw std::out_of_range(std::to_string(style_index)); - } + auto number_format_id = number_format_ids.at(style_index); if(number_format_id == 0) // integer { diff --git a/source/relationship.cpp b/source/relationship.cpp index 3f006362..8786c571 100644 --- a/source/relationship.cpp +++ b/source/relationship.cpp @@ -2,16 +2,15 @@ namespace xlnt { -relationship::relationship(const std::string &t, const std::string &r_id, const std::string &target_uri) : id_(r_id), source_uri_(""), target_uri_(target_uri) +relationship::relationship(type t, const std::string &r_id, const std::string &target_uri) : type_(t), id_(r_id), source_uri_(""), target_uri_(target_uri) { - if(t == "hyperlink") + if(t == type::hyperlink) { - type_ = type::hyperlink; target_mode_ = target_mode::external; } } -relationship::relationship() : id_(""), source_uri_(""), target_uri_("") +relationship::relationship() : type_(type::invalid), id_(""), source_uri_(""), target_uri_("") { } diff --git a/source/workbook.cpp b/source/workbook.cpp index 82db0986..c9aaa44c 100644 --- a/source/workbook.cpp +++ b/source/workbook.cpp @@ -52,6 +52,9 @@ workbook_impl::workbook_impl() : active_sheet_index_(0), date_1904_(false) workbook::workbook() : d_(new detail::workbook_impl()) { create_sheet("Sheet"); + create_relationship("rId2", "sharedStrings.xml", relationship::type::shared_strings); + create_relationship("rId3", "styles.xml", relationship::type::styles); + create_relationship("rId4", "theme/theme1.xml", relationship::type::theme); } workbook::~workbook() @@ -176,6 +179,7 @@ worksheet workbook::create_sheet() } d_->worksheets_.push_back(detail::worksheet_impl(this, title)); + create_relationship("rId" + std::to_string(d_->relationships_.size() + 1), "worksheets/sheet" + std::to_string(d_->worksheets_.size()) + ".xml", relationship::type::worksheet); return worksheet(&d_->worksheets_.back()); } @@ -284,21 +288,22 @@ bool workbook::load(const std::string &filename) zip_file f(filename, file_mode::open); //auto core_properties = read_core_properties(); //auto app_properties = read_app_properties(); - auto root_relationships = reader::read_relationships(f.get_file_contents("_rels/.rels")); auto content_types = reader::read_content_types(f.get_file_contents("[Content_Types].xml")); - auto type = reader::determine_document_type(root_relationships, content_types.second); + auto type = reader::determine_document_type(content_types.second); if(type != "excel") { throw std::runtime_error("unsupported document type: " + filename); } + clear(); + auto workbook_relationships = reader::read_relationships(f.get_file_contents("xl/_rels/workbook.xml.rels")); for(auto relationship : workbook_relationships) { - create_relationship(relationship.first, relationship.second.first, relationship.second.second); + create_relationship(relationship.get_id(), relationship.get_target_uri(), relationship.get_type()); } pugi::xml_document doc; @@ -311,24 +316,25 @@ bool workbook::load(const std::string &filename) auto sheets_node = root_node.child("sheets"); - clear(); - std::vector shared_strings; if(f.has_file("xl/sharedStrings.xml")) { shared_strings = xlnt::reader::read_shared_string(f.get_file_contents("xl/sharedStrings.xml")); } - pugi::xml_document styles_doc; - styles_doc.load(f.get_file_contents("xl/styles.xml").c_str()); - auto stylesheet_node = styles_doc.child("styleSheet"); - auto cell_xfs_node = stylesheet_node.child("cellXfs"); - std::vector number_format_ids; - - for(auto xf_node : cell_xfs_node.children("xf")) + + if(f.has_file("xl/styles.xml")) { - number_format_ids.push_back(xf_node.attribute("numFmtId").as_int()); + pugi::xml_document styles_doc; + styles_doc.load(f.get_file_contents("xl/styles.xml").c_str()); + auto stylesheet_node = styles_doc.child("styleSheet"); + auto cell_xfs_node = stylesheet_node.child("cellXfs"); + + for(auto xf_node : cell_xfs_node.children("xf")) + { + number_format_ids.push_back(xf_node.attribute("numFmtId").as_int()); + } } for(auto sheet_node : sheets_node.children("sheet")) @@ -343,7 +349,7 @@ bool workbook::load(const std::string &filename) return true; } -void workbook::create_relationship(const std::string &id, const std::string &target, const std::string &type) +void workbook::create_relationship(const std::string &id, const std::string &target, relationship::type type) { d_->relationships_.push_back(relationship(type, id, target)); } @@ -468,6 +474,10 @@ worksheet workbook::operator[](std::size_t index) void workbook::clear() { d_->worksheets_.clear(); + d_->relationships_.clear(); + d_->active_sheet_index_ = 0; + d_->date_1904_ = false; + d_->drawings_.clear(); } bool workbook::save(std::vector &data) @@ -495,6 +505,16 @@ bool workbook::save(const std::string &filename) f.set_file_contents("xl/_rels/workbook.xml.rels", writer::write_workbook_rels(*this)); f.set_file_contents("xl/workbook.xml", writer::write_workbook(*this)); + + for(auto relationship : d_->relationships_) + { + if(relationship.get_type() == relationship::type::worksheet) + { + std::string sheet_index_string = relationship.get_target_uri().substr(16); + std::size_t sheet_index = std::stoi(sheet_index_string.substr(0, sheet_index_string.find('.'))) - 1; + f.set_file_contents("xl/" + relationship.get_target_uri(), writer::write_worksheet(get_sheet_by_index(sheet_index))); + } + } return true; } @@ -514,7 +534,7 @@ std::vector xlnt::workbook::get_content_types() const std::vector content_types; content_types.push_back({ true, "xml", "", "application/xml" }); content_types.push_back({ true, "rels", "", "application/vnd.openxmlformats-package.relationships+xml" }); - content_types.push_back({ false, "", "xl/workbook.xml", "applications/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" }); + content_types.push_back({ false, "", "/xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" }); return content_types; } diff --git a/source/worksheet.cpp b/source/worksheet.cpp index 8d67af2b..c82bcf72 100644 --- a/source/worksheet.cpp +++ b/source/worksheet.cpp @@ -299,10 +299,10 @@ std::vector worksheet::get_relationships() return d_->relationships_; } -relationship worksheet::create_relationship(const std::string &relationship_type, const std::string &target_uri) +relationship worksheet::create_relationship(relationship::type type, const std::string &target_uri) { std::string r_id = "rId" + std::to_string(d_->relationships_.size() + 1); - d_->relationships_.push_back(relationship(relationship_type, r_id, target_uri)); + d_->relationships_.push_back(relationship(type, r_id, target_uri)); return d_->relationships_.back(); } @@ -496,4 +496,19 @@ void worksheet::reserve(std::size_t n) d_->cell_map_.reserve(n); } +void worksheet::add_comment(const xlnt::comment &c) +{ + d_->comments_.push_back(c); +} + +void worksheet::remove_comment(const xlnt::comment &c) +{ + d_->comments_.pop_back(); +} + +std::size_t worksheet::get_comment_count() const +{ + return d_->comments_.size(); +} + } // namespace xlnt diff --git a/source/writer.cpp b/source/writer.cpp index fe5a9a46..697d1f14 100644 --- a/source/writer.cpp +++ b/source/writer.cpp @@ -162,25 +162,30 @@ std::string writer::write_workbook(const workbook &wb) auto sheets_node = root_node.append_child("sheets"); auto defined_names_node = root_node.append_child("definedNames"); - int i = 0; - for(auto ws : wb) + for(auto relationship : wb.get_relationships()) { - auto sheet_node = sheets_node.append_child("sheet"); - sheet_node.append_attribute("name").set_value(ws.get_title().c_str()); - sheet_node.append_attribute("r:id").set_value((std::string("rId") + std::to_string(i + 1)).c_str()); - sheet_node.append_attribute("sheetId").set_value(std::to_string(i + 1).c_str()); - - if(ws.has_auto_filter()) + if(relationship.get_type() == relationship::type::worksheet) { - auto defined_name_node = defined_names_node.append_child("definedName"); - defined_name_node.append_attribute("name").set_value("_xlnm._FilterDatabase"); - defined_name_node.append_attribute("hidden").set_value(1); - defined_name_node.append_attribute("localSheetId").set_value(0); - std::string name = "'" + ws.get_title() + "'!" + range_reference::make_absolute(ws.get_auto_filter()).to_string(); - defined_name_node.text().set(name.c_str()); - } + std::string sheet_index_string = relationship.get_target_uri().substr(16); + std::size_t sheet_index = std::stoi(sheet_index_string.substr(0, sheet_index_string.find('.'))) - 1; + + auto ws = wb.get_sheet_by_index(sheet_index); + + auto sheet_node = sheets_node.append_child("sheet"); + sheet_node.append_attribute("name").set_value(ws.get_title().c_str()); + sheet_node.append_attribute("r:id").set_value(relationship.get_id().c_str()); + sheet_node.append_attribute("sheetId").set_value(std::to_string(sheet_index + 1).c_str()); - i++; + if(ws.has_auto_filter()) + { + auto defined_name_node = defined_names_node.append_child("definedName"); + defined_name_node.append_attribute("name").set_value("_xlnm._FilterDatabase"); + defined_name_node.append_attribute("hidden").set_value(1); + defined_name_node.append_attribute("localSheetId").set_value(0); + std::string name = "'" + ws.get_title() + "'!" + range_reference::make_absolute(ws.get_auto_filter()).to_string(); + defined_name_node.text().set(name.c_str()); + } + } } auto calc_pr_node = root_node.append_child("calcPr"); @@ -507,9 +512,9 @@ std::string xlnt::writer::write_root_rels() { std::vector relationships; - relationships.push_back(relationship("a")); - relationships.push_back(relationship("b")); - relationships.push_back(relationship("c")); + relationships.push_back(relationship(relationship::type::extended_properties, "rId3", "docProps/app.xml")); + relationships.push_back(relationship(relationship::type::core_properties, "rId2", "docProps/core.xml")); + relationships.push_back(relationship(relationship::type::office_document, "rId1", "xl/workbook.xml")); return write_relationships(relationships); } @@ -525,7 +530,11 @@ std::string writer::write_relationships(const std::vector &relatio auto app_props_node = root_node.append_child("Relationship"); app_props_node.append_attribute("Id").set_value(relationship.get_id().c_str()); app_props_node.append_attribute("Target").set_value(relationship.get_target_uri().c_str()); - app_props_node.append_attribute("Type").set_value(relationship.get_target_uri().c_str()); + app_props_node.append_attribute("Type").set_value(relationship.get_type_string().c_str()); + if(relationship.get_target_mode() == target_mode::external) + { + app_props_node.append_attribute("TargetMode").set_value("External"); + } } std::stringstream ss; diff --git a/tests/helpers/helper.hpp b/tests/helpers/helper.hpp index a0496887..74c55076 100644 --- a/tests/helpers/helper.hpp +++ b/tests/helpers/helper.hpp @@ -8,25 +8,116 @@ class Helper { public: - static bool EqualsFileContent(const std::string &reference_file, const std::string &fixture) + enum class difference_type { - std::string fixture_content; - - if(false)//PathHelper::FileExists(fixture)) + names_differ, + missing_attribute, + attribute_values_differ, + missing_text, + text_values_differ, + missing_child, + child_order_differs, + equivalent, + }; + + struct comparison_result + { + difference_type difference; + std::string value_left; + std::string value_right; + operator bool() const { return difference == difference_type::equivalent; } + }; + + static comparison_result compare_xml(const pugi::xml_node &left, const pugi::xml_node &right) + { + std::string left_temp = left.name(); + std::string right_temp = right.name(); + + if(left_temp != right_temp) { - std::fstream fixture_file; - fixture_file.open(fixture); - std::stringstream ss; - ss << fixture_file.rdbuf(); - fixture_content = ss.str(); + return {difference_type::names_differ, left_temp, right_temp}; } - else + + for(auto left_attribute : left.attributes()) { - fixture_content = fixture; + left_temp = left_attribute.name(); + auto right_attribute = right.attribute(left_attribute.name()); + + if(right_attribute == nullptr) + { + return {difference_type::missing_attribute, left_temp, "((empty))"}; + } + + left_temp = left_attribute.value(); + right_temp = right_attribute.value(); + + if(left_temp != right_temp) + { + return {difference_type::attribute_values_differ, left_temp, right_temp}; + } } - + + if(left.text() != nullptr) + { + left_temp = left.text().as_string(); + + if(right.text() == nullptr) + { + return {difference_type::missing_text, left_temp, "((empty))"}; + } + + right_temp = right.text().as_string(); + + if(left_temp != right_temp) + { + return {difference_type::text_values_differ, left_temp, right_temp}; + } + } + else if(right.text() != nullptr) + { + right_temp = right.text().as_string(); + return {difference_type::text_values_differ, "((empty))", right_temp}; + } + + auto right_child_iter = right.children().begin(); + for(auto left_child : left.children()) + { + left_temp = left_child.name(); + + if(right_child_iter == right.children().end()) + { + return {difference_type::child_order_differs, left_temp, "((end))"}; + } + + auto right_child = *right_child_iter; + right_child_iter++; + + if(left_child.type() == pugi::xml_node_type::node_cdata || left_child.type() == pugi::xml_node_type::node_pcdata) + { + continue; // ignore text children, these have already been compared + } + + auto child_comparison_result = compare_xml(left_child, right_child); + + if(!child_comparison_result) + { + return child_comparison_result; + } + } + + if(right_child_iter != right.children().end()) + { + right_temp = right_child_iter->name(); + return {difference_type::child_order_differs, "((end))", right_temp}; + } + + return {difference_type::equivalent, "", ""}; + } + + static bool EqualsFileContent(const std::string &reference_file, const std::string &observed_content) + { std::string expected_content; - + if(PathHelper::FileExists(reference_file)) { std::fstream file; @@ -39,23 +130,13 @@ public: { throw std::runtime_error("file not found"); } - - { - pugi::xml_document doc; - doc.load(fixture_content.c_str()); - std::stringstream ss; - doc.save(ss); - fixture_content = ss.str(); - } - - { - pugi::xml_document doc; - doc.load(expected_content.c_str()); - std::stringstream ss; - doc.save(ss); - expected_content = ss.str(); - } - - return expected_content == fixture_content; + + pugi::xml_document doc_observed; + doc_observed.load(observed_content.c_str()); + + pugi::xml_document doc_expected; + doc_expected.load(expected_content.c_str()); + + return compare_xml(doc_expected, doc_observed); } }; diff --git a/tests/runner-autogen.cpp b/tests/runner-autogen.cpp index 83e3ae57..8ef6a19c 100644 --- a/tests/runner-autogen.cpp +++ b/tests/runner-autogen.cpp @@ -21,7 +21,7 @@ int main( int argc, char *argv[] ) { return status; } bool suite_test_cell_init = false; -#include "c:\Users\taf656\Development\xlnt\tests\test_cell.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_cell.hpp" static test_cell suite_test_cell; @@ -238,7 +238,25 @@ public: void runTest() { suite_test_cell.test_is_not_date_color_format(); } } testDescription_suite_test_cell_test_is_not_date_color_format; -#include "c:\Users\taf656\Development\xlnt\tests\test_chart.hpp" +static class TestDescription_suite_test_cell_test_comment_count : public CxxTest::RealTestDescription { +public: + TestDescription_suite_test_cell_test_comment_count() : CxxTest::RealTestDescription( Tests_test_cell, suiteDescription_test_cell, 394, "test_comment_count" ) {} + void runTest() { suite_test_cell.test_comment_count(); } +} testDescription_suite_test_cell_test_comment_count; + +static class TestDescription_suite_test_cell_test_comment_assignment : public CxxTest::RealTestDescription { +public: + TestDescription_suite_test_cell_test_comment_assignment() : CxxTest::RealTestDescription( Tests_test_cell, suiteDescription_test_cell, 410, "test_comment_assignment" ) {} + void runTest() { suite_test_cell.test_comment_assignment(); } +} testDescription_suite_test_cell_test_comment_assignment; + +static class TestDescription_suite_test_cell_test_cell_offset : public CxxTest::RealTestDescription { +public: + TestDescription_suite_test_cell_test_cell_offset() : CxxTest::RealTestDescription( Tests_test_cell, suiteDescription_test_cell, 424, "test_cell_offset" ) {} + void runTest() { suite_test_cell.test_cell_offset(); } +} testDescription_suite_test_cell_test_cell_offset; + +#include "/Users/thomas/Development/xlnt/tests/test_chart.hpp" static test_chart suite_test_chart; @@ -329,7 +347,7 @@ public: void runTest() { suite_test_chart.test_write_chart_scatter(); } } testDescription_suite_test_chart_test_write_chart_scatter; -#include "c:\Users\taf656\Development\xlnt\tests\test_named_range.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_named_range.hpp" static test_named_range suite_test_named_range; @@ -420,7 +438,7 @@ public: void runTest() { suite_test_named_range.test_can_be_saved(); } } testDescription_suite_test_named_range_test_can_be_saved; -#include "c:\Users\taf656\Development\xlnt\tests\test_number_format.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_number_format.hpp" static test_number_format suite_test_number_format; @@ -523,7 +541,7 @@ public: void runTest() { suite_test_number_format.test_mac_date(); } } testDescription_suite_test_number_format_test_mac_date; -#include "c:\Users\taf656\Development\xlnt\tests\test_props.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_props.hpp" static test_props suite_test_props; @@ -566,7 +584,7 @@ public: void runTest() { suite_test_props.test_write_properties_app(); } } testDescription_suite_test_props_test_write_properties_app; -#include "c:\Users\taf656\Development\xlnt\tests\test_read.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_read.hpp" static test_read suite_test_read; @@ -699,7 +717,7 @@ public: void runTest() { suite_test_read.test_read_date_value(); } } testDescription_suite_test_read_test_read_date_value; -#include "c:\Users\taf656\Development\xlnt\tests\test_strings.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_strings.hpp" static test_strings suite_test_strings; @@ -730,7 +748,7 @@ public: void runTest() { suite_test_strings.test_formatted_string_table(); } } testDescription_suite_test_strings_test_formatted_string_table; -#include "c:\Users\taf656\Development\xlnt\tests\test_style.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_style.hpp" static test_style suite_test_style; @@ -827,7 +845,7 @@ public: void runTest() { suite_test_style.test_read_cell_style(); } } testDescription_suite_test_style_test_read_cell_style; -#include "c:\Users\taf656\Development\xlnt\tests\test_theme.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_theme.hpp" static test_theme suite_test_theme; @@ -840,7 +858,7 @@ public: void runTest() { suite_test_theme.test_write_theme(); } } testDescription_suite_test_theme_test_write_theme; -#include "c:\Users\taf656\Development\xlnt\tests\test_workbook.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_workbook.hpp" static test_workbook suite_test_workbook; @@ -961,7 +979,7 @@ public: void runTest() { suite_test_workbook.test_write_regular_float(); } } testDescription_suite_test_workbook_test_write_regular_float; -#include "c:\Users\taf656\Development\xlnt\tests\test_worksheet.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_worksheet.hpp" static test_worksheet suite_test_worksheet; @@ -1130,7 +1148,7 @@ public: void runTest() { suite_test_worksheet.test_printer_settings(); } } testDescription_suite_test_worksheet_test_printer_settings; -#include "c:\Users\taf656\Development\xlnt\tests\test_write.hpp" +#include "/Users/thomas/Development/xlnt/tests/test_write.hpp" static test_write suite_test_write; diff --git a/tests/test_cell.hpp b/tests/test_cell.hpp index 4fcc248c..e148cc36 100644 --- a/tests/test_cell.hpp +++ b/tests/test_cell.hpp @@ -70,13 +70,13 @@ public: void test_bad_column_index() { - for(auto bad_string : {"JJJJ", "", "$", "1"}) + for(auto bad_string : {"JJJJ", "", "$", "1"}) { TS_ASSERT_THROWS(xlnt::cell_reference::column_index_from_string(bad_string), xlnt::column_string_index_exception); } } - void test_column_letter_boundries() + void test_column_letter_boundaries() { TS_ASSERT_THROWS(xlnt::cell_reference::column_string_from_index(0), xlnt::column_string_index_exception); @@ -336,6 +336,16 @@ public: TS_ASSERT_EQUALS(xlnt::cell::type::numeric, cell.get_data_type()); TS_ASSERT_EQUALS(cell, xlnt::time(3, 40)); } + + void test_timedelta() + { + xlnt::worksheet ws = wb.create_sheet(); + xlnt::cell cell(ws, "A1"); + + cell = xlnt::timedelta().days(1).hours(3); + TS_ASSERT_EQUALS(cell, 1.125); + TS_ASSERT_EQUALS(cell.get_data_type(), xlnt::cell::type::numeric); + } void test_date_format_on_non_date() { @@ -390,6 +400,42 @@ public: TS_ASSERT(!cell.is_date()); } + + void test_comment_count() + { + xlnt::worksheet ws = wb.create_sheet(); + xlnt::cell cell(ws, "A1"); + + TS_ASSERT(ws.get_comment_count() == 0); + cell.set_comment(xlnt::comment("text", "author")); + TS_ASSERT(ws.get_comment_count() == 1); + cell.set_comment(xlnt::comment("text", "author")); + TS_ASSERT(ws.get_comment_count() == 1); + cell.clear_comment(); + TS_ASSERT(ws.get_comment_count() == 0); + cell.clear_comment(); + TS_ASSERT(ws.get_comment_count() == 0); + } + + void test_comment_assignment() + { + xlnt::worksheet ws = wb.create_sheet(); + xlnt::cell cell(ws, "A1"); + + xlnt::comment c("text", "author"); + cell.set_comment(c); + TS_ASSERT_THROWS_ANYTHING(ws.get_cell("A2").set_comment(c)); + ws.get_cell("A2").set_comment(xlnt::comment("text2", "author2")); + TS_ASSERT_THROWS_ANYTHING(ws.get_cell("A1").set_comment(ws.get_cell("A2").get_comment())); + ws.get_cell("A1").clear_comment(); + TS_ASSERT_THROWS_NOTHING(ws.get_cell("A2").set_comment(c)); + } + + void test_cell_offset() + { + xlnt::worksheet ws = wb.create_sheet(); + TS_ASSERT(ws.get_cell("B15").offset(1, 2).get_reference() == "C17"); + } private: xlnt::workbook wb; diff --git a/tests/test_worksheet.hpp b/tests/test_worksheet.hpp index d27acbed..35b9fa65 100644 --- a/tests/test_worksheet.hpp +++ b/tests/test_worksheet.hpp @@ -164,7 +164,7 @@ public: void test_bad_relationship_type() { - xlnt::relationship rel("bad_type"); + xlnt::relationship rel("bad"); } void test_append_list()