diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index f8603003..b4f4f7b1 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -75,28 +75,6 @@ public: /// static const std::unordered_map &error_codes(); - // TODO: Should it be possible to construct and use a cell without a parent worksheet? - //(cont'd) If so, it would need to be responsible for allocating and deleting its PIMPL. - - /// - /// Construct a null cell without a parent. - /// Most methods will throw an exception if this cell is not further initialized. - /// - cell(); - - /// - /// Construct a cell in worksheet, sheet, at the given reference location (e.g. A1). - /// - cell(worksheet sheet, const cell_reference &reference); - - /// - /// This constructor, provided for convenience, is equivalent to calling: - /// cell c(sheet, reference); - /// c.set_value(initial_value); - /// - template - cell(worksheet sheet, const cell_reference &reference, const T &initial_value); - // value /// diff --git a/include/xlnt/packaging/app_properties.hpp b/include/xlnt/packaging/app_properties.hpp index cf3e2383..c2c1d225 100644 --- a/include/xlnt/packaging/app_properties.hpp +++ b/include/xlnt/packaging/app_properties.hpp @@ -35,14 +35,7 @@ namespace xlnt { class XLNT_CLASS app_properties { public: - std::string application = "libxlnt"; - int doc_security = 0; - bool scale_crop = false; - std::string company = ""; - bool links_up_to_date = false; - bool shared_doc = false; - bool hyperlinks_changed = false; - std::string app_version = "0.9"; + }; } // namespace xlnt diff --git a/include/xlnt/packaging/document_properties.hpp b/include/xlnt/packaging/document_properties.hpp index 5cbe90de..ad8a3fcf 100644 --- a/include/xlnt/packaging/document_properties.hpp +++ b/include/xlnt/packaging/document_properties.hpp @@ -38,17 +38,7 @@ class XLNT_CLASS document_properties public: document_properties(); - std::string creator; - std::string last_modified_by; - datetime created; - datetime modified; - std::string title = "Untitled"; - std::string subject; - std::string description; - std::string keywords; - std::string category; - std::string company; - calendar excel_base_date; + }; } // namespace xlnt diff --git a/include/xlnt/packaging/manifest.hpp b/include/xlnt/packaging/manifest.hpp index 5ae55a79..3a076444 100644 --- a/include/xlnt/packaging/manifest.hpp +++ b/include/xlnt/packaging/manifest.hpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace xlnt { @@ -39,21 +40,24 @@ namespace xlnt { class XLNT_CLASS manifest { public: + using default_types_container = std::unordered_map; + using override_types_container = std::unordered_map>; + void clear(); bool has_default_type(const std::string &extension) const; std::string get_default_type(const std::string &extension) const; - const std::unordered_map &get_default_types() const; + const default_types_container &get_default_types() const; void add_default_type(const std::string &extension, const std::string &content_type); - bool has_override_type(const std::string &part_name) const; - std::string get_override_type(const std::string &part_name) const; - const std::unordered_map &get_override_types() const; - void add_override_type(const std::string &part_name, const std::string &content_type); + bool has_override_type(const path &part) const; + std::string get_override_type(const path &part) const; + const override_types_container &get_override_types() const; + void add_override_type(const path &part, const std::string &content_type); private: - std::unordered_map default_types_; - std::unordered_map override_types_; + default_types_container default_types_; + override_types_container override_types_; }; } // namespace xlnt diff --git a/include/xlnt/packaging/override_type.hpp b/include/xlnt/packaging/override_type.hpp index 902be80f..645cb793 100644 --- a/include/xlnt/packaging/override_type.hpp +++ b/include/xlnt/packaging/override_type.hpp @@ -26,6 +26,7 @@ #include #include +#include namespace xlnt { @@ -37,15 +38,15 @@ class XLNT_CLASS override_type { public: override_type(); - override_type(const std::string &extension, const std::string &content_type); + override_type(const path &part, const std::string &content_type); override_type(const override_type &other); override_type &operator=(const override_type &other); - std::string get_part_name() const; + path get_part() const; std::string get_content_type() const; private: - std::string part_name_; + path part_; std::string content_type_; }; diff --git a/include/xlnt/packaging/zip_file.hpp b/include/xlnt/packaging/zip_file.hpp index 329de102..f5c5ab7b 100644 --- a/include/xlnt/packaging/zip_file.hpp +++ b/include/xlnt/packaging/zip_file.hpp @@ -30,6 +30,7 @@ #include #include +#include // Note: this comes from https://github.com/tfussell/miniz-cpp @@ -61,7 +62,7 @@ struct XLNT_CLASS zip_info zip_info(); date_time_t date_time; - std::string filename; + path filename; std::string comment; std::string extra; uint16_t create_system; @@ -85,18 +86,18 @@ class XLNT_CLASS zip_file { public: zip_file(); - zip_file(const std::string &filename); - zip_file(const std::vector &bytes); + zip_file(const path &filename); + zip_file(const std::vector &bytes); zip_file(std::istream &stream); ~zip_file(); // to/from file - void load(const std::string &filename); - void save(const std::string &filename); + void load(const path &filename); + void save(const path &filename); // to/from byte vector - void load(const std::vector &bytes); - void save(std::vector &bytes); + void load(const std::vector &bytes); + void save(std::vector &bytes); // to/from iostream void load(std::istream &stream); @@ -104,42 +105,29 @@ public: void reset(); - bool has_file(const std::string &name); + bool has_file(const path &name); bool has_file(const zip_info &name); - zip_info getinfo(const std::string &name); + zip_info getinfo(const path &name); std::vector infolist(); - std::vector namelist(); + std::vector namelist(); - std::ostream &open(const std::string &name); + std::ostream &open(const path &name); std::ostream &open(const zip_info &name); - void extract(const std::string &name); - void extract(const std::string &name, const std::string &path); - void extract(const zip_info &name); - void extract(const zip_info &name, const std::string &path); - - void extractall(); - void extractall(const std::string &path); - void extractall(const std::string &path, const std::vector &members); - void extractall(const std::string &path, const std::vector &members); - - void printdir(); - void printdir(std::ostream &stream); - - std::string read(const std::string &name); + std::string read(const path &name); std::string read(const zip_info &name); - std::pair testzip(); + bool check_crc(); - void write(const std::string &filename); - void write(const std::string &filename, const std::string &arcname); + void write_file(const path &source_file); + void write_file(const path &source_file, const path &archive_path); - void writestr(const std::string &arcname, const std::string &bytes); - void writestr(const zip_info &arcname, const std::string &bytes); + void write_string(const std::string &string, const path &archive_path); + void write_string(const std::string &string, const zip_info &archive_path); - std::string get_filename() const; + path get_filename() const; std::string comment; @@ -155,7 +143,7 @@ private: std::unique_ptr archive_; std::vector buffer_; std::stringstream open_stream_; - std::string filename_; + path filename_; }; } // namespace xlnt diff --git a/include/xlnt/utils/path.hpp b/include/xlnt/utils/path.hpp new file mode 100644 index 00000000..4b652527 --- /dev/null +++ b/include/xlnt/utils/path.hpp @@ -0,0 +1,247 @@ +// Copyright (c) 2014-2016 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 + +#include +#include + +namespace xlnt { + +/// +/// Encapsulates a path that points to location in a filesystem. +/// +class XLNT_CLASS path : public hashable +{ +public: + /// + /// The parts of this path are held in a container of this type. + /// + using container = std::vector; + + /// + /// Expose the container's iterator as the iterator for this class. + /// + using iterator = container::iterator; + /// + /// Expose the container's const_iterator as the const_iterator for this class. + /// + using const_iterator = container::const_iterator; + + /// + /// Expose the container's reverse_iterator as the reverse_iterator for this class. + /// + using reverse_iterator = container::reverse_iterator; + + /// + /// Expose the container's const_reverse_iterator as the const_reverse_iterator for this class. + /// + using const_reverse_iterator = container::const_reverse_iterator; + + /// + /// The system-specific path separator character (e.g. '/' or '\'). + /// + static char separator(); + + /// + /// Construct an empty path. + /// + path(); + + /// + /// Counstruct a path from a string representing the path. + /// + explicit path(const std::string &path_string); + + /// + /// Construct a path from a string with an explicit directory seprator. + /// + path(const std::string &path_string, char sep); + + // general attributes + + /// + /// Return true iff this path doesn't begin with / (or a drive letter on Windows). + /// + bool is_relative() const; + + /// + /// Return true iff path::is_relative() is false. + /// + bool is_absolute() const; + + /// + /// Return true iff this path is the root of the host's filesystem. + /// + bool is_root() const; + + /// + /// Return a new path that points to the directory containing the current path + /// Return the path unchanged if this path is the absolute or relative root. + /// + path parent() const; + + /// + /// Return the last component of this path. + /// + std::string basename(); + + // conversion + + /// + /// Create a string representing this path separated by the provided + /// separator or the system-default separator if not provided. + /// + std::string to_string(char sep = separator()) const; + + /// + /// If this path is relative, append each component of this path + /// to base_path and return the resulting absolute path. Otherwise, + /// the the current path will be returned and base_path will be ignored. + /// + path make_absolute(const path &base_path) const; + + // filesystem attributes + + /// + /// Return true iff the file or directory pointed to by this path + /// exists on the filesystem. + /// + bool exists() const; + + /// + /// Return true if the file or directory pointed to by this path + /// is a directory. + /// + bool is_directory() const; + + /// + /// Return true if the file or directory pointed to by this path + /// is a regular file. + /// + bool is_file() const; + + // filesystem + + /// + /// Open the file pointed to by this path and return a string containing + /// the files contents. + /// + std::string read_contents() const; + + // mutators + + /// + /// Append the provided part to this path and return a reference to this same path + /// so calls can be chained. + /// + path &append(const std::string &to_append); + + /// + /// Append the provided part to this path and return the result. + /// + path append(const std::string &to_append) const; + + /// + /// Append the provided part to this path and return a reference to this same path + /// so calls can be chained. + /// + path &append(const path &to_append); + + /// + /// Append the provided part to this path and return the result. + /// + path append(const path &to_append) const; + + // iterators + + /// + /// An iterator to the first element in this path. + /// + iterator begin(); + + /// + /// An iterator to one past the last element in this path. + /// + iterator end(); + + /// + /// An iterator to the first element in this path. + /// + const_iterator begin() const; + + /// + /// A const iterator to one past the last element in this path. + /// + const_iterator end() const; + + /// + /// An iterator to the first element in this path. + /// + const_iterator cbegin() const; + + /// + /// A const iterator to one past the last element in this path. + /// + const_iterator cend() const; + + /// + /// A reverse iterator to the last element in this path. + /// + reverse_iterator rbegin(); + + /// + /// A reverse iterator to one before the first element in this path. + /// + reverse_iterator rend(); + + /// + /// A const reverse iterator to the last element in this path. + /// + const_reverse_iterator rbegin() const; + + /// + /// A const reverse iterator to one before the first element in this path. + /// + const_reverse_iterator rend() const; + + /// + /// A const reverse iterator to the last element in this path. + /// + const_reverse_iterator crbegin() const; + + /// + /// A const reverse iterator to one before the first element in this path. + /// + const_reverse_iterator crend() const; + +protected: + std::string to_hash_string() const override; + +private: + container parts_; +}; + +} // namespace xlnt diff --git a/include/xlnt/workbook/workbook.hpp b/include/xlnt/workbook/workbook.hpp index d18bdb09..a387685f 100644 --- a/include/xlnt/workbook/workbook.hpp +++ b/include/xlnt/workbook/workbook.hpp @@ -36,13 +36,11 @@ namespace xlnt { class alignment; -class app_properties; class border; class cell; class cell_style; class color; class const_worksheet_iterator; -class document_properties; class drawing; class fill; class font; @@ -50,6 +48,7 @@ class format; class manifest; class named_range; class number_format; +class path; class pattern_fill; class protection; class range; @@ -63,6 +62,9 @@ class worksheet; class worksheet_iterator; class zip_file; +struct datetime; + +enum class calendar; enum class relationship_type; namespace detail { @@ -85,6 +87,14 @@ public: /// friend void swap(workbook &left, workbook &right); + static workbook minimal(); + + static workbook empty_excel(); + + static workbook empty_libre_office(); + + static workbook empty_numbers(); + // constructors /// @@ -297,11 +307,47 @@ public: /// std::vector get_sheet_titles() const; - document_properties &get_properties(); - const document_properties &get_properties() const; + std::string get_application() const; + void set_application(const std::string &application); - app_properties &get_app_properties(); - const app_properties &get_app_properties() const; + calendar get_base_date() const; + void set_base_date(calendar base_date); + + std::string get_creator() const; + void set_creator(const std::string &creator); + + std::string get_last_modified_by() const; + void set_last_modified_by(const std::string &last_modified_by); + + datetime get_created() const; + void set_created(const datetime &when); + + datetime get_modified() const; + void set_modified(const datetime &when); + + int get_doc_security() const; + void set_doc_security(int doc_security); + + bool get_scale_crop() const; + void set_scale_crop(bool scale_crop); + + std::string get_company() const; + void set_company(const std::string &company); + + bool links_up_to_date() const; + void set_links_up_to_date(bool links_up_to_date); + + bool is_shared_doc() const; + void set_shared_doc(bool shared_doc); + + bool hyperlinks_changed() const; + void set_hyperlinks_changed(bool hyperlinks_changed); + + std::string get_app_version() const; + void set_app_version(const std::string &version); + + std::string get_title() const; + void set_title(const std::string &title); // named ranges @@ -314,19 +360,23 @@ public: // serialization - bool save(std::vector &data); + bool save(std::vector &data); bool save(const std::string &filename); - bool load(const std::vector &data); + bool save(const xlnt::path &filename); + bool save(std::ostream &stream); + + bool load(const std::vector &data); bool load(const std::string &filename); + bool load(const xlnt::path &filename); bool load(std::istream &stream); - bool load(zip_file &archive); void set_code_name(const std::string &code_name); // theme - bool has_loaded_theme() const; - const theme &get_loaded_theme() const; + bool has_theme() const; + const theme &get_theme() const; + void set_theme(const theme &value); // formats @@ -407,6 +457,8 @@ private: friend class excel_serializer; friend class worksheet; + workbook(detail::workbook_impl *impl); + /// /// Get the name of the next unused relationship. /// diff --git a/include/xlnt/xlnt.hpp b/include/xlnt/xlnt.hpp index 7bd0ade9..92326abd 100644 --- a/include/xlnt/xlnt.hpp +++ b/include/xlnt/xlnt.hpp @@ -65,6 +65,7 @@ #include #include #include +#include #include #include diff --git a/source/cell/cell.cpp b/source/cell/cell.cpp index 5f5855a7..17fdf45f 100644 --- a/source/cell/cell.cpp +++ b/source/cell/cell.cpp @@ -155,26 +155,10 @@ std::string cell::check_string(const std::string &to_check) return s; } -cell::cell() : d_(nullptr) -{ -} - cell::cell(detail::cell_impl *d) : d_(d) { } -cell::cell(worksheet worksheet, const cell_reference &reference) : d_(nullptr) -{ - cell self = worksheet.get_cell(reference); - d_ = self.d_; -} - -template -cell::cell(worksheet worksheet, const cell_reference &reference, const T &initial_value) : cell(worksheet, reference) -{ - set_value(initial_value); -} - bool cell::garbage_collectible() const { return !(get_data_type() != type::null || is_merged() || has_comment() || has_formula() || has_format()); @@ -953,7 +937,7 @@ void cell::set_format(const format &new_format) calendar cell::get_base_date() const { - return get_workbook().get_properties().excel_base_date; + return get_workbook().get_base_date(); } std::ostream &operator<<(std::ostream &stream, const xlnt::cell &cell) diff --git a/source/cell/tests/test_cell.hpp b/source/cell/tests/test_cell.hpp index 39c085d4..92e69317 100644 --- a/source/cell/tests/test_cell.hpp +++ b/source/cell/tests/test_cell.hpp @@ -604,12 +604,6 @@ public: TS_ASSERT_EQUALS(cell.get_value(), std::string(32'767, 'a')); } - void test_operators() - { - xlnt::cell cell; - TS_ASSERT_EQUALS(cell, nullptr); - } - void test_comment() { xlnt::workbook wb; @@ -646,7 +640,7 @@ public: void test_anchor() { xlnt::workbook wb; - xlnt::cell cell(wb.get_active_sheet(), "A1"); + auto cell = wb.get_active_sheet().get_cell("A1"); auto anchor = cell.get_anchor(); TS_ASSERT_EQUALS(anchor.first, 0); TS_ASSERT_EQUALS(anchor.second, 0); @@ -655,7 +649,7 @@ public: void test_hyperlink() { xlnt::workbook wb; - xlnt::cell cell(wb.get_active_sheet(), "A1"); + auto cell = wb.get_active_sheet().get_cell("A1"); TS_ASSERT(!cell.has_hyperlink()); TS_ASSERT_THROWS(cell.get_hyperlink(), std::runtime_error); TS_ASSERT_THROWS(cell.set_hyperlink("notaurl"), std::runtime_error); diff --git a/source/detail/constants.cpp b/source/detail/constants.cpp index 8045b6a6..966675ee 100644 --- a/source/detail/constants.cpp +++ b/source/detail/constants.cpp @@ -23,8 +23,7 @@ #include #include - -#include "xlnt_config.hpp" +#include namespace xlnt { @@ -49,20 +48,20 @@ const column_t constants::max_column() } // constants -const std::string constants::package_properties() { return "docProps"; } -const std::string constants::package_xl() { return "xl"; } -const std::string constants::package_root_rels() { return "_rels"; } -const std::string constants::package_theme() { return package_xl() + "/" + "theme"; } -const std::string constants::package_worksheets() { return package_xl() + "/" + "worksheets"; } +const path constants::package_properties() { return path("docProps"); } +const path constants::package_xl() { return path("xl"); } +const path constants::package_root_rels() { return path(std::string("_rels")); } +const path constants::package_theme() { return package_xl().append("theme"); } +const path constants::package_worksheets() { return package_xl().append("worksheets"); } -const std::string constants::part_content_types() { return "[Content_Types].xml"; } -const std::string constants::part_root_relationships() { return package_root_rels() + "/.rels"; } -const std::string constants::part_core() { return package_properties() + "/core.xml"; } -const std::string constants::part_app() { return package_properties() + "/app.xml"; } -const std::string constants::part_workbook() { return package_xl() + "/workbook.xml"; } -const std::string constants::part_styles() { return package_xl() + "/styles.xml"; } -const std::string constants::part_theme() { return package_theme() + "/theme1.xml"; } -const std::string constants::part_shared_strings() { return package_xl() + "/sharedStrings.xml"; } +const path constants::part_content_types() { return path("[Content_Types].xml"); } +const path constants::part_root_relationships() { return package_root_rels().append(".rels"); } +const path constants::part_core() { return package_properties().append("core.xml"); } +const path constants::part_app() { return package_properties().append("app.xml"); } +const path constants::part_workbook() { return package_xl().append("workbook.xml"); } +const path constants::part_styles() { return package_xl().append("styles.xml"); } +const path constants::part_theme() { return package_theme().append("theme1.xml"); } +const path constants::part_shared_strings() { return package_xl().append("sharedStrings.xml"); } const std::unordered_map &constants::get_namespaces() { diff --git a/source/detail/constants.hpp b/source/detail/constants.hpp index 432752c9..a4d746c3 100644 --- a/source/detail/constants.hpp +++ b/source/detail/constants.hpp @@ -26,10 +26,11 @@ #include #include +#include namespace xlnt { -struct constants +struct XLNT_CLASS constants { /// /// Returns the lowest allowable row index in a worksheet. @@ -54,67 +55,67 @@ struct constants /// /// Returns the URI of the directory containing package properties. /// - static const std::string package_properties(); + static const path package_properties(); /// /// Returns the URI of the directory containing SpreatsheetML package parts. /// - static const std::string package_xl(); + static const path package_xl(); /// /// Returns the URI of the directory containing root relationships package part. /// - static const std::string package_root_rels(); + static const path package_root_rels(); /// /// Returns the URI of the directory containing package themes. /// - static const std::string package_theme(); + static const path package_theme(); /// /// Returns the URI of the directory containing package worksheets. /// - static const std::string package_worksheets(); + static const path package_worksheets(); /// /// Returns the URI of the content types package part. /// - static const std::string part_content_types(); + static const path part_content_types(); /// /// Returns the URI of the core properties package part. /// - static const std::string part_core(); + static const path part_core(); /// /// Returns the URI of the app properties package part. /// - static const std::string part_app(); + static const path part_app(); /// /// Returns the URI of the workbook package part. /// - static const std::string part_workbook(); + static const path part_workbook(); /// /// Returns the URI of the root relationships package part. /// - static const std::string part_root_relationships(); + static const path part_root_relationships(); /// /// Returns the URI of the styles package part. /// - static const std::string part_styles(); + static const path part_styles(); /// /// Returns the URI of the theme package part. /// - static const std::string part_theme(); + static const path part_theme(); /// /// Returns the URI of the shared strings package part. /// - static const std::string part_shared_strings(); + static const path part_shared_strings(); /// /// Returns an unordered_map mapping namespace names to namespaces. diff --git a/source/detail/excel_serializer.cpp b/source/detail/excel_serializer.cpp index 334fd0a4..90ed270c 100644 --- a/source/detail/excel_serializer.cpp +++ b/source/detail/excel_serializer.cpp @@ -70,9 +70,6 @@ bool load_workbook(xlnt::zip_file &archive, bool guess_types, bool data_only, xl { wb.clear(); - wb.set_guess_types(guess_types); - wb.set_data_only(data_only); - if(!archive.has_file(xlnt::constants::part_content_types())) { throw xlnt::invalid_file("missing [Content Types].xml"); @@ -112,7 +109,7 @@ bool load_workbook(xlnt::zip_file &archive, bool guess_types, bool data_only, xl wb.create_root_relationship(relationship.get_id(), relationship.get_target_uri(), relationship.get_type()); } - auto workbook_relationships = relationship_serializer_.read_relationships(xlnt::constants::part_workbook()); + auto workbook_relationships = relationship_serializer_.read_relationships(xlnt::constants::part_workbook().to_string('/')); for (const auto &relationship : workbook_relationships) { @@ -123,12 +120,17 @@ bool load_workbook(xlnt::zip_file &archive, bool guess_types, bool data_only, xl xml.load(archive.read(xlnt::constants::part_workbook()).c_str()); auto root_node = xml.child("workbook"); - auto workbook_pr_node = root_node.child("workbookPr"); - wb.get_properties().excel_base_date = - (workbook_pr_node.attribute("date1904") && workbook_pr_node.attribute("date1904").value() != std::string("0")) - ? xlnt::calendar::mac_1904 - : xlnt::calendar::windows_1900; + + if (workbook_pr_node.attribute("date1904")) + { + std::string value = workbook_pr_node.attribute("date1904").value(); + + if (value == "1" || value == "true") + { + wb.set_base_date(xlnt::calendar::mac_1904); + } + } if(archive.has_file(xlnt::constants::part_shared_strings())) { @@ -162,14 +164,15 @@ bool load_workbook(xlnt::zip_file &archive, bool guess_types, bool data_only, xl ws.set_id(static_cast(sheet_node.attribute("sheetId").as_ullong())); xlnt::worksheet_serializer worksheet_serializer(ws); pugi::xml_document worksheet_xml; - worksheet_xml.load(archive.read("xl/" + rel.get_target_uri()).c_str()); + auto target_uri = xlnt::constants::package_xl().append(rel.get_target_uri()); + worksheet_xml.load(archive.read(target_uri).c_str()); worksheet_serializer.read_worksheet(worksheet_xml, stylesheet); } - if (archive.has_file("docProps/thumbnail.jpeg")) + if (archive.has_file(xlnt::path("docProps/thumbnail.jpeg"))) { - auto thumbnail_data = archive.read("docProps/thumbnail.jpeg"); + auto thumbnail_data = archive.read(xlnt::path("docProps/thumbnail.jpeg")); wb.set_thumbnail(std::vector(thumbnail_data.begin(), thumbnail_data.end())); } @@ -210,7 +213,7 @@ bool excel_serializer::load_stream_workbook(std::istream &stream, bool guess_typ return load_virtual_workbook(bytes, guess_types, data_only); } -bool excel_serializer::load_workbook(const std::string &filename, bool guess_types, bool data_only) +bool excel_serializer::load_workbook(const path &filename, bool guess_types, bool data_only) { try { @@ -218,7 +221,7 @@ bool excel_serializer::load_workbook(const std::string &filename, bool guess_typ } catch (std::runtime_error) { - throw invalid_file(filename); + throw invalid_file(filename.to_string()); } return ::load_workbook(archive_, guess_types, data_only, workbook_, get_stylesheet()); @@ -239,7 +242,7 @@ void excel_serializer::write_data(bool /*as_template*/) { relationship_serializer relationship_serializer_(archive_); relationship_serializer_.write_relationships(workbook_.get_root_relationships(), ""); - relationship_serializer_.write_relationships(workbook_.get_relationships(), constants::part_workbook()); + relationship_serializer_.write_relationships(workbook_.get_relationships(), constants::part_workbook().to_string('/')); pugi::xml_document properties_app_xml; workbook_serializer workbook_serializer_(workbook_); @@ -248,7 +251,7 @@ void excel_serializer::write_data(bool /*as_template*/) { std::ostringstream ss; properties_app_xml.save(ss); - archive_.writestr(constants::part_app(), ss.str()); + archive_.write_string(ss.str(), constants::part_app()); } pugi::xml_document properties_core_xml; @@ -257,17 +260,17 @@ void excel_serializer::write_data(bool /*as_template*/) { std::ostringstream ss; properties_core_xml.save(ss); - archive_.writestr(constants::part_core(), ss.str()); + archive_.write_string(ss.str(), constants::part_core()); } pugi::xml_document theme_xml; theme_serializer theme_serializer_; - theme_serializer_.write_theme(workbook_.get_loaded_theme(), theme_xml); + theme_serializer_.write_theme(workbook_.get_theme(), theme_xml); { std::ostringstream ss; theme_xml.save(ss); - archive_.writestr(constants::part_theme(), ss.str()); + archive_.write_string(ss.str(), constants::part_theme()); } if (!workbook_.get_shared_strings().empty()) @@ -278,7 +281,7 @@ void excel_serializer::write_data(bool /*as_template*/) std::ostringstream ss; shared_strings_xml.save(ss); - archive_.writestr(constants::part_shared_strings(), ss.str()); + archive_.write_string(ss.str(), constants::part_shared_strings()); } pugi::xml_document workbook_xml; @@ -287,7 +290,7 @@ void excel_serializer::write_data(bool /*as_template*/) { std::ostringstream ss; workbook_xml.save(ss); - archive_.writestr(constants::part_workbook(), ss.str()); + archive_.write_string(ss.str(), constants::part_workbook()); } style_serializer style_serializer(workbook_.d_->stylesheet_); @@ -297,7 +300,7 @@ void excel_serializer::write_data(bool /*as_template*/) { std::ostringstream ss; style_xml.save(ss); - archive_.writestr(constants::part_styles(), ss.str()); + archive_.write_string(ss.str(), constants::part_styles()); } manifest_serializer manifest_serializer_(workbook_.get_manifest()); @@ -307,7 +310,7 @@ void excel_serializer::write_data(bool /*as_template*/) { std::ostringstream ss; manifest_xml.save(ss); - archive_.writestr(constants::part_content_types(), ss.str()); + archive_.write_string(ss.str(), constants::part_content_types()); } write_worksheets(); @@ -315,7 +318,7 @@ void excel_serializer::write_data(bool /*as_template*/) if(!workbook_.get_thumbnail().empty()) { const auto &thumbnail = workbook_.get_thumbnail(); - archive_.writestr("docProps/thumbnail.jpeg", std::string(thumbnail.begin(), thumbnail.end())); + archive_.write_string(std::string(thumbnail.begin(), thumbnail.end()), path("docProps/thumbnail.jpeg")); } } @@ -332,12 +335,13 @@ void excel_serializer::write_worksheets() if (rel.get_target_uri() != target) continue; worksheet_serializer serializer_(ws); - std::string ws_filename = (rel.get_target_uri().substr(0, 3) != "xl/" ? "xl/" : "") + rel.get_target_uri(); + path ws_path(rel.get_target_uri().substr(0, 3) != "xl/" ? "xl/" : "", '/'); + ws_path.append(rel.get_target_uri()); std::ostringstream ss; pugi::xml_document worksheet_xml; serializer_.write_worksheet(worksheet_xml); worksheet_xml.save(ss); - archive_.writestr(ws_filename, ss.str()); + archive_.write_string(ss.str(), ws_path); break; } @@ -356,7 +360,7 @@ bool excel_serializer::save_stream_workbook(std::ostream &stream, bool as_templa return true; } -bool excel_serializer::save_workbook(const std::string &filename, bool as_template) +bool excel_serializer::save_workbook(const path &filename, bool as_template) { write_data(as_template); archive_.save(filename); diff --git a/source/detail/excel_serializer.hpp b/source/detail/excel_serializer.hpp index 0b13d3e0..fafdc620 100644 --- a/source/detail/excel_serializer.hpp +++ b/source/detail/excel_serializer.hpp @@ -62,7 +62,7 @@ public: /// Create a ZIP file in memory, load archive from filename, then populate workbook /// with data from archive. /// - bool load_workbook(const std::string &filename, bool guess_types = false, bool data_only = false); + bool load_workbook(const path &filename, bool guess_types = false, bool data_only = false); /// /// Create a ZIP file in memory, load archive from stream, then populate workbook @@ -81,7 +81,7 @@ public: /// Create a ZIP file in memory, save workbook to this archive, then save archive /// to filename. /// - bool save_workbook(const std::string &filename, bool as_template = false); + bool save_workbook(const path &filename, bool as_template = false); /// /// Create a ZIP file in memory, save workbook to this archive, then assign ZIP file diff --git a/source/detail/manifest_serializer.cpp b/source/detail/manifest_serializer.cpp index 2fb7b040..ef5fde2f 100644 --- a/source/detail/manifest_serializer.cpp +++ b/source/detail/manifest_serializer.cpp @@ -46,7 +46,7 @@ void manifest_serializer::read_manifest(const pugi::xml_document &xml) } else if (child.name() == std::string("Override")) { - manifest_.add_override_type(child.attribute("PartName").value(), child.attribute("ContentType").value()); + manifest_.add_override_type(path(child.attribute("PartName").value()), child.attribute("ContentType").value()); } } } @@ -66,7 +66,7 @@ void manifest_serializer::write_manifest(pugi::xml_document &xml) const for (const auto override_type : manifest_.get_override_types()) { auto type_node = root_node.append_child("Override"); - type_node.append_attribute("PartName").set_value(override_type.second.get_part_name().c_str()); + type_node.append_attribute("PartName").set_value(override_type.second.get_part().to_string('/').c_str()); type_node.append_attribute("ContentType").set_value(override_type.second.get_content_type().c_str()); } } diff --git a/source/detail/relationship_serializer.cpp b/source/detail/relationship_serializer.cpp index eb2bb94b..02598998 100644 --- a/source/detail/relationship_serializer.cpp +++ b/source/detail/relationship_serializer.cpp @@ -38,7 +38,7 @@ std::string make_rels_name(const std::string &target) if (target.empty()) { - return xlnt::constants::part_root_relationships(); + return xlnt::constants::part_root_relationships().to_string(); } auto sep_pos = target.find_last_of(sep); @@ -58,7 +58,7 @@ relationship_serializer::relationship_serializer(zip_file &archive) : archive_(a std::vector relationship_serializer::read_relationships(const std::string &target) { pugi::xml_document xml; - xml.load(archive_.read(make_rels_name(target)).c_str()); + xml.load(archive_.read(path(make_rels_name(target))).c_str()); auto root_node = xml.child("Relationships"); @@ -101,7 +101,7 @@ bool relationship_serializer::write_relationships(const std::vector +#include #include #include #include -#include -#include #include +#include #include #include #include @@ -42,7 +42,26 @@ struct worksheet_impl; struct workbook_impl { - workbook_impl(); + workbook_impl() + : active_sheet_index_(0), + guess_types_(false), + data_only_(false), + has_theme_(false), + write_core_properties_(false), + created_(xlnt::datetime::now()), + modified_(xlnt::datetime::now()), + title_("Untitled"), + base_date_(calendar::windows_1900), + write_app_properties_(false), + application_("libxlnt"), + doc_security_(0), + scale_crop_(false), + links_up_to_date_(false), + shared_doc_(false), + hyperlinks_changed_(false), + app_version_("0.9") + { + } workbook_impl(const workbook_impl &other) : active_sheet_index_(other.active_sheet_index_), @@ -50,12 +69,32 @@ struct workbook_impl relationships_(other.relationships_), root_relationships_(other.root_relationships_), shared_strings_(other.shared_strings_), - properties_(other.properties_), - app_properties_(other.app_properties_), guess_types_(other.guess_types_), data_only_(other.data_only_), stylesheet_(other.stylesheet_), - manifest_(other.manifest_) + has_theme_(other.has_theme_), + theme_(other.theme_), + manifest_(other.manifest_), + write_core_properties_(other.write_core_properties_), + creator_(other.creator_), + last_modified_by_(other.last_modified_by_), + created_(other.created_), + modified_(other.modified_), + title_(other.title_), + subject_(other.subject_), + description_(other.description_), + keywords_(other.keywords_), + category_(other.category_), + company_(other.company_), + base_date_(other.base_date_), + write_app_properties_(other.write_app_properties_), + application_(other.application_), + doc_security_(other.doc_security_), + scale_crop_(other.scale_crop_), + links_up_to_date_(other.links_up_to_date_), + shared_doc_(other.shared_doc_), + hyperlinks_changed_(other.hyperlinks_changed_), + app_version_(other.app_version_) { } @@ -71,32 +110,78 @@ struct workbook_impl std::back_inserter(root_relationships_)); shared_strings_.clear(); std::copy(other.shared_strings_.begin(), other.shared_strings_.end(), std::back_inserter(shared_strings_)); - properties_ = other.properties_; - app_properties_ = other.app_properties_; guess_types_ = other.guess_types_; data_only_ = other.data_only_; + has_theme_ = other.has_theme_; + theme_ = other.theme_; manifest_ = other.manifest_; + write_core_properties_ = other.write_core_properties_; + creator_ = other.creator_; + last_modified_by_ = other.last_modified_by_; + created_ = other.created_; + modified_ = other.modified_; + title_ = other.title_; + subject_ = other.subject_; + description_ = other.description_; + keywords_ = other.keywords_; + category_ = other.category_; + company_ = other.company_; + base_date_ = other.base_date_; + + write_app_properties_ = other.write_app_properties_; + application_ = other.application_; + doc_security_ = other.doc_security_; + scale_crop_ = other.scale_crop_; + links_up_to_date_ = other.links_up_to_date_; + shared_doc_ = other.shared_doc_; + hyperlinks_changed_ = other.hyperlinks_changed_; + app_version_ = other.app_version_; + return *this; } std::size_t active_sheet_index_; - std::vector worksheets_; + std::list worksheets_; std::vector relationships_; std::vector root_relationships_; std::vector shared_strings_; - document_properties properties_; - app_properties app_properties_; - bool guess_types_; bool data_only_; stylesheet stylesheet_; manifest manifest_; + bool has_theme_; theme theme_; std::vector thumbnail_; + + // core properties + + bool write_core_properties_; + std::string creator_; + std::string last_modified_by_; + datetime created_; + datetime modified_; + std::string title_; + std::string subject_; + std::string description_; + std::string keywords_; + std::string category_; + std::string company_; + calendar base_date_; + + // application properties + + bool write_app_properties_; + std::string application_; + int doc_security_; + bool scale_crop_; + bool links_up_to_date_; + bool shared_doc_; + bool hyperlinks_changed_; + std::string app_version_; }; } // namespace detail diff --git a/source/detail/workbook_serializer.cpp b/source/detail/workbook_serializer.cpp index 029e5769..61def9c4 100644 --- a/source/detail/workbook_serializer.cpp +++ b/source/detail/workbook_serializer.cpp @@ -85,83 +85,78 @@ workbook_serializer::workbook_serializer(workbook &wb) : workbook_(wb) void workbook_serializer::read_properties_core(const pugi::xml_document &xml) { - auto &props = workbook_.get_properties(); auto root_node = xml.child("cp:coreProperties"); - props.excel_base_date = calendar::windows_1900; - if (root_node.child("dc:creator")) { - props.creator = root_node.child("dc:creator").text().get(); + workbook_.set_creator(root_node.child("dc:creator").text().get()); } if (root_node.child("cp:lastModifiedBy")) { - props.last_modified_by = root_node.child("cp:lastModifiedBy").text().get(); + workbook_.set_last_modified_by(root_node.child("cp:lastModifiedBy").text().get()); } if (root_node.child("dcterms:created")) { std::string created_string = root_node.child("dcterms:created").text().get(); - props.created = w3cdtf_to_datetime(created_string); + workbook_.set_created(w3cdtf_to_datetime(created_string)); } if (root_node.child("dcterms:modified")) { std::string modified_string = root_node.child("dcterms:modified").text().get(); - props.modified = w3cdtf_to_datetime(modified_string); + workbook_.set_modified(w3cdtf_to_datetime(modified_string)); } } void workbook_serializer::read_properties_app(const pugi::xml_document &xml) { - auto &props = workbook_.get_app_properties(); auto root_node = xml.child("Properties"); if(root_node.child("Application")) { - props.application = root_node.child("Application").text().get(); + workbook_.set_application(root_node.child("Application").text().get()); } if(root_node.child("DocSecurity")) { - props.doc_security = std::stoi(root_node.child("DocSecurity").text().get()); + workbook_.set_doc_security(std::stoi(root_node.child("DocSecurity").text().get())); } if(root_node.child("ScaleCrop")) { - props.scale_crop = root_node.child("ScaleCrop").text().get() == std::string("true"); + workbook_.set_scale_crop(root_node.child("ScaleCrop").text().get() == std::string("true")); } if(root_node.child("Company")) { - props.company = root_node.child("Company").text().get(); + workbook_.set_company(root_node.child("Company").text().get()); } if(root_node.child("ScaleCrop")) { - props.links_up_to_date = root_node.child("ScaleCrop").text().get() == std::string("true"); + workbook_.set_links_up_to_date(root_node.child("ScaleCrop").text().get() == std::string("true")); } if(root_node.child("SharedDoc")) { - props.shared_doc = root_node.child("SharedDoc").text().get() == std::string("true"); + workbook_.set_shared_doc(root_node.child("SharedDoc").text().get() == std::string("true")); } if(root_node.child("HyperlinksChanged")) { - props.hyperlinks_changed = root_node.child("HyperlinksChanged").text().get() == std::string("true"); + workbook_.set_hyperlinks_changed(root_node.child("HyperlinksChanged").text().get() == std::string("true")); } if(root_node.child("AppVersion")) { - props.app_version = root_node.child("AppVersion").text().get(); + workbook_.set_app_version(root_node.child("AppVersion").text().get()); } } void workbook_serializer::write_properties_core(pugi::xml_document &xml) const { - auto &props = workbook_.get_properties(); auto root_node = xml.append_child("cp:coreProperties"); root_node.append_attribute("xmlns:cp").set_value("http://schemas.openxmlformats.org/package/2006/metadata/core-properties"); @@ -170,13 +165,13 @@ void workbook_serializer::write_properties_core(pugi::xml_document &xml) const root_node.append_attribute("xmlns:dcterms").set_value("http://purl.org/dc/terms/"); root_node.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance"); - root_node.append_child("dc:creator").text().set(props.creator.c_str()); - root_node.append_child("cp:lastModifiedBy").text().set(props.last_modified_by.c_str()); - root_node.append_child("dcterms:created").text().set(datetime_to_w3cdtf(props.created).c_str()); + root_node.append_child("dc:creator").text().set(workbook_.get_creator().c_str()); + root_node.append_child("cp:lastModifiedBy").text().set(workbook_.get_last_modified_by().c_str()); + root_node.append_child("dcterms:created").text().set(datetime_to_w3cdtf(workbook_.get_created()).c_str()); root_node.child("dcterms:created").append_attribute("xsi:type").set_value("dcterms:W3CDTF"); - root_node.append_child("dcterms:modified").text().set(datetime_to_w3cdtf(props.modified).c_str()); + root_node.append_child("dcterms:modified").text().set(datetime_to_w3cdtf(workbook_.get_modified()).c_str()); root_node.child("dcterms:modified").append_attribute("xsi:type").set_value("dcterms:W3CDTF"); - root_node.append_child("dc:title").text().set(props.title.c_str()); + root_node.append_child("dc:title").text().set(workbook_.get_title().c_str()); root_node.append_child("dc:description"); root_node.append_child("dc:subject"); root_node.append_child("cp:keywords"); @@ -189,24 +184,22 @@ void workbook_serializer::write_properties_app(pugi::xml_document &xml) const root_node.append_attribute("xmlns").set_value("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"); root_node.append_attribute("xmlns:vt").set_value("http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"); - - const auto &properties = workbook_.get_app_properties(); - root_node.append_child("Application").text().set(properties.application.c_str()); - root_node.append_child("DocSecurity").text().set(std::to_string(properties.doc_security).c_str()); - root_node.append_child("ScaleCrop").text().set(properties.scale_crop ? "true" : "false"); + root_node.append_child("Application").text().set(workbook_.get_application().c_str()); + root_node.append_child("DocSecurity").text().set(std::to_string(workbook_.get_doc_security()).c_str()); + root_node.append_child("ScaleCrop").text().set(workbook_.get_scale_crop() ? "true" : "false"); auto company_node = root_node.append_child("Company"); - if (!properties.company.empty()) + if (!workbook_.get_company().empty()) { - company_node.text().set(properties.company.c_str()); + company_node.text().set(workbook_.get_company().c_str()); } - root_node.append_child("LinksUpToDate").text().set(properties.links_up_to_date ? "true" : "false"); - root_node.append_child("SharedDoc").text().set(properties.shared_doc ? "true" : "false"); - root_node.append_child("HyperlinksChanged").text().set(properties.hyperlinks_changed ? "true" : "false"); - root_node.append_child("AppVersion").text().set(properties.app_version.c_str()); + root_node.append_child("LinksUpToDate").text().set(workbook_.links_up_to_date() ? "true" : "false"); + root_node.append_child("SharedDoc").text().set(workbook_.is_shared_doc() ? "true" : "false"); + root_node.append_child("HyperlinksChanged").text().set(workbook_.hyperlinks_changed() ? "true" : "false"); + root_node.append_child("AppVersion").text().set(workbook_.get_app_version().c_str()); // TODO what is this stuff? @@ -261,7 +254,7 @@ void workbook_serializer::write_workbook(pugi::xml_document &xml) const auto workbook_pr_node = root_node.append_child("workbookPr"); workbook_pr_node.append_attribute("codeName").set_value("ThisWorkbook"); workbook_pr_node.append_attribute("defaultThemeVersion").set_value("124226"); - workbook_pr_node.append_attribute("date1904").set_value(workbook_.get_properties().excel_base_date == calendar::mac_1904 ? "1" : "0"); + workbook_pr_node.append_attribute("date1904").set_value(workbook_.get_base_date() == calendar::mac_1904 ? "1" : "0"); auto book_views_node = root_node.append_child("bookViews"); auto workbook_view_node = book_views_node.append_child("workbookView"); diff --git a/source/detail/xlsx_writer.cpp b/source/detail/xlsx_writer.cpp new file mode 100644 index 00000000..353a4bb7 --- /dev/null +++ b/source/detail/xlsx_writer.cpp @@ -0,0 +1,322 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void write_document_to_archive(const pugi::xml_document &document, + const xlnt::path &archive_path, xlnt::zip_file &archive) +{ + std::ostringstream out_stream; + document.save(out_stream); + archive.write_string(out_stream.str(), archive_path); +} + +// Package Parts + +void write_package_relationships(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + + auto relationships_node = document.append_child("Relationships"); + relationships_node.append_attribute("xmlns").set_value("http://schemas.openxmlformats.org/package/2006/relationships"); + + for (const auto &relationship : target.get_root_relationships()) + { + auto relationship_node = relationships_node.append_child("Relationship"); + + relationship_node.append_attribute("Id").set_value(relationship.get_id().c_str()); + relationship_node.append_attribute("Type").set_value(relationship.get_type_string().c_str()); + relationship_node.append_attribute("Target").set_value(relationship.get_target_uri().c_str()); + } + + write_document_to_archive(document, xlnt::constants::part_root_relationships(), archive); +} + +void write_content_types(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + + auto types_node = document.append_child("Types"); + types_node.append_attribute("xmlns").set_value("http://schemas.openxmlformats.org/package/2006/content-types"); + + auto default_node = types_node.append_child("Default"); + default_node.append_attribute("Extension").set_value("rels"); + default_node.append_attribute("ContentType").set_value("application/vnd.openxmlformats-package.relationships+xml"); + + auto workbook_override_node = types_node.append_child("Override"); + workbook_override_node.append_attribute("PartName").set_value("/workbook.xml"); + workbook_override_node.append_attribute("ContentType").set_value("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"); + + auto sheet_override_node = types_node.append_child("Default"); + sheet_override_node.append_attribute("PartName").set_value("/sheet1.xml"); + sheet_override_node.append_attribute("ContentType").set_value("application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"); + + write_document_to_archive(document, xlnt::constants::part_content_types(), archive); +} + +void write_app_properties(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("appProperties"); + write_document_to_archive(document, xlnt::constants::part_app(), archive); +} + +void write_core_properties(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("coreProperties"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_custom_file_properties(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("customFileProperties"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +// SpreadsheetML Package Parts + +void write_workbook(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + + auto workbook_node = document.append_child("workbook"); + workbook_node.append_attribute("xmlns").set_value("http://schemas.openxmlformats.org/spreadsheetml/2006/main"); + workbook_node.append_attribute("xmlns:r").set_value("http://schemas.openxmlformats.org/officeDocument/2006/relationships"); + + auto sheets_node = workbook_node.append_child("sheets"); + auto sheet_node = sheets_node.append_child("sheet"); + sheet_node.append_attribute("name").set_value(1); + sheet_node.append_attribute("sheetId").set_value(1); + sheet_node.append_attribute("r:id").set_value("rId1"); + + write_document_to_archive(document, xlnt::path("workbook.xml"), archive); +} + +void write_workbook_relationships(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + + auto relationships_node = document.append_child("Relationships"); + relationships_node.append_attribute("xmlns").set_value("http://schemas.openxmlformats.org/package/2006/relationships"); + + auto relationship_node = relationships_node.append_child("Relationship"); + relationship_node.append_attribute("Id").set_value("rId1"); + relationship_node.append_attribute("Type").set_value("http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet"); + relationship_node.append_attribute("Target").set_value("sheet1.xml"); + + write_document_to_archive(document, xlnt::path("_rels/workbook.xml.rels"), archive); +} + +// Workbook Relationship Target Parts + +void write_calculation_chain(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("calcChain"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_chartsheet(const xlnt::worksheet &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("chartsheet"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_connections(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("connections"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_custom_property(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("customProperty"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_custom_xml_mappings(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("connections"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_dialogsheet(const xlnt::worksheet &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("dialogsheet"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_external_workbook_references(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("externalLink"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_metadata(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("metadata"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_pivot_table(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("pivotTableDefinition"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_shared_string_table(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("sst"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_shared_workbook_revision_headers(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("headers"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_shared_workbook(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("revisions"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_shared_workbook_user_data(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("users"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_styles(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("styleSheet"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_theme(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("theme"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_volatile_dependencies(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("volTypes"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_worksheet(const xlnt::worksheet &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + + auto worksheet_node = document.append_child("worksheet"); + worksheet_node.append_attribute("xmlns").set_value("http://schemas.openxmlformats.org/spreadsheetml/2006/main"); + worksheet_node.append_attribute("xmlns:r").set_value("http://schemas.openxmlformats.org/package/2006/relationships"); + + worksheet_node.append_child("sheetData"); + + write_document_to_archive(document, xlnt::path("sheet1.xml"), archive); +} + +// Sheet Relationship Target Parts + +void write_comments(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("comments"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_drawings(const xlnt::worksheet &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("wsDr"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +// Unknown Parts + +void write_unknown_parts(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("relationships"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +void write_unknown_relationships(const xlnt::workbook &target, xlnt::zip_file &archive) +{ + pugi::xml_document document; + auto root_node = document.append_child("Relationships"); + write_document_to_archive(document, xlnt::constants::part_core(), archive); +} + +} // namespace + +namespace xlnt +{ + +xlsx_writer::xlsx_writer(const workbook &target) : target_(target) +{ +} + +void xlsx_writer::write(const path &destination) +{ + xlnt::zip_file archive; + populate_archive(archive); + archive.save(destination); +} + +void xlsx_writer::write(std::ostream &destination) +{ + xlnt::zip_file archive; + populate_archive(archive); + archive.save(destination); +} + +void xlsx_writer::write(std::vector &destination) +{ + xlnt::zip_file archive; + populate_archive(archive); + archive.save(destination); +} + +void xlsx_writer::populate_archive(zip_file &archive) +{ + write_package_relationships(target_, archive); + write_content_types(target_, archive); + + write_workbook(target_, archive); + write_workbook_relationships(target_, archive); + + for (auto ws : target_) + { + write_worksheet(ws, archive); + } +} + +} // namepsace xlnt diff --git a/source/detail/xlsx_writer.hpp b/source/detail/xlsx_writer.hpp new file mode 100644 index 00000000..6d59ff0f --- /dev/null +++ b/source/detail/xlsx_writer.hpp @@ -0,0 +1,65 @@ +// Copyright (c) 2014-2016 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 +#pragma once + +#include +#include +#include + +#include + +namespace xlnt { + +class path; +class workbook; +class zip_file; + +/// +/// Handles writing a workbook into an XLSX file. +/// +class XLNT_CLASS xlsx_writer +{ +public: + xlsx_writer(const workbook &target); + + void write(const path &destination); + + void write(std::ostream &destination); + + void write(std::vector &destination); + +private: + /// + /// Write all files needed to create a valid XLSX file which represents all + /// data contained in workbook. + /// + void populate_archive(zip_file &archive); + + /// + /// A reference to the workbook which is the object of read/write operations. + /// + const workbook &target_; +}; + +} // namespace xlnt diff --git a/source/packaging/manifest.cpp b/source/packaging/manifest.cpp index d719584e..56993bec 100644 --- a/source/packaging/manifest.cpp +++ b/source/packaging/manifest.cpp @@ -39,9 +39,9 @@ bool manifest::has_default_type(const std::string &extension) const return default_types_.find(extension) != default_types_.end(); } -bool manifest::has_override_type(const std::string &part_name) const +bool manifest::has_override_type(const path &part) const { - auto absolute = part_name.front() == '/' ? part_name : ("/" + part_name); + auto absolute = part.is_absolute() ? part : part.make_absolute(path("/")); return override_types_.find(absolute) != override_types_.end(); } @@ -50,9 +50,9 @@ void manifest::add_default_type(const std::string &extension, const std::string default_types_[extension] = default_type(extension, content_type); } -void manifest::add_override_type(const std::string &part_name, const std::string &content_type) +void manifest::add_override_type(const path &part, const std::string &content_type) { - auto absolute = part_name.front() == '/' ? part_name : ("/" + part_name); + auto absolute = part.is_absolute() ? part : part.make_absolute(path("/")); override_types_[absolute] = override_type(absolute, content_type); } @@ -61,18 +61,18 @@ std::string manifest::get_default_type(const std::string &extension) const return default_types_.at(extension).get_content_type(); } -std::string manifest::get_override_type(const std::string &part_name) const +std::string manifest::get_override_type(const path &part) const { - auto absolute = part_name.front() == '/' ? part_name : ("/" + part_name); + auto absolute = part.is_absolute() ? part : part.make_absolute(path("/")); return override_types_.at(absolute).get_content_type(); } -const std::unordered_map &manifest::get_default_types() const +const manifest::default_types_container &manifest::get_default_types() const { return default_types_; } -const std::unordered_map &manifest::get_override_types() const +const manifest::override_types_container &manifest::get_override_types() const { return override_types_; } diff --git a/source/packaging/override_type.cpp b/source/packaging/override_type.cpp index 988afd6b..74968d30 100644 --- a/source/packaging/override_type.cpp +++ b/source/packaging/override_type.cpp @@ -29,27 +29,27 @@ override_type::override_type() { } -override_type::override_type(const std::string &part_name, const std::string &content_type) - : part_name_(part_name), content_type_(content_type) +override_type::override_type(const path &part, const std::string &content_type) + : part_(part), content_type_(content_type) { } override_type::override_type(const override_type &other) - : part_name_(other.part_name_), content_type_(other.content_type_) + : part_(other.part_), content_type_(other.content_type_) { } override_type &override_type::operator=(const override_type &other) { - part_name_ = other.part_name_; + part_ = other.part_; content_type_ = other.content_type_; return *this; } -std::string override_type::get_part_name() const +path override_type::get_part() const { - return part_name_; + return part_; } std::string override_type::get_content_type() const diff --git a/source/packaging/tests/test_core.hpp b/source/packaging/tests/test_core.hpp index b489e598..4da9bf73 100644 --- a/source/packaging/tests/test_core.hpp +++ b/source/packaging/tests/test_core.hpp @@ -14,19 +14,18 @@ class test_core : public CxxTest::TestSuite public: void test_read_properties_core() { - auto path = path_helper::get_data_directory() + "/genuine/empty.xlsx"; xlnt::workbook wb; - wb.load(path); - auto &prop = wb.get_properties(); - TS_ASSERT_EQUALS(prop.creator, "*.*"); - TS_ASSERT_EQUALS(prop.last_modified_by, "Charlie Clark"); - TS_ASSERT_EQUALS(prop.created, xlnt::datetime(2010, 4, 9, 20, 43, 12)); - TS_ASSERT_EQUALS(prop.modified, xlnt::datetime(2014, 1, 2, 14, 53, 6)); + wb.load(path_helper::get_data_directory("genuine/empty.xlsx")); + + TS_ASSERT_EQUALS(wb.get_creator(), "*.*"); + TS_ASSERT_EQUALS(wb.get_last_modified_by(), "Charlie Clark"); + TS_ASSERT_EQUALS(wb.get_created(), xlnt::datetime(2010, 4, 9, 20, 43, 12)); + TS_ASSERT_EQUALS(wb.get_modified(), xlnt::datetime(2014, 1, 2, 14, 53, 6)); } void test_read_sheets_titles() { - auto path = path_helper::get_data_directory() + "/genuine/empty.xlsx"; + auto path = path_helper::get_data_directory("genuine/empty.xlsx"); const std::vector expected_titles = {"Sheet1 - Text", "Sheet2 - Numbers", "Sheet3 - Formulas", "Sheet4 - Dates"}; @@ -43,58 +42,49 @@ public: void test_read_properties_core_libre() { - xlnt::zip_file archive(path_helper::get_data_directory() + "/genuine/empty_libre.xlsx"); - auto content = archive.read("docProps/core.xml"); - xlnt::workbook wb; - xlnt::workbook_serializer serializer(wb); - pugi::xml_document xml; - serializer.read_properties_core(xml); - auto &prop = wb.get_properties(); - TS_ASSERT_EQUALS(prop.excel_base_date, xlnt::calendar::windows_1900); + xlnt::workbook wb; + wb.load(path_helper::get_data_directory("genuine/empty_libre.xlsx")); + TS_ASSERT_EQUALS(wb.get_base_date(), xlnt::calendar::windows_1900); } void test_read_sheets_titles_libre() { - auto path = path_helper::get_data_directory() + "/genuine/empty_libre.xlsx"; - const std::vector expected_titles = {"Sheet1 - Text", "Sheet2 - Numbers", "Sheet3 - Formulas", "Sheet4 - Dates"}; - std::size_t i = 0; - xlnt::workbook wb; - wb.load(path); + wb.load(path_helper::get_data_directory("genuine/empty_libre.xlsx")); + auto title_iter = expected_titles.begin(); - for(auto sheet : wb) + for(auto ws : wb) { - TS_ASSERT_EQUALS(sheet.get_title(), expected_titles.at(i++)); + TS_ASSERT_EQUALS(ws.get_title(), *(title_iter++)); } } void test_write_properties_core() { xlnt::workbook wb; - xlnt::document_properties &prop = wb.get_properties(); - prop.creator = "TEST_USER"; - prop.last_modified_by = "SOMEBODY"; - prop.created = xlnt::datetime(2010, 4, 1, 20, 30, 00); - prop.modified = xlnt::datetime(2010, 4, 5, 14, 5, 30); + wb.set_creator("TEST_USER"); + wb.set_last_modified_by("SOMEBODY"); + wb.set_created(xlnt::datetime(2010, 4, 1, 20, 30, 00)); + wb.set_modified(xlnt::datetime(2010, 4, 5, 14, 5, 30)); xlnt::workbook_serializer serializer(wb); pugi::xml_document xml; serializer.write_properties_core(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/core.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/core.xml"), xml)); } void test_write_properties_app() { xlnt::workbook wb; - wb.get_app_properties().application = "Microsoft Excel"; - wb.get_app_properties().app_version = "12.0000"; - wb.get_app_properties().company = "Company"; + wb.set_application("Microsoft Excel"); + wb.set_app_version("12.0000"); + wb.set_company("Company"); wb.create_sheet(); wb.create_sheet(); xlnt::workbook_serializer serializer(wb); pugi::xml_document xml; serializer.write_properties_app(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/app.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/app.xml"), xml)); } }; diff --git a/source/packaging/zip_file.cpp b/source/packaging/zip_file.cpp index f8d89f13..30e5a0fd 100644 --- a/source/packaging/zip_file.cpp +++ b/source/packaging/zip_file.cpp @@ -38,104 +38,23 @@ #include #include +#include namespace { -std::string get_working_directory() +void mkdir_recursive(const xlnt::path &path) { -#ifdef _WIN32 - TCHAR buffer[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, buffer); - std::basic_string working_directory(buffer); - return std::string(working_directory.begin(), working_directory.end()); -#else - char buffer[2048]; - getcwd(buffer, 2048); - return std::string(buffer); -#endif -} - -#ifdef _WIN32 -char directory_separator = '\\'; -char alt_directory_separator = '/'; -#else -char directory_separator = '/'; -char alt_directory_separator = '\\'; -#endif - -std::string join_path(const std::vector &parts) -{ - std::string joined; - std::size_t i = 0; - for (auto part : parts) - { - joined.append(part); - - if (i++ != parts.size() - 1) - { - joined.append(1, '/'); - } - } - return joined; -} - -std::vector split_path(const std::string &path, char delim = directory_separator) -{ - std::vector split; - std::string::size_type previous_index = 0; - auto separator_index = path.find(delim); - - while (separator_index != std::string::npos) - { - auto part = path.substr(previous_index, separator_index - previous_index); - if (part != "..") - { - split.push_back(part); - } - else - { - split.pop_back(); - } - previous_index = separator_index + 1; - separator_index = path.find(delim, previous_index); - } - - split.push_back(path.substr(previous_index)); - - if (split.size() == 1 && delim == directory_separator) - { - auto alternative = split_path(path, alt_directory_separator); - if (alternative.size() > 1) - { - return alternative; - } - } - - return split; -} - -bool directory_exists(const std::string path) -{ - struct stat info; - return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR); -} - -void mkdir_recursive(const std::string path) -{ - if (directory_exists(path)) return; - - auto split = split_path(path); - auto last = split.back(); - split.pop_back(); + if (path.exists()) return; - if (!split.empty()) + auto parent = path.parent(); + + if (!parent.is_root()) { - auto parent = join_path(split); mkdir_recursive(parent); } #ifdef _WIN32 - _mkdir(path.c_str()); + _mkdir(path.to_string().c_str()); #else mkdir(path.c_str(), 0755); #endif @@ -249,7 +168,7 @@ zip_file::zip_file() : archive_(new mz_zip_archive()) reset(); } -zip_file::zip_file(const std::string &filename) : zip_file() +zip_file::zip_file(const path &filename) : zip_file() { load(filename); } @@ -277,10 +196,10 @@ void zip_file::load(std::istream &stream) start_read(); } -void zip_file::load(const std::string &filename) +void zip_file::load(const path &filename) { filename_ = filename; - std::ifstream stream(filename, std::ios::binary); + std::ifstream stream(filename.to_string(), std::ios::binary); load(stream); } @@ -292,10 +211,10 @@ void zip_file::load(const std::vector &bytes) start_read(); } -void zip_file::save(const std::string &filename) +void zip_file::save(const path &filename) { filename_ = filename; - std::ofstream stream(filename, std::ios::binary); + std::ofstream stream(filename.to_string(), std::ios::binary); save(stream); } @@ -409,14 +328,14 @@ void zip_file::reset() mz_zip_writer_end(archive_.get()); } -zip_info zip_file::getinfo(const std::string &name) +zip_info zip_file::getinfo(const path &name) { if (archive_->m_zip_mode != MZ_ZIP_MODE_READING) { start_read(); } - int index = mz_zip_reader_locate_file(archive_.get(), name.c_str(), nullptr, 0); + int index = mz_zip_reader_locate_file(archive_.get(), name.to_string('/').c_str(), nullptr, 0); if (index == -1) { @@ -438,7 +357,7 @@ zip_info zip_file::getinfo(int index) zip_info result; - result.filename = std::string(stat.m_filename, stat.m_filename + std::strlen(stat.m_filename)); + result.filename = path(std::string(stat.m_filename, stat.m_filename + std::strlen(stat.m_filename))); result.comment = std::string(stat.m_comment, stat.m_comment + stat.m_comment_size); result.compress_size = static_cast(stat.m_comp_size); result.file_size = static_cast(stat.m_uncomp_size); @@ -531,40 +450,54 @@ void zip_file::start_write() mz_zip_writer_init(archive_.get(), 0); } -void zip_file::write(const std::string &filename) +void zip_file::write_file(const path &filename) { - auto split = split_path(filename); - if (split.size() > 1) - { - split.erase(split.begin()); - } - auto arcname = join_path(split); - write(filename, arcname); + path arcname(filename); + + if (filename.is_absolute()) + { + arcname = path(); + bool first = true; + + for (auto part : filename) + { + if (first) + { + first = false; + continue; + } + + arcname.append(part); + } + } + + write_file(filename, arcname); } -void zip_file::write(const std::string &filename, const std::string &arcname) +void zip_file::write_file(const path &filename, const path &arcname) { - std::fstream file(filename, std::ios::binary | std::ios::in); + std::fstream file(filename.to_string(), std::ios::binary | std::ios::in); + std::stringstream ss; ss << file.rdbuf(); - std::string bytes = ss.str(); - writestr(arcname, bytes); + write_string(ss.str(), arcname); } -void zip_file::writestr(const std::string &arcname, const std::string &bytes) +void zip_file::write_string(const std::string &bytes, const path &arcname) { if (archive_->m_zip_mode != MZ_ZIP_MODE_WRITING) { start_write(); } - mz_zip_writer_add_mem(archive_.get(), arcname.c_str(), bytes.data(), bytes.size(), MZ_BEST_COMPRESSION); + mz_zip_writer_add_mem(archive_.get(), arcname.to_string('/').c_str(), + bytes.data(), bytes.size(), MZ_BEST_COMPRESSION); } -void zip_file::writestr(const zip_info &info, const std::string &bytes) +void zip_file::write_string(const std::string &bytes, const zip_info &info) { - if (info.filename.empty() || info.date_time.year < 1980) + if (info.filename.to_string().empty() || info.date_time.year < 1980) { throw std::runtime_error("must specify a filename and valid date (year >= 1980"); } @@ -576,7 +509,7 @@ void zip_file::writestr(const zip_info &info, const std::string &bytes) auto crc = crc32buf(bytes.c_str(), bytes.size()); - mz_zip_writer_add_mem_ex(archive_.get(), info.filename.c_str(), bytes.data(), bytes.size(), + mz_zip_writer_add_mem_ex(archive_.get(), info.filename.to_string('/').c_str(), bytes.data(), bytes.size(), info.comment.c_str(), static_cast(info.comment.size()), MZ_BEST_COMPRESSION, 0, crc); } @@ -584,30 +517,34 @@ void zip_file::writestr(const zip_info &info, const std::string &bytes) std::string zip_file::read(const zip_info &info) { std::size_t size; - char *data = - static_cast(mz_zip_reader_extract_file_to_heap(archive_.get(), info.filename.c_str(), &size, 0)); - if (data == nullptr) + void *data_raw = mz_zip_reader_extract_file_to_heap(archive_.get(), + info.filename.to_string('/').c_str(), &size, 0); + + if (data_raw == nullptr) { throw std::runtime_error("file couldn't be read"); } + + auto data = static_cast(data_raw); std::string extracted(data, data + size); mz_free(data); + return extracted; } -std::string zip_file::read(const std::string &name) +std::string zip_file::read(const path &name) { return read(getinfo(name)); } -bool zip_file::has_file(const std::string &name) +bool zip_file::has_file(const path &name) { if (archive_->m_zip_mode != MZ_ZIP_MODE_READING) { start_read(); } - int index = mz_zip_reader_locate_file(archive_.get(), name.c_str(), nullptr, 0); + int index = mz_zip_reader_locate_file(archive_.get(), name.to_string('/').c_str(), nullptr, 0); return index != -1; } @@ -634,19 +571,19 @@ std::vector zip_file::infolist() return info; } -std::vector zip_file::namelist() +std::vector zip_file::namelist() { - std::vector names; + std::vector names; for (auto &info : infolist()) { names.push_back(info.filename); } - return names; + return names; } -std::ostream &zip_file::open(const std::string &name) +std::ostream &zip_file::open(const path &name) { return open(getinfo(name)); } @@ -659,118 +596,7 @@ std::ostream &zip_file::open(const zip_info &name) return open_stream_; } -void zip_file::extract(const std::string &member) -{ - extract(member, get_working_directory()); -} - -void zip_file::extract(const std::string &member, const std::string &path) -{ - auto member_split = split_path(member); - auto fullpath_parts = split_path(path); - fullpath_parts.insert(fullpath_parts.end(), member_split.begin(), member_split.end()); - auto fullpath = join_path(fullpath_parts); - - auto directory = fullpath_parts; - directory.pop_back(); - mkdir_recursive(join_path(directory)); - - std::fstream stream(fullpath, std::ios::binary | std::ios::out); - stream << open(member).rdbuf(); -} - -void zip_file::extract(const zip_info &member) -{ - extract(member, get_working_directory()); -} - -void zip_file::extract(const zip_info &member, const std::string &path) -{ - std::fstream stream(join_path({ path, member.filename }), std::ios::binary | std::ios::out); - stream << open(member).rdbuf(); -} - -void zip_file::extractall(const std::string &path) -{ - extractall(path, infolist()); -} - -void zip_file::extractall(const std::string &path, const std::vector &members) -{ - for (auto &member : members) - { - extract(member, path); - } -} - -void zip_file::extractall(const std::string &path, const std::vector &members) -{ - for (auto &member : members) - { - extract(member, path); - } -} - -void zip_file::printdir() -{ - printdir(std::cout); -} - -void zip_file::printdir(std::ostream &stream) -{ - stream << " Length " - << " " - << " " - << "Date" - << " " - << " " - << "Time " - << " " - << "Name" << std::endl; - stream << "--------- ---------- ----- ----" << std::endl; - - std::size_t sum_length = 0; - std::size_t file_count = 0; - - for (auto &member : infolist()) - { - sum_length += member.file_size; - file_count++; - - std::string length_string = std::to_string(member.file_size); - while (length_string.length() < 9) - { - length_string = " " + length_string; - } - stream << length_string; - - stream << " "; - stream << (member.date_time.month < 10 ? "0" : "") << member.date_time.month; - stream << "/"; - stream << (member.date_time.day < 10 ? "0" : "") << member.date_time.day; - stream << "/"; - stream << member.date_time.year; - stream << " "; - stream << (member.date_time.hours < 10 ? "0" : "") << member.date_time.hours; - stream << ":"; - stream << (member.date_time.minutes < 10 ? "0" : "") << member.date_time.minutes; - stream << " "; - stream << member.filename; - stream << std::endl; - } - - stream << "--------- -------" << std::endl; - - std::string length_string = std::to_string(sum_length); - while (length_string.length() < 9) - { - length_string = " " + length_string; - } - stream << length_string << " " << file_count << " " << (file_count == 1 ? "file" : "files"); - stream << std::endl; -} - -std::pair zip_file::testzip() +bool zip_file::check_crc() { if (archive_->m_zip_mode == MZ_ZIP_MODE_INVALID) { @@ -784,14 +610,14 @@ std::pair zip_file::testzip() if (crc != file.crc) { - return { false, file.filename }; + return true; } } - return { true, "" }; + return false; } -std::string zip_file::get_filename() const +path zip_file::get_filename() const { return filename_; } diff --git a/source/styles/tests/test_stylesheet.hpp b/source/styles/tests/test_stylesheet.hpp index f3a42331..29d84400 100644 --- a/source/styles/tests/test_stylesheet.hpp +++ b/source/styles/tests/test_stylesheet.hpp @@ -17,11 +17,12 @@ public: void test_from_simple() { pugi::xml_document doc; - auto xml = path_helper::read_file(path_helper::get_data_directory("/reader/styles/simple-styles.xml")); - doc.load(xml.c_str()); + doc.load_file(path_helper::get_data_directory("reader/styles/simple-styles.xml").to_string().c_str()); + xlnt::workbook wb; xlnt::excel_serializer e(wb); xlnt::style_serializer s(e.get_stylesheet()); + TS_ASSERT(s.read_stylesheet(doc)); TS_ASSERT_EQUALS(e.get_stylesheet().number_formats.size(), 1); } @@ -29,11 +30,12 @@ public: void test_from_complex() { pugi::xml_document doc; - auto xml = path_helper::read_file(path_helper::get_data_directory("/reader/styles/complex-styles.xml")); - doc.load(xml.c_str()); + doc.load_file(path_helper::get_data_directory("reader/styles/complex-styles.xml").to_string().c_str()); + xlnt::workbook wb; xlnt::excel_serializer e(wb); xlnt::style_serializer s(e.get_stylesheet()); + TS_ASSERT(s.read_stylesheet(doc)); TS_ASSERT_EQUALS(e.get_stylesheet().borders.size(), 7); TS_ASSERT_EQUALS(e.get_stylesheet().fills.size(), 6); diff --git a/source/utils/path.cpp b/source/utils/path.cpp new file mode 100644 index 00000000..bac0a10f --- /dev/null +++ b/source/utils/path.cpp @@ -0,0 +1,391 @@ +// Copyright (c) 2014-2016 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 +#include +#include + +#include + +#ifdef __APPLE__ +#include +#include +#elif defined(_MSC_VER) +#include +#elif defined(__linux) +#include +#include +#include +#include +#endif + +#include + +namespace { + +#ifdef _MSC_VER + +char system_separator() +{ + return '\\'; +} + +bool is_root(const std::string &part) +{ + return part.size() == 2 && part.back() == ':' + && part.front() >= 'A' && part.front() <= 'Z'; +} + +bool file_exists(const std::string &path_string) +{ + std::wstring path_wide(path_string.begin(), path_string.end()); + return PathFileExists(path_wide.c_str()) && !PathIsDirectory(path_wide.c_str()); +} + +std::string get_working_directory() +{ + TCHAR buffer[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, buffer); + std::basic_string working_directory(buffer); + return std::string(working_directory.begin(), working_directory.end()); +} + +#else + +char system_separator() +{ + return '/'; +} + +bool is_root(const std::string &part) +{ + return part.empty(); +} + +bool file_exists(const std::string &path_string) +{ + try + { + struct stat fileAtt; + + if (stat(path.c_str(), &fileAtt) == 0) + { + return S_ISREG(fileAtt.st_mode); + } + } + catch (...) {} + + return false; +} + +std::string get_working_directory() +{ + std::size_t buffer_size = 100; + std::vector buffer(buffer_size, '\0'); + + while (getcwd(&buffer[0], buffer_size) == nullptr) + { + buffer_size *= 2; + buffer.resize(buffer_size, '\0'); + } + + return std::string(&buffer[0]); +} + +#endif + +std::vector split_path(const std::string &path, char delim) +{ + std::vector split; + std::string::size_type previous_index = 0; + auto separator_index = path.find(delim); + + while (separator_index != std::string::npos) + { + auto part = path.substr(previous_index, separator_index - previous_index); + split.push_back(part); + + previous_index = separator_index + 1; + separator_index = path.find(delim, previous_index); + } + + // Don't add trailing slash + if (previous_index < path.size()) + { + split.push_back(path.substr(previous_index)); + } + + return split; +} + +std::vector split_path_universal(const std::string &path) +{ + auto initial = split_path(path, system_separator()); + + if (initial.size() == 1 && system_separator() == '\\') + { + auto alternative = split_path(path, '/'); + + if (alternative.size() > 1) + { + return alternative; + } + } + + return initial; +} + +bool directory_exists(const std::string path) +{ + struct stat info; + return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR); +} + +} // namespace + +namespace xlnt { + +char path::separator() +{ + return system_separator(); +} + +path::path() +{ + +} + +path::path(const std::string &path_string) : parts_(split_path_universal(path_string)) +{ +} + +path::path(const std::string &path_string, char sep) : parts_(split_path(path_string, sep)) +{ +} + +// general attributes + +bool path::is_relative() const +{ + return !is_absolute(); +} + +bool path::is_absolute() const +{ + return !parts_.empty() && ::is_root(parts_.front()); +} + +bool path::is_root() const +{ + return parts_.size() == 1 && ::is_root(parts_.front()); +} + +path path::parent() const +{ + path result; + result.parts_ = parts_; + + if (result.parts_.size() > 1) + { + result.parts_.pop_back(); + } + + return result; +} + +std::string path::basename() +{ + return parts_.empty() ? "" : parts_.back(); +} + +// conversion + +std::string path::to_string(char sep) const +{ + if (parts_.empty()) return ""; + + std::string result; + + for (const auto &part : parts_) + { + result.append(part); + result.push_back(sep); + } + + result.pop_back(); + + return result; +} + +path path::make_absolute(const path &base_path) const +{ + if (is_absolute()) + { + return *this; + } + + path copy = base_path; + + for (const auto &part : parts_) + { + if (part == ".") + { + continue; + } + else if (part == ".." && copy.parts_.size() > 1) + { + copy.parts_.pop_back(); + } + else + { + copy.parts_.push_back(part); + } + } + + return copy; +} + +// filesystem attributes + +bool path::exists() const +{ + return is_file() || is_directory(); +} + +bool path::is_directory() const +{ + return directory_exists(to_string()); +} + +bool path::is_file() const +{ + return file_exists(to_string()); +} + +// filesystem + +std::string path::read_contents() const +{ + std::ifstream f(to_string()); + std::ostringstream ss; + ss << f.rdbuf(); + + return ss.str(); +} + +// mutators + +path &path::append(const std::string &to_append) +{ + parts_.push_back(to_append); + return *this; +} + +path path::append(const std::string &to_append) const +{ + path copy(*this); + copy.append(to_append); + + return copy; +} + +path &path::append(const path &to_append) +{ + parts_.insert(parts_.end(), to_append.begin(), to_append.end()); + return *this; +} + +path path::append(const path &to_append) const +{ + path copy(*this); + copy.append(to_append); + + return copy; +} + +// iterators + +path::iterator path::begin() +{ + return parts_.begin(); +} + +path::iterator path::end() +{ + return parts_.end(); +} + +path::const_iterator path::begin() const +{ + return cbegin(); +} + +path::const_iterator path::end() const +{ + return cend(); +} + +path::const_iterator path::cbegin() const +{ + return parts_.cbegin(); +} + +path::const_iterator path::cend() const +{ + return parts_.cend(); +} + +path::reverse_iterator path::rbegin() +{ + return parts_.rbegin(); +} + +path::reverse_iterator path::rend() +{ + return parts_.rend(); +} + +path::const_reverse_iterator path::rbegin() const +{ + return crbegin(); +} + +path::const_reverse_iterator path::rend() const +{ + return crend(); +} + +path::const_reverse_iterator path::crbegin() const +{ + return parts_.crbegin(); +} + +path::const_reverse_iterator path::crend() const +{ + return parts_.crend(); +} + +std::string path::to_hash_string() const +{ + return to_string('/'); +} + +} // namespace xlnt diff --git a/source/utils/tests/test_zip_file.hpp b/source/utils/tests/test_zip_file.hpp index 6a4ad4ab..f5190625 100644 --- a/source/utils/tests/test_zip_file.hpp +++ b/source/utils/tests/test_zip_file.hpp @@ -11,7 +11,7 @@ class test_zip_file : public CxxTest::TestSuite public: test_zip_file() { - existing_file = path_helper::get_data_directory("/genuine/empty.xlsx"); + existing_file = path_helper::get_data_directory("genuine/empty.xlsx"); expected_content_types_string = "\r\n"; expected_atxt_string = "\nThis is cell A1 in Sheet 1This is cell G5"; expected_printdir_string = @@ -35,31 +35,19 @@ public: " 22849 14 files\n"; } - void remove_temp_file() + bool files_equal(const xlnt::path &left, const xlnt::path &right) { - std::remove(temp_file.get_filename().c_str()); - } - - void make_temp_directory() - { - } - - void remove_temp_directory() - { - } - - bool files_equal(const std::string &a, const std::string &b) - { - if(a == b) + if(left.to_string() == right.to_string()) { return true; } - std::ifstream stream_a(a, std::ios::binary), stream_b(a, std::ios::binary); + std::ifstream stream_left(left.to_string(), std::ios::binary); + std::ifstream stream_right(right.to_string(), std::ios::binary); - while(stream_a && stream_b) + while(stream_left && stream_right) { - if(stream_a.get() != stream_b.get()) + if(stream_left.get() != stream_right.get()) { return false; } @@ -70,46 +58,46 @@ public: void test_load_file() { - remove_temp_file(); + temporary_file temp_file; xlnt::zip_file f(existing_file); - f.save(temp_file.get_filename()); - TS_ASSERT(files_equal(existing_file, temp_file.get_filename())); - remove_temp_file(); + f.save(temp_file.get_path()); + TS_ASSERT(files_equal(existing_file, temp_file.get_path())); } void test_load_stream() { - remove_temp_file(); - { - std::ifstream in_stream(existing_file, std::ios::binary); - xlnt::zip_file f(in_stream); - std::ofstream out_stream(temp_file.get_filename(), std::ios::binary); - f.save(out_stream); - } - TS_ASSERT(files_equal(existing_file, temp_file.get_filename())); - remove_temp_file(); + temporary_file temp; + + std::ifstream in_stream(existing_file.to_string(), std::ios::binary); + xlnt::zip_file f(in_stream); + std::ofstream out_stream(temp.get_path().to_string(), std::ios::binary); + f.save(out_stream); + out_stream.close(); + + TS_ASSERT(files_equal(existing_file, temp.get_path())); } void test_load_bytes() { - remove_temp_file(); - std::vector source_bytes, result_bytes; - std::ifstream in_stream(existing_file, std::ios::binary); + temporary_file temp_file; + + std::vector source_bytes; + std::ifstream in_stream(existing_file.to_string(), std::ios::binary); + while(in_stream) { - source_bytes.push_back((unsigned char)in_stream.get()); + source_bytes.push_back(static_cast(in_stream.get())); } + xlnt::zip_file f(source_bytes); - f.save(temp_file.get_filename()); + f.save(temp_file.get_path()); xlnt::zip_file f2; - f2.load(temp_file.get_filename()); - result_bytes = std::vector(); + f2.load(temp_file.get_path()); + std::vector result_bytes; f2.save(result_bytes); TS_ASSERT(source_bytes == result_bytes); - - remove_temp_file(); } void test_reset() @@ -120,7 +108,7 @@ public: try { - f.read("[Content_Types].xml"); + f.read(xlnt::path("[Content_Types].xml")); } catch(std::exception e) { @@ -133,28 +121,28 @@ public: try { - f.read("[Content_Types].xml"); + f.read(xlnt::path("[Content_Types].xml")); TS_ASSERT(false); } catch(std::exception e) { } - f.writestr("a", "b"); + f.write_string("b", xlnt::path("a")); f.reset(); TS_ASSERT(f.namelist().empty()); - f.writestr("a", "b"); + f.write_string("b", xlnt::path("a")); - TS_ASSERT_DIFFERS(f.getinfo("a").file_size, 0); + TS_ASSERT_DIFFERS(f.getinfo(xlnt::path("a")).file_size, 0); } void test_getinfo() { xlnt::zip_file f(existing_file); - auto info = f.getinfo("[Content_Types].xml"); - TS_ASSERT(info.filename == "[Content_Types].xml"); + auto info = f.getinfo(xlnt::path("[Content_Types].xml")); + TS_ASSERT(info.filename.to_string() == "[Content_Types].xml"); } void test_infolist() @@ -173,7 +161,7 @@ public: { xlnt::zip_file f(existing_file); std::stringstream ss; - ss << f.open("[Content_Types].xml").rdbuf(); + ss << f.open(xlnt::path("[Content_Types].xml")).rdbuf(); std::string result = ss.str(); TS_ASSERT(result == expected_content_types_string); } @@ -182,149 +170,84 @@ public: { xlnt::zip_file f(existing_file); std::stringstream ss; - ss << f.open("[Content_Types].xml").rdbuf(); + ss << f.open(xlnt::path("[Content_Types].xml")).rdbuf(); std::string result = ss.str(); TS_ASSERT(result == expected_content_types_string); } - void test_extract_current_directory() - { - xlnt::zip_file f(existing_file); - } - - void test_extract_path() - { - xlnt::zip_file f(existing_file); - } - - void test_extractall_current_directory() - { - xlnt::zip_file f(existing_file); - } - - void test_extractall_path() - { - xlnt::zip_file f(existing_file); - } - - void test_extractall_members_name() - { - xlnt::zip_file f(existing_file); - } - - void test_extractall_members_info() - { - xlnt::zip_file f(existing_file); - } - - void test_printdir() - { - xlnt::zip_file f(existing_file); - std::stringstream ss; - f.printdir(ss); - auto printed = ss.str(); - TS_ASSERT_EQUALS(printed, expected_printdir_string); - } void test_read() { xlnt::zip_file f(existing_file); - TS_ASSERT(f.read("[Content_Types].xml") == expected_content_types_string); - TS_ASSERT(f.read(f.getinfo("[Content_Types].xml")) == expected_content_types_string); + TS_ASSERT(f.read(xlnt::path("[Content_Types].xml")) == expected_content_types_string); + TS_ASSERT(f.read(f.getinfo(xlnt::path("[Content_Types].xml"))) == expected_content_types_string); } void test_testzip() { xlnt::zip_file f(existing_file); - TS_ASSERT(f.testzip().first); + TS_ASSERT(!f.check_crc()); } - void test_write() + void test_write_file() { - remove_temp_file(); - + temporary_file temp_file; + xlnt::zip_file f; - auto text_file = path_helper::get_data_directory("/reader/sharedStrings.xml"); - f.write(text_file); - f.write(text_file, "sharedStrings2.xml"); - f.save(temp_file.get_filename()); + auto text_file = path_helper::get_data_directory("reader/sharedStrings.xml"); + f.write_file(text_file); + f.write_file(text_file, xlnt::path("sharedStrings2.xml")); + f.save(temp_file.get_path()); - xlnt::zip_file f2(temp_file.get_filename()); + xlnt::zip_file f2(temp_file.get_path()); for(auto &info : f2.infolist()) { - if(info.filename == "sharedStrings2.xml") + if(info.filename.to_string() == "sharedStrings2.xml") { TS_ASSERT(f2.read(info) == expected_atxt_string); } - else if(info.filename.substr(info.filename.size() - 17) == "sharedStrings.xml") + else if(info.filename.basename() == "sharedStrings.xml") { TS_ASSERT(f2.read(info) == expected_atxt_string); } } - - remove_temp_file(); } - void test_writestr() + void test_write_string() { - remove_temp_file(); - xlnt::zip_file f; - f.writestr("a.txt", "a\na"); + f.write_string("a\na", xlnt::path("a.txt")); xlnt::zip_info info; - info.filename = "b.txt"; + info.filename = xlnt::path("b.txt"); info.date_time.year = 2014; - f.writestr(info, "b\nb"); - f.save(temp_file.get_filename()); + f.write_string("b\nb", info); + + temporary_file temp_file; + f.save(temp_file.get_path()); - xlnt::zip_file f2(temp_file.get_filename()); - TS_ASSERT(f2.read("a.txt") == "a\na"); - TS_ASSERT(f2.read(f2.getinfo("b.txt")) == "b\nb"); - - remove_temp_file(); + xlnt::zip_file f2(temp_file.get_path()); + TS_ASSERT(f2.read(xlnt::path("a.txt")) == "a\na"); + TS_ASSERT(f2.read(f2.getinfo(xlnt::path("b.txt"))) == "b\nb"); } void test_comment() { - remove_temp_file(); - xlnt::zip_file f; f.comment = "comment"; - f.save(temp_file.get_filename()); + temporary_file temp_file; + f.save(temp_file.get_path()); - xlnt::zip_file f2(temp_file.get_filename()); + xlnt::zip_file f2(temp_file.get_path()); TS_ASSERT(f2.comment == "comment"); xlnt::zip_file f3; std::vector bytes { 1, 2, 3 }; TS_ASSERT_THROWS(f3.load(bytes), std::runtime_error); - - remove_temp_file(); - } - - void test_extract() - { - xlnt::zip_file f; - f.load(path_helper::get_data_directory("/genuine/empty.xlsx")); - - auto expected = path_helper::get_working_directory() + "/xl/styles.xml"; - - TS_ASSERT(!path_helper::file_exists(expected)); - f.extract("xl/styles.xml"); - TS_ASSERT(path_helper::file_exists(expected)); - path_helper::delete_file(expected); - - auto info = f.getinfo("xl/styles.xml"); - TS_ASSERT(!path_helper::file_exists(expected)); - f.extract(info); - TS_ASSERT(path_helper::file_exists(expected)); - path_helper::delete_file(expected); } private: - temporary_file temp_file; - std::string existing_file; + xlnt::path existing_file; std::string expected_content_types_string; std::string expected_atxt_string; std::string expected_printdir_string; diff --git a/source/workbook/tests/test_read.hpp b/source/workbook/tests/test_read.hpp index 16d35c6b..35c3ba35 100644 --- a/source/workbook/tests/test_read.hpp +++ b/source/workbook/tests/test_read.hpp @@ -21,7 +21,7 @@ public: xlnt::workbook standard_workbook() { xlnt::workbook wb; - auto path = path_helper::get_data_directory("/genuine/empty.xlsx"); + auto path = path_helper::get_data_directory("genuine/empty.xlsx"); wb.load(path); return wb; @@ -34,8 +34,8 @@ public: void test_read_standard_workbook_from_fileobj() { - auto path = path_helper::get_data_directory("/genuine/empty.xlsx"); - std::ifstream fo(path, std::ios::binary); + auto path = path_helper::get_data_directory("genuine/empty.xlsx"); + std::ifstream fo(path.to_string(), std::ios::binary); xlnt::workbook wb; TS_ASSERT(wb.load(fo)); @@ -55,7 +55,7 @@ public: void test_read_nostring_workbook() { - auto path = path_helper::get_data_directory("/genuine/empty-no-string.xlsx"); + auto path = path_helper::get_data_directory("genuine/empty-no-string.xlsx"); xlnt::workbook wb; TS_ASSERT_THROWS_NOTHING(wb.load(path)); @@ -63,42 +63,28 @@ public: void test_read_empty_file() { - auto path = path_helper::get_data_directory("/reader/null_file.xlsx"); - xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - TS_ASSERT_THROWS(serializer.load_workbook(path), xlnt::invalid_file); + TS_ASSERT_THROWS(wb.load(path_helper::get_data_directory("reader/null_file.xlsx")), xlnt::invalid_file); } void test_read_empty_archive() { - auto path = path_helper::get_data_directory("/reader/null_archive.xlsx"); - - xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - TS_ASSERT_THROWS(serializer.load_workbook(path), xlnt::invalid_file); + xlnt::workbook wb; + TS_ASSERT_THROWS(wb.load(path_helper::get_data_directory("reader/null_archive.xlsx")), xlnt::invalid_file); } void test_read_workbook_with_no_properties() { - auto path = path_helper::get_data_directory("/genuine/empty_with_no_properties.xlsx"); - - xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path); + xlnt::workbook wb; + TS_ASSERT_THROWS_NOTHING(wb.load(path_helper::get_data_directory("genuine/empty_with_no_properties.xlsx"))); } xlnt::workbook workbook_with_styles() { - auto path = path_helper::get_data_directory("/genuine/empty-with-styles.xlsx"); + xlnt::workbook wb; + wb.load(path_helper::get_data_directory("genuine/empty-with-styles.xlsx")); - xlnt::workbook wb; - wb.load(path); - - return wb; + return wb; } void test_read_workbook_with_styles_general() @@ -148,12 +134,9 @@ public: void test_read_charset_excel() { - auto path = path_helper::get_data_directory("/reader/charset-excel.xlsx"); - xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path); + auto path = path_helper::get_data_directory("reader/charset-excel.xlsx"); + wb.load(path); auto ws = wb["Sheet1"]; auto val = ws.get_cell("A1").get_value(); @@ -162,13 +145,10 @@ public: void test_read_shared_strings_max_range() { - auto path = path_helper::get_data_directory("/reader/shared_strings-max_range.xlsx"); - xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path); - + const auto path = path_helper::get_data_directory("reader/shared_strings-max_range.xlsx"); + wb.load(path); + auto ws = wb["Sheet1"]; auto val = ws.get_cell("A1").get_value(); TS_ASSERT_EQUALS(val, "Donald"); @@ -176,12 +156,10 @@ public: void test_read_shared_strings_multiple_r_nodes() { - auto path = path_helper::get_data_directory("/reader/shared_strings-multiple_r_nodes.xlsx"); + auto path = path_helper::get_data_directory("reader/shared_strings-multiple_r_nodes.xlsx"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path); + wb.load(path); auto ws = wb["Sheet1"]; auto val = ws.get_cell("A1").get_value(); @@ -190,34 +168,30 @@ public: xlnt::workbook date_mac_1904() { - auto path = path_helper::get_data_directory("/reader/date_1904.xlsx"); - xlnt::workbook wb; - wb.load(path); - + wb.load(path_helper::get_data_directory("reader/date_1904.xlsx")); + return wb; } xlnt::workbook date_std_1900() { - auto path = path_helper::get_data_directory("/reader/date_1900.xlsx"); - xlnt::workbook wb; - wb.load(path); - + wb.load(path_helper::get_data_directory("reader/date_1900.xlsx")); + return wb; } void test_read_win_base_date() { auto wb = date_std_1900(); - TS_ASSERT_EQUALS(wb.get_properties().excel_base_date, xlnt::calendar::windows_1900); + TS_ASSERT_EQUALS(wb.get_base_date(), xlnt::calendar::windows_1900); } void test_read_mac_base_date() { auto wb = date_mac_1904(); - TS_ASSERT_EQUALS(wb.get_properties().excel_base_date, xlnt::calendar::mac_1904); + TS_ASSERT_EQUALS(wb.get_base_date(), xlnt::calendar::mac_1904); } void test_read_date_style_win() @@ -260,7 +234,7 @@ public: void test_read_no_theme() { - auto path = path_helper::get_data_directory("/genuine/libreoffice_nrt.xlsx"); + auto path = path_helper::get_data_directory("genuine/libreoffice_nrt.xlsx"); xlnt::workbook wb; TS_ASSERT_THROWS_NOTHING(wb.load(path)); @@ -269,7 +243,7 @@ public: void _test_read_complex_formulae() { /* - auto path = PathHelper::GetDataDirectory("/reader/formulae.xlsx"); + auto path = PathHelper::GetDataDirectory("reader/formulae.xlsx"); auto wb = xlnt::reader::load_workbook(path); auto ws = wb.get_active_sheet(); @@ -325,12 +299,11 @@ public: void test_data_only() { - auto path = path_helper::get_data_directory("/reader/formulae.xlsx"); + auto path = path_helper::get_data_directory("reader/formulae.xlsx"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path, false, true); + wb.set_data_only(true); + wb.load(path); auto ws = wb.get_active_sheet(); @@ -362,7 +335,7 @@ public: {xlnt::relationship::type::styles, "rId4", "styles.xml"} }; - auto path = path_helper::get_data_directory("/reader/bug137.xlsx"); + auto path = path_helper::get_data_directory("reader/bug137.xlsx"); xlnt::zip_file archive(path); xlnt::relationship_serializer serializer(archive); @@ -382,7 +355,7 @@ public: {xlnt::relationship::type::theme, "rId4", "/xl/theme/theme.xml"} }; - auto path = path_helper::get_data_directory("/reader/bug304.xlsx"); + auto path = path_helper::get_data_directory("reader/bug304.xlsx"); xlnt::zip_file archive(path); xlnt::relationship_serializer serializer(archive); @@ -410,7 +383,7 @@ public: {"/docProps/app.xml", "application/vnd.openxmlformats-officedocument.extended-properties+xml"} }; - auto path = path_helper::get_data_directory("/reader/contains_chartsheets.xlsx"); + auto path = path_helper::get_data_directory("reader/contains_chartsheets.xlsx"); xlnt::workbook wb; wb.load(path); @@ -425,8 +398,8 @@ public: for(std::size_t i = 0; i < expected.size(); i++) { - TS_ASSERT(wb.get_manifest().has_override_type(expected[i].first)); - TS_ASSERT_EQUALS(wb.get_manifest().get_override_type(expected[i].first), expected[i].second); + TS_ASSERT(wb.get_manifest().has_override_type(xlnt::path(expected[i].first))); + TS_ASSERT_EQUALS(wb.get_manifest().get_override_type(xlnt::path(expected[i].first)), expected[i].second); } } @@ -439,12 +412,11 @@ public: for(const auto &expected : test_cases) { std::tie(guess, dtype) = expected; - auto path = path_helper::get_data_directory("/genuine/guess_types.xlsx"); + auto path = path_helper::get_data_directory("genuine/guess_types.xlsx"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path, guess); + wb.set_guess_types(guess); + wb.load(path); auto ws = wb.get_active_sheet(); TS_ASSERT(ws.get_cell("D2").get_data_type() == dtype); @@ -453,12 +425,10 @@ public: void test_read_autofilter() { - auto path = path_helper::get_data_directory("/reader/bug275.xlsx"); + auto path = path_helper::get_data_directory("reader/bug275.xlsx"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - serializer.load_workbook(path); + wb.load(path); auto ws = wb.get_active_sheet(); TS_ASSERT_EQUALS(ws.get_auto_filter().to_string(), "A1:B6"); @@ -466,32 +436,23 @@ public: void test_bad_formats_xlsb() { - auto path = path_helper::get_data_directory("/genuine/a.xlsb"); - + auto path = path_helper::get_data_directory("genuine/a.xlsb"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - TS_ASSERT_THROWS(serializer.load_workbook(path), xlnt::invalid_file); + TS_ASSERT_THROWS(wb.load(path), xlnt::invalid_file); } void test_bad_formats_xls() { - auto path = path_helper::get_data_directory("/genuine/a.xls"); - + auto path = path_helper::get_data_directory("genuine/a.xls"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - TS_ASSERT_THROWS(serializer.load_workbook(path), xlnt::invalid_file); + TS_ASSERT_THROWS(wb.load(path), xlnt::invalid_file); } void test_bad_formats_no() { - auto path = path_helper::get_data_directory("/genuine/a.no-format"); - + auto path = path_helper::get_data_directory("genuine/a.no-format"); xlnt::workbook wb; - xlnt::excel_serializer serializer(wb); - - TS_ASSERT_THROWS(serializer.load_workbook(path), xlnt::invalid_file); + TS_ASSERT_THROWS(wb.load(path), xlnt::invalid_file); } @@ -550,7 +511,7 @@ public: void test_read_inlinestr() { xlnt::workbook wb; - wb.load(path_helper::get_data_directory("/genuine/empty.xlsx")); + wb.load(path_helper::get_data_directory("genuine/empty.xlsx")); TS_ASSERT_EQUALS(wb.get_sheet_by_index(0).get_cell("A1").get_value(), "This is cell A1 in Sheet 1"); } diff --git a/source/workbook/tests/test_style_reader.hpp b/source/workbook/tests/test_style_reader.hpp index 37c8f85f..17c465f5 100644 --- a/source/workbook/tests/test_style_reader.hpp +++ b/source/workbook/tests/test_style_reader.hpp @@ -14,7 +14,7 @@ public: void test_complex_formatting() { xlnt::workbook wb; - wb.load(path_helper::get_data_directory("/reader/formatting.xlsx")); + wb.load(path_helper::get_data_directory("reader/formatting.xlsx")); // border_style auto ws = wb.get_active_sheet(); diff --git a/source/workbook/tests/test_style_writer.hpp b/source/workbook/tests/test_style_writer.hpp index 80c64d9c..c616fe5b 100644 --- a/source/workbook/tests/test_style_writer.hpp +++ b/source/workbook/tests/test_style_writer.hpp @@ -3,25 +3,23 @@ #include #include -#include -#include -#include -#include +#include class test_style_writer : public CxxTest::TestSuite { public: bool style_xml_matches(const std::string &expected_string, xlnt::workbook &wb) { - xlnt::excel_serializer excel_serializer(wb); - xlnt::style_serializer style_serializer(excel_serializer.get_stylesheet()); - pugi::xml_document observed; - style_serializer.write_stylesheet(observed); - pugi::xml_document expected; - expected.load(expected_string.c_str()); + std::vector bytes; + wb.save(bytes); - auto comparison = xml_helper::compare_xml(expected.root(), observed.root()); - return (bool)comparison; + xlnt::zip_file archive; + archive.load(bytes); + + pugi::xml_document observed; + observed.load(archive.read(xlnt::constants::part_styles()).c_str()); + + return xml_helper::string_matches_document(expected_string, observed); } void test_write_custom_number_format() @@ -100,8 +98,8 @@ public: b.set_side(xlnt::border::side::top, prop); ws.get_cell("D10").set_border(b); - std::string expected = path_helper::read_file(path_helper::get_data_directory("/writer/expected/simple-styles.xml")); - TS_ASSERT(style_xml_matches(expected, wb)); + auto expected = path_helper::get_data_directory("writer/expected/simple-styles.xml"); + TS_ASSERT(style_xml_matches(expected.read_contents(), wb)); } void test_empty_workbook() diff --git a/source/workbook/tests/test_theme.hpp b/source/workbook/tests/test_theme.hpp index c0b7ce28..4c237bdd 100644 --- a/source/workbook/tests/test_theme.hpp +++ b/source/workbook/tests/test_theme.hpp @@ -16,7 +16,8 @@ public: xlnt::workbook wb; xlnt::theme_serializer serializer; pugi::xml_document xml; - serializer.write_theme(wb.get_loaded_theme(), xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/theme1.xml", xml)); + serializer.write_theme(wb.get_theme(), xml); + + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/theme1.xml"), xml)); } }; diff --git a/source/workbook/tests/test_workbook.hpp b/source/workbook/tests/test_workbook.hpp index 4f54d8c0..23953da4 100644 --- a/source/workbook/tests/test_workbook.hpp +++ b/source/workbook/tests/test_workbook.hpp @@ -181,10 +181,10 @@ public: auto sheet = book.get_active_sheet(); sheet.get_cell("A1").set_value(today); temporary_file temp_file; - book.save(temp_file.get_filename()); + book.save(temp_file.get_path()); xlnt::workbook test_book; - test_book.load(temp_file.get_filename()); + test_book.load(temp_file.get_path()); auto test_sheet = test_book.get_active_sheet(); TS_ASSERT_EQUALS(test_sheet.get_cell("A1").get_value(), today); @@ -198,10 +198,10 @@ public: auto sheet = book.get_active_sheet(); sheet.get_cell("A1").set_value(float_value); temporary_file temp_file; - book.save(temp_file.get_filename()); + book.save(temp_file.get_path()); xlnt::workbook test_book; - test_book.load(temp_file.get_filename()); + test_book.load(temp_file.get_path()); auto test_sheet = test_book.get_active_sheet(); TS_ASSERT_EQUALS(test_sheet.get_cell("A1").get_value(), float_value); @@ -265,13 +265,13 @@ public: xlnt::override_type o; TS_ASSERT(o.get_content_type().empty()); - TS_ASSERT(o.get_part_name().empty()); + TS_ASSERT(o.get_part().to_string().empty()); xlnt::manifest m; TS_ASSERT(!m.has_default_type("xml")); TS_ASSERT_THROWS(m.get_default_type("xml"), std::out_of_range); - TS_ASSERT(!m.has_override_type("xl/workbook.xml")); - TS_ASSERT_THROWS(m.get_override_type("xl/workbook.xml"), std::out_of_range); + TS_ASSERT(!m.has_override_type(xlnt::path("xl/workbook.xml"))); + TS_ASSERT_THROWS(m.get_override_type(xlnt::path("xl/workbook.xml")), std::out_of_range); } void test_get_bad_relationship() @@ -322,19 +322,13 @@ public: const auto &wb_const = wb; //TODO these aren't tests... - wb_const.get_app_properties(); wb_const.get_manifest(); - TS_ASSERT(!wb.has_loaded_theme()); + TS_ASSERT(wb.has_theme()); wb.create_style("style1"); wb.get_style("style1"); wb_const.get_style("style1"); wb.get_style_by_id(0); } - - void test_limits() - { - - } }; diff --git a/source/workbook/tests/test_write.hpp b/source/workbook/tests/test_write.hpp index f8896c9c..c1eb1e1b 100644 --- a/source/workbook/tests/test_write.hpp +++ b/source/workbook/tests/test_write.hpp @@ -21,16 +21,16 @@ public: wbk.get_active_sheet().get_cell("A2").set_value("xlnt"); wbk.get_active_sheet().get_cell("B5").set_value(88); wbk.get_active_sheet().get_cell("B5").set_number_format(xlnt::number_format::percentage_00()); - wbk.save(temp_file.get_filename()); + wbk.save(temp_file.get_path()); - if(path_helper::file_exists(temp_file.get_filename())) + if(temp_file.get_path().exists()) { - path_helper::delete_file(temp_file.get_filename()); + path_helper::delete_file(temp_file.get_path()); } - TS_ASSERT(!path_helper::file_exists(temp_file.get_filename())); - wb_.save(temp_file.get_filename()); - TS_ASSERT(path_helper::file_exists(temp_file.get_filename())); + TS_ASSERT(!temp_file.get_path().exists()); + wb_.save(temp_file.get_path()); + TS_ASSERT(temp_file.get_path().exists()); } void test_write_virtual_workbook() @@ -50,9 +50,9 @@ public: xlnt::relationship_serializer serializer(archive); serializer.write_relationships(wb.get_relationships(), "xl/workbook.xml"); pugi::xml_document xml; - xml.load(archive.read("xl/_rels/workbook.xml.rels").c_str()); + xml.load(archive.read(xlnt::path("xl/_rels/workbook.xml.rels")).c_str()); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/workbook.xml.rels", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/workbook.xml.rels"), xml)); } void test_write_workbook() @@ -62,7 +62,7 @@ public: pugi::xml_document xml; serializer.write_workbook(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/workbook.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/workbook.xml"), xml)); } void test_write_string_table() @@ -77,7 +77,7 @@ public: pugi::xml_document xml; xlnt::shared_strings_serializer::write_shared_strings(wb.get_shared_strings(), xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sharedStrings.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sharedStrings.xml"), xml)); } void test_write_worksheet() @@ -89,7 +89,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1.xml"), xml)); } void test_write_hidden_worksheet() @@ -102,7 +102,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1.xml"), xml)); } void test_write_bool() @@ -115,7 +115,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_bool.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_bool.xml"), xml)); } void test_write_formula() @@ -129,7 +129,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_formula.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_formula.xml"), xml)); } void test_write_height() @@ -142,7 +142,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_height.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_height.xml"), xml)); } void test_write_hyperlink() @@ -157,7 +157,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_hyperlink.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_hyperlink.xml"), xml)); } void test_write_hyperlink_rels() @@ -175,9 +175,9 @@ public: xlnt::relationship_serializer serializer(archive); serializer.write_relationships(ws.get_relationships(), "xl/worksheets/sheet1.xml"); pugi::xml_document xml; - xml.load(archive.read("xl/worksheets/_rels/sheet1.xml.rels").c_str()); + xml.load(archive.read(xlnt::path("xl/worksheets/_rels/sheet1.xml.rels")).c_str()); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_hyperlink.xml.rels", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_hyperlink.xml.rels"), xml)); } void _test_write_hyperlink_image_rels() @@ -205,13 +205,13 @@ public: pugi::xml_document xml; ws_serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_auto_filter.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_auto_filter.xml"), xml)); xlnt::workbook_serializer wb_serializer(wb); pugi::xml_document xml2; wb_serializer.write_workbook(xml2); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/workbook_auto_filter.xml", xml2)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/workbook_auto_filter.xml"), xml2)); } void test_write_auto_filter_filter_column() @@ -234,7 +234,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_freeze_panes_horiz.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_freeze_panes_horiz.xml"), xml)); } void test_freeze_panes_vert() @@ -247,7 +247,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_freeze_panes_vert.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_freeze_panes_vert.xml"), xml)); } void test_freeze_panes_both() @@ -260,7 +260,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/sheet1_freeze_panes_both.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/sheet1_freeze_panes_both.xml"), xml)); } void test_long_number() @@ -272,7 +272,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/long_number.xml", xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/long_number.xml"), xml)); } void test_short_number() @@ -284,12 +284,7 @@ public: pugi::xml_document xml; serializer.write_worksheet(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory() + "/writer/expected/short_number.xml", xml)); - } - - void _test_write_images() - { - TS_SKIP("not implemented"); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/short_number.xml"), xml)); } void test_write_page_setup() @@ -318,7 +313,7 @@ public: xlnt::zip_file archive; archive.load(bytes); - auto worksheet_xml_string = archive.read("xl/worksheets/sheet1.xml"); + auto worksheet_xml_string = archive.read(xlnt::path("xl/worksheets/sheet1.xml")); pugi::xml_document worksheet_xml; worksheet_xml.load(worksheet_xml_string.c_str()); @@ -342,7 +337,7 @@ public: " " ""; - TS_ASSERT(xml_helper::compare_xml(expected, worksheet_xml)); + TS_ASSERT(xml_helper::string_matches_document(expected, worksheet_xml)); } private: diff --git a/source/workbook/tests/test_write_workbook.hpp b/source/workbook/tests/test_write_workbook.hpp index 126e20ad..2b72b52a 100644 --- a/source/workbook/tests/test_write_workbook.hpp +++ b/source/workbook/tests/test_write_workbook.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -22,7 +23,7 @@ public: pugi::xml_document xml; serializer.write_workbook(xml); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory("/writer/expected/workbook_auto_filter.xml"), xml)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/workbook_auto_filter.xml"), xml)); } void test_write_hidden_worksheet() @@ -53,7 +54,7 @@ public: pugi::xml_document expected; expected.load(expected_string.c_str()); - TS_ASSERT(xml_helper::compare_xml(expected.child("workbook").child("sheets"), + TS_ASSERT(xml_helper::compare_xml_nodes(expected.child("workbook").child("sheets"), xml.child("workbook").child("sheets"))); } @@ -74,10 +75,10 @@ public: xlnt::workbook wb; temporary_file file; + TS_ASSERT(!file.get_path().exists()) xlnt::excel_serializer serializer(wb); - serializer.save_workbook(file.get_filename()); - - TS_ASSERT(path_helper::file_exists(file.get_filename())); + wb.save(file.get_path()); + TS_ASSERT(file.get_path().exists()); } void test_write_virtual_workbook() @@ -102,9 +103,9 @@ public: xlnt::relationship_serializer serializer(archive); serializer.write_relationships(wb.get_relationships(), "xl/workbook.xml"); pugi::xml_document observed; - observed.load(archive.read("xl/_rels/workbook.xml.rels").c_str()); + observed.load(archive.read(xlnt::path("xl/_rels/workbook.xml.rels")).c_str()); - TS_ASSERT(xml_helper::compare_xml(path_helper::get_data_directory("/writer/expected/workbook.xml.rels"), observed)); + TS_ASSERT(xml_helper::file_matches_document(path_helper::get_data_directory("writer/expected/workbook.xml.rels"), observed)); } void test_write_workbook_part() @@ -113,9 +114,9 @@ public: xlnt::workbook_serializer serializer(wb); pugi::xml_document xml; serializer.write_workbook(xml); - auto filename = path_helper::get_data_directory("/writer/expected/workbook.xml"); - TS_ASSERT(xml_helper::compare_xml(filename, xml)); + auto filename = path_helper::get_data_directory("writer/expected/workbook.xml"); + TS_ASSERT(xml_helper::file_matches_document(filename, xml)); } void test_write_named_range() @@ -132,7 +133,7 @@ public: "'Sheet'!$A$1:$B$5" ""; - TS_ASSERT(xml_helper::compare_xml(expected, xml)); + TS_ASSERT(xml_helper::string_matches_document(expected, xml)); } void test_read_workbook_code_name() @@ -165,7 +166,7 @@ public: pugi::xml_document expected_xml; expected_xml.load(expected.c_str()); - TS_ASSERT(xml_helper::compare_xml(expected_xml.child("workbook").child("workbookPr"), + TS_ASSERT(xml_helper::compare_xml_nodes(expected_xml.child("workbook").child("workbookPr"), xml.child("workbook").child("workbookPr"))); } @@ -176,7 +177,7 @@ public: xlnt::relationship_serializer serializer(archive); serializer.write_relationships(wb.get_root_relationships(), ""); pugi::xml_document observed; - observed.load(archive.read("_rels/.rels").c_str()); + observed.load(archive.read(xlnt::path("_rels/.rels")).c_str()); std::string expected = "" @@ -185,7 +186,7 @@ public: " " ""; - TS_ASSERT(xml_helper::compare_xml(expected, observed)); + TS_ASSERT(xml_helper::string_matches_document(expected, observed)); } void test_write_shared_strings_with_runs() @@ -223,44 +224,27 @@ public: " " ""; - TS_ASSERT(xml_helper::compare_xml(expected, xml)); + TS_ASSERT(xml_helper::string_matches_document(expected, xml)); } void test_write_worksheet_order() { - auto path = path_helper::get_data_directory("/genuine/tab_order.xlsx"); + auto path = path_helper::get_data_directory("genuine/tab_order.xlsx"); // Load an original workbook produced by Excel xlnt::workbook wb_src; - { - xlnt::excel_serializer serializer(wb_src); - serializer.load_workbook(path); - } + wb_src.load(path); // Save it to a new file, unmodified temporary_file file; - { - xlnt::excel_serializer serializer(wb_src); - serializer.save_workbook(file.get_filename()); - TS_ASSERT(path_helper::file_exists(file.get_filename())); - } + wb_src.save(file.get_path()); + TS_ASSERT(file.get_path().exists()); // Load it again xlnt::workbook wb_dst; - { - xlnt::excel_serializer serializer(wb_dst); - serializer.load_workbook(file.get_filename()); - } + wb_dst.load(file.get_path()); - // Make sure the number of worksheets is the same - auto count_src = std::distance(wb_src.begin(), wb_src.end()); - auto count_dst = std::distance(wb_dst.begin(), wb_dst.end()); - TS_ASSERT(count_src == count_dst); - - // Make sure the title of the first sheet matches - auto ws1title_src = wb_src[0].get_title(); - auto ws1title_dst = wb_dst[0].get_title(); - TS_ASSERT(ws1title_src.compare(ws1title_dst) == 0); + TS_ASSERT_EQUALS(wb_src.get_sheet_titles(), wb_dst.get_sheet_titles()); } private: diff --git a/source/workbook/workbook.cpp b/source/workbook/workbook.cpp index 32d4a545..ee3f5c7f 100644 --- a/source/workbook/workbook.cpp +++ b/source/workbook/workbook.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -57,39 +58,64 @@ #include namespace xlnt { -namespace detail { -workbook_impl::workbook_impl() - : active_sheet_index_(0), - guess_types_(false), - data_only_(false) +workbook workbook::minimal() { + auto impl = new detail::workbook_impl(); + workbook wb(impl); + + return wb; } -} // namespace detail - -workbook::workbook() : d_(new detail::workbook_impl()) +workbook workbook::empty_excel() { - create_sheet(); - - create_relationship("rId2", "styles.xml", relationship::type::styles); - create_relationship("rId3", "theme/theme1.xml", relationship::type::theme); - - d_->manifest_.add_default_type("rels", "application/vnd.openxmlformats-package.relationships+xml"); - d_->manifest_.add_default_type("xml", "application/xml"); - - d_->manifest_.add_override_type("/" + constants::part_workbook(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"); - d_->manifest_.add_override_type("/" + constants::part_theme(), "application/vnd.openxmlformats-officedocument.theme+xml"); - d_->manifest_.add_override_type("/" + constants::part_styles(), "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); - d_->manifest_.add_override_type("/" + constants::part_core(), "application/vnd.openxmlformats-package.core-properties+xml"); - d_->manifest_.add_override_type("/" + constants::part_app(), "application/vnd.openxmlformats-officedocument.extended-properties+xml"); - - add_format(format()); - create_style("Normal"); - d_->stylesheet_.format_styles.front() = "Normal"; + auto impl = new detail::workbook_impl(); + xlnt::workbook wb(impl); - xlnt::fill gray125 = xlnt::fill::pattern(xlnt::pattern_fill::type::gray125); - d_->stylesheet_.fills.push_back(gray125); + wb.set_application("Microsoft Excel"); + wb.create_sheet(); + wb.add_format(format()); + wb.create_style("Normal"); + wb.set_theme(theme()); + + auto &manifest = wb.d_->manifest_; + manifest.add_override_type(constants::part_workbook(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"); + manifest.add_override_type(constants::part_theme(), "application/vnd.openxmlformats-officedocument.theme+xml"); + manifest.add_override_type(constants::part_styles(), "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); + manifest.add_override_type(constants::part_core(), "application/vnd.openxmlformats-package.core-properties+xml"); + manifest.add_override_type(constants::part_app(), "application/vnd.openxmlformats-officedocument.extended-properties+xml"); + + wb.d_->stylesheet_.format_styles.front() = "Normal"; + + xlnt::fill gray125 = xlnt::fill::pattern(xlnt::pattern_fill::type::gray125); + wb.d_->stylesheet_.fills.push_back(gray125); + + return wb; +} + +workbook workbook::empty_libre_office() +{ + auto impl = new detail::workbook_impl(); + workbook wb(impl); + + return wb; +} + +workbook workbook::empty_numbers() +{ + auto impl = new detail::workbook_impl(); + workbook wb(impl); + + return wb; +} + +workbook::workbook() +{ + swap(*this, empty_excel()); +} + +workbook::workbook(detail::workbook_impl *impl) : d_(impl) +{ } const worksheet workbook::get_sheet_by_name(const std::string &name) const @@ -120,17 +146,29 @@ worksheet workbook::get_sheet_by_name(const std::string &name) worksheet workbook::get_sheet_by_index(std::size_t index) { - return worksheet(&d_->worksheets_[index]); + auto iter = d_->worksheets_.begin(); + + for (std::size_t i = 0; i < index; ++i, ++iter) + { + } + + return worksheet(&*iter); } const worksheet workbook::get_sheet_by_index(std::size_t index) const { - return worksheet(&d_->worksheets_.at(index)); + auto iter = d_->worksheets_.begin(); + + for (std::size_t i = 0; i < index; ++i, ++iter) + { + } + + return worksheet(&*iter); } worksheet workbook::get_active_sheet() { - return worksheet(&d_->worksheets_[d_->active_sheet_index_]); + return get_sheet_by_index(d_->active_sheet_index_); } bool workbook::has_named_range(const std::string &name) const @@ -157,13 +195,13 @@ worksheet workbook::create_sheet() auto sheet_id = d_->worksheets_.size() + 1; std::string sheet_filename = "sheet" + std::to_string(sheet_id) + ".xml"; + path sheet_path = constants::package_worksheets().append(sheet_filename); d_->worksheets_.push_back(detail::worksheet_impl(this, sheet_id, title)); create_relationship("rId" + std::to_string(sheet_id), "worksheets/" + sheet_filename, relationship::type::worksheet); - d_->manifest_.add_override_type("/" + constants::package_worksheets() - + "/" + sheet_filename, + d_->manifest_.add_override_type(sheet_path, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"); return worksheet(&d_->worksheets_.back()); @@ -185,7 +223,13 @@ void workbook::copy_sheet(xlnt::worksheet worksheet, std::size_t index) if (index != d_->worksheets_.size() - 1) { - d_->worksheets_.insert(d_->worksheets_.begin() + index, d_->worksheets_.back()); + auto iter = d_->worksheets_.begin(); + + for (std::size_t i = 0; i < index; ++i, ++iter) + { + } + + d_->worksheets_.insert(iter, d_->worksheets_.back()); d_->worksheets_.pop_back(); } } @@ -257,10 +301,15 @@ bool workbook::load(const std::vector &data) bool workbook::load(const std::string &filename) { - excel_serializer serializer_(*this); - serializer_.load_workbook(filename); + return load(path(filename)); +} - return true; +bool workbook::load(const path &filename) +{ + excel_serializer serializer_(*this); + serializer_.load_workbook(filename); + + return true; } void workbook::set_guess_types(bool guess) @@ -320,11 +369,17 @@ worksheet workbook::create_sheet(std::size_t index) if (index != d_->worksheets_.size() - 1) { - d_->worksheets_.insert(d_->worksheets_.begin() + index, d_->worksheets_.back()); + auto iter = d_->worksheets_.begin(); + + for (std::size_t i = 0; i < index; ++i, ++iter) + { + } + + d_->worksheets_.insert(iter, d_->worksheets_.back()); d_->worksheets_.pop_back(); } - return worksheet(&d_->worksheets_[index]); + return get_sheet_by_index(index); } worksheet workbook::create_sheet_with_rel(const std::string &title, const relationship &rel) @@ -384,7 +439,7 @@ worksheet workbook::operator[](const std::string &name) worksheet workbook::operator[](std::size_t index) { - return worksheet(&d_->worksheets_.at(index)); + return get_sheet_by_index(index); } void workbook::clear() @@ -392,7 +447,6 @@ void workbook::clear() d_->worksheets_.clear(); d_->relationships_.clear(); d_->active_sheet_index_ = 0; - d_->properties_ = document_properties(); d_->manifest_.clear(); clear_styles(); clear_formats(); @@ -408,10 +462,15 @@ bool workbook::save(std::vector &data) bool workbook::save(const std::string &filename) { - excel_serializer serializer(*this); - serializer.save_workbook(filename); + return save(path(filename)); +} - return true; +bool workbook::save(const path &filename) +{ + excel_serializer serializer(*this); + serializer.save_workbook(filename); + + return true; } bool workbook::operator==(const workbook &rhs) const @@ -429,41 +488,26 @@ const std::vector &xlnt::workbook::get_relationships() const return d_->relationships_; } -document_properties &workbook::get_properties() -{ - return d_->properties_; -} - -const document_properties &workbook::get_properties() const -{ - return d_->properties_; -} - -app_properties &workbook::get_app_properties() -{ - return d_->app_properties_; -} - -const app_properties &workbook::get_app_properties() const -{ - return d_->app_properties_; -} - - void swap(workbook &left, workbook &right) { using std::swap; swap(left.d_, right.d_); - for (auto ws : left) - { - ws.set_parent(left); - } + if (left.d_ != nullptr) + { + for (auto ws : left) + { + ws.set_parent(left); + } + } - for (auto ws : right) - { - ws.set_parent(right); - } + if (right.d_ != nullptr) + { + for (auto ws : right) + { + ws.set_parent(right); + } + } } workbook &workbook::operator=(workbook other) @@ -472,7 +516,7 @@ workbook &workbook::operator=(workbook other) return *this; } -workbook::workbook(workbook &&other) : workbook() +workbook::workbook(workbook &&other) : workbook(nullptr) { swap(*this, other); } @@ -505,16 +549,42 @@ void workbook::set_code_name(const std::string & /*code_name*/) { } -bool workbook::has_loaded_theme() const +bool workbook::has_theme() const { - return false; + return d_->has_theme_; } -const theme &workbook::get_loaded_theme() const +const theme &workbook::get_theme() const { return d_->theme_; } +void workbook::set_theme(const theme &value) +{ + if (!d_->has_theme_) + { + bool has_theme_relationship = false; + + for (const auto &rel : get_relationships()) + { + if (rel.get_type() == relationship::type::theme) + { + has_theme_relationship = true; + break; + } + } + + if (!has_theme_relationship) + { + create_relationship(next_relationship_id(), "theme/theme1.xml", relationship::type::theme); + d_->manifest_.add_override_type(constants::part_theme(), "application/vnd.openxmlformats-officedocument.spreadsheetml.theme+xml"); + } + } + + d_->has_theme_ = true; + d_->theme_ = value; +} + std::vector workbook::get_named_ranges() const { std::vector named_ranges; @@ -532,11 +602,51 @@ std::vector workbook::get_named_ranges() const std::size_t workbook::add_format(const format &to_add) { + if (d_->stylesheet_.formats.empty()) + { + bool has_style_relationship = false; + + for (const auto &rel : get_relationships()) + { + if (rel.get_type() == relationship::type::styles) + { + has_style_relationship = true; + break; + } + } + + if (!has_style_relationship) + { + create_relationship(next_relationship_id(), "styles.xml", relationship::type::styles); + d_->manifest_.add_override_type(constants::part_styles(), "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); + } + } + return d_->stylesheet_.add_format(to_add); } std::size_t workbook::add_style(const style &to_add) { + if (d_->stylesheet_.formats.empty()) + { + bool has_style_relationship = false; + + for (const auto &rel : get_relationships()) + { + if (rel.get_type() == relationship::type::styles) + { + has_style_relationship = true; + break; + } + } + + if (!has_style_relationship) + { + create_relationship(next_relationship_id(), "styles.xml", relationship::type::styles); + d_->manifest_.add_override_type(constants::part_styles(), "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); + } + } + return d_->stylesheet_.add_style(to_add); } @@ -604,11 +714,11 @@ const std::vector &workbook::get_root_relationships() const if (d_->root_relationships_.empty()) { d_->root_relationships_.push_back( - relationship(relationship::type::core_properties, "rId1", constants::part_core())); + relationship(relationship::type::core_properties, "rId1", constants::part_core().to_string())); d_->root_relationships_.push_back( - relationship(relationship::type::extended_properties, "rId2", constants::part_app())); + relationship(relationship::type::extended_properties, "rId2", constants::part_app().to_string())); d_->root_relationships_.push_back( - relationship(relationship::type::office_document, "rId3", constants::part_workbook())); + relationship(relationship::type::office_document, "rId3", constants::part_workbook().to_string())); } return d_->root_relationships_; @@ -642,7 +752,7 @@ void workbook::add_shared_string(const text &shared, bool allow_duplicates) if (!has_shared_strings) { create_relationship(next_relationship_id(), "sharedStrings.xml", relationship::type::shared_strings); - d_->manifest_.add_override_type("/" + constants::part_shared_strings(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"); + d_->manifest_.add_override_type(constants::part_shared_strings(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"); } } @@ -723,4 +833,146 @@ std::string workbook::next_relationship_id() const return "rId" + std::to_string(i); } +std::string workbook::get_application() const +{ + return d_->application_; +} + +void workbook::set_application(const std::string &application) +{ + d_->write_app_properties_ = true; + d_->application_ = application; +} + +calendar workbook::get_base_date() const +{ + return d_->base_date_; +} + +void workbook::set_base_date(calendar base_date) +{ + d_->base_date_ = base_date; +} + +std::string workbook::get_creator() const +{ + return d_->creator_; +} + +void workbook::set_creator(const std::string &creator) +{ + d_->creator_ = creator; +} + +std::string workbook::get_last_modified_by() const +{ + return d_->last_modified_by_; +} + +void workbook::set_last_modified_by(const std::string &last_modified_by) +{ + d_->last_modified_by_ = last_modified_by; +} + +datetime workbook::get_created() const +{ + return d_->created_; +} + +void workbook::set_created(const datetime &when) +{ + d_->created_ = when; +} + +datetime workbook::get_modified() const +{ + return d_->modified_; +} + +void workbook::set_modified(const datetime &when) +{ + d_->modified_ = when; +} + +int workbook::get_doc_security() const +{ + return d_->doc_security_; +} + +void workbook::set_doc_security(int doc_security) +{ + d_->doc_security_ = doc_security; +} + +bool workbook::get_scale_crop() const +{ + return d_->scale_crop_; +} + +void workbook::set_scale_crop(bool scale_crop) +{ + d_->scale_crop_ = scale_crop; +} + +std::string workbook::get_company() const +{ + return d_->company_; +} + +void workbook::set_company(const std::string &company) +{ + d_->company_ = company; +} + +bool workbook::links_up_to_date() const +{ + return d_->links_up_to_date_; +} + +void workbook::set_links_up_to_date(bool links_up_to_date) +{ + d_->links_up_to_date_ = links_up_to_date; +} + +bool workbook::is_shared_doc() const +{ + return d_->shared_doc_; +} + +void workbook::set_shared_doc(bool shared_doc) +{ + d_->shared_doc_ = shared_doc; +} + +bool workbook::hyperlinks_changed() const +{ + return d_->hyperlinks_changed_; +} + +void workbook::set_hyperlinks_changed(bool hyperlinks_changed) +{ + d_->hyperlinks_changed_ = hyperlinks_changed; +} + +std::string workbook::get_app_version() const +{ + return d_->app_version_; +} + +void workbook::set_app_version(const std::string &version) +{ + d_->app_version_ = version; +} + +std::string workbook::get_title() const +{ + return d_->title_; +} + +void workbook::set_title(const std::string &title) +{ + d_->title_ = title; +} + + } // namespace xlnt diff --git a/tests/helpers/path_helper.hpp b/tests/helpers/path_helper.hpp index 48302d09..73a0df2f 100644 --- a/tests/helpers/path_helper.hpp +++ b/tests/helpers/path_helper.hpp @@ -22,39 +22,8 @@ class path_helper { public: - static std::string read_file(const std::string &filename) + static xlnt::path get_executable_directory() { - std::ifstream f(filename); - std::ostringstream ss; - ss << f.rdbuf(); - - return ss.str(); - } - - static std::string windows_to_universal_path(const std::string &windows_path) - { - std::string fixed; - std::stringstream ss(windows_path); - std::string part; - - while(std::getline(ss, part, '\\')) - { - if(fixed == "") - { - fixed = part; - } - else - { - fixed += "/" + part; - } - } - - return fixed; - } - - static std::string get_executable_directory() - { - #ifdef __APPLE__ std::array path; uint32_t size = static_cast(path.size()); @@ -74,7 +43,8 @@ public: { throw std::runtime_error("GetModuleFileName failed or buffer was too small"); } - return windows_to_universal_path(std::string(buffer.begin(), buffer.begin() + result - 13)) + "/"; + + return xlnt::path(std::string(buffer.begin(), buffer.begin() + result - 13)); #else char arg1[20]; char exepath[PATH_MAX + 1] = {0}; @@ -86,13 +56,17 @@ public: #endif } - static std::string get_working_directory() + static xlnt::path get_working_directory(const std::string &append = "") { -#ifdef _WIN32 +#ifdef _MSC_VER TCHAR buffer[MAX_PATH]; GetCurrentDirectory(MAX_PATH, buffer); + std::basic_string working_directory(buffer); - return windows_to_universal_path(std::string(working_directory.begin(), working_directory.end())); + std::string working_directory_narrow(working_directory.begin(), working_directory.end()); + + return xlnt::path(working_directory_narrow) + .append(xlnt::path(append)); #else char buffer[PATH_MAX]; @@ -105,59 +79,28 @@ public: #endif } - static std::string get_data_directory(const std::string &append = "") + static xlnt::path get_data_directory(const std::string &append = "") { - auto path = get_executable_directory() + "../../tests/data"; - - if (!append.empty()) - { - if (append.front() != '/') - { - path.push_back('/'); - } - - path.append(append); - } - - return path; + return xlnt::path("../../tests/data") + .make_absolute(get_executable_directory()) + .append(xlnt::path(append)); } - static void copy_file(const std::string &source, const std::string &destination, bool overwrite) + static void copy_file(const xlnt::path &source, const xlnt::path &destination, bool overwrite) { - if(!overwrite && file_exists(destination)) + if(!overwrite && destination.exists()) { throw std::runtime_error("destination file already exists and overwrite==false"); } - std::ifstream src(source, std::ios::binary); - std::ofstream dst(destination, std::ios::binary); + std::ifstream src(source.to_string(), std::ios::binary); + std::ofstream dst(destination.to_string(), std::ios::binary); dst << src.rdbuf(); } - static void delete_file(const std::string &path) + static void delete_file(const xlnt::path &path) { - std::remove(path.c_str()); - } - - static bool file_exists(const std::string &path) - { -#ifdef _MSC_VER - std::wstring path_wide(path.begin(), path.end()); - return PathFileExists(path_wide.c_str()) && !PathIsDirectory(path_wide.c_str()); -#else - try - { - struct stat fileAtt; - - if (stat(path.c_str(), &fileAtt) == 0) - { - return S_ISREG(fileAtt.st_mode); - } - } - catch(...) {} - - return false; -#endif + std::remove(path.to_string().c_str()); } }; diff --git a/tests/helpers/temporary_file.hpp b/tests/helpers/temporary_file.hpp index b710783d..a6f693d6 100644 --- a/tests/helpers/temporary_file.hpp +++ b/tests/helpers/temporary_file.hpp @@ -7,47 +7,50 @@ #include #include -class temporary_file +namespace { + +static std::string create_temporary_filename() { -public: - static std::string create() - { #ifdef _MSC_VER std::array buffer; DWORD result = GetTempPath(static_cast(buffer.size()), buffer.data()); - - if(result > MAX_PATH) + + if (result > MAX_PATH) { - throw std::runtime_error("buffer is too small"); + throw std::runtime_error("buffer is too small"); } - - if(result == 0) + + if (result == 0) { - throw std::runtime_error("GetTempPath failed"); + throw std::runtime_error("GetTempPath failed"); } - - std::string directory(buffer.begin(), buffer.begin() + result); - return path_helper::windows_to_universal_path(directory + "xlnt.xlsx"); + + return std::string(buffer.begin(), buffer.begin() + result) + "xlnt.xlsx"; #else return "/tmp/xlnt.xlsx"; #endif } - temporary_file() : filename_(create()) +} // namespace + +class temporary_file +{ +public: + temporary_file() : path_(create_temporary_filename()) { - if(path_helper::file_exists(get_filename())) + if(path_.exists()) { - std::remove(filename_.c_str()); + std::remove(path_.to_string().c_str()); } } ~temporary_file() { - std::remove(filename_.c_str()); + std::remove(path_.to_string().c_str()); } - std::string get_filename() const { return filename_; } + xlnt::path get_path() const { return path_; } private: - const std::string filename_; + const xlnt::path path_; }; diff --git a/tests/helpers/xml_helper.hpp b/tests/helpers/xml_helper.hpp index 5202fe8e..420ce6a7 100644 --- a/tests/helpers/xml_helper.hpp +++ b/tests/helpers/xml_helper.hpp @@ -32,46 +32,56 @@ public: } }; - static comparison_result compare_xml(const pugi::xml_document &expected, const pugi::xml_document &observed) + static bool documents_match(const pugi::xml_document &expected, + const pugi::xml_document &observed) { - return compare_xml(expected.root(), observed.root()); + auto result = compare_xml_nodes(expected.root(), observed.root()); + + if (!result) + { + std::cout << "documents don't match" << std::endl; + + std::cout << "expected:" << std::endl; + expected.save(std::cout); + std::cout << std::endl; + + std::cout << "observed:" << std::endl; + observed.save(std::cout); + std::cout << std::endl; + + return false; + } + + return true; } - static comparison_result compare_xml(const std::string &expected, const pugi::xml_document &observed) - { - std::string expected_contents = expected; - - if(path_helper::file_exists(expected)) - { - std::ifstream f(expected); - std::ostringstream s; - f >> s.rdbuf(); - - expected_contents = s.str(); - } - - pugi::xml_document expected_xml; - expected_xml.load(expected_contents.c_str()); + static bool file_matches_document(const xlnt::path &expected, + const pugi::xml_document &observed) + { + return string_matches_document(expected.read_contents(), observed); + } - std::ostringstream ss; - observed.save(ss); - auto observed_string = ss.str(); + static bool string_matches_document(const std::string &expected_string, + const pugi::xml_document &observed_document) + { + pugi::xml_document expected_document; + expected_document.load(expected_string.c_str()); - return compare_xml(expected_xml.root(), observed.root()); + return documents_match(expected_document, observed_document); } - static comparison_result compare_xml(const std::string &left_contents, const std::string &right_contents) + static bool strings_match(const std::string &expected_string, const std::string &observed_string) { pugi::xml_document left_xml; - left_xml.load(left_contents.c_str()); + left_xml.load(expected_string.c_str()); pugi::xml_document right_xml; - right_xml.load(right_contents.c_str()); + right_xml.load(observed_string.c_str()); - return compare_xml(left_xml.root(), right_xml.root()); + return documents_match(left_xml, right_xml); } - static comparison_result compare_xml(const pugi::xml_node &left, const pugi::xml_node &right) + static comparison_result compare_xml_nodes(const pugi::xml_node &left, const pugi::xml_node &right) { std::string left_temp = left.name(); std::string right_temp = right.name(); @@ -136,7 +146,7 @@ public: auto right_child = *right_child_iter; right_child_iter++; - auto child_comparison_result = compare_xml(left_child, right_child); + auto child_comparison_result = compare_xml_nodes(left_child, right_child); if(!child_comparison_result) {