diff --git a/.travis.yml b/.travis.yml index 3c9e7c85..0e25f336 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,35 +1,175 @@ -language: cpp +# cpp takes longer +language: minimal sudo: false dist: trusty +git: + depth: false + notifications: email: false + +# set up build matrix +matrix: + include: + # ============= GCC ================== + # gcc-6, c++11, debug build, dynamic linking + - os: linux + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-6 + env: + - CXX_COMPILER=g++-6 + - C_COMPILER=gcc-6 + - CXX_VER=11 + - BUILD_TYPE=Release + - COVERAGE=OFF + - STATIC=OFF + - SAMPLES=OFF + + # gcc-7, c++14, release build, static linking, samples + benchmarks compiled + - os: linux + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-7 + env: + - CXX_COMPILER=g++-7 + - C_COMPILER=gcc-7 + - CXX_VER=14 + - BUILD_TYPE=Release + - COVERAGE=OFF + - STATIC=ON + - SAMPLES=ON + + # gcc-8, c++17, release build, static linking, samples + benchmarks compiled + - os: linux + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-8 + env: + - CXX_COMPILER=g++-8 + - C_COMPILER=gcc-8 + - CXX_VER=17 + - BUILD_TYPE=Release + - COVERAGE=OFF + - STATIC=ON + - SAMPLES=ON -env: - - COVERAGE=ON STATIC=ON SAMPLES=OFF BENCHMARKS=OFF BUILD_TYPE=Debug - - COVERAGE=OFF STATIC=ON SAMPLES=ON BENCHMARKS=OFF BUILD_TYPE=Debug - - COVERAGE=OFF STATIC=OFF SAMPLES=ON BENCHMARKS=OFF BUILD_TYPE=Debug - - COVERAGE=OFF STATIC=ON SAMPLES=ON BENCHMARKS=ON BUILD_TYPE=Release - - COVERAGE=OFF STATIC=OFF SAMPLES=ON BENCHMARKS=ON BUILD_TYPE=Release + # =========== CLANG ============= + # clang 4, c++11, release build, dynamic linking + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty-4.0 + packages: + - clang-4.0 + env: + - CXX_COMPILER=clang++-4.0 + - C_COMPILER=clang-4.0 + - CXX_VER=11 + - BUILD_TYPE=Release + - COVERAGE=OFF + - STATIC=OFF + - SAMPLES=OFF + + # clang 5, c++14, release build, dynamic linking, samples + benchmarks compiled + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty-5.0 + packages: + - clang-5.0 + env: + - CXX_COMPILER=clang++-5.0 + - C_COMPILER=clang-5.0 + - CXX_VER=14 + - BUILD_TYPE=Release + - COVERAGE=OFF + - STATIC=ON + - SAMPLES=ON + + # clang 6, c++17, release build, static linking, samples compiled + - os: linux + compiler: clang + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - llvm-toolchain-trusty-6.0 + packages: + - clang-6.0 + env: + - CXX_COMPILER=clang++-6.0 + - C_COMPILER=clang-6.0 + - CXX_VER=17 + - BUILD_TYPE=Release + - COVERAGE=OFF + - STATIC=ON + - SAMPLES=ON -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-6 lcov + # ============= CODE COVERAGE =============== + # gcc-6, c++11, debug build, static linking, code coverage enabled + - os: linux + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-6 + - lcov + env: + - CXX_COMPILER=g++-6 + - C_COMPILER=gcc-6 + - CXX_VER=11 + - BUILD_TYPE=Debug + - COVERAGE=ON + - STATIC=ON + - SAMPLES=OFF + +before_install: + - export CC=${C_COMPILER} + - export CXX=${CXX_COMPILER} install: - - export CC=gcc-6 - - export CXX=g++-6 - - gem install coveralls-lcov + - | + if [[ "${COVERAGE}" == "ON" ]]; then + gem install coveralls-lcov; + fi + + - ${CXX} --version + - cmake --version script: - - mkdir build - - cd build - - cmake -D STATIC=$STATIC -D BENCHMARKS=$BENCHMARKS -D SAMPLES=$SAMPLES -D COVERAGE=$COVERAGE -D CMAKE_BUILD_TYPE=$BUILD_TYPE .. - - cmake --build . - - ./tests/xlnt.test + - | + if [[ "${BUILD_TYPE}" == "Release" && "${STATIC}" == "ON" ]]; + then BENCHMARKS="ON"; + else BENCHMARKS="OFF"; + fi + + - mkdir build + - cd build + - cmake .. -DXLNT_CXX_LANG=${CXX_VER} -DSTATIC=$STATIC -DBENCHMARKS=$BENCHMARKS -DSAMPLES=$SAMPLES -DCOVERAGE=$COVERAGE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + - cmake --build . -- -j2 + - ./tests/xlnt.test after_success: - | diff --git a/CMakeLists.txt b/CMakeLists.txt index 0761378d..d2a50996 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,18 @@ set(COMBINED_PROJECT TRUE) # Library type option(STATIC "Set to ON to build xlnt as a static library instead of a shared library" OFF) +# c++ language standard to use +set(XLNT_VALID_LANGS 11 14 17) +set(XLNT_CXX_LANG "14" CACHE STRING "c++ language features to compile with") +# enumerate allowed values for cmake gui +set_property(CACHE XLNT_CXX_LANG PROPERTY STRINGS ${XLNT_VALID_LANGS}) +# validate value is in XLNT_VALID_LANGS +list(FIND XLNT_VALID_LANGS ${XLNT_CXX_LANG} index) +if(index EQUAL -1) + message(FATAL_ERROR "XLNT_CXX_LANG must be one of ${XLNT_VALID_LANGS}") +endif() + + # Optional components option(TESTS "Set to OFF to skip building test executable (in ./tests)" ON) option(SAMPLES "Set to ON to build executable code samples (in ./samples)" OFF) diff --git a/include/xlnt/cell/cell.hpp b/include/xlnt/cell/cell.hpp index f77f6d31..9ff5fb93 100644 --- a/include/xlnt/cell/cell.hpp +++ b/include/xlnt/cell/cell.hpp @@ -262,26 +262,21 @@ public: /// class hyperlink hyperlink() const; - /// - /// Adds a hyperlink to this cell pointing to the URL of the given value. - /// - void hyperlink(const std::string &url); - /// /// Adds a hyperlink to this cell pointing to the URI of the given value and sets /// the text value of the cell to the given parameter. /// - void hyperlink(const std::string &url, const std::string &display); + void hyperlink(const std::string &url, const std::string &display = ""); /// /// Adds an internal hyperlink to this cell pointing to the given cell. /// - void hyperlink(xlnt::cell target); + void hyperlink(xlnt::cell target, const std::string& display = ""); /// /// Adds an internal hyperlink to this cell pointing to the given range. /// - void hyperlink(xlnt::range target); + void hyperlink(xlnt::range target, const std::string& display = ""); /// /// Returns true if this cell has a hyperlink set. @@ -613,9 +608,9 @@ public: bool operator==(const cell &comparand) const; /// - /// Returns true if this cell is uninitialized. + /// Returns false if this cell the same cell as comparand (compared by reference). /// - bool operator==(std::nullptr_t) const; + bool operator!=(const cell &comparand) const; private: friend class style; @@ -651,6 +646,11 @@ private: /// XLNT_API bool operator==(std::nullptr_t, const cell &cell); +/// +/// Returns true if this cell is uninitialized. +/// +XLNT_API bool operator==(const cell &cell, std::nullptr_t); + /// /// Convenience function for writing cell to an ostream. /// Uses cell::to_string() internally. diff --git a/include/xlnt/cell/hyperlink.hpp b/include/xlnt/cell/hyperlink.hpp index 8034098b..e77f8830 100644 --- a/include/xlnt/cell/hyperlink.hpp +++ b/include/xlnt/cell/hyperlink.hpp @@ -44,12 +44,22 @@ class XLNT_API hyperlink public: bool external() const; class relationship relationship() const; + // external target std::string url() const; + // internal target std::string target_range() const; + + bool has_display() const; void display(const std::string &value); - std::string display() const; + const std::string& display() const; + + bool has_tooltip() const; void tooltip(const std::string &value); - std::string tooltip() const; + const std::string& tooltip() const; + + bool has_location() const; + void location(const std::string &value); + const std::string& location() const; private: friend class cell; diff --git a/include/xlnt/cell/rich_text_run.hpp b/include/xlnt/cell/rich_text_run.hpp index 0988a7e7..dc31fe52 100644 --- a/include/xlnt/cell/rich_text_run.hpp +++ b/include/xlnt/cell/rich_text_run.hpp @@ -34,7 +34,7 @@ namespace xlnt { /// /// Typedef a rich_text_run as a pair of string and optional font. /// -struct rich_text_run +struct XLNT_API rich_text_run { std::string first; optional second; diff --git a/include/xlnt/packaging/relationship.hpp b/include/xlnt/packaging/relationship.hpp index 00d7e942..2dcbb4af 100644 --- a/include/xlnt/packaging/relationship.hpp +++ b/include/xlnt/packaging/relationship.hpp @@ -115,7 +115,7 @@ public: /// /// Returns a string of the form rId# that identifies the relationship. /// - std::string id() const; + const std::string& id() const; /// /// Returns the type of this relationship. @@ -130,12 +130,12 @@ public: /// /// Returns the URI of the package part this relationship points to. /// - uri source() const; + const uri &source() const; /// /// Returns the URI of the package part this relationship points to. /// - uri target() const; + const uri &target() const; /// /// Returns true if and only if rhs is equal to this relationship. diff --git a/include/xlnt/packaging/uri.hpp b/include/xlnt/packaging/uri.hpp index 7e945168..66d6358e 100644 --- a/include/xlnt/packaging/uri.hpp +++ b/include/xlnt/packaging/uri.hpp @@ -123,7 +123,7 @@ public: /// Returns the path of this URI. /// E.g. the path of http://example.com/document is "/document" /// - class path path() const; + const class path& path() const; /// /// Returns true if this URI has a non-null query string section. diff --git a/include/xlnt/styles/color.hpp b/include/xlnt/styles/color.hpp index 609ede8a..db35e6ef 100644 --- a/include/xlnt/styles/color.hpp +++ b/include/xlnt/styles/color.hpp @@ -252,19 +252,37 @@ public: /// Returns the internal indexed color representing this color. If this is not an RGB color, /// an invalid_attribute exception will be thrown. /// - rgb_color rgb() const; + const rgb_color& rgb() const; + + /// + /// Returns the internal indexed color representing this color. If this is not an RGB color, + /// an invalid_attribute exception will be thrown. + /// + rgb_color &rgb(); /// /// Returns the internal indexed color representing this color. If this is not an indexed color, /// an invalid_attribute exception will be thrown. /// - indexed_color indexed() const; + const indexed_color& indexed() const; + + /// + /// Returns the internal indexed color representing this color. If this is not an indexed color, + /// an invalid_attribute exception will be thrown. + /// + indexed_color &indexed(); /// /// Returns the internal indexed color representing this color. If this is not a theme color, /// an invalid_attribute exception will be thrown. /// - theme_color theme() const; + const theme_color& theme() const; + + /// + /// Returns the internal indexed color representing this color. If this is not a theme color, + /// an invalid_attribute exception will be thrown. + /// + theme_color& theme(); /// /// Returns the tint of this color. diff --git a/include/xlnt/utils/exceptions.hpp b/include/xlnt/utils/exceptions.hpp index bf088480..0618a0fb 100644 --- a/include/xlnt/utils/exceptions.hpp +++ b/include/xlnt/utils/exceptions.hpp @@ -120,7 +120,7 @@ public: /// /// Default constructor. /// - missing_number_format(); + missing_number_format() = default; /// /// Default copy constructor. diff --git a/include/xlnt/utils/optional.hpp b/include/xlnt/utils/optional.hpp index 143c38ec..8248ebd2 100644 --- a/include/xlnt/utils/optional.hpp +++ b/include/xlnt/utils/optional.hpp @@ -25,6 +25,7 @@ #include #include +#include namespace xlnt { @@ -34,39 +35,207 @@ namespace xlnt { /// within the optional class. /// template -class XLNT_API optional +class optional { +#if defined(_MSC_VER) && _MSC_VER <= 1900 // v14, visual studio 2015 +#define XLNT_NOEXCEPT_VALUE_COMPAT(...) (false) +#else +#define XLNT_NOEXCEPT_VALUE_COMPAT(...) (__VA_ARGS__) + using ctor_copy_T_noexcept = typename std::conditional{}, std::true_type, std::false_type>::type; + using ctor_move_T_noexcept = typename std::conditional{}, std::true_type, std::false_type>::type; + using copy_ctor_noexcept = ctor_copy_T_noexcept; + using move_ctor_noexcept = ctor_move_T_noexcept; + using set_copy_noexcept_t = typename std::conditional{} && std::is_nothrow_assignable{}, std::true_type, std::false_type>::type; + using set_move_noexcept_t = typename std::conditional{} && std::is_nothrow_move_assignable{}, std::true_type, std::false_type>::type; + using clear_noexcept_t = typename std::conditional{}, std::true_type, std::false_type>::type; +#endif + public: /// /// Default contructor. is_set() will be false initially. /// - optional() : has_value_(false), value_(T()) + optional() noexcept + : has_value_(false) { } /// /// Constructs this optional with a value. + /// noexcept if T copy ctor is noexcept /// - optional(const T &value) : has_value_(true), value_(value) + optional(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_copy_T_noexcept{})) + : has_value_(true) { + new (&storage_) T(value); + } + + /// + /// Constructs this optional with a value. + /// noexcept if T move ctor is noexcept + /// + optional(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(ctor_move_T_noexcept{})) + : has_value_(true) + { + new (&storage_) T(std::move(value)); + } + + /// + /// Copy constructs this optional from other + /// noexcept if T copy ctor is noexcept + /// + optional(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(copy_ctor_noexcept{})) + : has_value_(other.has_value_) + { + if (has_value_) + { + new (&storage_) T(other.value_ref()); + } + } + + /// + /// Move constructs this optional from other. Clears the value from other if set + /// noexcept if T move ctor is noexcept + /// + optional(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(move_ctor_noexcept{})) + : has_value_(other.has_value_) + { + if (has_value_) + { + new (&storage_) T(std::move(other.value_ref())); + other.clear(); + } + } + + /// + /// Copy assignment of this optional from other + /// noexcept if set and clear are noexcept for T& + /// + optional &operator=(const optional &other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{} && clear_noexcept_t{})) + { + if (other.has_value_) + { + set(other.value_ref()); + } + else + { + clear(); + } + return *this; + } + + /// + /// Move assignment of this optional from other + /// noexcept if set and clear are noexcept for T&& + /// + optional &operator=(optional &&other) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{} && clear_noexcept_t{})) + { + if (other.has_value_) + { + set(std::move(other.value_ref())); + other.clear(); + } + else + { + clear(); + } + return *this; + } + + /// + /// Destructor cleans up the T instance if set + /// + ~optional() noexcept // note:: unconditional because msvc freaks out otherwise + { + clear(); } /// /// Returns true if this object currently has a value set. This should /// be called before accessing the value with optional::get(). /// - bool is_set() const + bool is_set() const noexcept { return has_value_; } /// - /// Sets the value to value. + /// Copies the value into the stored value /// - void set(const T &value) + void set(const T &value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{})) { - has_value_ = true; - value_ = value; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (has_value_) + { + value_ref() = value; + } + else + { + new (&storage_) T(value); + has_value_ = true; + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + } + + /// + /// Moves the value into the stored value + /// + void set(T &&value) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{})) + { + // note seperate overload for two reasons (as opposed to perfect forwarding) + // 1. have to deal with implicit conversions internally with perfect forwarding + // 2. have to deal with the noexcept specfiers for all the different variations + // overload is just far and away the simpler solution +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (has_value_) + { + value_ref() = std::move(value); + } + else + { + new (&storage_) T(std::move(value)); + has_value_ = true; + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + } + + /// + /// Assignment operator overload. Equivalent to setting the value using optional::set. + /// + optional &operator=(const T &rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_copy_noexcept_t{})) + { + set(rhs); + return *this; + } + + /// + /// Assignment operator overload. Equivalent to setting the value using optional::set. + /// + optional &operator=(T &&rhs) noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(set_move_noexcept_t{})) + { + set(std::move(rhs)); + return *this; + } + + /// + /// After this is called, is_set() will return false until a new value is provided. + /// + void clear() noexcept(XLNT_NOEXCEPT_VALUE_COMPAT(clear_noexcept_t{})) + { + if (has_value_) + { + reinterpret_cast(&storage_)->~T(); + } + has_value_ = false; } /// @@ -80,7 +249,7 @@ public: throw invalid_attribute(); } - return value_; + return value_ref(); } /// @@ -94,28 +263,7 @@ public: throw invalid_attribute(); } - return value_; - } - - /// - /// Resets the internal value using its default constructor. After this is - /// called, is_set() will return false until a new value is provided. - /// - void clear() - { - has_value_ = false; - value_ = T(); - } - - /// - /// Assignment operator. Equivalent to setting the value using optional::set. - /// - optional &operator=(const T &rhs) - { - has_value_ = true; - value_ = rhs; - - return *this; + return value_ref(); } /// @@ -123,21 +271,47 @@ public: /// or both have a value and those values are equal according to /// their equality operator. /// - bool operator==(const optional &other) const + bool operator==(const optional &other) const noexcept { - if (has_value_ != other.has_value_) { + if (has_value_ != other.has_value_) + { return false; } if (!has_value_) { return true; } - return value_ == other.value_; + return value_ref() == other.value_ref(); + } + + /// + /// Returns false if neither this nor other have a value + /// or both have a value and those values are equal according to + /// their equality operator. + /// + bool operator!=(const optional &other) const noexcept + { + return !operator==(other); } private: + // helpers for getting a T out of storage + T &value_ref() noexcept + { + return *reinterpret_cast(&storage_); + } + + const T &value_ref() const noexcept + { + return *reinterpret_cast(&storage_); + } + bool has_value_; - T value_; + typename std::aligned_storage::type storage_; }; -} // namespace xlnt +#ifdef XLNT_NOEXCEPT_VALUE_COMPAT +#undef XLNT_NOEXCEPT_VALUE_COMPAT +#endif + +} // namespace xlnt \ No newline at end of file diff --git a/include/xlnt/utils/path.hpp b/include/xlnt/utils/path.hpp index beff0165..ba16526f 100644 --- a/include/xlnt/utils/path.hpp +++ b/include/xlnt/utils/path.hpp @@ -107,7 +107,7 @@ public: /// Create a string representing this path separated by the provided /// separator or the system-default separator if not provided. /// - std::string string() const; + const std::string& string() const; #ifdef _MSC_VER /// @@ -176,6 +176,11 @@ public: /// bool operator==(const path &other) const; + /// + /// Returns true if left path is equal to right path. + /// + bool operator!=(const path &other) const; + private: /// /// Returns the character that separates directories in the path. diff --git a/include/xlnt/workbook/document_security.hpp b/include/xlnt/workbook/document_security.hpp index 7f9cec49..b6d5624b 100644 --- a/include/xlnt/workbook/document_security.hpp +++ b/include/xlnt/workbook/document_security.hpp @@ -67,7 +67,7 @@ public: /// /// Constructs a new document security object with default values. /// - document_security(); + document_security() = default; /// /// If true, the workbook is locked for revisions. diff --git a/include/xlnt/worksheet/phonetic_pr.hpp b/include/xlnt/worksheet/phonetic_pr.hpp index 592cb8e8..74561f9c 100644 --- a/include/xlnt/worksheet/phonetic_pr.hpp +++ b/include/xlnt/worksheet/phonetic_pr.hpp @@ -38,7 +38,7 @@ namespace xlnt { class XLNT_API phonetic_pr { public: - static const std::string Serialised_ID; + static std::string Serialised_ID(); /// /// possible values for alignment property diff --git a/include/xlnt/worksheet/range.hpp b/include/xlnt/worksheet/range.hpp index 0abebbcf..02b88a13 100644 --- a/include/xlnt/worksheet/range.hpp +++ b/include/xlnt/worksheet/range.hpp @@ -290,11 +290,11 @@ public: /// bool operator!=(const range &comparand) const; -private: + private: /// /// The worksheet this range is within /// - worksheet ws_; + class worksheet ws_; /// /// The reference of this range diff --git a/include/xlnt/worksheet/range_reference.hpp b/include/xlnt/worksheet/range_reference.hpp index f5956c75..93643f68 100644 --- a/include/xlnt/worksheet/range_reference.hpp +++ b/include/xlnt/worksheet/range_reference.hpp @@ -56,12 +56,7 @@ public: /// top_left:bottom_right. /// explicit range_reference(const char *range_string); - - /// - /// Constructs a range reference from a pair of cell references. - /// - explicit range_reference(const std::pair &reference_pair); - + /// /// Constructs a range reference from cell references indicating top /// left and bottom right coordinates of the range. diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ded96a82..5c924e84 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,11 +1,9 @@ cmake_minimum_required(VERSION 3.1) project(xlnt VERSION 1.2) -# Require C99 and C++11 compilers -set(CMAKE_C_STANDARD 99) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD ${XLNT_CXX_LANG}) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CXX_EXTENSIONS OFF) # Project metadata set(PROJECT_VENDOR "Thomas Fussell") @@ -37,12 +35,18 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") # ignore MSVC and Clang pragmas elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weverything") # all warnings - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") # warnings are errors + # blacklist warnings that are not relevant set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat") # ignore warnings about C++98 compatibility set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++98-compat-pedantic") # ignore pedantic warnings about C++98 compatibility set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-padded") # ignore padding warnings set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-documentation-unknown-command") # ignore unknown commands in Javadoc-style comments set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") # ignore Windows and GCC pragmas + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option") # ignore Windows and GCC pragmas + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-float-equal") # don't warn on uses of == for fp types + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-newline-eof") # no longer an issue with post-c++11 standards which mandate include add a newline if neccesary + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-covered-switch-default") # default is often added to switches for completeness or to cover future alternatives + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-exit-time-destructors") # this is just a warning to notify that the destructor will run during exit + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces") # Wmissing-field-initializers has less false positives endif() if(STATIC_CRT) @@ -151,6 +155,9 @@ else() target_compile_definitions(xlnt PUBLIC XLNT_STATIC=1) endif() +# requires cmake 3.8+ +#target_compile_features(xlnt PUBLIC cxx_std_${XLNT_CXX_LANG}) + # Includes target_include_directories(xlnt PUBLIC ${XLNT_INCLUDE_DIR}) target_include_directories(xlnt PRIVATE ${XLNT_SOURCE_DIR}) @@ -173,23 +180,23 @@ if(MSVC) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/cryptography/aes.cpp PROPERTIES COMPILE_FLAGS "/wd\"4996\"") -endif() - -# Platform- and file-specific settings, Clang -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/serialization/miniz.cpp - PROPERTIES - COMPILE_FLAGS "-Wno-undef") - set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/serialization/zstream.cpp - PROPERTIES - COMPILE_FLAGS "-Wno-undef -Wno-shorten-64-to-32") -endif() - -# Platform- and file-specific settings, GCC -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/serialization/miniz.cpp - PROPERTIES - COMPILE_FLAGS "-Wno-strict-aliasing") +else() + # Platform- and file-specific settings, Clang + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/serialization/miniz.cpp + PROPERTIES + COMPILE_FLAGS "-Wno-undef") + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/serialization/zstream.cpp + PROPERTIES + COMPILE_FLAGS "-Wno-undef -Wno-shorten-64-to-32") + endif() + + # Platform- and file-specific settings, GCC + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/detail/serialization/miniz.cpp + PROPERTIES + COMPILE_FLAGS "-Wno-strict-aliasing") + endif() endif() # Group files into pseudo-folders in IDEs diff --git a/source/cell/cell.cpp b/source/cell/cell.cpp index 4426a5dc..bc5f558f 100644 --- a/source/cell/cell.cpp +++ b/source/cell/cell.cpp @@ -341,38 +341,24 @@ cell_reference cell::reference() const return {d_->column_, d_->row_}; } -bool cell::operator==(std::nullptr_t) const -{ - return d_ == nullptr; -} - bool cell::operator==(const cell &comparand) const { return d_ == comparand.d_; } -cell &cell::operator=(const cell &rhs) +bool cell::operator!=(const cell &comparand) const { - d_->column_ = rhs.d_->column_; - d_->format_ = rhs.d_->format_; - d_->formula_ = rhs.d_->formula_; - d_->hyperlink_ = rhs.d_->hyperlink_; - d_->is_merged_ = rhs.d_->is_merged_; - d_->parent_ = rhs.d_->parent_; - d_->row_ = rhs.d_->row_; - d_->type_ = rhs.d_->type_; - d_->value_numeric_ = rhs.d_->value_numeric_; - d_->value_text_ = rhs.d_->value_text_; - - return *this; + return d_ != comparand.d_; } +cell &cell::operator=(const cell &rhs) = default; + hyperlink cell::hyperlink() const { return xlnt::hyperlink(&d_->hyperlink_.get()); } -void cell::hyperlink(const std::string &url) +void cell::hyperlink(const std::string &url, const std::string &display) { if (url.empty() || std::find(url.begin(), url.end(), ':') == url.end()) { @@ -388,7 +374,8 @@ void cell::hyperlink(const std::string &url) auto relationships = manifest.relationships(ws.path(), relationship_type::hyperlink); auto relation = std::find_if(relationships.cbegin(), relationships.cend(), [&url](xlnt::relationship rel) { return rel.target().path().string() == url; }); - if (relation != relationships.end()) { + if (relation != relationships.end()) + { d_->hyperlink_.get().relationship = *relation; } else @@ -400,29 +387,20 @@ void cell::hyperlink(const std::string &url) target_mode::external); // TODO: make manifest::register_relationship return the created relationship instead of rel id d_->hyperlink_.get().relationship = manifest.relationship(ws.path(), rel_id); - } - - if (!has_value()) // hyperlink on an empty cell sets the value to the hyperlink string + } + // if a value is already present, the display string is ignored + if (has_value()) { - value(url); + d_->hyperlink_.get().display.set(to_string()); + } + else + { + d_->hyperlink_.get().display.set(display.empty() ? url : display); + value(hyperlink().display()); } } -void cell::hyperlink(const std::string &url, const std::string &display) -{ - if (!display.empty()) // if the display string isn't empty use that - { - value(display); - } - else // empty display string sets the value to the link text - { - value(url); - } - hyperlink(url); - d_->hyperlink_.get().display = display; -} - -void cell::hyperlink(xlnt::cell target) +void cell::hyperlink(xlnt::cell target, const std::string& display) { // TODO: should this computed value be a method on a cell? const auto cell_address = target.worksheet().title() + "!" + target.reference().to_string(); @@ -430,8 +408,37 @@ void cell::hyperlink(xlnt::cell target) d_->hyperlink_ = detail::hyperlink_impl(); d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink, uri(""), uri(cell_address), target_mode::internal); - d_->hyperlink_.get().display = cell_address; - value(cell_address); + // if a value is already present, the display string is ignored + if (has_value()) + { + d_->hyperlink_.get().display.set(to_string()); + } + else + { + d_->hyperlink_.get().display.set(display.empty() ? cell_address : display); + value(hyperlink().display()); + } +} + +void cell::hyperlink(xlnt::range target, const std::string &display) +{ + // TODO: should this computed value be a method on a cell? + const auto range_address = target.target_worksheet().title() + "!" + target.reference().to_string(); + + d_->hyperlink_ = detail::hyperlink_impl(); + d_->hyperlink_.get().relationship = xlnt::relationship("", relationship_type::hyperlink, + uri(""), uri(range_address), target_mode::internal); + + // if a value is already present, the display string is ignored + if (has_value()) + { + d_->hyperlink_.get().display.set(to_string()); + } + else + { + d_->hyperlink_.get().display.set(display.empty() ? range_address : display); + value(hyperlink().display()); + } } void cell::formula(const std::string &formula) @@ -472,6 +479,15 @@ void cell::clear_formula() } } +std::string cell::error() const +{ + if (d_->type_ != type::error) + { + throw xlnt::exception("called error() when cell type is not error"); + } + return value(); +} + void cell::error(const std::string &error) { if (error.length() == 0 || error[0] != '#') @@ -743,6 +759,16 @@ calendar cell::base_date() const return workbook().base_date(); } +bool operator==(std::nullptr_t, const cell &cell) +{ + return cell.data_type() == cell::type::empty; +} + +bool operator==(const cell &cell, std::nullptr_t) +{ + return nullptr == cell; +} + XLNT_API std::ostream &operator<<(std::ostream &stream, const xlnt::cell &cell) { return stream << cell.to_string(); @@ -802,8 +828,11 @@ void cell::value(const std::string &value_string, bool infer_type) void cell::clear_format() { - format().d_->references -= format().d_->references > 0 ? 1 : 0; - d_->format_.clear(); + if (d_->format_.is_set()) + { + format().d_->references -= format().d_->references > 0 ? 1 : 0; + d_->format_.clear(); + } } void cell::clear_style() diff --git a/source/cell/hyperlink.cpp b/source/cell/hyperlink.cpp index 8d28d84e..28539107 100644 --- a/source/cell/hyperlink.cpp +++ b/source/cell/hyperlink.cpp @@ -67,24 +67,49 @@ bool hyperlink::external() const return d_->relationship.target_mode() == target_mode::external; } -void hyperlink::display(const std::string &value) +bool hyperlink::has_display() const { - d_->display = value; + return d_->display.is_set(); } -std::string hyperlink::display() const +void hyperlink::display(const std::string &value) { - return d_->display; + d_->display.set(value); +} + +const std::string& hyperlink::display() const +{ + return d_->display.get(); +} + +bool hyperlink::has_tooltip() const +{ + return d_->tooltip.is_set(); } void hyperlink::tooltip(const std::string &value) { - d_->tooltip = value; + d_->tooltip.set(value); } -std::string hyperlink::tooltip() const +const std::string& hyperlink::tooltip() const { - return d_->tooltip; + return d_->tooltip.get(); +} + +bool hyperlink::has_location() const +{ + return d_->location.is_set(); +} + +void hyperlink::location(const std::string &value) +{ + d_->location.set(value); +} + +const std::string &hyperlink::location() const +{ + return d_->location.get(); } } // namespace xlnt diff --git a/source/cell/rich_text.cpp b/source/cell/rich_text.cpp index ad5cccbd..6cda69db 100644 --- a/source/cell/rich_text.cpp +++ b/source/cell/rich_text.cpp @@ -26,15 +26,22 @@ #include #include +namespace { +bool has_trailing_whitespace(const std::string &s) +{ + return !s.empty() && (s.front() == ' ' || s.back() == ' '); +}; +} + namespace xlnt { rich_text::rich_text(const std::string &plain_text) - : rich_text(rich_text_run{plain_text, optional(), false}) + : rich_text(rich_text_run{plain_text, optional(), has_trailing_whitespace(plain_text)}) { } rich_text::rich_text(const std::string &plain_text, const class font &text_font) - : rich_text(rich_text_run{plain_text, optional(text_font), false}) + : rich_text(rich_text_run{plain_text, optional(text_font), has_trailing_whitespace(plain_text)}) { } @@ -65,6 +72,11 @@ std::vector rich_text::runs() const return runs_; } +void rich_text::runs(const std::vector &new_runs) +{ + runs_ = new_runs; +} + void rich_text::add_run(const rich_text_run &t) { runs_.push_back(t); diff --git a/source/detail/implementations/cell_impl.hpp b/source/detail/implementations/cell_impl.hpp index ed1833ed..64756770 100644 --- a/source/detail/implementations/cell_impl.hpp +++ b/source/detail/implementations/cell_impl.hpp @@ -27,22 +27,26 @@ #include #include -#include #include +#include #include #include +#include #include namespace xlnt { namespace detail { -struct format_impl; struct worksheet_impl; struct cell_impl { cell_impl(); - + cell_impl(const cell_impl &other) = default; + cell_impl(cell_impl &&other) = default; + cell_impl &operator=(const cell_impl &other) = default; + cell_impl &operator=(cell_impl &&other) = default; + cell_type type_; worksheet_impl *parent_; @@ -72,9 +76,8 @@ inline bool operator==(const cell_impl &lhs, const cell_impl &rhs) && lhs.value_numeric_ == rhs.value_numeric_ && lhs.formula_ == rhs.formula_ && lhs.hyperlink_ == rhs.hyperlink_ - // comparing pointers is unlikely to be what is wanted - /*&& lhs.format_ == rhs.format_ - && lhs.comment_ == rhs.comment_*/; + && (lhs.format_.is_set() == rhs.format_.is_set() && (!lhs.format_.is_set() || *lhs.format_.get() == *rhs.format_.get())) + && (lhs.comment_.is_set() == rhs.comment_.is_set() && (!lhs.comment_.is_set() || *lhs.comment_.get() == *rhs.comment_.get())); } } // namespace detail diff --git a/source/detail/implementations/hyperlink_impl.hpp b/source/detail/implementations/hyperlink_impl.hpp index 84172986..274997c3 100644 --- a/source/detail/implementations/hyperlink_impl.hpp +++ b/source/detail/implementations/hyperlink_impl.hpp @@ -26,15 +26,18 @@ #include #include +#include namespace xlnt { namespace detail { +// [serialised] struct hyperlink_impl { xlnt::relationship relationship; - std::string tooltip; - std::string display; + xlnt::optional location; + xlnt::optional tooltip; + xlnt::optional display; }; inline bool operator==(const hyperlink_impl &lhs, const hyperlink_impl &rhs) diff --git a/source/detail/serialization/xlsx_consumer.cpp b/source/detail/serialization/xlsx_consumer.cpp index 336ddaa3..a634bf7b 100644 --- a/source/detail/serialization/xlsx_consumer.cpp +++ b/source/detail/serialization/xlsx_consumer.cpp @@ -387,7 +387,7 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) props.sync_horizontal.set(parser().attribute("syncHorizontal")); } if (parser().attribute_present("syncVertical")) - {// optional, boolean, false + { // optional, boolean, false props.sync_vertical.set(parser().attribute("syncVertical")); } if (parser().attribute_present("syncRef")) @@ -399,11 +399,11 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) props.transition_evaluation.set(parser().attribute("transitionEvaluation")); } if (parser().attribute_present("transitionEntry")) - {// optional, boolean, false + { // optional, boolean, false props.transition_entry.set(parser().attribute("transitionEntry")); } if (parser().attribute_present("published")) - {// optional, boolean, true + { // optional, boolean, true props.published.set(parser().attribute("published")); } if (parser().attribute_present("codeName")) @@ -411,11 +411,11 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) props.code_name.set(parser().attribute("codeName")); } if (parser().attribute_present("filterMode")) - {// optional, boolean, false + { // optional, boolean, false props.filter_mode.set(parser().attribute("filterMode")); } if (parser().attribute_present("enableFormatConditionsCalculation")) - {// optional, boolean, true + { // optional, boolean, true props.enable_format_condition_calculation.set(parser().attribute("enableFormatConditionsCalculation")); } ws.d_->sheet_properties_.set(props); @@ -606,19 +606,22 @@ std::string xlsx_consumer::read_worksheet_begin(const std::string &rel_id) auto min = static_cast(std::stoull(parser().attribute("min"))); auto max = static_cast(std::stoull(parser().attribute("max"))); - optional width; - - if (parser().attribute_present("width")) - { - width = (parser().attribute("width") * 7 - 5) / 7; - } - - optional column_style; - - if (parser().attribute_present("style")) - { - column_style = parser().attribute("style"); - } + // avoid uninitialised warnings in GCC by using a lambda to make the conditional initialisation + optional width = [](xml::parser &p) -> xlnt::optional { + if (p.attribute_present("width")) + { + return (p.attribute("width") * 7 - 5) / 7; + } + return xlnt::optional(); + }(parser()); + // avoid uninitialised warnings in GCC by using a lambda to make the conditional initialisation + optional column_style = [](xml::parser &p) -> xlnt::optional { + if (p.attribute_present("style")) + { + return p.attribute("style"); + } + return xlnt::optional(); + }(parser()); auto custom = parser().attribute_present("customWidth") ? is_true(parser().attribute("customWidth")) @@ -1843,7 +1846,8 @@ void xlsx_consumer::read_office_document(const std::string &content_type) // CT_ target_.d_->sheet_title_rel_id_map_.end(), [&](const std::pair &p) { return p.second == worksheet_rel.id(); - })->first; + }) + ->first; auto id = sheet_title_id_map_[title]; auto index = sheet_title_index_map_[title]; diff --git a/source/detail/serialization/xlsx_producer.cpp b/source/detail/serialization/xlsx_producer.cpp index aef2a050..5d854d7d 100644 --- a/source/detail/serialization/xlsx_producer.cpp +++ b/source/detail/serialization/xlsx_producer.cpp @@ -816,7 +816,6 @@ void xlsx_producer::write_pivot_table(const relationship & /*rel*/) void xlsx_producer::write_shared_string_table(const relationship & /*rel*/) { static const auto &xmlns = constants::ns("spreadsheetml"); - static const auto &xmlns_xml = constants::ns("xml"); write_start_element(xmlns, "sst"); write_namespace(xmlns, ""); @@ -853,18 +852,13 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/) write_attribute("count", string_count); write_attribute("uniqueCount", source_.shared_strings().size()); - auto has_trailing_whitespace = [](const std::string &s) - { - return !s.empty() && (s.front() == ' ' || s.back() == ' '); - }; - for (const auto &string : source_.shared_strings()) { if (string.runs().size() == 1 && !string.runs().at(0).second.is_set()) { write_start_element(xmlns, "si"); write_start_element(xmlns, "t"); - write_characters(string.plain_text(),string.runs().front().preserve_space); + write_characters(string.plain_text(), string.runs().front().preserve_space); write_end_element(xmlns, "t"); write_end_element(xmlns, "si"); @@ -926,7 +920,7 @@ void xlsx_producer::write_shared_string_table(const relationship & /*rel*/) } write_start_element(xmlns, "t"); - write_characters(run.first, has_trailing_whitespace(run.first)); + write_characters(run.first, run.preserve_space); write_end_element(xmlns, "t"); write_end_element(xmlns, "r"); } @@ -2755,7 +2749,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) if (ws.has_phonetic_properties()) { - write_start_element(xmlns, phonetic_pr::Serialised_ID); + write_start_element(xmlns, phonetic_pr::Serialised_ID()); const auto &ph_props = ws.phonetic_properties(); write_attribute("fontId", ph_props.font_id()); if (ph_props.has_type()) @@ -2766,7 +2760,7 @@ void xlsx_producer::write_worksheet(const relationship &rel) { write_attribute("alignment", phonetic_pr::alignment_as_string(ph_props.alignment())); } - write_end_element(xmlns, phonetic_pr::Serialised_ID); + write_end_element(xmlns, phonetic_pr::Serialised_ID()); } if (ws.has_page_margins()) diff --git a/source/detail/serialization/zstream.cpp b/source/detail/serialization/zstream.cpp index 9888d316..be72bd50 100644 --- a/source/detail/serialization/zstream.cpp +++ b/source/detail/serialization/zstream.cpp @@ -366,7 +366,7 @@ public: deflateEnd(&strm); if (header) { - std::ios::streampos final_position = ostream.tellp(); + auto final_position = ostream.tellp(); header->uncompressed_size = uncompressed_size; header->crc = crc; ostream.seekp(header->header_offset); @@ -457,14 +457,14 @@ ozstream::ozstream(std::ostream &stream) ozstream::~ozstream() { // Write all file headers - std::ios::streampos final_position = destination_stream_.tellp(); + auto final_position = destination_stream_.tellp(); for (const auto &header : file_headers_) { write_header(header, destination_stream_, true); } - std::ios::streampos central_end = destination_stream_.tellp(); + auto central_end = destination_stream_.tellp(); // Write end of central write_int(destination_stream_, static_cast(0x06054b50)); // end of central @@ -507,12 +507,12 @@ bool izstream::read_central_header() // Find the header // NOTE: this assumes the zip file header is the last thing written to file... source_stream_.seekg(0, std::ios_base::end); - std::ios::streampos end_position = source_stream_.tellg(); + auto end_position = static_cast(source_stream_.tellg()); auto max_comment_size = std::uint32_t(0xffff); // max size of header auto read_size_before_comment = std::uint32_t(22); - std::ios::streamoff read_start = max_comment_size + read_size_before_comment; + std::uint32_t read_start = max_comment_size + read_size_before_comment; if (read_start > end_position) { diff --git a/source/packaging/relationship.cpp b/source/packaging/relationship.cpp index 7788be5a..9c104e08 100644 --- a/source/packaging/relationship.cpp +++ b/source/packaging/relationship.cpp @@ -36,7 +36,7 @@ relationship::relationship( { } -std::string relationship::id() const +const std::string& relationship::id() const { return id_; } @@ -46,12 +46,12 @@ target_mode relationship::target_mode() const return mode_; } -uri relationship::source() const +const uri &relationship::source() const { return source_; } -uri relationship::target() const +const uri &relationship::target() const { return target_; } diff --git a/source/packaging/uri.cpp b/source/packaging/uri.cpp index 10b43777..850234a3 100644 --- a/source/packaging/uri.cpp +++ b/source/packaging/uri.cpp @@ -16,7 +16,7 @@ std::string uri::to_string() const return path_.string(); } -path uri::path() const +const path& uri::path() const { return path_; } diff --git a/source/styles/color.cpp b/source/styles/color.cpp index e9e1c739..01561eec 100644 --- a/source/styles/color.cpp +++ b/source/styles/color.cpp @@ -57,6 +57,11 @@ std::size_t indexed_color::index() const return index_; } +void indexed_color::index(std::size_t index) +{ + index_ = index; +} + // theme_color implementation theme_color::theme_color(std::size_t index) : index_(index) @@ -68,6 +73,11 @@ std::size_t theme_color::index() const return index_; } +void theme_color::index(std::size_t index) +{ + index_ = index; +} + // rgb_color implementation std::string rgb_color::hex_string() const @@ -223,19 +233,37 @@ void color::auto_(bool value) auto__ = value; } -indexed_color color::indexed() const +const indexed_color& color::indexed() const { assert_type(color_type::indexed); return indexed_; } -theme_color color::theme() const +indexed_color &color::indexed() +{ + assert_type(color_type::indexed); + return indexed_; +} + +const theme_color& color::theme() const { assert_type(color_type::theme); return theme_; } -rgb_color color::rgb() const +theme_color &color::theme() +{ + assert_type(color_type::theme); + return theme_; +} + +const rgb_color& color::rgb() const +{ + assert_type(color_type::rgb); + return rgb_; +} + +rgb_color &color::rgb() { assert_type(color_type::rgb); return rgb_; diff --git a/source/styles/conditional_format.cpp b/source/styles/conditional_format.cpp index 52736b47..9a5852d5 100644 --- a/source/styles/conditional_format.cpp +++ b/source/styles/conditional_format.cpp @@ -81,6 +81,11 @@ bool conditional_format::operator!=(const conditional_format &other) const return !(*this == other); } +bool conditional_format::has_border() const +{ + return d_->border_id.is_set(); +} + xlnt::border conditional_format::border() const { return d_->parent->borders.at(d_->border_id.get()); @@ -92,6 +97,11 @@ conditional_format conditional_format::border(const xlnt::border &new_border) return *this; } +bool conditional_format::has_fill() const +{ + return d_->fill_id.is_set(); +} + xlnt::fill conditional_format::fill() const { return d_->parent->fills.at(d_->fill_id.get()); @@ -103,6 +113,11 @@ conditional_format conditional_format::fill(const xlnt::fill &new_fill) return *this; } +bool conditional_format::has_font() const +{ + return d_->font_id.is_set(); +} + xlnt::font conditional_format::font() const { return d_->parent->fonts.at(d_->font_id.get()); diff --git a/source/styles/font.cpp b/source/styles/font.cpp index b474ab07..6bd31783 100644 --- a/source/styles/font.cpp +++ b/source/styles/font.cpp @@ -27,7 +27,11 @@ #include namespace { -const std::string Default_Name = "Calibri"; +const std::string &Default_Name() +{ + static const std::string Default("Calibri"); + return Default; +} constexpr double Default_Size = 12.0; } // namespace @@ -161,13 +165,13 @@ font &font::name(const std::string &name) return *this; } -const std::string& font::name() const +const std::string &font::name() const { if (name_.is_set()) { return name_.get(); } - return Default_Name; + return Default_Name(); } bool font::has_color() const @@ -229,7 +233,7 @@ std::size_t font::family() const return family_.get(); } -const std::string& font::scheme() const +const std::string &font::scheme() const { return scheme_.get(); } diff --git a/source/styles/protection.cpp b/source/styles/protection.cpp index 96b32785..3d1fd326 100644 --- a/source/styles/protection.cpp +++ b/source/styles/protection.cpp @@ -26,6 +26,26 @@ namespace xlnt { +protection protection::unlocked_and_visible() +{ + return protection(); +} + +protection protection::locked_and_visible() +{ + return protection().locked(true); +} + +protection protection::unlocked_and_hidden() +{ + return protection().hidden(true); +} + +protection protection::locked_and_hidden() +{ + return protection().locked(true).hidden(true); +} + protection::protection() : locked_(false), hidden_(false) { diff --git a/source/styles/style.cpp b/source/styles/style.cpp index d10f6896..4dd5418d 100755 --- a/source/styles/style.cpp +++ b/source/styles/style.cpp @@ -66,6 +66,11 @@ std::size_t style::builtin_id() const return d_->builtin_id.get(); } +bool style::builtin() const +{ + return d_->builtin_id.is_set(); +} + std::string style::name() const { return d_->name; @@ -87,6 +92,11 @@ bool style::operator==(const style &other) const return name() == other.name(); } +bool style::operator!=(const style &other) const +{ + return !operator==(other); +} + xlnt::alignment style::alignment() const { return d_->parent->alignments.at(d_->alignment_id.get()); diff --git a/source/utils/path.cpp b/source/utils/path.cpp index e846a6e5..34c36cf0 100644 --- a/source/utils/path.cpp +++ b/source/utils/path.cpp @@ -141,6 +141,22 @@ path::path(const std::string &path_string) { } +path::path(const std::string &path_string, char sep) + : internal_(path_string) +{ + char curr_sep = guess_separator(); + if (curr_sep != sep) + { + for (char& c : internal_) // simple find and replace + { + if (c == curr_sep) + { + c = sep; + } + } + } +} + // general attributes bool path::is_relative() const @@ -205,7 +221,7 @@ std::pair path::split_extension() const // conversion -std::string path::string() const +const std::string& path::string() const { return internal_; } @@ -331,4 +347,9 @@ bool path::operator==(const path &other) const return internal_ == other.internal_; } +bool path::operator!=(const path &other) const +{ + return !operator==(other); +} + } // namespace xlnt diff --git a/source/utils/variant.cpp b/source/utils/variant.cpp index 44d349be..4c420eea 100644 --- a/source/utils/variant.cpp +++ b/source/utils/variant.cpp @@ -27,9 +27,8 @@ namespace xlnt { variant::variant() -{ - -} + : type_(type::null) +{} variant::variant(const std::string &value) : type_(type::lpstr), @@ -41,7 +40,7 @@ variant::variant(const char *value) : variant(std::string(value)) { } -variant::variant(std::int32_t value) +variant::variant(int32_t value) : type_(type::i4), i4_value_(value) { diff --git a/source/workbook/workbook.cpp b/source/workbook/workbook.cpp index 55c460c4..c4739f03 100644 --- a/source/workbook/workbook.cpp +++ b/source/workbook/workbook.cpp @@ -28,16 +28,6 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -61,12 +51,22 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace { using xlnt::detail::open_stream; -template +template std::vector keys(const std::vector> &container) { auto result = std::vector(); @@ -80,7 +80,7 @@ std::vector keys(const std::vector> &container) return result; } -template +template bool contains(const std::vector> &container, const T key) { for (const auto &iter : container) @@ -451,29 +451,29 @@ workbook workbook::empty() stylesheet.parent = &wb; auto default_border = border() - .side(border_side::bottom, border::border_property()) - .side(border_side::top, border::border_property()) - .side(border_side::start, border::border_property()) - .side(border_side::end, border::border_property()) - .side(border_side::diagonal, border::border_property()); + .side(border_side::bottom, border::border_property()) + .side(border_side::top, border::border_property()) + .side(border_side::start, border::border_property()) + .side(border_side::end, border::border_property()) + .side(border_side::diagonal, border::border_property()); wb.d_->stylesheet_.get().borders.push_back(default_border); auto default_fill = fill(pattern_fill() - .type(pattern_fill_type::none)); + .type(pattern_fill_type::none)); stylesheet.fills.push_back(default_fill); auto gray125_fill = pattern_fill() - .type(pattern_fill_type::gray125); + .type(pattern_fill_type::gray125); stylesheet.fills.push_back(gray125_fill); auto default_font = font() - .name("Calibri") - .size(12) - .scheme("minor") - .family(2) - .color(theme_color(1)); + .name("Calibri") + .size(12) + .scheme("minor") + .family(2) + .color(theme_color(1)); stylesheet.fonts.push_back(default_font); - wb.create_builtin_style(0) + wb.create_builtin_style(0) .border(default_border) .fill(default_fill) .font(default_font) @@ -550,7 +550,7 @@ void workbook::register_package_part(relationship_type type) void workbook::register_workbook_part(relationship_type type) { auto wb_rel = manifest().relationship(path("/"), relationship_type::office_document); - auto wb_path = manifest().canonicalize({ wb_rel }); + auto wb_path = manifest().canonicalize({wb_rel}); if (!manifest().has_relationship(wb_path, type)) { @@ -738,22 +738,35 @@ worksheet workbook::create_sheet() std::string title = "Sheet1"; int index = 1; + // make a unique sheet name. Sheet<1...n> while (contains(title)) { title = "Sheet" + std::to_string(++index); } - + // unique sheet id size_t sheet_id = 1; for (const auto ws : *this) { sheet_id = std::max(sheet_id, ws.id() + 1); } - std::string sheet_filename = "sheet" + std::to_string(sheet_id) + ".xml"; - d_->worksheets_.push_back(detail::worksheet_impl(this, sheet_id, title)); - + // unique sheet file name auto workbook_rel = d_->manifest_.relationship(path("/"), relationship_type::office_document); - uri relative_sheet_uri(path("worksheets").append(sheet_filename).string()); + auto workbook_files = d_->manifest_.relationships(workbook_rel.target().path()); + auto rel_vec_contains = [&workbook_files](const xlnt::path &new_file_id) { + return workbook_files.end() != std::find_if(workbook_files.begin(), workbook_files.end(), [&new_file_id](const xlnt::relationship &rel) { + return rel.target().path() == new_file_id; + }); + }; + + size_t file_id = sheet_id; + xlnt::path sheet_relative_path; + do + { + sheet_relative_path = path("worksheets").append("sheet" + std::to_string(file_id++) + ".xml"); + } while (rel_vec_contains(sheet_relative_path)); + + uri relative_sheet_uri(sheet_relative_path.string()); auto absolute_sheet_path = path("/xl").append(relative_sheet_uri.path()); d_->manifest_.register_override_type( absolute_sheet_path, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"); @@ -1025,7 +1038,8 @@ void workbook::remove_sheet(worksheet ws) for (auto &title_rel_id_pair : d_->sheet_title_rel_id_map_) { title_rel_id_pair.second = rel_id_map.count(title_rel_id_pair.second) > 0 - ? rel_id_map[title_rel_id_pair.second] : title_rel_id_pair.second; + ? rel_id_map[title_rel_id_pair.second] + : title_rel_id_pair.second; } update_sheet_properties(); @@ -1390,7 +1404,7 @@ style workbook::create_style(const std::string &name) style workbook::create_builtin_style(const std::size_t builtin_id) { - return d_->stylesheet_.get().create_builtin_style(builtin_id); + return d_->stylesheet_.get().create_builtin_style(builtin_id); } style workbook::style(const std::string &name) @@ -1563,69 +1577,83 @@ void workbook::update_sheet_properties() } } -void workbook::reorder_relationships() +namespace { +// true if a sheet index is != worksheet relationship index +bool needs_reorder(const std::unordered_map &title_to_rels, + const std::vector &titles, + std::vector &relation_ids) { - const auto wb_rel = manifest().relationship(path("/"), relationship_type::office_document); - const auto wb_path = wb_rel.target().path(); - const auto relationships = manifest().relationships(wb_path); - std::unordered_map rel_map; - const auto titles = sheet_titles(); - const auto title_rel_id_map = d_->sheet_title_rel_id_map_; - bool needs_reorder = false; - - for (const auto &rel : relationships) + bool all_match = true; + for (std::size_t title_index = 0; title_index < titles.size(); ++title_index) { - rel_map[rel.id()] = rel; - - if (rel.type() == relationship_type::worksheet) + const auto &rel = title_to_rels.at(titles[title_index]); + relation_ids.push_back(rel); + const auto expected_rel_id = "rId" + std::to_string(title_index + 1); + if (rel != expected_rel_id) { - for (auto title_index = std::size_t(0); title_index < titles.size(); ++title_index) - { - const auto title = titles[title_index]; - - if (title_rel_id_map.at(title) == rel.id()) - { - const auto expected_rel_id = "rId" + std::to_string(title_index + 1); - - if (expected_rel_id != rel.id()) - { - needs_reorder = true; - } - - break; - } - } + all_match = false; } } + return !all_match; // if all are as expected, reorder not required +}; - if (!needs_reorder) +struct rel_id_sorter +{ + // true if lhs < rhs + bool operator()(const xlnt::relationship &lhs, const xlnt::relationship &rhs) + { + // format is rTd + if (lhs.id().size() < rhs.id().size()) // a number with more digits will be larger + { + return true; + } + return lhs.id() < rhs.id(); + } +}; +} // namespace + +void workbook::reorder_relationships() +{ + const auto titles = sheet_titles(); + // the relation ID corresponding to the title at the same index is copied into here + std::vector worksheet_rel_ids; + worksheet_rel_ids.reserve(titles.size()); + if (!needs_reorder(d_->sheet_title_rel_id_map_, titles, worksheet_rel_ids)) { return; } - - for (const auto &rel : relationships) + // copy of existing relations + const auto wb_rel_target = manifest().relationship(path("/"), relationship_type::office_document).target(); + auto rel_copy = manifest().relationships(wb_rel_target.path()); + std::sort(rel_copy.begin(), rel_copy.end(), rel_id_sorter{}); + // clear existing relations + for (const auto &rel : rel_copy) { - manifest().unregister_relationship(uri(wb_path.string()), rel.id()); + manifest().unregister_relationship(wb_rel_target, rel.id()); } - - for (auto index = std::size_t(0); index < rel_map.size(); ++index) + // create new relations + std::size_t index = 0; + auto new_id = [&index]() { return "rId" + std::to_string(++index); }; // ids start from 1 + // worksheets first + while (index < worksheet_rel_ids.size()) { - auto rel_id = "rId" + std::to_string(index + 1); - auto old_rel_id = std::string(); + auto rel_it = std::find_if(rel_copy.begin(), rel_copy.end(), + [&](const relationship &rel) { return rel.id() == worksheet_rel_ids[index]; }); - if (index < titles.size()) + std::string rel_id = new_id(); + d_->sheet_title_rel_id_map_.at(titles[index - 1]) = rel_id; // update title -> relation mapping + manifest().register_relationship(relationship(rel_id, rel_it->type(), + rel_it->source(), rel_it->target(), rel_it->target_mode())); + } + // then all the other relations in the same order they started (just new indices) + for (const auto &old_rel : rel_copy) + { + if (old_rel.type() == relationship_type::worksheet) { - auto title = titles[index]; - old_rel_id = title_rel_id_map.at(title); - d_->sheet_title_rel_id_map_[title] = rel_id; - } else { - old_rel_id = "rId" + std::to_string(index - titles.size() + 2); + continue; } - - auto old_rel = rel_map[old_rel_id]; - auto new_rel = relationship(rel_id, old_rel.type(), - old_rel.source(), old_rel.target(), old_rel.target_mode()); - manifest().register_relationship(new_rel); + manifest().register_relationship(relationship(new_id(), old_rel.type(), + old_rel.source(), old_rel.target(), old_rel.target_mode())); } } diff --git a/source/worksheet/phonetic_pr.cpp b/source/worksheet/phonetic_pr.cpp index 97efe752..f046fa58 100644 --- a/source/worksheet/phonetic_pr.cpp +++ b/source/worksheet/phonetic_pr.cpp @@ -25,18 +25,26 @@ #include namespace { // Order of elements defined by phonetic_pr::Type enum -const std::array Types{ - "fullwidthKatakana", - "halfwidthKatakana", - "Hiragana", - "noConversion"}; +const std::array &Types() +{ + static const std::array types{ + std::string("fullwidthKatakana"), + std::string("halfwidthKatakana"), + std::string("Hiragana"), + std::string("noConversion")}; + return types; +} // Order of elements defined by phonetic_pr::alignment enum -const std::array alignments{ - "Center", - "Distributed", - "Left", - "NoControl"}; +const std::array &Alignments() +{ + static const std::array alignments{ + std::string("Center"), + std::string("Distributed"), + std::string("Left"), + std::string("NoControl")}; + return alignments; +} } // namespace @@ -44,7 +52,10 @@ namespace xlnt { /// /// out of line initialiser for static const member /// -const std::string phonetic_pr::Serialised_ID = "phoneticPr"; +std::string phonetic_pr::Serialised_ID() +{ + return "phoneticPr"; +} phonetic_pr::phonetic_pr(font_id_t font) : font_id_(font) @@ -53,7 +64,7 @@ phonetic_pr::phonetic_pr(font_id_t font) void phonetic_pr::serialise(std::ostream &output_stream) const { - output_stream << '<' << Serialised_ID << R"( fontID=")" << std::to_string(font_id_) << '"'; + output_stream << '<' << Serialised_ID() << R"( fontID=")" << std::to_string(font_id_) << '"'; if (has_type()) { output_stream << R"( type=")" << type_as_string(type_.get()) << '"'; @@ -108,14 +119,14 @@ void phonetic_pr::alignment(align align) // serialisation const std::string &phonetic_pr::type_as_string(phonetic_pr::phonetic_type type) { - return Types[static_cast(type)]; + return Types()[static_cast(type)]; } phonetic_pr::phonetic_type phonetic_pr::type_from_string(const std::string &str) { - for (std::size_t i = 0; i < Types.size(); ++i) + for (std::size_t i = 0; i < Types().size(); ++i) { - if (str == Types[i]) + if (str == Types()[i]) { return static_cast(i); } @@ -125,14 +136,14 @@ phonetic_pr::phonetic_type phonetic_pr::type_from_string(const std::string &str) const std::string &phonetic_pr::alignment_as_string(align type) { - return alignments[static_cast(type)]; + return Alignments()[static_cast(type)]; } phonetic_pr::align phonetic_pr::alignment_from_string(const std::string &str) { - for (std::size_t i = 0; i < alignments.size(); ++i) + for (std::size_t i = 0; i < Alignments().size(); ++i) { - if (str == alignments[i]) + if (str == Alignments()[i]) { return static_cast(i); } diff --git a/source/worksheet/range.cpp b/source/worksheet/range.cpp index fa8e8eb2..7649edc7 100644 --- a/source/worksheet/range.cpp +++ b/source/worksheet/range.cpp @@ -31,7 +31,7 @@ namespace xlnt { -range::range(worksheet ws, const range_reference &reference, major_order order, bool skip_null) +range::range(class worksheet ws, const range_reference &reference, major_order order, bool skip_null) : ws_(ws), ref_(reference), order_(order), @@ -69,6 +69,11 @@ cell_vector range::operator[](std::size_t index) return vector(index); } +const cell_vector range::operator[](std::size_t index) const +{ + return vector(index); +} + const worksheet &range::target_worksheet() const { return ws_; @@ -112,6 +117,22 @@ cell_vector range::vector(std::size_t vector_index) return cell_vector(ws_, cursor, ref_, order_, skip_null_, false); } +const cell_vector range::vector(std::size_t vector_index) const +{ + auto cursor = ref_.top_left(); + + if (order_ == major_order::row) + { + cursor.row(cursor.row() + static_cast(vector_index)); + } + else + { + cursor.column_index(cursor.column_index() + static_cast(vector_index)); + } + + return cell_vector(ws_, cursor, ref_, order_, skip_null_, false); +} + bool range::contains(const cell_reference &ref) { return ref_.top_left().column_index() <= ref.column_index() @@ -188,6 +209,11 @@ cell range::cell(const cell_reference &ref) return (*this)[ref.row() - 1][ref.column().index - 1]; } +const cell range::cell(const cell_reference &ref) const +{ + return (*this)[ref.row() - 1][ref.column().index - 1]; +} + cell_vector range::front() { return *begin(); diff --git a/source/worksheet/worksheet.cpp b/source/worksheet/worksheet.cpp index 9d59e895..5ab28fa1 100644 --- a/source/worksheet/worksheet.cpp +++ b/source/worksheet/worksheet.cpp @@ -98,7 +98,7 @@ void worksheet::create_named_range(const std::string &name, const range_referenc throw invalid_parameter(); //("named range name must be outside the range A1-XFD1048576"); } } - catch (xlnt::invalid_cell_reference) + catch (xlnt::invalid_cell_reference&) { // name is not a valid reference, that's good } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b472636d..74c24352 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 3.1) project(xlnt.test) -# Require C++11 compiler -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD ${XLNT_CXX_LANG}) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CXX_EXTENSIONS OFF) if(NOT COMBINED_PROJECT) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../source ${CMAKE_CURRENT_BINARY_DIR}/source) @@ -48,6 +48,8 @@ target_include_directories(xlnt.test set(XLNT_TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data) target_compile_definitions(xlnt.test PRIVATE XLNT_TEST_DATA_DIR=${XLNT_TEST_DATA_DIR}) +# requires cmake 3.8+ +#target_compile_features(xlnt.test PRIVATE cxx_std_${XLNT_CXX_LANG}) if(MSVC) # bigobj because there are so many headers in one source file diff --git a/tests/cell/cell_test_suite.cpp b/tests/cell/cell_test_suite.cpp index a0753a49..510786b3 100644 --- a/tests/cell/cell_test_suite.cpp +++ b/tests/cell/cell_test_suite.cpp @@ -29,18 +29,20 @@ #include #include #include -#include #include #include #include #include +#include #include #include -#include #include #include #include #include +#include +#include +#include class cell_test_suite : public test_suite { @@ -79,6 +81,7 @@ public: register_test(test_anchor); register_test(test_hyperlink); register_test(test_comment); + register_test(test_copy_and_compare); } private: @@ -251,11 +254,27 @@ private: xlnt::workbook wb; auto ws = wb.active_sheet(); auto cell = ws.cell(xlnt::cell_reference(1, 1)); + // error string can't be empty + xlnt_assert_throws(cell.error(""), xlnt::exception); + // error string has to have a leading '#' + xlnt_assert_throws(cell.error("not an error"), xlnt::exception); for (auto error_code : xlnt::cell::error_codes()) { + // error type from the string format cell.value(error_code.first, true); xlnt_assert(cell.data_type() == xlnt::cell::type::error); + std::string error; + xlnt_assert_throws_nothing(error = cell.error()); + xlnt_assert_equals(error, error_code.first); + // clearing the value clears the error state + cell.clear_value(); + xlnt_assert_throws(cell.error(), xlnt::exception); + // can explicitly set the error + xlnt_assert_throws_nothing(cell.error(error_code.first)); + std::string error2; + xlnt_assert_throws_nothing(error2 = cell.error()); + xlnt_assert_equals(error2, error_code.first); } } @@ -665,13 +684,81 @@ private: { xlnt::workbook wb; auto cell = wb.active_sheet().cell("A1"); + xlnt_assert(!cell.has_hyperlink()); xlnt_assert_throws(cell.hyperlink(), xlnt::invalid_attribute); xlnt_assert_throws(cell.hyperlink("notaurl"), xlnt::invalid_parameter); xlnt_assert_throws(cell.hyperlink(""), xlnt::invalid_parameter); - cell.hyperlink("http://example.com"); + // link without optional display + const std::string link1("http://example.com"); + cell.hyperlink(link1); xlnt_assert(cell.has_hyperlink()); - xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), "http://example.com"); + xlnt_assert(cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().url(), link1); + xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), link1); + xlnt_assert_equals(cell.hyperlink().display(), link1); + cell.clear_value(); + // link with display + const std::string link2("http://example2.com"); + const std::string display_txt("notaurl"); + cell.hyperlink(link2, display_txt); + xlnt_assert(cell.has_hyperlink()); + xlnt_assert(cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().url(), link2); + xlnt_assert_equals(cell.hyperlink().relationship().target().to_string(), link2); + xlnt_assert_equals(cell.hyperlink().display(), display_txt); + // value + int cell_test_val = 123; + cell.value(cell_test_val); + std::string cell_value_str = std::to_string(cell_test_val); + cell.hyperlink(link2, display_txt); + xlnt_assert_equals(cell.value(), 123); + xlnt_assert_equals(cell.hyperlink().display(), cell_value_str); // display text ignored + cell.clear_value(); + // cell overload without display + const std::string cell_target_str("A2"); + auto cell_target = wb.active_sheet().cell(cell_target_str); + std::string link3 = wb.active_sheet().title() + '!' + cell_target_str; // Sheet1!A2 + cell.hyperlink(cell_target); + xlnt_assert(cell.has_hyperlink()); + xlnt_assert(!cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().target_range(), link3); + xlnt_assert_equals(cell.hyperlink().display(), link3); + cell.clear_value(); + // cell overload with display + cell.hyperlink(cell_target, display_txt); + xlnt_assert(cell.has_hyperlink()); + xlnt_assert(!cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().target_range(), link3); + xlnt_assert_equals(cell.hyperlink().display(), display_txt); + // value + cell.value(cell_test_val); + cell.hyperlink(cell_target, display_txt); + xlnt_assert_equals(cell.value(), 123); + xlnt_assert_equals(cell.hyperlink().display(), cell_value_str); // display text ignored + cell.clear_value(); + // range overload without display + const std::string range_target_str("A2:A5"); + xlnt::range range_target(wb.active_sheet(), xlnt::range_reference(range_target_str)); + std::string link4 = wb.active_sheet().title() + '!' + range_target_str; // Sheet1!A2:A5 + cell.hyperlink(range_target); + xlnt_assert(cell.has_hyperlink()); + xlnt_assert(!cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().target_range(), link4); + xlnt_assert_equals(cell.hyperlink().display(), link4); + cell.clear_value(); + // range overload with display + cell.hyperlink(range_target, display_txt); + xlnt_assert(cell.has_hyperlink()); + xlnt_assert(!cell.hyperlink().external()); + xlnt_assert_equals(cell.hyperlink().target_range(), link4); + xlnt_assert_equals(cell.hyperlink().display(), display_txt); + // value + cell.value(cell_test_val); + cell.hyperlink(range_target, display_txt); + xlnt_assert_equals(cell.value(), 123); + xlnt_assert_equals(cell.hyperlink().display(), cell_value_str); // display text ignored + cell.clear_value(); } void test_comment() @@ -688,6 +775,31 @@ private: xlnt_assert(!cell.has_comment()); xlnt_assert_throws(cell.comment(), xlnt::exception); } + + void test_copy_and_compare() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + auto cell1 = ws.cell("A1"); + auto cell2 = ws.cell("A2"); + + xlnt_assert_equals(cell1, cell1); + xlnt_assert_equals(cell2, cell2); + xlnt_assert_differs(cell1, cell2); + xlnt_assert_differs(cell2, cell1); + // nullptr equality + xlnt_assert(nullptr == cell1); + xlnt_assert(cell1 == nullptr); + cell1.value("test"); + xlnt_assert(!(nullptr == cell1)); + xlnt_assert(!(cell1 == nullptr)); + // copy + xlnt::cell cell3(cell1); + xlnt_assert_equals(cell1, cell3); + // assign + cell3 = cell2; + xlnt_assert_equals(cell2, cell3); + } }; static cell_test_suite x{}; \ No newline at end of file diff --git a/tests/cell/rich_text_test_suite.cpp b/tests/cell/rich_text_test_suite.cpp index af44f815..3af26383 100644 --- a/tests/cell/rich_text_test_suite.cpp +++ b/tests/cell/rich_text_test_suite.cpp @@ -35,6 +35,7 @@ public: rich_text_test_suite() { register_test(test_operators); + register_test(test_runs); } void test_operators() @@ -100,5 +101,19 @@ public: text_family_differs.add_run(run_family_differs); xlnt_assert_differs(text_formatted, text_family_differs); } + + void test_runs() + { + xlnt::rich_text rt; + xlnt_assert(rt.runs().empty()); + std::vector test_runs{xlnt::rich_text_run{"1_abc_test_123"}, xlnt::rich_text_run{"2_abc_test_123"}, xlnt::rich_text_run{"3_abc_test_123"}}; + // just one + rt.add_run(test_runs[0]); + xlnt_assert_equals(1, rt.runs().size()); + xlnt_assert_equals(test_runs[0], rt.runs()[0]); + // whole set + rt.runs(test_runs); + xlnt_assert_equals(test_runs, rt.runs()); + } }; static rich_text_test_suite x{}; \ No newline at end of file diff --git a/tests/data/Issue279_workbook_delete_rename.xlsx b/tests/data/Issue279_workbook_delete_rename.xlsx new file mode 100644 index 00000000..981af413 Binary files /dev/null and b/tests/data/Issue279_workbook_delete_rename.xlsx differ diff --git a/tests/helpers/xml_helper.hpp b/tests/helpers/xml_helper.hpp index 9c051536..9a90e9e3 100644 --- a/tests/helpers/xml_helper.hpp +++ b/tests/helpers/xml_helper.hpp @@ -3,17 +3,17 @@ #include #include +#include +#include #include #include #include -#include -#include class xml_helper { public: static bool compare_files(const std::string &left, - const std::string &right, const std::string &content_type) + const std::string &right, const std::string &content_type) { // content types are stored in unordered maps, too complicated to compare if (content_type == "[Content_Types].xml") @@ -34,7 +34,7 @@ public: } auto is_xml = (content_type.substr(0, 12) == "application/" - && content_type.substr(content_type.size() - 4) == "+xml") + && content_type.substr(content_type.size() - 4) == "+xml") || content_type == "application/xml" || content_type == "[Content_Types].xml" || content_type == "application/vnd.openxmlformats-officedocument.vmlDrawing"; @@ -63,8 +63,7 @@ public: bool difference = false; auto right_iter = right_parser.begin(); - auto is_whitespace = [](const std::string &v) - { + auto is_whitespace = [](const std::string &v) { return v.find_first_not_of("\n\r\t ") == std::string::npos; }; @@ -198,26 +197,26 @@ public: ++right_iter; } - if (difference && !suppress_debug_info) - { - std::cout << "documents don't match" << std::endl; + if (difference && !suppress_debug_info) + { + std::cout << "documents don't match" << std::endl; - std::cout << "left:" << std::endl; + std::cout << "left:" << std::endl; for (auto c : left) { std::cout << c << std::flush; } - std::cout << std::endl; + std::cout << std::endl; - std::cout << "right:" << std::endl; + std::cout << "right:" << std::endl; for (auto c : right) { std::cout << c << std::flush; } - std::cout << std::endl; - } + std::cout << std::endl; + } - return !difference; + return !difference; } static bool compare_relationships(const xlnt::manifest &left, @@ -271,27 +270,26 @@ public: return true; } - static bool xlsx_archives_match(const std::vector &left, + static bool xlsx_archives_match(const std::vector &left, const std::vector &right) - { + { xlnt::detail::vector_istreambuf left_buffer(left); std::istream left_stream(&left_buffer); xlnt::detail::izstream left_archive(left_stream); - const auto left_info = left_archive.files(); + const auto left_info = left_archive.files(); xlnt::detail::vector_istreambuf right_buffer(right); std::istream right_stream(&right_buffer); xlnt::detail::izstream right_archive(right_stream); - const auto right_info = right_archive.files(); + const auto right_info = right_archive.files(); auto difference_is_missing_calc_chain = false; if (std::abs(int(left_info.size()) - int(right_info.size())) == 1) { - auto is_calc_chain = [](const xlnt::path &p) - { + auto is_calc_chain = [](const xlnt::path &p) { return p.filename() == "calcChain.xml"; }; @@ -306,7 +304,7 @@ public: } } - if (left_info.size() != right_info.size() && ! difference_is_missing_calc_chain) + if (left_info.size() != right_info.size() && !difference_is_missing_calc_chain) { std::cout << "left has a different number of files than right" << std::endl; @@ -338,12 +336,41 @@ public: if (!compare_relationships(left_manifest, right_manifest)) { + std::cout << "relationship mismatch\n" + << "Left:\n"; + for (const auto &part : left_manifest.parts()) + { + std::cout << "-part: " << part.string() << '\n'; + auto rels = left_manifest.relationships(part); + for (auto &rel : rels) + { + std::cout << rel.id() << ':' + << static_cast(rel.type()) + << ':' << static_cast(rel.target_mode()) + << ':' << rel.source().path().string() + << ':' << rel.target().path().string() << '\n'; + } + } + std::cout << "\nRight:\n"; + for (const auto &part : right_manifest.parts()) + { + std::cout << "-part: " << part.string() << '\n'; + auto rels = right_manifest.relationships(part); + for (auto &rel : rels) + { + std::cout << rel.id() + << ':' << static_cast(rel.type()) + << ':' << static_cast(rel.target_mode()) + << ':' << rel.source().path().string() + << ':' << rel.target().path().string() << '\n'; + } + } return false; } - for (auto left_member : left_info) - { - if (!right_archive.has_file(left_member)) + for (auto left_member : left_info) + { + if (!right_archive.has_file(left_member)) { if (difference_is_missing_calc_chain) { @@ -357,32 +384,34 @@ public: } auto left_content_type = left_member.string() == "[Content_Types].xml" - ? "[Content_Types].xml" : left_manifest.content_type(left_member); + ? "[Content_Types].xml" + : left_manifest.content_type(left_member); auto right_content_type = left_member.string() == "[Content_Types].xml" - ? "[Content_Types].xml" : right_manifest.content_type(left_member); + ? "[Content_Types].xml" + : right_manifest.content_type(left_member); if (left_content_type != right_content_type) { std::cout << "content types differ: " - << left_member.string() - << " " - << left_content_type - << " " - << right_content_type - << std::endl; + << left_member.string() + << " " + << left_content_type + << " " + << right_content_type + << std::endl; match = false; break; } if (!compare_files(left_archive.read(left_member), - right_archive.read(left_member), left_content_type)) - { - std::cout << left_member.string() << std::endl; + right_archive.read(left_member), left_content_type)) + { + std::cout << left_member.string() << std::endl; match = false; break; - } - } + } + } - return match; - } + return match; + } }; diff --git a/tests/styles/color_test_suite.cpp b/tests/styles/color_test_suite.cpp index ac61291b..e65f540a 100644 --- a/tests/styles/color_test_suite.cpp +++ b/tests/styles/color_test_suite.cpp @@ -38,37 +38,39 @@ public: void test_known_colors() { - const std::vector> known_colors - { - { xlnt::color::black(), "FF000000" }, - { xlnt::color::white(), "FFFFFFFF" }, - { xlnt::color::red(), "FFFF0000" }, - { xlnt::color::darkred(), "FF8B0000" }, - { xlnt::color::blue(), "FF0000FF" }, - { xlnt::color::darkblue(), "FF00008B" }, - { xlnt::color::green(), "FF00FF00" }, - { xlnt::color::darkgreen(), "FF008B00" }, - { xlnt::color::yellow(), "FFFFFF00" }, - { xlnt::color::darkyellow(), "FFCCCC00" } - }; - + const std::vector> known_colors{ + {xlnt::color::black(), "FF000000"}, + {xlnt::color::white(), "FFFFFFFF"}, + {xlnt::color::red(), "FFFF0000"}, + {xlnt::color::darkred(), "FF8B0000"}, + {xlnt::color::blue(), "FF0000FF"}, + {xlnt::color::darkblue(), "FF00008B"}, + {xlnt::color::green(), "FF00FF00"}, + {xlnt::color::darkgreen(), "FF008B00"}, + {xlnt::color::yellow(), "FFFFFF00"}, + {xlnt::color::darkyellow(), "FFCCCC00"}}; + for (auto pair : known_colors) { xlnt_assert_equals(pair.first.rgb().hex_string(), pair.second); } } - + void test_non_rgb_colors() { - xlnt::color indexed = xlnt::indexed_color(1); - xlnt_assert(!indexed.auto_()); + xlnt::color indexed = xlnt::indexed_color(1); + xlnt_assert(!indexed.auto_()); xlnt_assert_equals(indexed.indexed().index(), 1); + indexed.indexed().index(2); + xlnt_assert_equals(indexed.indexed().index(), 2); xlnt_assert_throws(indexed.theme(), xlnt::invalid_attribute); xlnt_assert_throws(indexed.rgb(), xlnt::invalid_attribute); - xlnt::color theme = xlnt::theme_color(3); - xlnt_assert(!theme.auto_()); + xlnt::color theme = xlnt::theme_color(3); + xlnt_assert(!theme.auto_()); xlnt_assert_equals(theme.theme().index(), 3); + theme.theme().index(4); + xlnt_assert_equals(theme.theme().index(), 4); xlnt_assert_throws(theme.indexed(), xlnt::invalid_attribute); xlnt_assert_throws(theme.rgb(), xlnt::invalid_attribute); } diff --git a/tests/styles/conditional_format_test_suite.cpp b/tests/styles/conditional_format_test_suite.cpp new file mode 100644 index 00000000..1410774f --- /dev/null +++ b/tests/styles/conditional_format_test_suite.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2014-2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#include +#include +#include + +class conditional_format_test_suite : public test_suite +{ +public: + conditional_format_test_suite() + { + register_test(test_all); + } + + void test_all() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + auto format = ws.conditional_format(xlnt::range_reference("A1:A10"), xlnt::condition::text_contains("test")); + xlnt_assert(!format.has_border()); + xlnt_assert(!format.has_fill()); + xlnt_assert(!format.has_font()); + // set border + auto border = xlnt::border().diagonal(xlnt::diagonal_direction::both); + format.border(border); + xlnt_assert(format.has_border()); + xlnt_assert_equals(format.border(), border); + // set fill + auto fill = xlnt::fill(xlnt::gradient_fill().type(xlnt::gradient_fill_type::path)); + format.fill(fill); + xlnt_assert(format.has_fill()); + xlnt_assert_equals(format.fill(), fill); + // set font + auto font = xlnt::font().color(xlnt::color::darkblue()); + format.font(font); + xlnt_assert(format.has_font()); + xlnt_assert_equals(format.font(), font); + // copy ctor + auto format_copy(format); + xlnt_assert_equals(format, format_copy); + } +}; +static conditional_format_test_suite x; \ No newline at end of file diff --git a/tests/styles/protection_test_suite.cpp b/tests/styles/protection_test_suite.cpp new file mode 100644 index 00000000..039d987c --- /dev/null +++ b/tests/styles/protection_test_suite.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2014-2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#include +#include +#include + +class protection_test_suite : public test_suite +{ +public: + protection_test_suite() + { + register_test(test_all); + } + + void test_all() + { + auto prot = xlnt::protection::unlocked_and_visible(); + xlnt_assert(!prot.hidden()); + xlnt_assert(!prot.locked()); + + prot = xlnt::protection::locked_and_visible(); + xlnt_assert(!prot.hidden()); + xlnt_assert(prot.locked()); + + prot = xlnt::protection::unlocked_and_hidden(); + xlnt_assert(prot.hidden()); + xlnt_assert(!prot.locked()); + + prot = xlnt::protection::locked_and_hidden(); + xlnt_assert(prot.hidden()); + xlnt_assert(prot.locked()); + } +}; +static protection_test_suite x; \ No newline at end of file diff --git a/tests/styles/style_test_suite.cpp b/tests/styles/style_test_suite.cpp new file mode 100644 index 00000000..3cd22a15 --- /dev/null +++ b/tests/styles/style_test_suite.cpp @@ -0,0 +1,66 @@ +// Copyright (c) 2014-2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#include +#include +#include + +class style_test_suite : public test_suite +{ +public: + style_test_suite() + { + register_test(test_all); + } + + void test_all() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + auto test_style = wb.create_style("test_style"); + test_style.number_format(xlnt::number_format::date_ddmmyyyy()); + + auto copy_style(test_style); + xlnt_assert_equals(test_style, copy_style); + + // number format + xlnt_assert_equals(copy_style.name(), "test_style"); + xlnt_assert_equals(copy_style.number_format(), xlnt::number_format::date_ddmmyyyy()); + //xlnt_assert(!copy_style.number_format_applied()); // this doesn't seem to have sensible behaviour? + copy_style.number_format(xlnt::number_format::date_datetime(), true); // true applied param + xlnt_assert_equals(copy_style.number_format(), xlnt::number_format::date_datetime()); + xlnt_assert(copy_style.number_format_applied()); + copy_style.number_format(xlnt::number_format::date_dmminus(), false); // false applied param + xlnt_assert_equals(copy_style.number_format(), xlnt::number_format::date_dmminus()); + xlnt_assert(!copy_style.number_format_applied()); + + xlnt_assert(!copy_style.pivot_button()); + copy_style.pivot_button(true); + xlnt_assert(copy_style.pivot_button()); + + xlnt_assert(!copy_style.quote_prefix()); + copy_style.quote_prefix(true); + xlnt_assert(copy_style.quote_prefix()); + } +}; +static style_test_suite x; \ No newline at end of file diff --git a/tests/utils/optional_tests.cpp b/tests/utils/optional_tests.cpp new file mode 100644 index 00000000..6313e167 --- /dev/null +++ b/tests/utils/optional_tests.cpp @@ -0,0 +1,307 @@ +// Copyright (c) 2014-2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#include +#include +#include + +// test helpers +namespace { +// increments count when constructed, decrements when destructed +// use to ensure correct ctor/dtor pairing +struct alive_count +{ + alive_count() + { + ++count; + } + + alive_count(const alive_count &) + { + ++count; + } + + alive_count(alive_count &&) + { + ++count; + } + + ~alive_count() + { + --count; + } + + alive_count &operator=(const alive_count &) = default; + alive_count &operator=(alive_count &&) = default; + + static int count; +}; +int alive_count::count = 0; + +// implicitly convertible from int +struct convertible +{ + // implicit construction from int + convertible(int i) + : val(i) + { + } + + int val; +}; + +// default ctor deleted +struct no_default +{ + no_default() = delete; + int i; +}; +} // namespace + +class optional_test_suite : public test_suite +{ +public: + optional_test_suite() + : test_suite() + { + register_test(test_ctor); + register_test(test_copy_ctor); + register_test(test_move_ctor); + register_test(test_copy_assign); + register_test(test_move_assign); + register_test(test_set_and_get); + register_test(test_equality); + register_test(test_const); + } + + void test_ctor() + { + // default + xlnt::optional opt1; + xlnt_assert(!opt1.is_set()); + // value + const int test_val = 3; + xlnt::optional opt2(test_val); + xlnt_assert(opt2.is_set()); + xlnt_assert_equals(opt2.get(), test_val); + // converting + xlnt::optional opt3(test_val); + xlnt_assert(opt3.is_set()); + xlnt_assert_equals(opt3.get().val, test_val); + // no default ctor + xlnt::optional no_def_opt; + } + + void test_copy_ctor() + { + { // copy behaviour + xlnt::optional opt1; + xlnt::optional opt2(opt1); + xlnt_assert_equals(opt1, opt2); + + const int test_val = 123; + xlnt::optional opt3(test_val); + xlnt::optional opt4(opt3); + xlnt_assert_equals(opt3, opt4); + } + { // lifetime checks + xlnt::optional opt1(alive_count{}); + xlnt_assert_equals(1, alive_count::count); + { + xlnt::optional opt2(opt1); + xlnt_assert_equals(2, alive_count::count); + } + xlnt_assert_equals(1, alive_count::count); + } + xlnt_assert_equals(0, alive_count::count); // dtor test + } + + void test_move_ctor() + { + { // move behaviour + xlnt::optional opt1; + xlnt::optional opt2(std::move(opt1)); + xlnt_assert_equals(opt2, xlnt::optional{}); // can't test against opt1 so use a temporary + + const int test_val = 123; + xlnt::optional opt3(test_val); + xlnt::optional opt4(std::move(opt3)); + xlnt_assert(opt4.is_set()); // moved to optional contains the value + xlnt_assert_equals(opt4.get(), test_val); + } + { // lifetime checks + xlnt::optional opt1(alive_count{}); + xlnt_assert_equals(1, alive_count::count); + { + xlnt::optional opt2(std::move(opt1)); + xlnt_assert_equals(1, alive_count::count); // opt1 is in a no-value state + } + xlnt_assert_equals(0, alive_count::count); + } + xlnt_assert_equals(0, alive_count::count); // dtor test + } + + void test_copy_assign() + { + { // copy assign behaviour + xlnt::optional opt1; + xlnt::optional opt_assign1; // to actually test assignment, the value needs to be already created. using '=' is not enough + opt_assign1 = opt1; + xlnt_assert_equals(opt1, opt_assign1); + + const int test_val = 123; + xlnt::optional opt2(test_val); + xlnt::optional opt_assign2; + opt_assign2 = opt2; + xlnt_assert_equals(opt2, opt_assign2); + } + { // lifetime checks + xlnt::optional opt1(alive_count{}); + xlnt_assert_equals(1, alive_count::count); + { + xlnt::optional opt_assign1; + opt_assign1 = opt1; + xlnt_assert_equals(2, alive_count::count); + } + xlnt_assert_equals(1, alive_count::count); + } + xlnt_assert_equals(0, alive_count::count); // dtor test + } + + void test_move_assign() + { + { // copy assign behaviour + xlnt::optional opt1; + xlnt::optional opt_assign1; // to actually test assignment, the value needs to be already created. using '=' is not enough + opt_assign1 = std::move(opt1); + xlnt_assert_equals(opt_assign1, xlnt::optional{}); // can't test against opt1 so use a temporary + + const int test_val = 123; + xlnt::optional opt2(test_val); + xlnt::optional opt_assign2; + opt_assign2 = std::move(opt2); + xlnt_assert(opt_assign2.is_set()); // moved to optional contains the value + xlnt_assert_equals(opt_assign2.get(), test_val); + } + { // lifetime checks + xlnt::optional opt1(alive_count{}); + xlnt_assert_equals(1, alive_count::count); + { + xlnt::optional opt_assign1; + opt_assign1 = std::move(opt1); + xlnt_assert_equals(1, alive_count::count); // opt1 is in a no-value state + } + xlnt_assert_equals(0, alive_count::count); + } + xlnt_assert_equals(0, alive_count::count); // dtor test + } + + void test_set_and_get() + { + { + xlnt::optional test_opt; + xlnt_assert(!test_opt.is_set()); + xlnt_assert_throws(test_opt.get(), xlnt::invalid_attribute); + // set + const int test_val1 = 321; + test_opt.set(test_val1); + xlnt_assert(test_opt.is_set()); + xlnt_assert_equals(test_opt.get(), test_val1); + // set again + const int test_val2 = 123; + test_opt.set(test_val2); + xlnt_assert(test_opt.is_set()); + xlnt_assert_equals(test_opt.get(), test_val2); + // clear + test_opt.clear(); + xlnt_assert(!test_opt.is_set()); + xlnt_assert_throws(test_opt.get(), xlnt::invalid_attribute); + // set again + const int test_val3 = 3; + test_opt.set(test_val3); + xlnt_assert(test_opt.is_set()); + xlnt_assert_equals(test_opt.get(), test_val3); + // operator= set + xlnt::optional test_opt2; + test_opt2 = test_val1; + xlnt_assert_equals(test_opt2.get(), test_val1); + } + { // lifetime checks + xlnt::optional test_opt; + xlnt_assert_equals(0, alive_count::count); + test_opt.set(alive_count()); + xlnt_assert_equals(1, alive_count::count); + test_opt.set(alive_count()); // reassignment doesn't somehow skip the dtor + xlnt_assert_equals(1, alive_count::count); + test_opt.clear(); + xlnt_assert_equals(0, alive_count::count); + } + } + + void test_equality() + { + xlnt::optional test_opt1; + xlnt::optional test_opt2; + // no value opts compare equal + xlnt_assert(test_opt1 == test_opt2); + xlnt_assert(!(test_opt1 != test_opt2)); + xlnt_assert(test_opt2 == test_opt1); + xlnt_assert(!(test_opt2 != test_opt1)); + // value compares false with no value + const int test_val = 1; + test_opt1.set(test_val); + xlnt_assert(test_opt1 != test_opt2); + xlnt_assert(!(test_opt1 == test_opt2)); + xlnt_assert(test_opt2 != test_opt1); + xlnt_assert(!(test_opt2 == test_opt1)); + // value compares false with a different value + const int test_val2 = 2; + test_opt2.set(test_val2); + xlnt_assert(test_opt1 != test_opt2); + xlnt_assert(!(test_opt1 == test_opt2)); + xlnt_assert(test_opt2 != test_opt1); + xlnt_assert(!(test_opt2 == test_opt1)); + // value compares equal with same value + test_opt2.set(test_val); + xlnt_assert(test_opt1 == test_opt2); + xlnt_assert(!(test_opt1 != test_opt2)); + xlnt_assert(test_opt2 == test_opt1); + xlnt_assert(!(test_opt2 != test_opt1)); + } + + void test_const() + { + // functions on a const optional + const int test_val = 1; + const xlnt::optional opt(test_val); + xlnt_assert(opt.is_set()); + xlnt_assert(opt.get() == test_val); + + xlnt::optional opt2(test_val); + xlnt_assert(opt == opt2); + xlnt_assert(opt2 == opt); + xlnt_assert(!(opt != opt2)); + xlnt_assert(!(opt2 != opt)); + } +}; +static optional_test_suite x; \ No newline at end of file diff --git a/tests/utils/path_test_suite.cpp b/tests/utils/path_test_suite.cpp index f8d4f24c..bfc6fdfa 100644 --- a/tests/utils/path_test_suite.cpp +++ b/tests/utils/path_test_suite.cpp @@ -26,6 +26,7 @@ #include #include #include +#include class path_test_suite : public test_suite { diff --git a/tests/utils/variant_tests.cpp b/tests/utils/variant_tests.cpp new file mode 100644 index 00000000..10de95ec --- /dev/null +++ b/tests/utils/variant_tests.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2014-2018 Thomas Fussell +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, WRISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE +// +// @license: http://www.opensource.org/licenses/mit-license.php +// @author: see AUTHORS file + +#include +#include + +class variant_test_suite : public test_suite +{ +public: + variant_test_suite() + : test_suite() + { + register_test(test_null); + register_test(test_int32); + register_test(test_string); + } + + void test_null() + { + xlnt::variant var_null; + xlnt_assert_equals(var_null.value_type(), xlnt::variant::type::null); + xlnt_assert(var_null.is(xlnt::variant::type::null)); + } + + void test_int32() + { + xlnt::variant var_int(std::int32_t(10)); + xlnt_assert_equals(var_int.value_type(), xlnt::variant::type::i4); + xlnt_assert(var_int.is(xlnt::variant::type::i4)); + xlnt_assert_throws_nothing(var_int.get()); + xlnt_assert_equals(10, var_int.get()); + } + + void test_string() + { + xlnt::variant var_str1("test1"); + xlnt_assert_equals(var_str1.value_type(), xlnt::variant::type::lpstr); + xlnt_assert(var_str1.is(xlnt::variant::type::lpstr)); + xlnt_assert_throws_nothing(var_str1.get()); + xlnt_assert_equals("test1", var_str1.get()); + + xlnt::variant var_str2(std::string("test2")); + xlnt_assert_equals(var_str2.value_type(), xlnt::variant::type::lpstr); + xlnt_assert(var_str2.is(xlnt::variant::type::lpstr)); + xlnt_assert_throws_nothing(var_str2.get()); + xlnt_assert_equals("test2", var_str2.get()); + } +}; +static variant_test_suite x; \ No newline at end of file diff --git a/tests/workbook/serialization_test_suite.cpp b/tests/workbook/serialization_test_suite.cpp index 3533159c..744103ee 100644 --- a/tests/workbook/serialization_test_suite.cpp +++ b/tests/workbook/serialization_test_suite.cpp @@ -575,7 +575,7 @@ public: std::vector destination; source_workbook.save(destination); - source_workbook.save("temp.xlsx"); + source_workbook.save("temp" + source.filename()); #ifdef _MSC_VER std::ifstream source_stream(source.wstring(), std::ios::binary); diff --git a/tests/workbook/workbook_test_suite.cpp b/tests/workbook/workbook_test_suite.cpp index 86e8ae1e..b1a83df8 100644 --- a/tests/workbook/workbook_test_suite.cpp +++ b/tests/workbook/workbook_test_suite.cpp @@ -66,6 +66,7 @@ public: register_test(test_comparison); register_test(test_id_gen); register_test(test_load_file); + register_test(test_Issue279); } void test_active_sheet() @@ -460,11 +461,35 @@ public: // load with vector std::ifstream file_reader3(file.string(), std::ios::binary); file_reader3.unsetf(std::ios::skipws); - std::vector data(std::istream_iterator{file_reader3}, - std::istream_iterator()); + std::vector data(std::istream_iterator{file_reader3}, + std::istream_iterator()); xlnt::workbook wb_load5; wb_load5.load(data); xlnt_assert_equals(wb_path, wb_load5); } + + void test_Issue279() + { + xlnt::workbook wb(path_helper::test_file("Issue279_workbook_delete_rename.xlsx")); + while (wb.sheet_count() > 1) + { + if (wb[1].title() != "BOM") + { + wb.remove_sheet(wb[1]); + } + else + { + wb.remove_sheet(wb[0]); + } + } + // get sheet bom change title + auto ws1 = wb.sheet_by_index(0); + ws1.title("checkedBom"); + // report sheet + auto ws2 = wb.create_sheet(1); + ws2.title("REPORT"); + //save a copy file + wb.save("temp.xlsx"); + } }; static workbook_test_suite x; \ No newline at end of file diff --git a/tests/worksheet/range_test_suite.cpp b/tests/worksheet/range_test_suite.cpp index 98012289..b8c14fb6 100644 --- a/tests/worksheet/range_test_suite.cpp +++ b/tests/worksheet/range_test_suite.cpp @@ -37,10 +37,40 @@ class range_test_suite : public test_suite public: range_test_suite() { + register_test(test_construction); register_test(test_batch_formatting); register_test(test_clear_cells); } + void test_construction() + { + xlnt::workbook wb; + auto ws = wb.active_sheet(); + + xlnt::range range_1(ws, xlnt::range_reference("A1:D10")); + xlnt_assert_equals(range_1.target_worksheet(), ws); + xlnt_assert_equals(1, range_1.front()[0].row()); // NOTE: querying row/column here desperately needs some shortcuts + xlnt_assert_equals(xlnt::column_t("D"), range_1.front().back().column()); + xlnt_assert_equals(10, range_1.back()[0].row()); + xlnt_assert_equals(xlnt::column_t("D"), range_1.back().back().column()); + // assert default parameters in ctor + xlnt::range range_2(ws, xlnt::range_reference("A1:D10"), xlnt::major_order::row, false); + xlnt_assert_equals(range_1, range_2); + // assert copy + xlnt::range range_3(range_2); + xlnt_assert_equals(range_1, range_3); + + // column order + xlnt::range range_4(ws, xlnt::range_reference("A1:D10"), xlnt::major_order::column); + xlnt_assert_equals(xlnt::column_t("A"), range_4.front()[0].column()); // NOTE: querying row/column here desperately needs some shortcuts + xlnt_assert_equals(10, range_4.front().back().row()); + xlnt_assert_equals(xlnt::column_t("D"), range_4.back()[0].column()); + xlnt_assert_equals(10, range_4.back().back().row()); + // assignment + range_3 = range_4; + xlnt_assert_equals(range_3, range_4); + } + void test_batch_formatting() { xlnt::workbook wb; diff --git a/tests/worksheet/worksheet_test_suite.cpp b/tests/worksheet/worksheet_test_suite.cpp index 84fa2693..b25e0d61 100644 --- a/tests/worksheet/worksheet_test_suite.cpp +++ b/tests/worksheet/worksheet_test_suite.cpp @@ -50,7 +50,6 @@ public: register_test(test_remove_named_range_bad); register_test(test_cell_alternate_coordinates); register_test(test_cell_range_name); - register_test(test_hyperlink_value); register_test(test_rows); register_test(test_no_rows); register_test(test_no_cols); @@ -225,50 +224,6 @@ public: xlnt_assert(c_range_coord[0][0] == c_cell); } - void test_hyperlink_value() - { - xlnt::workbook wb; - auto ws = wb.active_sheet(); - std::string test_link = "http://test.com"; - xlnt::cell_reference test_cell("A1"); - ws.cell(test_cell).hyperlink(test_link); - // when a hyperlink is added to an empty cell, the display text becomes == to the link - xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link); - xlnt_assert_equals(ws.cell(test_cell).value(), test_link); - // if the display value changes, the hyperlink remains the same - std::string test_string = "test"; - ws.cell(test_cell).value(test_string); - xlnt_assert_equals(test_string, ws.cell(test_cell).value()); - xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link); - // changing the link doesn't change the cell value - std::string test_link2 = "http://test-123.com"; - ws.cell(test_cell).hyperlink(test_link2); - xlnt_assert_equals(test_string, ws.cell(test_cell).value()); - xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link2); - // and we can edit both value and hyperlink together - std::string test_string3 = "123test"; - std::string test_link3 = "http://123-test.com"; - ws.cell(test_cell).hyperlink(test_link3, test_string3); - xlnt_assert_equals(test_string3, ws.cell(test_cell).value()); - xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link3); - // hyperlinks can also be applied to cells with non-string values - int numeric_test_val = 123; - std::string test_link4 = "http://test-numeric.com"; - xlnt::cell_reference numeric_test_cell("B1"); - ws.cell(numeric_test_cell).value(numeric_test_val); - ws.cell(numeric_test_cell).hyperlink(test_link4); - xlnt_assert_equals(ws.cell(numeric_test_cell).hyperlink().url(), test_link4); - xlnt_assert_equals(ws.cell(numeric_test_cell).value(), numeric_test_val); - // and there should be no issues if two cells use the same hyperlink - ws.cell(numeric_test_cell).hyperlink(test_link3); // still in use on 'A1' - // 'A1' - xlnt_assert_equals(test_string3, ws.cell(test_cell).value()); - xlnt_assert_equals(ws.cell(test_cell).hyperlink().url(), test_link3); - // 'B1' - xlnt_assert_equals(ws.cell(numeric_test_cell).hyperlink().url(), test_link3); - xlnt_assert_equals(ws.cell(numeric_test_cell).value(), numeric_test_val); - } - void test_rows() { xlnt::workbook wb;